10.10 마이크로컨트롤러 환경의 디버깅 및 트러블슈팅
클라우드의 컨테이너가 죽으면 로그가 남지만, 칩셋이 죽으면 아무런 흔적도 남지 않는다.
LED 만 깜빡이다가 로봇이 장렬히 산화하는 모습을 지켜봐야 하는 눈먼 엔지니어들을 위한 최후의 구명정이다.
Zenoh-Pico 가 칩을 박살낼 때, 도대체 어디서부터 네트워크가 막혔고, 내 메모리를 언제 다 갉아먹었는지를 찾아내는 하드웨어 제어 엔지니어의 부검(Autopsy) 런북이다.
1. 시리얼 출력을 활용한 Zenoh-Pico 내부 로그 추적
“연결이 안 돼요” 에 대한 해답은 칩 안에 숨겨져 있다. Pico 는 내부적으로 무엇을 하고 있는지 계속 당신에게 소리치고 싶어 하지만, 당신이 귀를 막아뒀을 뿐이다.
1.0.1 [Runbook] JTAG/UART 로그 터널 개통 전술
1. CMake 빌드 옵션 개방
Pico 를 빌드할 때 무조건 꺼뒀던 디버그 모드를 켠다.
add_definitions(-DZ_DEBUG_LEVEL=4) # 0: None, 1: Error, 2: Warn, 3: Info, 4: Trace
## 주의: 이 옵션을 켜는 순간 펌웨어 용량이 10KB~20KB 팽창할 수 있다!
2. UART(시리얼) 출력 포팅
Pico 엔진이 printf 를 호출할 때, 이 함수가 실제로 당신이 연결한 USB 라인이나 UART 라인을 타고 나오게 끔 _write 시스템 콜을 재정의(Override)해야 한다. (GCC 기준)
#include <stdio.h>
// 외부 UART 하드웨어 핸들러 (예: STM32)
extern UART_HandleTypeDef huart1;
int _write(int file, char *ptr, int len) {
// Pico 엔진 내부의 printf 가 결국 이 함수를 거쳐 나간다.
HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 10);
return len;
}
이제 테라텀(TeraTerm) 이나 푸티(PuTTY) 로 시리얼 포트를 열어보면, Pico 가 TCP 악수(Handshake)를 할 때마다, 패킷을 조립할 때마다 화면을 미친 듯이 스크롤 하며 생존 신고를 할 것이다. 에러가 나면 정확히 “어떤 버퍼가 모자른지” 로그가 찍힌다.
2. 메모리 릭(Memory Leak) 및 스택 오버플로우(Stack Overflow) 방지
펌웨어를 올려서 3시간은 잘 돌아가는데, 다음 날 출근해 보면 로봇이 돌덩이가 되어 있다. 전형적인 메모리 파괴 증상이다.
2.0.1 [Runbook] 콜백(Callback) 스택 압살 방어 전술
1. 스택 터짐(Stack Overflow)의 원인
Pico 의 콜백 함수(cmd_callback 등)는 메인 함수의 z_session_yield 가 도는 아주 협소한 스택 공간 위에서 기생하여 돌아간다.
그런데 당신이 만약 콜백 함수 안에서 버퍼를 한 1KB 짜리 잡았다 치자.
void cmd_callback(const z_sample_t *s, void *ctx) {
// ❌ 절대 금지: 콜백 안에서 거대한 배열 할당!
char big_buffer[1024];
sprintf(big_buffer, "...");
}
함수가 실행되는 순간 스택 포인터(SP)가 한도를 넘어가버려(Overflow), 다른 태스크의 메모리나 운영체제 코어를 박살 내버리고 하드 파울트(Hard Fault)를 일으킨다.
콜백 안에서는 무조건 전역 변수(Static)를 쓰거나 극도로 작은 지역 변수(Heap 도 안 됨)만 써야 한다.
2. FreeRTOS 스택 워터마크 모니터링
통신 태스크가 스택을 얼마나 파먹었는지 수시로 감시하라.
// 통신 task의 무한루프 안에서 호출
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
// 이 값이 0 에 가까워진다면 당장 태스크 생성 시 스택 크기(Stack size)를 두 배로 늘려라!
printf("남은 스택 공간: %lu\n", uxHighWaterMark);
3. 네트워크 연결 단절 감지 및 자동 재연결(Auto-reconnect) 루틴 구현
로봇이 터널에 들어갔다. 공유기와의 연결이 끊어졌다. Zenoh 의 세션 파이프는 깨져버렸고, 터널을 빠져나와도 영원히 통신은 복구되지 않는다. 소프트웨어 리셋이 필요하다.
3.0.1 [Runbook] 피닉스(Phoenix) 불사신 파이프라인 전술
가장 좋은 리셋은 “시스템 보드 자체를 초기화(Hard Reset)” 하는 것이지만, 모터가 도는 도중에 그럴 순 없다. Pico 세션만 닫았다가 다시 개통해야 한다.
#include <zenoh-pico.h>
extern z_session_t session;
// 타임아웃 감시자
uint32_t last_rx_time = 0;
void keepalive_callback(const z_sample_t *s, void *ctx) {
// 서버가 나에게 주기적으로 쏴주는 Heartbeat
last_rx_time = HAL_GetTick();
}
void network_monitor_loop() {
uint32_t current = HAL_GetTick();
// 5초 동안 단 하나의 데이터도 도착하지 않았다면? 망이 끊긴 것이다!
if (current - last_rx_time > 5000) {
printf("🚨 통신 단절 감지! Zenoh 세션을 강제 부활시킨다!\n");
// 1. 기존 세션을 파괴한다
// (이 때 LwIP 소켓 자원이 반환되며 메모리 누수를 막는다)
z_close(&session);
// 시간차를 둔다 (공유기에서 소켓 할당 해제할 시간)
vTaskDelay(pdMS_TO_TICKS(1000));
// 2. 다시 환경 설정 세팅
z_config_t config = z_config_default();
zp_config_insert_json(&config, ZP_PEER_KEY, "\"tcp/192.168.0.10:7447\"");
// 3. 재개통 (성공할 때까지 무한 재시도!)
while (z_open(&session, &config, NULL) < 0 || !z_check(session)) {
printf("재접속 시도 중...\n");
vTaskDelay(pdMS_TO_TICKS(2000));
}
// 4. 구독(Subscriber) 라인 재구축
// (세션이 부활했으므로 이전에 세팅했던 구독자 선언은 다 날아갔다!)
setup_my_subscribers();
// 5. 부활 완료
last_rx_time = HAL_GetTick();
printf("✅ 예비 파이프라인 개통 완료.\n");
}
}
이 끊임없는 Re-declare(재선언) 아키텍처는 스마트 기기가 와이파이를 잡았다 놨다 하는 현실 환경에서 생존하기 위한 최소한의 발버둥이자 궁극의 해결책이다.
4. 패킷 스니핑(Wireshark)을 통한 프로토콜 레벨 디버깅
MCU 쪽 코드를 백날 들여다봐도 문제가 어딘지 모르겠다면, 전파 속으로 손을 집어넣어 데이터가 공중에 날아다니는 형태 자체를 눈으로 확인해야 한다.
4.0.1 [Runbook] 허공 낚시(Air-sniffing) 전술
라우터(Raspberry Pi 등)나 로컬 PC의 네트워크 인터페이스를 도청한다.
1. Wireshark 준비 및 Zenoh 플러그인 장착
Zenoh 통신망은 UDP 든 TCP 든 7447 포트를 기점으로 작동한다. 일반 와이어샤크(Wireshark)로 보면 그냥 “데이터(Data)” 로 뭉뚱그려 보여서 읽을 수가 없지만, Zenoh 가 제공하는 전용 Wireshark Dissector(해석 플러그인)를 깔면 완전히 까발려진다.
2. 도청 필터(Filter) 설정
Wireshark 의 필터 창에 다음 커맨드를 갈겨 넣는다.
tcp.port == 7447 || udp.port == 7447
3. 트러블슈팅 부검 결과 해석
-
현상 1: 로봇의 센서가 계속 변하는데, 대시보드는 멈춰있다?
-> 와이어샤크를 보니PUT프레임은 계속 날아가고 있는데, 대상 Key 가 매핑 ID로 변환되는 과정(DECLARE) 에서 꼬여서 라우터가 못 알아먹고 버리고 있다! (ID 설정 코드 확인) -
현상 2: 로봇이 공유기에 연결이 안 된다?
-> 와이어샤크를 보니, 내 로봇 IP (192.x.x.x) 에서 목적지 라우터로Init패킷을 쐈는데, 라우터가 불쌍한 센서에게Ack(확인)패킷을 쏴주지 않고 있다! 라우터의zenohd가 꺼져있거나, 라우터 PC 윈도우 방화벽이 7447 포트를 블로킹한 게 확실하다!
MCU 개발자는 코딩 절반, 와이어샤크 절반의 비율로 일해야만 퇴근할 수 있음을 명심하라.