|
 |
| 목록으로 가기.. |
|
STM32H747I-DISCO 이더넷(RMII) 및 LwIP 완벽 구현 및 트러블슈팅 가이드 |
김종호
|
삭제하기
|
|
# STM32H747I-DISCO Ethernet (RMII) + LwIP Complete Implementation & Troubleshooting Guide
# STM32H747I-DISCO 이더넷(RMII) 및 LwIP 완벽 구현 및 트러블슈팅 가이드
---
## 0. Introduction & Purpose / 서론 및 목적
### [KO] 서론 및 목적
이 문서는 **STMicroelectronics STM32H747I-DISCO 개발 보드**에서 고성능 듀얼 코어(Cortex-M7)와 LAN8742A 이더넷 PHY 칩을 결합하여, **LwIP 네트워크 스택과 FreeRTOS 기반의 양방향 이더넷 통신(TX/RX, ICMP Ping, 고속 UDP 송수신)**을 완벽하게 구현하기 위한 실전 기술 가이드입니다.
STM32H7 시리즈는 고성능 Cortex-M7 코어의 초고속 L1 데이터 캐시(D-Cache)와 독립된 도메인별 메모리 구조(D1 AXI-SRAM, D2 SRAM1~3)를 갖추고 있어, 이전 세대 MCU(F4, F7)에 비해 이더넷 DMA 구성 난이도가 매우 높습니다. 특히 ST 공식 툴체인(STM32CubeMX)의 자동 생성 코드에 존재하는 링커 스크립트 오타, D-Cache 데이터 불일치(Coherency) 문제, 그리고 보드 내 온보드 회로(MEMS 마이크)와의 핀 충돌 등으로 인해 많은 개발자들이 **"Ping 응답 실패", "무한 TX BUSY 잠김", "MISCOMPARE 데이터 불일치 오류"**를 겪게 됩니다.
**본 문서의 목적은 다음과 같습니다:**
1. **하드웨어 개조 포인트 명확화**: DISCO 보드의 기본 온보드 충돌 회로를 안전하게 분리하고 이더넷 RMII 신호선을 100% 활성화하는 솔더 브릿지 납땜 개조 포인트를 상세히 제시합니다.
2. **소프트웨어 난제의 근본 원인 규명 및 해결**: MPU(메모리 보호 유닛) 비캐시 설정, 링커 스크립트 매핑 버그 패치, Cortex-M7 D-Cache Clean/Invalidate의 완벽한 타이밍과 순서를 명쾌히 설명합니다.
3. **100% 동작 검증된 소스 코드 제공**: 1ms 이내의 무손실 Ping(ICMP) 응답과 100ms 주기의 고속 UDP 전송이 완벽히 검증된 실전 코드를 공유하여, 개발자가 불필요한 시행착오 없이 즉시 상용 임베디드 프로젝트, 서보 모터 제어, 산업용 IoT 시스템에 적용할 수 있도록 돕습니다.
---
### [EN] Introduction & Purpose
This document serves as a comprehensive, step-by-step engineering and troubleshooting guide for establishing robust, bi-directional Ethernet communication (TX/RX, ICMP Echo Reply, high-speed UDP) on the **STMicroelectronics STM32H747I-DISCO** discovery kit using the **LAN8742A RMII PHY**, **FreeRTOS (CMSIS-RTOS V2)**, and the **LwIP 2.1.2** TCP/IP stack running on the high-performance ARM Cortex-M7 core.
The STM32H7 family incorporates a high-performance L1 Data Cache (D-Cache), an AXI bus matrix, and segmented memory domains (D1 AXI-SRAM, D2 SRAM1–3). While this architecture delivers remarkable throughput, it introduces intricate challenges when synchronizing Ethernet DMA hardware with the CPU. Developers frequently encounter pervasive issues such as **perpetual `TX BUSY` lockups, packet loss, `MISCOMPARE at offset 28` errors during Ping requests**, and silent network freezes caused by CubeMX linker script bugs, MPU misconfigurations, and cache coherency hazards.
**The primary purposes of this guide are:**
1. **Provide a Definitive Hardware Blueprint**: Detail the exact solder bridge rework required on the STM32H747I-DISCO to isolate onboard MEMS microphone conflicts and properly route all RMII interface signals.
2. **Resolve Fundamental Software & Cache Roadblocks**: Explain the correct setup for MPU non-cacheable descriptor regions, fix linker script section naming bugs, and demonstrate the precise placement of D-Cache `Clean` and `Invalidate` operations.
3. **Deliver a 100% Production-Verified Reference Codebase**: Supply fully verified, end-to-end source code achieving deterministic sub-millisecond (<1ms) Ping response times and continuous UDP transmission, empowering engineers to immediately integrate reliable networking into motor control, industrial IoT, and real-time embedded systems.
---
## 1. System Specifications / 하드웨어 및 시스템 사양
### [KO] 하드웨어 및 소프트웨어 사양
- **대상 보드**: STMicroelectronics **STM32H747I-DISCO** (Discovery Kit)
- **메인 MCU**: **STM32H747XIH6** Dual-core (ARM Cortex-M7 @ 400/480MHz + Cortex-M4 @ 200/240MHz)
- **이더넷 PHY 칩**: Microchip / SMSC **LAN8742A** (10/100 Mbps RMII Physical Layer Transceiver)
- **인터페이스 모드**: RMII (Reduced Media Independent Interface, 50 MHz 기준 클럭)
- **클럭 구조**:
- 보드 내장 25 MHz 크리스탈(X3) 또는 MCU `PA8 (MCO1: 25MHz HSE)` $\rightarrow$ LAN8742A 내부 PLL로 50 MHz 체배 $\rightarrow$ LAN8742A Pin 5(`CLKOUT`) $\rightarrow$ **SB17** $\rightarrow$ STM32 `PA1 (ETH_REF_CLK)`
- **운영체제 및 스택**: FreeRTOS (CMSIS-RTOS V2) + LwIP 2.1.2 (ST HAL Driver)
- **네트워크 설정**: 고정 IP `192.168.31.60` (서브넷 `255.255.255.0`, 게이트웨이 `192.168.31.1`), PC IP: `192.168.31.58`
### [EN] Hardware & Software Specifications
- **Target Board**: STMicroelectronics **STM32H747I-DISCO** (Discovery Kit)
- **Main MCU**: **STM32H747XIH6** Dual-core (ARM Cortex-M7 @ 400/480MHz + Cortex-M4 @ 200/240MHz)
- **Ethernet PHY Chip**: Microchip / SMSC **LAN8742A** (10/100 Mbps RMII Transceiver)
- **Interface Mode**: RMII (50 MHz Reference Clock)
- **Clock Architecture**:
- Onboard 25 MHz Crystal (X3) or MCU `PA8 (MCO1: 25MHz HSE)` $\rightarrow$ LAN8742A internal PLL generates 50 MHz $\rightarrow$ LAN8742A Pin 5(`CLKOUT`) $\rightarrow$ **SB17** $\rightarrow$ STM32 `PA1 (ETH_REF_CLK)`
- **OS & Network Stack**: FreeRTOS (CMSIS-RTOS V2) + LwIP 2.1.2 (ST HAL Driver)
- **Network Configuration**: Static IP `192.168.31.60` (Subnet `255.255.255.0`, Gateway `192.168.31.1`), Target PC IP: `192.168.31.58`
---
## 2. Hardware Modifications: Solder Bridges / 하드웨어 솔더 브릿지 개조
STM32H747I-DISCO 보드는 기본 공장 출하 시 일부 이더넷 핀이 온보드 MEMS 마이크 및 오디오 코덱과 핀을 공유합니다. 이더넷 RMII 기능을 온전히 사용하려면 아래와 같이 솔더 브릿지(납땜 점퍼)를 개조해야 합니다.
On the STM32H747I-DISCO, certain Ethernet RMII pins are shared with onboard MEMS microphones and audio circuitry. To use Ethernet RMII, the following solder bridge modifications are required.
| Signal / 신호 | STM32 Pin | Solder Bridge to OPEN (단선) | Solder Bridge to CLOSE (쇼트) | Description / 설명 |
| :--- | :--- | :--- | :--- | :--- |
| **ETH_REF_CLK** | `PA1` | - | **SB17** | 50 MHz RMII Reference Clock from PHY / PHY 클럭 공급 |
| **ETH_MDIO** | `PA2` | - | **SB8** | MDIO Management Data / PHY 레지스터 데이터 통신 |
| **ETH_CRS_DV** | `PA7` | **SB45** (Disconnect Mic) | **SB44** | Carrier Sense / Data Valid (수신 캐리어 감지) |
| **ETH_MDC** | `PC1` | **R87** (Disconnect Mic) | **SB21** / Direct | Management Data Clock / PHY 레지스터 클럭 |
| **ETH_RXD0** | `PC4` | **SB21** (Disconnect Mic) | **SB22** | Receive Data Bit 0 / 수신 데이터 비트 0 |
| **ETH_RXD1** | `PC5` | - | **Direct** | Receive Data Bit 1 / 수신 데이터 비트 1 |
| **ETH_TX_EN** | `PG11` | - | **Direct** | Transmit Enable / 송신 활성화 |
| **ETH_TXD1** | `PG12` | - | **Direct** | Transmit Data Bit 1 / 송신 데이터 비트 1 |
| **ETH_TXD0** | `PG13` | - | **Direct** | Transmit Data Bit 0 / 송신 데이터 비트 0 |
---
## 3. Root Causes of Failure & Solutions / 핵심 문제 원인 및 해결책
### [KO] 3대 핵심 난제 및 완벽 해결책
#### ① 링커 스크립트 오타 및 D2 비캐시 RAM 매핑 버그
- **문제점**: `ethernetif.c`에서는 DMA 디스크립터 섹션을 `.RxDescripSection`, `.TxDescripSection`으로 선언했으나, ST CubeMX 생성 링커 스크립트의 오타 및 잘못된 `RAM_D1` 매핑으로 인해 디스크립터가 캐시가 켜진 일반 AXI SRAM(`0x24000000`)에 배치됨. 이로 인해 CPU의 전송 요청(`OWN=1`)을 DMA가 읽지 못해 무한 `BUSY` 및 멈춤 발생.
- **해결책**: 링커 스크립트의 `.lwip_sec` 섹션을 `RAM_D2 (0x30040000, SRAM3)`에 고정 배치하고 스펠링 오타 방어 코드 적용.
#### ② MPU (Memory Protection Unit) 비캐시 및 DMA 공유 설정
- **문제점**: Cortex-M7의 고성능 D-Cache로 인해 DMA와 CPU 간 디스크립터 플래그가 불일치됨.
- **해결책**: `MPU_Config()`에서 `0x30040000` (SRAM3, 32KB) 영역을 `MPU_ACCESS_NOT_CACHEABLE`, `MPU_ACCESS_NOT_BUFFERABLE`, `MPU_ACCESS_SHAREABLE`, `MPU_TEX_LEVEL0` (Strongly-Ordered/Device 속성)으로 설정.
#### ③ Cortex-M7 D-Cache Coherency (수신 Invalidate / 송신 Clean)
- **문제점**:
- 송신 시: CPU가 작성한 Ping 응답 및 UDP 데이터가 D-Cache에 머물러 RAM에 반영되지 않아 PC에서 `MISCOMPARE at offset 28` 에러 발생.
- 수신 시: Invalidate 명령어가 struct pbuf 메타데이터 작성 뒤에 위치하여 패킷 길이 정보가 소실됨.
- **해결책**:
- `low_level_output`: 패킷 송신 전 `SCB_CleanDCache_by_Addr`를 호출하여 CPU 캐시 내용을 물리 RAM으로 즉시 Flush.
- `HAL_ETH_RxLinkCallback`: 메타데이터를 쓰기 전 맨 첫 줄에서 `SCB_InvalidateDCache_by_Addr`를 호출하여 물리 RAM의 수신 패킷을 최신화.
---
### [EN] 3 Root Causes & Solutions
#### ① Linker Script Section Mismatch & D2 Non-Cacheable RAM Mapping
- **Issue**: Descriptors declared in `ethernetif.c` were incorrectly placed into Cacheable `RAM_D1` due to CubeMX section naming mismatches. The Ethernet DMA was unable to see CPU descriptor updates (`OWN=1`), causing perpetual `BUSY` lockups.
- **Solution**: Explicitly map `.lwip_sec` containing `.RxDescripSection`, `.TxDescripSection`, and `.Rx_PoolSection` into `RAM_D2 (0x30040000, SRAM3)`.
#### ② MPU (Memory Protection Unit) Non-Cacheable & Shareable Configuration
- **Issue**: Cache coherency mismatch between Ethernet DMA and Cortex-M7 CPU on descriptor ring buffers.
- **Solution**: Configure `0x30040000` (SRAM3, 32KB) as Non-Cacheable, Non-Bufferable, and Shareable with `MPU_TEX_LEVEL0`.
#### ③ Cortex-M7 D-Cache Management (TX Clean / RX Invalidate)
- **Issue**:
- TX: CPU payload writes remained in D-Cache without being flushed to RAM, causing `MISCOMPARE at offset 28` on Ping replies.
- RX: Invalidate was called after writing pbuf metadata, wiping out the received packet length.
- **Solution**:
- `low_level_output`: Call `SCB_CleanDCache_by_Addr` before triggering DMA transmission.
- `HAL_ETH_RxLinkCallback`: Call `SCB_InvalidateDCache_by_Addr` on the raw payload buffer *before* writing pbuf metadata.
---
## 4. Complete Code Implementation / 전체 핵심 코드
### 1) Linker Script (`stm32h747xx_flash_CM7.ld`)
```ld
/* Specify memory areas */
MEMORY
{
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D2 (xrw) : ORIGIN = 0x30040000, LENGTH = 32K /* SRAM3: Non-Cacheable Ethernet Area */
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
SDRAM (xrw) : ORIGIN = 0xD0000000, LENGTH = 32M
}
SECTIONS
{
/* ... standard flash, text, data, bss sections ... */
/* Force Ethernet Descriptors and Buffers into SRAM3 (0x30040000) */
.lwip_sec (NOLOAD) :
{
. = ABSOLUTE(0x30040000);
KEEP(*(.RxDescripSection))
KEEP(*(.RxDecripSection))
. = ABSOLUTE(0x300400A0);
KEEP(*(.TxDescripSection))
KEEP(*(.TxDecripSection))
. = ABSOLUTE(0x30040140);
KEEP(*(.Rx_PoolSection))
KEEP(*(.RxArraySection))
} >RAM_D2
}
```
---
### 2) MPU Configuration (`main.c`)
```c
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};
/* Disables the MPU */
HAL_MPU_Disable();
/** Configure Ethernet DMA Descriptors Region (SRAM3: 0x30040000, 32KB) */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE; /* DMA Shareable */
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; /* Non-Cacheable */
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; /* Non-Bufferable */
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enables the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
```
---
### 3) Hardware MSP Initialization (`stm32h7xx_hal_msp.c`)
```c
void HAL_ETH_MspInit(ETH_HandleTypeDef* heth)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(heth->Instance == ETH)
{
/* 1. CRITICAL: Select RMII mode in SYSCFG BEFORE enabling Ethernet peripheral clocks */
__HAL_RCC_SYSCFG_CLK_ENABLE();
HAL_SYSCFG_ETHInterfaceSelect(SYSCFG_ETH_RMII);
/* 2. Enable GPIO Clocks */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/** ETH GPIO Configuration
PA1 ------> ETH_REF_CLK
PA2 ------> ETH_MDIO
PA7 ------> ETH_CRS_DV
PC1 ------> ETH_MDC
PC4 ------> ETH_RXD0
PC5 ------> ETH_RXD1
PG11 ------> ETH_TX_EN
PG12 ------> ETH_TXD1
PG13 ------> ETH_TXD0
*/
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* MDIO requires Pull-Up */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
/* 3. Enable Ethernet Peripheral Clocks */
__HAL_RCC_ETH1MAC_CLK_ENABLE();
__HAL_RCC_ETH1TX_CLK_ENABLE();
__HAL_RCC_ETH1RX_CLK_ENABLE();
/* 4. Configure ETH Global Interrupt */
HAL_NVIC_SetPriority(ETH_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);
}
}
```
---
### 4) Low-Level Ethernet Driver (`ethernetif.c`)
```c
extern volatile uint32_t eth_rx_packet_count;
/**
* @brief TX Output function: Transmits packet with D-Cache Clean
*/
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
uint32_t i = 0U;
struct pbuf *q = NULL;
err_t errval = ERR_OK;
ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0};
ETH_TxPacketConfig tx_config;
memset(Txbuffer, 0, ETH_TX_DESC_CNT * sizeof(ETH_BufferTypeDef));
memset(&tx_config, 0, sizeof(ETH_TxPacketConfig));
tx_config.Attributes = ETH_TX_PACKETS_FEATURES_CSUM | ETH_TX_PACKETS_FEATURES_CRCPAD;
tx_config.ChecksumCtrl = ETH_CHECKSUM_IPHDR_PAYLOAD_INSERT_PHDR_CALC;
tx_config.CRCPadCtrl = ETH_CRC_PAD_INSERT;
for(q = p; q != NULL; q = q->next)
{
if(i >= ETH_TX_DESC_CNT)
return ERR_IF;
Txbuffer[i].buffer = q->payload;
Txbuffer[i].len = q->len;
/* CRITICAL: Flush outgoing packet data from D-Cache to physical RAM */
SCB_CleanDCache_by_Addr((uint32_t *)q->payload, q->len);
if(i > 0)
{
Txbuffer[i-1].next = &Txbuffer[i];
}
if(q->next == NULL)
{
Txbuffer[i].next = NULL;
}
i++;
}
tx_config.Length = p->tot_len;
tx_config.TxBuffer = Txbuffer;
tx_config.pData = p;
pbuf_ref(p);
do
{
if(HAL_ETH_Transmit_IT(&heth, &tx_config) == HAL_OK)
{
errval = ERR_OK;
}
else
{
if(HAL_ETH_GetError(&heth) & HAL_ETH_ERROR_BUSY)
{
/* Wait for descriptors to become available */
osSemaphoreAcquire(TxPktSemaphore, ETHIF_TX_TIMEOUT);
HAL_ETH_ReleaseTxPacket(&heth);
errval = ERR_BUF;
}
else
{
errval = ERR_IF;
}
}
} while(errval == ERR_BUF);
return errval;
}
/**
* @brief RX Link Callback: Invalidate D-Cache FIRST before writing metadata
*/
void HAL_ETH_RxLinkCallback(void **pStart, void **pEnd, uint8_t *buff, uint16_t Length)
{
/* 1. Invalidate cache on packet buffer FIRST before CPU writes pbuf metadata */
SCB_InvalidateDCache_by_Addr((uint32_t *)buff, Length);
struct pbuf **ppStart = (struct pbuf **)pStart;
struct pbuf **ppEnd = (struct pbuf **)pEnd;
struct pbuf *p = NULL;
/* Get struct pbuf from buff address */
p = (struct pbuf *)(buff - offsetof(RxBuff_t, buff));
p->next = NULL;
p->tot_len = 0;
p->len = Length;
/* Chain buffer */
if (!*ppStart)
{
*ppStart = p;
}
else
{
(*ppEnd)->next = p;
}
*ppEnd = p;
/* Update total length */
for (p = *ppStart; p != NULL; p = p->next)
{
p->tot_len += Length;
}
}
/**
* @brief RX Task thread: Receives packets and routes to LwIP Stack
*/
void ethernetif_input(void* argument)
{
struct pbuf *p = NULL;
struct netif *netif = (struct netif *) argument;
for( ;; )
{
if (osSemaphoreAcquire(RxPktSemaphore, TIME_WAITING_FOR_INPUT) == osOK)
{
do
{
p = low_level_input( netif );
if (p != NULL)
{
eth_rx_packet_count++;
printf("[RX] Packet Received! len=%d (Total: %lu)\r\n", (int)p->tot_len, (unsigned long)eth_rx_packet_count);
if (netif->input( p, netif) != ERR_OK )
{
pbuf_free(p);
}
}
} while(p != NULL);
}
}
}
```
---
### 5) Main Application Thread (`main.c`)
```c
volatile uint32_t eth_rx_packet_count = 0;
void myTaskFunc01(void *argument)
{
uint32_t cnt = 0;
char txBuffer[64];
uint32_t last_tick = 0;
uint32_t last_tx_tick = 0;
extern USBH_HandleTypeDef hUsbHostHS;
extern struct netif gnetif;
static uint8_t ip_assigned = 0;
printf("\r\n========================================\r\n");
printf("[ETH] Waiting for PHY Stabilization (500ms)...\r\n");
osDelay(500);
printf("[ETH] Starting MX_LWIP_Init()...\r\n");
MX_LWIP_Init();
printf("[ETH] MX_LWIP_Init Completed!\r\n");
/* LwIP UDP PCB Creation (TX Dedicated) */
LOCK_TCPIP_CORE();
struct udp_pcb *tx_pcb = udp_new();
ip_addr_t dest_ip;
IP4_ADDR(&dest_ip, 192, 168, 31, 58); /* Target PC IP */
UNLOCK_TCPIP_CORE();
printf("[ETH] UDP TX PCB Created! (Target: 192.168.31.58:5000)\r\n");
for (;;)
{
cnt = HAL_GetTick();
/* --- TX: Send UDP Packet every 100ms --- */
if (cnt - last_tx_tick >= 100) {
last_tx_tick = cnt;
LOCK_TCPIP_CORE();
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 64, PBUF_RAM);
if (p != NULL) {
int plen = snprintf((char*)p->payload, 64, "TICK=%lu\r\n", cnt);
p->len = plen;
p->tot_len = plen;
udp_sendto(tx_pcb, p, &dest_ip, 5000);
pbuf_free(p);
}
UNLOCK_TCPIP_CORE();
}
/* --- 1000ms Periodic Status Display (UART COM4) --- */
if (cnt - last_tick >= 1000) {
last_tick = cnt;
if (eth_rx_packet_count == 0) {
printf("[RX] Waiting for Ping (0 pkts)... | [TX] UDP Sending... Tick=%lu\r\n", cnt);
} else {
printf("[RX] Received! Total: %lu pkts | [TX] UDP Sending... Tick=%lu\r\n", (unsigned long)eth_rx_packet_count, cnt);
}
}
if (netif_is_link_up(&gnetif)) {
if (ip_assigned == 0) {
printf("[ETH] Link UP! Static IP: 192.168.31.60\r\n");
ip_assigned = 1;
}
}
osDelay(1);
}
}
```
---
## 5. Verification & Results / 최종 검증 결과
### 1) Ping Response Verification (ICMP) / Ping 응답 검증
```cmd
C:\> ping 192.168.31.60 -t
192.168.31.60의 응답: 바이트=32 시간=1ms TTL=255
192.168.31.60의 응답: 바이트=32 시간=1ms TTL=255
192.168.31.60의 응답: 바이트=32 시간=1ms TTL=255
192.168.31.60의 응답: 바이트=32 시간=1ms TTL=255
```
- **결과 / Result**: 0% Packet Loss, Steady 1ms latency, No payload corruption (MISCOMPARE resolved).
### 2) Serial Terminal Output (ST-Link VCP COM4 @ 115200 bps)
```text
========================================
[ETH] Waiting for PHY Stabilization (500ms)...
[ETH] Starting MX_LWIP_Init()...
[ETH] ethernet_link_thread started!
[ETH] LAN8742 PHY detected at Address 0 (Reg1=0x782D)
[ETH] Cable Connected (Link Up)! Static IP: 192.168.31.60
[ETH] MX_LWIP_Init Completed!
[ETH] UDP TX PCB Created! (Target: 192.168.31.58:5000)
[ETH] Link UP! Static IP: 192.168.31.60
[RX] Packet Received! len=60 (Total: 1)
[RX] Packet Received! len=74 (Total: 2)
[RX] Received! Total: 2 pkts | [TX] UDP Sending... Tick=3891
[RX] Received! Total: 3 pkts | [TX] UDP Sending... Tick=4895
```
|
| 
|