본문 바로가기

ESP32 Project

이리듐 보드 구성품 테스트 코드

반응형

 
모두 잘되는 코드다.
 

// ─────────────────────────────────────────────
//  geo_alltest.ino
//  전체 모듈 동작 확인용 테스트 프로그램
//  테스트 항목: LED / DS18B20 / DIP Switch / SD
// ─────────────────────────────────────────────

#include "driver/uart.h"
#include <OneWire.h>
#include <DallasTemperature.h>
#include <TinyGPSPlus.h>
#include <HardwareSerial.h>
#include "FS.h"
#include "SD_MMC.h"

// ── 디바이스 ID ───────────────────────────────
#define DEVICE_ID  "GEO0TEMP 1"

// ── 핀 정의 ──────────────────────────────────
#define POWER_VBAT_ENABLE   39

#define LED_GPIO             3

#define DS18B20_GPIO         2
#define DS18B20_ENABLE      40
#define DS18B20_RESOLUTION  12

#define DIP_POWERON         41
#define DIP_SW1_GPIO        46
#define DIP_SW2_GPIO        47
#define DIP_SW3_GPIO        48
#define DIP_SW4_GPIO        45

#define IRIDUM_UART_NUM      UART_NUM_1
#define IRIDUM_UART_RXD_PIN 44
#define IRIDUM_UART_TXD_PIN 43
#define IRIDUM_UART_BAUD    19200
#define IRIDUM_POWER_GPIO   38
#define IRIDUM_ONOFF_GPIO   37
#define IRIDUM_NETAV_PIN    14

#define GNSS_RX_GPIO        18
#define GNSS_TX_GPIO        17
#define GNSS_ENABLE         42
#define GNSS_BAUD           9600
#define GNSS_TEST_TIMEOUT   10000UL

#define SD_ENABLE           41
#define PIN_SD_CLK          12
#define PIN_SD_CMD          11
#define PIN_SD_DATA0         5
#define PIN_SD_DATA1         6
#define PIN_SD_DATA2        13
#define PIN_SD_DATA3         4

// ── 객체 ─────────────────────────────────────
OneWire           oneWire(DS18B20_GPIO);
DallasTemperature ds18b20(&oneWire);

TinyGPSPlus       gps;
HardwareSerial    gnssSerial(2);
HardwareSerial    iridiumSerial(1);

// ── 테스트 결과 ───────────────────────────────
bool result_led  = false;
bool result_temp = false;
bool result_dip  = false;
bool result_sd   = false;
bool result_gps     = false;
bool result_iridium = false;

// ─────────────────────────────────────────────
void printResult(const char* name, bool ok) {
  Serial.printf("  [%s] %s\n", ok ? "PASS" : "FAIL", name);
}

// ─────────────────────────────────────────────
//  1. LED 테스트
// ─────────────────────────────────────────────
void test_LED() {
  Serial.println("\n=== LED 테스트 ===");
  pinMode(LED_GPIO, OUTPUT);
  digitalWrite(LED_GPIO, HIGH);
  Serial.println("  LED ON (3초)");
  delay(3000);
  digitalWrite(LED_GPIO, LOW);
  Serial.println("  LED OFF");
  Serial.println("  육안으로 점등 확인 → PASS로 처리");
  result_led = true;
}

// ─────────────────────────────────────────────
//  2. DS18B20 온도 센서 테스트
// ─────────────────────────────────────────────
void test_Temperature() {
  Serial.println("\n=== DS18B20 온도 센서 테스트 ===");

  pinMode(DS18B20_ENABLE, OUTPUT);
  digitalWrite(DS18B20_ENABLE, LOW);
  delay(500);

  ds18b20.begin();
  delay(100);
  int count = ds18b20.getDeviceCount();
  Serial.printf("  감지된 센서 수: %d\n", count);

  if (count == 0) {
    Serial.println("  오류: 센서 없음");
    digitalWrite(DS18B20_ENABLE, HIGH);
    return;
  }

  ds18b20.setResolution(DS18B20_RESOLUTION);
  ds18b20.setWaitForConversion(false);
  ds18b20.requestTemperatures();
  delay(800); 

  float tempC = ds18b20.getTempCByIndex(0);
  digitalWrite(DS18B20_ENABLE, HIGH);

  if (tempC == DEVICE_DISCONNECTED_C || tempC == 85.0f ||
      tempC < -55.0f || tempC > 125.0f) {
    Serial.printf("  오류: 비정상 값 (%.2f°C)\n", tempC);
    return;
  }

  Serial.printf("  온도: %.4f°C\n", tempC);
  result_temp = true;
}

// ─────────────────────────────────────────────
//  3. DIP 스위치 테스트
// ─────────────────────────────────────────────
void test_DIP() {
  Serial.println("\n=== DIP 스위치 테스트 ===");

  pinMode(DIP_POWERON, OUTPUT);
  pinMode(DIP_SW1_GPIO, INPUT_PULLDOWN);
  pinMode(DIP_SW2_GPIO, INPUT_PULLDOWN);
  pinMode(DIP_SW3_GPIO, INPUT_PULLDOWN);
  pinMode(DIP_SW4_GPIO, INPUT_PULLDOWN);

  digitalWrite(DIP_POWERON, LOW);
  delay(10);

  uint8_t dip = ((digitalRead(DIP_SW4_GPIO) == HIGH) ? 0x08 : 0x00) |
                ((digitalRead(DIP_SW3_GPIO) == HIGH) ? 0x04 : 0x00) |
                ((digitalRead(DIP_SW2_GPIO) == HIGH) ? 0x02 : 0x00) |
                ((digitalRead(DIP_SW1_GPIO) == HIGH) ? 0x01 : 0x00);

  digitalWrite(DIP_POWERON, HIGH);

  Serial.printf("  SW1=%d SW2=%d SW3=%d SW4=%d → DIP값: %d\n",
                (dip >> 0) & 1, (dip >> 1) & 1,
                (dip >> 2) & 1, (dip >> 3) & 1, dip);

  result_dip = true;
}

// ─────────────────────────────────────────────
//  4. SD 카드 테스트
// ─────────────────────────────────────────────
void test_SD() {
  Serial.println("\n=== SD 카드 테스트 ===");

  pinMode(SD_ENABLE, OUTPUT);
  digitalWrite(SD_ENABLE, LOW);
  delay(50);

  SD_MMC.setPins(PIN_SD_CLK, PIN_SD_CMD,
                 PIN_SD_DATA0, PIN_SD_DATA1,
                 PIN_SD_DATA2, PIN_SD_DATA3);

  if (!SD_MMC.begin("/sdcard", true, false)) {
    Serial.println("  오류: 초기화 실패");
    digitalWrite(SD_ENABLE, HIGH);
    return;
  }

  uint64_t totalMB = SD_MMC.totalBytes() / (1024ULL * 1024ULL);
  uint64_t freeMB  = (SD_MMC.totalBytes() - SD_MMC.usedBytes()) / (1024ULL * 1024ULL);
  Serial.printf("  용량: %llu MB / 여유: %llu MB\n", totalMB, freeMB);

  // 쓰기 테스트
  File f = SD_MMC.open("/test_alltest.txt", FILE_WRITE);
  if (!f) {
    Serial.println("  오류: 파일 쓰기 실패");
    SD_MMC.end();
    digitalWrite(SD_ENABLE, HIGH);
    return;
  }
  String writeStr = "geo_alltest write OK";
  f.println(writeStr);
  f.close();
  Serial.printf("  쓰기 확인: %s\n", writeStr.c_str());

  // 읽기 테스트
  f = SD_MMC.open("/test_alltest.txt", FILE_READ);
  if (!f) {
    Serial.println("  오류: 파일 읽기 실패");
    SD_MMC.end();
    digitalWrite(SD_ENABLE, HIGH);
    return;
  }
  String line = f.readStringUntil('\n');
  f.close();

  SD_MMC.remove("/test_alltest.txt");
  SD_MMC.end();
  digitalWrite(SD_ENABLE, HIGH);

  Serial.printf("  읽기 확인: %s\n", line.c_str());
  result_sd = true;
}

// ─────────────────────────────────────────────
//  5. GPS 테스트
// ─────────────────────────────────────────────
void test_GPS() {
  Serial.println("\n=== GPS 테스트 ===");
  Serial.printf("  최대 대기: %lu초\n", GNSS_TEST_TIMEOUT / 1000);

  pinMode(GNSS_TX_GPIO, OUTPUT);
  digitalWrite(GNSS_TX_GPIO, HIGH);
  pinMode(GNSS_ENABLE, OUTPUT);
  digitalWrite(GNSS_ENABLE, LOW);
  delay(500);

  gnssSerial.begin(GNSS_BAUD, SERIAL_8N1, GNSS_RX_GPIO, -1);

  unsigned long start = millis();
  bool nmeaReceived = false;
  bool fixOk = false;

  while (millis() - start < GNSS_TEST_TIMEOUT) {
    while (gnssSerial.available()) {
      char c = gnssSerial.read();
      gps.encode(c);
      nmeaReceived = true;
    }
    if (gps.location.isValid() && gps.date.isValid() &&
        gps.time.isValid() && gps.altitude.isValid()) {
      fixOk = true;
      break;
    }
  }

  gnssSerial.end();
  digitalWrite(GNSS_ENABLE, HIGH);

  if (!nmeaReceived) {
    Serial.println("  오류: NMEA 수신 없음 (배선/전원 확인)");
    return;
  }

  Serial.printf("  NMEA 수신: OK | 위성 수: %d\n", gps.satellites.value());

  if (!fixOk) {
    Serial.println("  경고: Fix 획득 실패 (시간 초과) — NMEA 수신은 정상");
    result_gps = true;
    return;
  }

  Serial.printf("  위도: %.6f  경도: %.6f\n",
                gps.location.lat(), gps.location.lng());
  Serial.printf("  고도: %.1f m  UTC: %02d:%02d:%02d\n",
                gps.altitude.meters(),
                gps.time.hour(), gps.time.minute(), gps.time.second());
  result_gps = true;
}

// ─────────────────────────────────────────────
//  6. Iridium 9603N 테스트 (AT ping, 실내 가능)
// ─────────────────────────────────────────────
static bool iridiumAT(const char* cmd, const char* expect,
                      uint32_t timeoutMs, String& out) {
  while (iridiumSerial.available()) iridiumSerial.read();
  if (cmd && cmd[0]) {
    iridiumSerial.print(cmd);
    iridiumSerial.print(F("\r"));
  }
  out = "";
  unsigned long t = millis();
  while (millis() - t < timeoutMs) {
    while (iridiumSerial.available()) {
      char c = (char)iridiumSerial.read();
      out += c;
      if (out.indexOf(expect) >= 0) return true;
    }
  }
  return false;
}

void test_Iridium() {
  Serial.println("\n=== Iridium 9603N 테스트 ===");

  pinMode(IRIDUM_POWER_GPIO, OUTPUT);
  pinMode(IRIDUM_ONOFF_GPIO, OUTPUT);
  digitalWrite(IRIDUM_POWER_GPIO, HIGH);
  digitalWrite(IRIDUM_ONOFF_GPIO, HIGH);
  delay(500);

  // 모듈 켜기
  digitalWrite(IRIDUM_ONOFF_GPIO, LOW);
  delay(1500);
  digitalWrite(IRIDUM_ONOFF_GPIO, HIGH);
  delay(2000);

  iridiumSerial.begin(IRIDUM_UART_BAUD, SERIAL_8N1,
                      IRIDUM_UART_RXD_PIN, IRIDUM_UART_TXD_PIN);
  delay(100);
  uart_set_hw_flow_ctrl(IRIDUM_UART_NUM, UART_HW_FLOWCTRL_DISABLE, 0);

  pinMode(IRIDUM_NETAV_PIN, INPUT);

  String resp;

  // AT ping (최대 10회) — PASS 조건
  bool alive = false;
  for (int i = 1; i <= 10 && !alive; i++) {
    alive = iridiumAT("AT", "OK", 2000, resp);
    if (!alive) delay(1000);
  }

  if (!alive) {
    Serial.println("  오류: 모뎀 응답 없음 (전원/배선 확인)");
    iridiumSerial.end();
    digitalWrite(IRIDUM_ONOFF_GPIO, LOW);
    delay(1500);
    digitalWrite(IRIDUM_ONOFF_GPIO, HIGH);
    delay(200);
    digitalWrite(IRIDUM_POWER_GPIO, LOW);
    return;
  }
  Serial.println("  AT ping: OK");

  // CSQ / NETAV — 참고용 (실내 0/LOW 정상)
  int csq = 0;
  if (iridiumAT("AT+CSQ", "OK", 5000, resp)) {
    int idx = resp.indexOf("+CSQ:");
    if (idx >= 0) csq = resp.substring(idx + 5).toInt();
  }
  bool netav = (digitalRead(IRIDUM_NETAV_PIN) == HIGH);
  Serial.printf("  CSQ: %d  NETAV: %s (실내 0/LOW 정상)\n",
                csq, netav ? "HIGH" : "LOW");

  // 모듈 끄기
  iridiumSerial.end();
  digitalWrite(IRIDUM_ONOFF_GPIO, LOW);
  delay(1500);
  digitalWrite(IRIDUM_ONOFF_GPIO, HIGH);
  delay(200);
  digitalWrite(IRIDUM_POWER_GPIO, LOW);

  result_iridium = true;
}

// ─────────────────────────────────────────────
//  최종 결과 출력
// ─────────────────────────────────────────────
void printSummary() {
  Serial.println("\n=============================");
  Serial.println("  테스트 결과 요약");
  Serial.println("=============================");
  printResult("LED          ", result_led);
  printResult("DS18B20 온도 ", result_temp);
  printResult("DIP 스위치   ", result_dip);
  printResult("SD 카드      ", result_sd);
  printResult("GPS          ", result_gps);
  printResult("Iridium 9603N", result_iridium);
  Serial.println("=============================");

  int passed = result_led + result_temp + result_dip + result_sd + result_gps + result_iridium;
  Serial.printf("  %d / 6 PASS\n", passed);
  Serial.println("=============================");
}

// ─────────────────────────────────────────────
void setup() {
  pinMode(POWER_VBAT_ENABLE, OUTPUT);
  digitalWrite(POWER_VBAT_ENABLE, HIGH);

  Serial.begin(115200);
  delay(300);
  Serial.print(F("\n===== "));
  Serial.print(DEVICE_ID);
  Serial.println(F(" geo_alltest start ====="));
  Serial.println(F("  Build: " __DATE__ " " __TIME__));

  test_LED();
  test_Temperature();
  test_DIP();
  test_SD();
  test_GPS();
  test_Iridium();

  printSummary();
}

void loop() {}

 
 

 

/*
 * test_9603n.ino
 * ESP32-S3 - Iridium 9603N 위성 트랜시버 SBD 통신 테스트
 * ============================================================
 *
 * ────────────────────────────────────────────────────────────
 *  Iridium 위성 통신 원리 및 세부 과정
 * ────────────────────────────────────────────────────────────
 *
 * [1] Iridium 네트워크 구조
 *     ┌──────────────┐     RF (L-band, 1616~1626.5 MHz)
 *     │  9603N 모듈  │ ◄──────────────────────────────► [위성 66기 LEO 궤도]
 *     └──────┬───────┘                                        │
 *            │ UART (AT 명령)                                  │
 *     ┌──────▼───────┐                               [게이트웨이 GSS]
 *     │   ESP32-S3   │                                        │
 *     └──────────────┘                               [Iridium 서버(허브)]
 *                                                             │
 *                                               [수신측 장치 / 클라우드]
 *
 *   - Iridium 위성은 고도 약 780km LEO(저궤도)에 66개 운용
 *   - 지구 어디서든 최소 1개 위성이 시야각에 존재 (극지방 포함)
 *   - 9603N ↔ 위성 간 통신: L-band RF (단방향 Burst)
 *   - 위성 ↔ 지상국(GSS) ↔ Iridium 허브 ↔ 인터넷/목적지
 *
 * ────────────────────────────────────────────────────────────
 * [2] SBD (Short Burst Data) 프로토콜
 * ────────────────────────────────────────────────────────────
 *   SBD는 Iridium의 핵심 데이터 전송 방식으로 "짧은 메시지 버스트"
 *   방식으로 데이터를 패킷 단위로 전송합니다.
 *
 *   ▸ MO (Mobile Originated) : 단말 → 위성 → 서버  (최대 340 bytes)
 *   ▸ MT (Mobile Terminated)  : 서버 → 위성 → 단말  (최대 270 bytes)
 *   ▸ 세션 1회당 MO 1개 + MT 1개 동시 교환 가능
 *   ▸ 메시지는 버퍼에 적재 후 세션 시작 시 일괄 전송
 *
 * ────────────────────────────────────────────────────────────
 * [3] 통신 세부 과정 (AT 명령 흐름)
 * ────────────────────────────────────────────────────────────
 *
 *   Step 1. 모듈 전원 ON / 웨이크업
 *           - Sleep 핀 HIGH → LOW (또는 ON/OFF 핀 제어)
 *           - 내부 DSP, RF 회로 초기화 (약 1~2초)
 *
 *   Step 2. AT 응답 확인
 *           ► AT          → OK   (기본 통신 확인)
 *           ► ATZ         → OK   (공장 초기화 선택)
 *           ► AT+CGMM     → 9603N (모델 확인)
 *
 *   Step 3. 신호 품질 확인 (CSQ: Signal Quality)
 *           ► AT+CSQ      → +CSQ: <n>
 *             n = 0 : 신호 없음 (전송 불가)
 *             n = 1 : 매우 약함
 *             n = 2 : 약함
 *             n = 3 : 보통 ★ 전송 최소 권장
 *             n = 4 : 양호
 *             n = 5 : 우수
 *           - 위성이 수평선 위로 올라오는 데 최대 수 분 소요 가능
 *           - 실내/지하에서는 0 반환 → 외부 안테나 필수
 *
 *   Step 4. MO 버퍼에 전송 메시지 적재
 *           ► AT+SBDWT=<text>        (텍스트 메시지, 최대 340자)
 *           ► AT+SBDWB=<length>      (바이너리 메시지)
 *             → READY 응답 후 raw bytes 전송 + 2-byte checksum
 *
 *   Step 5. SBD 세션 개시 (AT+SBDIX)
 *           ► AT+SBDIX
 *             9603N이 위성과 RF 링크를 수립하고 MO 버퍼를 전송하며
 *             동시에 MT 버퍼가 비어 있으면 서버로부터 수신 시도
 *
 *             응답 형식:
 *             +SBDIX: <MO_status>, <MOMSN>, <MT_status>, <MTMSN>,
 *                     <MT_length>, <MT_queued>
 *
 *             MO_status 주요 코드:
 *               0  : MO 전송 성공
 *               1  : 전송 성공, MT 메시지가 너무 커 수신 불가
 *               2  : 전송 성공, 위치 업데이트 거부
 *              10  : 허용 시간 내 세션 완료 안 됨
 *              11  : GSS의 MO 큐 가득 참
 *              32  : 네트워크 없음 (위성 미탐지)
 *              35  : 안테나 이상
 *              36  : RF 비활성 상태
 *              38  : 3분 대기 필요 (이전 세션 직후 재시도 제한)
 *
 *             MT_status 주요 코드:
 *               0  : 수신할 MT 메시지 없음
 *               1  : MT 메시지 수신 성공
 *               2  : MT 수신 중 오류
 *
 *   Step 6. MT 메시지 읽기 (MT_status == 1 인 경우)
 *           ► AT+SBDRT   (텍스트)
 *           ► AT+SBDRB   (바이너리)
 *           - MT_queued > 0이면 대기 중인 추가 메시지 존재
 *             → 세션을 다시 열어 수신 반복
 *
 *   Step 7. 버퍼 초기화 및 슬립
 *           ► AT+SBDD0   (MO 버퍼 클리어)
 *           ► AT+SBDD1   (MT 버퍼 클리어)
 *           ► AT+SBDD2   (MO+MT 버퍼 모두 클리어)
 *           - Sleep 핀 HIGH → 절전 모드 진입 (<= 40uA)
 *
 * ────────────────────────────────────────────────────────────
 * [4] Ring Alert (RI 핀)
 * ────────────────────────────────────────────────────────────
 *   - 서버에서 MT 메시지가 대기 중일 때 9603N의 RI 핀이 LOW로 토글
 *   - ESP32-S3에서 인터럽트로 감지 → 즉시 세션 열어 MT 수신
 *   - AT+SBDMSSTM 명령으로 소프트웨어적 Ring Alert 확인 가능
 *
 * ────────────────────────────────────────────────────────────
 * [5] 타이밍 주의사항
 * ────────────────────────────────────────────────────────────
 *   - AT+SBDIX 세션 완료까지 평균 20~90초 소요 (조건 양호 시)
 *   - 위성이 없을 경우 최대 수 분 대기 → Timeout 설계 필수
 *   - 연속 세션 사이에는 최소 3분(180초) 간격 권장 (38번 오류 방지)
 *   - WatchDog은 세션 중 feedWatchdog()을 반드시 호출 필요
 *
 * ────────────────────────────────────────────────────────────
 *
 * 라이브러리 설치 (Arduino IDE Library Manager):
 *   - "IridiumSBD" by Mikal Hart  (v2.x 또는 SparkFun 포크)
 *   검색어: IridiumSBD
 *
 * Hardware Connections (9603N <-> ESP32-S3):
 *
 *   9603N 핀       ESP32-S3 핀    설명
 *   ──────────────────────────────────────────────────
 *   TXD          → GPIO44 (RX)   시리얼 데이터 수신
 *   RXD          ← GPIO43 (TX)   시리얼 데이터 송신
 *   ON/OFF(Sleep)← GPIO37         LOW=활성, HIGH=슬립
 *   RI (Ring)    → GPIO21         MT 알림 (인터럽트)
 *   NET_AV       → GPIO14         네트워크 가용 표시
 *   VCC          ── 5V           5V 전원 (피크 1.5A 이상 공급 필수)
 *   GND          ── GND
 *
 *   ※ GPIO43/44는 ESP32-S3 USB UART0 핀과 동일 — 실제 배선 핀과 반드시 일치 확인
 *   ※ 9603N은 5V 구동 / UART는 3.3V 호환 확인 필요
 *      (레벨 시프터 또는 분압 저항 사용 권장)
 *   ※ 안테나: 마그네틱 마운트 패치 안테나 (SMA 커넥터)
 *      옥외 또는 창문 근처에서 테스트 권장
 *
 * Expected Serial Monitor Output (115200 baud):
 *   === Iridium 9603N Test Start ===
 *   [INIT] 모듈 전원 ON...
 *   [INIT] 모듈 초기화 완료
 *   [CSQ]  신호 품질: 3 / 5
 *   [MO]   메시지 적재 완료: "Hello from ESP32-S3"
 *   [SBD]  세션 시작 (AT+SBDIX)...
 *   [SBD]  MO 전송 성공 (MO=0, MOMSN=42)
 *   [SBD]  MT 수신 없음
 *   [DONE] 세션 완료. 슬립 모드 진입.
 */

 

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩