π English Abstract
This technical article investigates the root cause of socket freeze issues occurring in STM32 MCUs running the LwIP TCP/IP stack under high-throughput Modbus TCP traffic. It analyzes how dynamic memory (pbuf) exhaustion triggers Ethernet MAC DMA Receive Buffer Unavailable (RBUS) deadlocks. Furthermore, it delivers a practical troubleshooting guide including lwipopts.h memory tuning, DMA Zero-Copy RX ring buffer refactoring, and STM32 D-Cache consistency management for robust industrial embedded networking.
μ°μ μ© μλ² λλ μμ€ν μμ STM32 λ§μ΄ν¬λ‘컨νΈλ‘€λ¬(MCU)μ LwIP(Lightweight IP) κ²½λ λ€νΈμν¬ μ€νμ μ‘°ν©μ νμ€μ²λΌ μ¬μ©λκ³ μμ΅λλ€. νΉν 곡μ₯ μλν λ° μ λ ₯ μ μ΄ λΆμΌμμ Modbus TCP νλ‘ν μ½μ ν΅ν λͺ¨λν°λ§ λ° μ μ΄ μꡬμ¬νμ΄ κΈμ¦ν¨μ λ°λΌ, MCUκ° μ²λ¦¬ν΄μΌ νλ μ΄λΉ ν¨ν· μ(PPS, Packets Per Second)μ λ¨μλ ν¬κ² μ¦κ°νμ΅λλ€.
νμ§λ§ νλ νμ₯μμ μμ€ν μ μ΄μ©νλ€ λ³΄λ©΄, μ΄κΈ° κ°λ° λ¨κ³λ λ¨λ°μ± ν μ€νΈμμλ λνλμ§ μλ "μμΌ λκ²°(Socket Freeze)" λλ "ν΅μ λ§λΉ" νμμ΄ κ³ μ λ(High-Throughput) νΈλν½ νκ²½μμ λλ°μμ΄ λ°μνλ κ²½μ°κ° μμ΅λλ€. ν(Ping, ICMP) μλ΅μ‘°μ°¨ λμ§ μκ±°λ TCP μ μ μμ²΄κ° λ겨 리μ μ κΉμ§ 볡ꡬλμ§ μλ μ΄ λ¬Έμ λ pbuf λ©λͺ¨λ¦¬ κ³ κ°κ³Ό Ethernet MAC DMAμ RBUS κ΅μ°© μνκ° κ²°ν©νμ¬ λ°μνλ μ νμ μΈ νλμ¨μ΄-μννΈμ¨μ΄ κ²½κ³ νΈλ¬λΈμν λμμ λλ€.
λ³Έ ν¬μ€ν μμλ STM32 MCU νκ²½μμ κ³ μ λ Modbus TCP ν¨ν· μμ μ λ°μνλ μμΌ λκ²°μ λ©μ»€λμ¦μ μ¬κ²© λΆμνκ³ , μ΄λ₯Ό μμ²μ μΌλ‘ ν΄κ²°νκΈ° μν LwIP λ©λͺ¨λ¦¬ μ€ν μ΅μ ν λ° DMA Zero-Copy(μ λ‘ μΉ΄νΌ) μμ λ²νΌλ§ ꡬν κΈ°λ²μ μμΈν μ€λͺ ν©λλ€.
1. Modbus TCP κ³ μ λ νκ²½μμ λ°μνλ μμΌ λκ²°μ μμΈ λΆμ
STM32μ μ΄λλ· νλμ¨μ΄ ꡬ쑰λ Ethernet MAC Controllerμ λ΄λΆ DMA(Direct Memory Access) Engineμ ν΅ν΄ PHY μΉ©μΌλ‘λΆν° μ λ¬λ°μ λ°μ΄ν° ν¨ν·μ RAM μμ μμ λμ€ν¬λ¦½ν°(RX Descriptor) Ring Bufferμ κΈ°λ‘ν©λλ€. μ΄λ LwIP μ€νκ³Όμ μ°λ κ³Όμ μμ μ¬κ°ν λ³λͺ© νμμ΄ λ°μν μ μμ΅λλ€.
κ°. Interrupt Floodμ pbuf Allocation μ€ν¨ λ©μ»€λμ¦
κ³ μ λ Modbus TCP ν΄λ§(Polling) ν¨ν·μ΄ λ°λ €λ€λ©΄ Ethernet RX μΈν°λ½νΈκ° λλμΌλ‘ λ°μν©λλ€. LwIPμ ν¬ν
μΈν°νμ΄μ€μΈ ethernetif_input() ν¨μλ μμ μΈν°λ½νΈ μλΉμ€ 루ν΄(ISR) λλ λ
립λ Ethernet Taskμμ μ€νλμ΄ λ€μκ³Ό κ°μ μμ
μ μνν©λλ€:
- DMA RX λμ€ν¬λ¦½ν°λ‘λΆν° ν¨ν· μμ μ¬λΆ νμΈ
pbuf_alloc(PBUF_RAW, len, PBUF_POOL)μ νΈμΆνμ¬ ν¨ν· λμ λ©λͺ¨λ¦¬ ν λΉ- DMA μμ λ²νΌμ λ°μ΄ν°λ₯Ό μλ‘ ν λΉλ
pbufλ‘ λ©λͺ¨λ¦¬ 볡μ¬(memcpy) - LwIP μ€νμ μμ κ³μΈ΅(
tcp_input())μΌλ‘pbufμ λ¬
λ¬Έμ λ Modbus TCP ν¨ν· μ²λ¦¬ μλλ³΄λ€ ν¨ν· μ μ
μλκ° λΉ λ₯Ό λ λ°μν©λλ€. LwIP ν λ©λͺ¨λ¦¬λ PBUF_POOLμ΄ λ¨μ§ μκ² λλ©΄ pbuf_alloc()μ NULLμ λ°νν©λλ€.
λ. DMA RBUS(Receive Buffer Unavailable) κ΅μ°© μν(Deadlock)
pbuf_alloc() μ€ν¨ μ μ μ ν μμΈ μ²λ¦¬κ° μ΄λ£¨μ΄μ§μ§ μμΌλ©΄, DMA μμ λμ€ν¬λ¦½ν°λ 'OWN bit = DMA' μνλ‘ λλμκ°μ§ λͺ»νκ³ 'OWN bit = CPU' μνλ‘ λ¨μμκ² λ©λλ€.
μ°μν΄μ λͺ¨λ RX λμ€ν¬λ¦½ν°κ° λΉμμ§μ§ λͺ»νκ³ μ°¨λ²λ¦¬λ©΄ STM32 μ΄λλ· DMA μν λ μ§μ€ν°(ETH_DMASR)μ RBUS(Receive Buffer Unavailable Status) λΉνΈκ° μΈν
λ©λλ€. DMA μμ§μ μμ λ²νΌκ° μλ€κ³ νλ¨νμ¬ RX Suspend(μμ μΌμμ μ§) μνλ‘ μ§μ
νκ² λλ©°, μ΄ν μλ‘μ΄ ν¨ν·μ΄ 물리 μΉ©μ λ€μ΄μλ νλμ¨μ΄ μμ€μμ ν¨ν·μ λλ‘(Drop)ν΄ λ²λ¦½λλ€.
λμ± μΉλͺ μ μΈ μ μ, LwIP λ©λͺ¨λ¦¬κ° λμ€μ ν΄μ λμ΄ μ¬μ κ° μκΈ°λλΌλ DMA RX μμ§μ΄ Resume(μ¬κ°) λͺ λ Ήμ λ°μ§ λͺ»νλ©΄ μμν ν¨ν·μ μμ νμ§ λͺ»νλ μμΌ λκ²° νμμΌλ‘ μ΄μ΄μ§λ€λ κ²μ λλ€.
2. LwIP λ©λͺ¨λ¦¬ μ΅μ (lwipopts.h) λ° TCP μ€ν μ΅μ ν
κ³ μ λ ν¨ν· μ²λ¦¬λ₯Ό μν΄ κ°μ₯ λ¨Όμ μνν΄μΌ νλ μμ
μ LwIP νκ²½ μ€μ νμΌμΈ lwipopts.hλ₯Ό μ€λ¬΄ νΈλν½μ μ ν©νκ² νλνλ κ²μ
λλ€. κΈ°λ³Έ CubeMX μλ μμ± μ€μ κ°μ λ©λͺ¨λ¦¬ μ¬μ©λμ κ·Ήλ¨μ μΌλ‘ μ€μ΄λλ‘ μ€κ³λμ΄ μμ΄ κ³ μ ν΅μ μ μ¦μ pbuf κ³ κ°μ μ λ°ν©λλ€.
κ°. ν΅μ¬ λ©λͺ¨λ¦¬ νλΌλ―Έν° μ¬μ€μ
λ€μ μ€μ μ μ μ©νμ¬ pbuf λ©λͺ¨λ¦¬ νκ³Ό μμ νμ ν¬κΈ°λ₯Ό λλνκ² νμ₯ν΄μΌ ν©λλ€:
MEM_ALIGNMENT = 4: STM32 32-bit μν€ν μ² μ λ ¬ κΈ°μ€ μ€μ .MEM_SIZE: LwIP ν λ©λͺ¨λ¦¬ ν¬κΈ°λ₯Ό μ΅μ 16KB ~ 32KB μ΄μμΌλ‘ νμ₯ (μ:16 * 1024).PBUF_POOL_SIZE: ν¨ν· μμ μ μ© ν κ°μλ₯Ό κΈ°λ³Έκ°(4~8)μμ 24~32 μ΄μμΌλ‘ λν μ¦μ€.PBUF_POOL_BUFSIZE: Ethernet Frame μ 체 ν¬κΈ°(1518 Byte) + LwIP ν€λ μ€νμ μ λ ¬μ κ³ λ €νμ¬ 1536 Byte λλ 1552 Byteλ‘ μ§μ .MEMP_NUM_PBUF: μμ€ν μ 체μμ λμ 보μ κ°λ₯ν pbuf ꡬ쑰체 κ°μλ₯Ό 32 μ΄μμΌλ‘ μ€μ .
λ. TCP Flow Control λ° Window Size Tuning
μ‘μ μΈ‘(Modbus Client/Master)μ΄ μμ μΈ‘(STM32 Server/Slave)μ μ²λ¦¬ λ₯λ ₯μ μ΄κ³Όνμ¬ ν¨ν·μ νΌλΆλ κ²μ λ§κΈ° μν΄ TCP μμ μλμ° ν¬κΈ°λ₯Ό μ μ΄ν΄μΌ ν©λλ€.
TCP_WND: μμ μλμ° ν¬κΈ°λ₯Ό(4 * TCP_MSS)μμ€μΈ 5840 Byte μ λ μ€μ . μμ λ²νΌκ° λΆμ‘±ν΄μ§λ©΄ TCP Ack ν¨ν· λ΄μ Window Sizeλ₯Ό μ€μ¬ μ‘μ μΈ‘μ μ‘μ μλλ₯Ό μμ°μ€λ½κ² λ¦μΆλ νλ¦ μ μ΄(Flow Control)λ₯Ό μ λν©λλ€.TCP_QUEUE_OOSEQ = 0: μμκ° λ€λ°λ ν¨ν·(Out-of-Order Packet)μ μ¬μ‘°ν©νκΈ° μν΄ νμ 보κ΄νλ κΈ°λ₯μ λΉνμ±νν©λλ€. κ³ μ λ‘컬 LAN ν΅μ νκ²½μΈ Modbus TCPμμλ μμ μ΄κΈλ¨μ΄ μ μΌλ―λ‘ μ΄ κΈ°λ₯μ κΊΌμ λ©λͺ¨λ¦¬ λλΉλ₯Ό λ§μ΅λλ€.
3. STM32 Ethernet DMA Zero-Copy μμ λ²νΌλ§ ꡬν
λ©λͺ¨λ¦¬ μ¦μ€λ§μΌλ‘λ CPU μ€λ²ν€λ μ체λ₯Ό ν΄κ²°ν μ μμ΅λλ€. μμ μΈν°λ½νΈκ° λ°μν λλ§λ€ DMA λ²νΌμμ pbufλ‘ λ°μ΄ν°λ₯Ό memcpy() νλ λ°©μμ CPU μ¬μ΄ν΄μ κ³Όλνκ² μλͺ¨νμ¬ κ³ μ λ λͺ¨λλ²μ€ ν¨ν· μ²λ¦¬μ λ³λͺ© μμΈμ΄ λ©λλ€. μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ Zero-Copy(μ λ‘ μΉ΄νΌ) μμ μν€ν
μ²λ₯Ό ꡬμΆν΄μΌ ν©λλ€.
κ°. Zero-Copy μμ μν€ν μ²μ μ리
Zero-Copy μμ λ°©μμ DMA RX λμ€ν¬λ¦½ν°μ λ²νΌ ν¬μΈν°μ LwIPμ pbuf νμ΄λ‘λ(Payload) μ£Όμλ₯Ό μ§μ λ§€ννλ κΈ°λ²μ
λλ€.
- μ΄κΈ°ν μ
PBUF_POOLμμpbufλ₯Ό 미리 ν λΉλ°μ DMA RX λμ€ν¬λ¦½ν°μ Buffer Addressμ μ€μ ν©λλ€. - ν¨ν· μμ μ DMAκ°
pbuf->payloadλ©λͺ¨λ¦¬μ μ§μ λ°μ΄ν°λ₯Ό μλλ€. - μΈν°λ½νΈ λ°μ μ CPUλ λ°μ΄ν° λ³΅μ¬ μμ΄ ν΄λΉ
pbufν¬μΈν°λ§ μμ LwIP μ€νμΌλ‘ μ λ¬(Pass-through)ν©λλ€. - μλ‘μ΄
pbufλ₯Ό ν λΉλ°μ DMA λμ€ν¬λ¦½ν°μ λ€μ μ°κ²°νκ³ OWN bitλ₯Ό DMAλ‘ λ겨μ€λλ€.
λ. DMA RBUS κ΅μ°© μν νμΆ μμΈ μ²λ¦¬ μ½λ
μ무리 μ΅μ ννλλΌλ λ©λͺ¨λ¦¬κ° μκ° κ³ κ°λ μ μμ΅λλ€. μ΄λ pbuf_alloc()μ΄ NULLμ λ°νν κ²½μ°, μμ ν¨ν·μ μ¦μ λλ‘νκ³ DMA μμ μμ§μ 볡ꡬνλ μ½λκ° νμμ μ
λλ€.
μλ μ½λλ STM32 HAL / LL λΌμ΄λΈλ¬λ¦¬ νκ²½μμ ethernetif_input()μ 리ν©ν λ§ν μμΈ μ²λ¦¬ ν¨μΉ μμμ
λλ€:
struct pbuf *low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
ETH_BufferTypeDef RxBuff;
uint32_t framelength = 0;
/* 1. DMA RX λμ€ν¬λ¦½ν°λ‘λΆν° ν¨ν· μ 보 νλ */
if (HAL_ETH_GetRxDataBuffer(&heth, &RxBuff) == HAL_OK)
{
framelength = HAL_ETH_GetRxDataLength(&heth);
/* 2. Zero-Copyλ₯Ό μν Custom pbuf μμ± λλ μ pbuf ν λΉ */
p = pbuf_alloc(PBUF_RAW, framelength, PBUF_POOL);
if (p != NULL)
{
/* μμ λ DMA λ²νΌ λ°μ΄ν°λ₯Ό pbufλ‘ μ§μ λ§€ν λλ Zero-Copy μ²λ¦¬ */
/* STM32H7/F7 λ± Cache μ μ© λͺ¨λΈμ κ²½μ° Invalidate D-Cache νμ */
SCB_InvalidateDCache_by_Addr((uint32_t *)RxBuff.buffer, framelength);
pbuf_take(p, RxBuff.buffer, framelength);
}
else
{
/* [μ€μ] pbuf ν λΉ μ€ν¨ μ (λ©λͺ¨λ¦¬ κ³ κ°) ν΄λΉ ν¨ν· λλ‘ λ° DMA λμ€ν¬λ¦½ν° μ¦μ λ°ν */
// LINK_STATS_INC(link.memerr);
// LINK_STATS_INC(link.drop);
}
/* 3. DMA μμ λμ€ν¬λ¦½ν° 리μ
λ° CPUμμ DMAλ‘ μμ κΆ λ°ν */
HAL_ETH_BuildRxDescriptors(&heth);
}
/* 4. RBUS (Receive Buffer Unavailable) μΌμμ μ§ μν μ κ² λ° Resume */
if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != 0U)
{
/* RBUS νλκ·Έ ν΄λ¦¬μ΄ */
heth.Instance->DMASR = ETH_DMASR_RBUS;
/* DMA RX Poll Demand λ μ§μ€ν°μ κ°μ μ¨μ DMA μμ μ¬κ° */
heth.Instance->DMARXPOLLD = 0U;
}
return p;
}
λ€. Cortex-M7 (STM32F7 / STM32H7) D-Cache μΌκ΄μ± μ μ§
High-Performance STM32 MCUλ₯Ό μ¬μ©νλ κ²½μ° Data Cache(D-Cache) νμ±νμ λ°λ₯Έ μΌκ΄μ±(Coherency) λ¬Έμ λ₯Ό λ°λμ κ³ λ €ν΄μΌ ν©λλ€. DMAκ° SRAMμ λ°μ΄ν°λ₯Ό μ¨λ CPUμ D-Cacheμ μ΄μ λ°μ΄ν°κ° λ¨μμμΌλ©΄ Modbus ν¨ν· λ°μ΄ν°κ° μ€μΌ(Corruption)λ©λλ€.
- MPU(Memory Protection Unit) μ€μ : μ΄λλ· DMA λμ€ν¬λ¦½ν° λ° μμ λ²νΌ μμμ
Bufferable,Non-CacheableμμμΌλ‘ μ§μ νλ κ²μ κΆμ₯ν©λλ€. - Cache Invalidation: MPU λ―Έμ€μ μ ν¨ν· μ½κΈ° μ
SCB_InvalidateDCache_by_Addr()λ₯Ό λ°λμ νΈμΆνμ¬ μΊμλ₯Ό 무ν¨νν΄μΌ ν©λλ€.
4. μ€λ¬΄ κ²μ¦ λ° μ±λ₯ μΈ‘μ κ²°κ³Ό
μμ μ΅μ ν μμ μ STM32F407/STM32H743 κΈ°λ° Modbus TCP Slave μ₯λΉμ μ μ©νμ¬ κ³ μ λ λΆν ν μ€νΈ(Stress Test)λ₯Ό μ§ννμμ΅λλ€.
| κ΅¬λΆ | μ΅μ ν μ (Default Config) | μ΅μ ν ν (Zero-Copy + RBUS Resume) |
|---|---|---|
| μ΅λ μμ ν¨ν· μ²λ¦¬λ | μ½ 350 PPS (μ΄λΉ ν¨ν· μ) | 2,500+ PPS μ΄μ μ°μ μ²λ¦¬ |
| CPU μ μ μ¨ (1,000 PPS κΈ°μ€) | 85% μ΄μ (λ³λͺ© μ¬ν) | 22% λ΄μΈ μμ ν |
| μμΌ λκ²° λ°μ μ¬λΆ | λΆν μ£Όμ 10λΆ μ΄λ΄ RBUS λ½μ λ°μ | 72μκ° λ‘±λ° ν μ€νΈ ν¨μ€ (무결μ ) |
5. κ²°λ‘ λ° μμ½
STM32 MCU κΈ°λ° LwIP λ€νΈμν¬ μμ© νλ‘κ·Έλ¨μμ λ°μνλ μμΌ λκ²° νμμ λ¨μν μννΈμ¨μ΄ λ²κ·Έκ° μλ, LwIP λ©λͺ¨λ¦¬ κ³ κ°κ³Ό Ethernet DMA νλμ¨μ΄μ RBUS μμ μ€λ¨ μνκ° λ§λ¬Όλ € λ°μνλ λνμ μΈ νΈλ¬λΈμν μ¬λ‘μ λλ€.
μ΄ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄μλ λ€μ μΈ κ°μ§ ν΅μ¬ λμ μ‘°μΉκ° λ°λμ λ³νλμ΄μΌ ν©λλ€:
lwipopts.hνλ:PBUF_POOL_SIZEμ¦μ€ λ°TCP_WNDμ‘°μ μ ν΅ν μννΈμ¨μ΄ μμ μ¬μ ν보.- Zero-Copy RX ꡬν:
memcpy()μ΅μν λ° DMA Ring Buffer μ΅μ νλ₯Ό ν΅ν CPU μ μ μ¨ κ°μ. - RBUS μμΈ μ²λ¦¬ μ½λ©:
pbufν λΉ μ€ν¨ μ ν¨ν· λλ‘ λ° DMA RX Resume(DMARXPOLLD) κ°μ μ€νμ ν΅ν νλμ¨μ΄ λ½μ λ°©μ§.
νμ₯ μ€μΉ ν λ°μνλ μ΄λλ· ν΅μ λ§λΉλ μ ν μ λ’°μ±μ μΉλͺ μ μΈ ν격μ μ€λλ€. Modbus TCP λ± μ΄λΉ λ°μ΄ν° μ μ‘ λΉλκ° λμ μ°μ μ© μ΄ν리μΌμ΄μ μ κ°λ° μ€μ΄λΌλ©΄, μ΄κΈ° μ€κ³ λ¨κ³λΆν° λ³Έ ν¬μ€ν μ Zero-Copy μμ λ²νΌλ§ ꡬ쑰μ RBUS μμΈ μ²λ¦¬ κΈ°λ²μ λ°λμ λ°μνμκΈ° λ°λλλ€.