lwIP 학습 로드맵: 보드 연결부터 TCP 문제 해결까지
lwIP 글이 어렵게 느껴질 때 필요한 순서를 안내합니다. 실행 문맥, netif와 Ethernet, pbuf 소유권, UDP, TCP 흐름 제어를 단계별로 읽고 증상에 맞는 글을 찾을 수 있습니다.
lwIP 글을 처음부터 전부 읽을 필요는 없습니다. 사용하는 실행 모델과 지금 겪는 증상에 맞는 단계부터 보면 됩니다.
lwIP는 MCU와 임베디드 장치에서 쓰는 TCP/IP 프로토콜 스택입니다. netif는 네트워크 인터페이스, pbuf는 패킷 데이터를 담는 버퍼 구조를 뜻합니다.
이 로드맵은 개념을 익히는 순서와 문제를 찾는 바로가기를 겸합니다. NO_SYS와 RTOS, RAW API와 netconn/socket API는 동작 규칙이 다르므로 각 글에서 다루는 조건을 먼저 확인하세요. 처음부터 읽는다면 1단계부터 시작하고, 이미 증상이 있다면 해당 단계의 글만 골라 읽으면 됩니다.
1. 실행 모델과 호출 문맥
먼저 타이머가 어디서 실행되고, 네트워크 API를 어떤 문맥에서 호출해야 하는지 확인합니다.
- NO_SYS에서 lwIP 타이머를 실행하는 방법: bare-metal에서 ARP, DHCP, TCP 타이머가 진행되지 않을 때 확인합니다.
- ISR에서 tcpip_thread로 작업 넘기기: 인터럽트에서 네트워크 처리를 직접 하지 않도록 경계를 잡습니다.
- tcpip_thread callback의 self-deadlock: 코어 callback에서 blocking netconn/socket 호출이 멈춤을 만드는 경우를 살펴봅니다.
2. 인터페이스와 Ethernet bring-up
링크 LED만으로 IP 통신 준비가 끝났다고 판단할 수 없습니다. 인터페이스 상태, DHCP, DMA 메모리와 checksum 책임을 나눠 봅니다.
- netif 상태와 DHCP 시작 순서: PHY 링크는 올라왔는데 DHCP가 시작되지 않거나 재연결 뒤 주소를 받지 못할 때 확인합니다.
- DMA 버퍼와 D-Cache 일관성: 패킷 payload가 간헐적으로 손상될 때 RX/TX cache 처리와 정렬을 확인합니다.
- checksum offload 설정 불일치: ping은 되지만 TCP/UDP가 실패하거나 송수신 방향에 따라 결과가 다를 때 확인합니다.
3. pbuf와 driver 사이의 소유권
패킷을 넘긴 뒤 누가 메모리를 보유하고 언제 반환하는지 정리합니다. 복사 방식으로 안정성을 먼저 확인한 뒤 zero-copy 최적화를 검토합니다.
- pbuf 종류와 zero-copy 수명: 외부 DMA 버퍼와 pbuf의 소유권, 참조 수명과 해제 책임을 이해합니다.
- pbuf 체인의 len과 tot_len: 한 pbuf 조각과 체인 전체 길이를 혼동해 데이터가 잘릴 때 확인합니다.
- tcpip_input에 넘긴 RX pbuf의 소유권: driver가 입력 함수에 pbuf를 넘긴 뒤 중복 해제하거나 너무 빨리 재사용하는 문제를 다룹니다.
- UDP 수신 callback의 pbuf 해제: callback 뒤 pbuf 반환을 놓쳐 pool이 고갈되는 경우를 확인합니다.
- linkoutput의 TX descriptor와 pbuf 수명: 송신 정체, descriptor 회수와 zero-copy TX 버퍼 반환 흐름을 살펴봅니다.
4. UDP 송수신과 링크 복구
UDP 송신 오류를 payload 문제로 단정하지 말고, route와 netif 상태부터 확인합니다.
- UDP send의 ERR_RTE와 netif route: 주소 할당, default netif, gateway와 link 상태를 구분합니다.
- UDP broadcast가 전송되지 않을 때: socket 옵션, netif 설정, broadcast 주소의 범위를 확인합니다.
- 링크 복구 뒤 오래된 ARP 상태: 케이블 재연결 뒤 첫 패킷이 사라지거나 통신이 늦게 회복될 때 확인합니다.
5. TCP 흐름 제어와 자원
TCP 송신 큐가 차는 문제, 수신 window가 열리지 않는 문제, 연결 종료 시점의 메모리 오류를 서로 다른 원인으로 나눠 봅니다.
- TCP 버퍼와 메모리 pool의 역할:
TCP_SND_BUF,TCP_WND,MEM_SIZE, segment와 pbuf pool을 구분합니다. - tcp_write()의 ERR_MEM과 송신 큐: free RAM뿐 아니라 송신 byte 예산, queue 길이와 ACK 진행을 확인합니다.
- tcp_recved()와 수신 window 정체: RAW API에서 수신 데이터를 소비한 뒤 window를 다시 여는 흐름을 확인합니다.
- tcp_close()가 ERR_MEM을 반환할 때: 연결 PCB를 보존하고 ACK callback 또는 polling에서 종료를 재시도합니다.
글을 읽을 때 확인할 것
예제의 설정값과 코드 조각은 그대로 복사하기보다 사용 중인 lwIP 버전, lwipopts.h, API 종류와 포트의 실행 문맥에 맞는지 확인하세요. 공식 API 설명과 실제 driver 동작이 다르면 프로젝트의 소스와 설정을 기준으로 판단합니다.
증상이 분명하면 위 단계를 건너뛰고 해당 문제 글부터 읽어도 됩니다. 원인을 좁힐 때는 재현 조건, interface 상태, pbuf/queue 통계와 패킷 캡처를 함께 남기세요. 다른 lwIP 글은 lwIP 카테고리에서 볼 수 있습니다.
참고로 Raspberry Pi Pico와 W5100S Azure IoT 프로젝트 회고는 W5100S의 hardwired TCP/IP stack을 사용하는 별도 구조이며 lwIP 예제가 아닙니다.