센서 여러 대를 한 곳에서 모아 보는 일을 맡았습니다. 현장은 넓고, 센서마다 선을 끌기는 어렵고, 그렇다고 대당 통신비가 나가는 방식도 곤란한 조건이었습니다. LoRaWAN으로 가기로 했습니다. 게이트웨이 한 대가 반경을 덮고, 노드가 센서를 물어서 올리는 구조입니다.
조건이 하나 더 있었습니다. 그 게이트웨이는 이미 다른 장치 40여 대가 붙어 돌고 있는 운영 장비였습니다. 전역 설정은 건드릴 수 없고, 애플리케이션과 디바이스만 추가하는 선에서 끝내야 했습니다.
그래서 계획은 간단했습니다. 노드를 등록하고, 조인시키고, 데이터가 올라오는지 본다. 반나절이면 될 줄 알았습니다.
LoRaWAN 조인 실패 — 패킷 캡처에 무수신
LoRaWAN 조인은 디바이스를 등록하고 모듈에 AT+JOIN=1 을 넣는 것으로 시작합니다.
OK 가 돌아옵니다. 그리고 아무 일도 일어나지 않습니다. 게이트웨이 쪽 디바이스 목록의
Last seen 은 계속 비어 있었습니다.
흔한 원인부터 짚었습니다. 밴드가 다른가, 키를 잘못 넣었나, 안테나가 덜 조여졌나. 전부 아니었습니다.
그래서 게이트웨이에 패킷 캡처를 걸었습니다. 여기서 처음으로 이상하다고 느꼈습니다.
우리 모듈의 패킷이 한 건도 안 잡혔습니다. “수신 실패”도 아니고, CRC 에러조차 남지 않았습니다.
같은 시간대에 다른 장치 수십 대의 패킷은 정상적으로 올라오고 있었습니다. 우리 것만 없었습니다. 실패한 흔적이 남는 게 아니라, 애초에 아무것도 보내지 않은 것처럼 보였습니다.
모듈을 불량으로 몰았습니다 — 펌웨어 재플래시와 교체
모듈 펌웨어를 먼저 의심했습니다. 모듈에 올라가 있던 것은 2021년 10월 빌드였고, 요즘 문서에 나오는 AT 명령 절반이 지원되지 않았습니다. 공식 최신 UF2를 받아 다시 구웠습니다.
“최신”이라는데 빌드 날짜가 원래 것과 하루 차이였습니다. 이 계열 모듈은 그 시점 이후로 사실상 유지보수가 멈춰 있었습니다.
재플래시 후에는 증상이 오히려 나빠졌습니다. AT+JOIN 이 바로 에러를 뱉었습니다.
이 시점에 저는 이렇게 적었습니다 — “하드웨어 불량 가능성 높음.”
예비 모듈로 교체했습니다. DevEUI가 다르니 확실히 다른 하드웨어입니다.
증상이 똑같았습니다.
여기서 멈췄어야 했는데, 그때는 “예비 모듈도 불량인가” 쪽으로 갔습니다.
진짜 원인 — LoRaWAN Public 토글과 sync word
게이트웨이 주파수 플랜 설정을 처음부터 다시 훑다가 상세 항목을 펼쳤습니다. 거기에 토글이 하나 있었습니다.
LoRaWAN Public— OFF
게이트웨이가 프라이빗 모드로 돌고 있었습니다. 반면 우리 모듈은 AT+STATUS 에
계속 Public Network 라고 답하고 있었습니다.
LoRa 물리계층에는 sync word 라는 값이 있습니다.
| 모드 | sync word |
|---|---|
| Public | 0x34 |
| Private | 0x12 |
이 값이 다르면 수신기는 프리앰블 검출 자체를 못 합니다. 복조를 시도했다가 실패하는 게 아니라, 그 신호가 애초에 존재하지 않는 것으로 처리됩니다. CRC 에러조차 안 남는 이유가 정확히 이것이었습니다.
이 한 줄로 그동안의 관측이 전부 설명됐습니다.
- 모듈 두 개가 똑같이 죽은 이유 → 둘 다 정상이었고, 둘 다 같은 기본값이었습니다
- 펌웨어·밴드·키·안테나가 다 정상인데 무수신이던 이유
- 같은 시간 다른 장치 수십 대는 멀쩡하던 이유 → 그 기기들은 전부 private으로 맞춰져 있었습니다
같은 증상이 개체 두 대에서 똑같이 재현되면, “둘 다 불량”보다 “둘 다 같은 기본값”을 먼저 의심해야 합니다. 저는 반대로 갔고, 그래서 하루를 썼습니다.
sync word를 맞추는 방법은 둘, 그중 하나는 금지
sync word를 맞추는 방법은 둘 중 하나입니다.
- 모듈을 private으로 맞춘다 — 우리 것만 바꾸면 됩니다
- 게이트웨이를 public으로 바꾼다 — 토글 한 번이면 끝납니다
2번이 압도적으로 쉬워 보이지만, 그건 이미 붙어 있는 40여 대를 전부 통신 두절시키는 조작입니다. 남의 설비를 끄고 내 것을 켜는 셈입니다. 1번으로 갔습니다.
펌웨어 소스를 받아 한 줄을 고쳤습니다.
// main.h
bool public_network = true; → bool public_network = false;
이 값이 실제로 스택까지 전달되는지도 소스에서 확인했습니다
(lorawan.cpp 에서 enable_public_network 로 넘어갑니다). 빌드는 Arduino IDE에
번들된 arduino-cli 로 했고, UF2는 자동 생성되지 않아 BSP에 들어 있는 elf2uf2 로
따로 변환했습니다. 구운 UF2가 정상인지는 공식 UF2와 헤더를 비교해서 확인했습니다 —
매직 넘버, 패밀리 ID, 시작 주소가 전부 일치했습니다.
플래시하고 상태를 읽으니 Private Network 로 바뀌어 있었습니다. 목표 달성입니다.
수동 조인에서 AT+NJS가 0에서 멈춘 이유
수동 조인을 걸면 AT+JOIN 은 OK 를 돌려주는데, 조인 상태를 묻는 AT+NJS 가 0에서 꿈쩍도 안 했습니다.
구 펌웨어에서는 최소한 2(진행 중) → 3(실패) 로 움직이기라도 했는데, 이번엔 아예
출발선에 서 있었습니다.
라이브러리 버전을 의심해서 두 버전으로 각각 빌드해봤지만 증상이 같았습니다. 디버그 빌드도 해봤는데, 로그가 USB로 쏟아지면서 포트 자체가 불안정해져 진단에 오히려 방해가 됐습니다.
결국 소스를 따라갔습니다. 수동 조인 신호를 받는 자리는 이랬습니다.
if ((event.value.signals & SIGNAL_JOIN) == SIGNAL_JOIN)
{
APP_LOG("APP", "Start Join");
init_lora(); // ← LoRa P2P 초기화 함수
...
}
init_lora() 는 LoRa P2P 를 초기화하는 함수입니다. LoRaWAN MAC은 건드리지 않습니다.
LoRaWAN 스택을 올리고 실제로 조인을 요청하는 것은 init_lorawan() 인데, 이 함수는
자동 조인이 켜져 있을 때 setup() 에서만 호출되고 있었습니다.
즉 자동 조인을 끈 상태에서 수동으로 조인을 걸면, LoRaWAN MAC이 아예 초기화되지 않아
조인 요청이 나가지 않습니다. 전파가 안 나가니 AT+NJS 는 영원히 0입니다.
구 펌웨어에 이 문제가 없었던 것은 P2P 기능이 들어오기 전이었기 때문입니다.
두 줄을 고쳤습니다.
if (g_lorawan_settings.lorawan_enable) { init_lorawan(); } else { init_lora(); }
+NJS:1. 업링크가 올라왔습니다.
“불량”이었던 LoRa 모듈은 처음부터 정상이었습니다
불량으로 치워뒀던 원래 모듈에 같은 UF2를 올려봤습니다.
정상 동작했습니다.
하드웨어는 처음부터 둘 다 멀쩡했습니다. 제가 이틀에 걸쳐 의심한 것은 순서대로 안테나, 펌웨어 버전, 하드웨어, 예비 하드웨어, 라이브러리 버전이었고, 정답은 게이트웨이 설정 화면의 토글 하나와 상류 소스의 함수 이름 하나였습니다.
무선이 “실패”하는 것과 “존재하지 않는 것처럼 보이는” 것은 다른 증상입니다. 에러조차 안 남으면 링크가 나쁜 게 아니라 서로 다른 규칙으로 말하고 있는 것입니다. 그때는 신호 세기를 재지 말고 양쪽의 기본값을 맞춰 봐야 합니다.
CRC 에러조차 남지 않는 무수신은 링크 품질 문제가 아니라, 양쪽이 서로 다른 sync word로 말하고 있다는 신호입니다.
▶️ 다음 편 — 실기기 없이 센서를 만듭니다: 무선이 뚫리자 이번엔 센서가 없었습니다. 실물이 현장에 있어서, PC에서 센서인 척하는 프로그램을 만들었습니다.
문의
- Email : [email protected]
- Insta : https://www.instagram.com/going.sen/
- Website : https://intosen.com/kr/consult/
댓글
닉네임만 입력하면 바로 댓글을 남길 수 있어요. Google/GitHub 로그인도 가능합니다.