모두 잘되는 코드다.
// ─────────────────────────────────────────────
// 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] 세션 완료. 슬립 모드 진입.
*/

'ESP32 Project' 카테고리의 다른 글
| ESP32 Arduino를 이용한 무선 마이크 (0) | 2026.05.29 |
|---|---|
| RF-Clown v2 Wi-Fi, BLE 방해 (0) | 2026.05.29 |
| ESP32 기반 Edge AI IoT 제품을 직접 설계, 구현하는 오프라인 실습 교육 (0) | 2026.05.21 |
| Iridium 위성 통신 10분 인터벌 데이터 출력 (0) | 2026.05.21 |
| ESP32를 배우는 것이 2026년 가장 큰 기술 변화 중 하나 (0) | 2026.05.14 |
| 스마트 약물 자동 투약 시스템 (0) | 2026.05.14 |
| ESP32 WiFi 보안에 대한 초보자용 설명 (0) | 2026.05.04 |
| 이리듐 위성통신 기반 환경 모니터링 보드 블럭도 (0) | 2026.04.26 |
취업, 창업의 막막함, 외주 관리, 제품 부재!
당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약,
아이디어는 있지만 구현할 기술이 없는 막막함.
우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.
이제 고민을 멈추고, 캐어랩을 만나세요!
코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.
제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!
귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.
캐어랩