公司动态

STM32H563ZI上NetX Duo UDP Echo Server不回显?从PHY到socket一步步排查

📅 2026/8/31 22:08:21
STM32H563ZI上NetX Duo UDP Echo Server不回显?从PHY到socket一步步排查
最近在调一块 STM32H563ZI 的开发板跑 STM32CubeFW 里 NetX Duo 的 UDP Echo Server 例程时遇到了一个非常典型的问题板子上电串口日志打印到 Link is upPC 端用 UDP 调试工具往板子发一串数据预期板子原样回显实际却什么都收不到。这个标题看着就一句话但背后涉及的层次一点都不少从 PHY、时钟、DMA到 NetX Duo 的 IP/ARP/UDP socket再到 PC 端的防火墙和路由。我把整个排查过程整理出来给所有在 H5 系列上玩 NetX Duo 的人当参考。1. 先把现象拆开UDP Echo Server 到底“不工作”在哪一层1.1 你遇到的是三种“不工作”里的哪一种很多人一上来就说“UDP 不通”但“不通”这个词太模糊了。UDP 是 IP 协议族里的 17 号协议它的底层还要依赖 Ethernet、ARP、IP、路由这些基础设施。所以 UDP 没反应要先分清是链路层没起来还是网络层没起来还是纯粹的 UDP socket 层没工作。现象表现大概率问题层次排查重点网口灯不亮ping 都 ping 不通PHY / 时钟 / 硬件链路ETH 时钟、PHY 地址、网线、RMII/MII 配置网口灯亮但 ping 不通IP 层 / ARPIP 地址、子网掩码、ARP 表、路由ping 能通但 UDP 没回显UDP socket / 防火墙 / 协议栈NetX Duo socket、端口绑定、PC 防火墙、接收线程ping 通UDP 偶尔回显压力测试丢包严重内存池 / 线程优先级 / cachePacket Pool 大小、中断优先级、DMA 描述符如果你只看到串口打印“初始化成功”就默认以太网链路是好的那大概率会卡在 UDP 这层出不来。我建议第一步先对着这张表确认“你到底卡在哪一行”后面才好下手。1.2 Echo Server 正常时数据是怎么走的NetX Duo 的 UDP Echo Server 例程逻辑其实很短创建 IP 实例开启 UDP创建一个 UDP socketbind 到一个固定端口然后开一个接收线程在nx_udp_socket_receive里阻塞等待。一旦收到包就把数据原样从同一个 socket 发回去。整个过程没有 TCP 那种三次握手和重传机制所以它更像一个“哑设备”上位机发一个 UDP 数据报到板子的 IP:port板子的 ETH MAC 收到帧DMA 把数据放到内存NetX Duo 的 UDP 层把 IP/UDP 头解析掉负载交给 socket接收线程拿到 packet再调用nx_udp_socket_send把同一份负载发回上位机。只要这一条链路上任何一个环节断裂现象都是“UDP 没反应”。但注意如果断了的是 PHY 或 DMA你 ping 也不会通如果断的是 socket 或接收线程就可能出现“ping 通但 UDP 不回”这种更隐蔽的情况。2. 链路层确认STM32H563ZI 的 Ethernet 配置最容易翻车2.1 片内 PHY 和外置 PHY 的配置差别STM32H563ZI 这颗料和很多老 H7 不一样它本身带了 10/100M 以太网 MAC 和 PHYNUCLEO-H563ZI 上 RJ45 可以直接挂在 MCU 上不需要额外接 LAN8720 这类外置 PHY。如果你以前在 STM32F407 上玩过 LAN8720那套经验部分适用但 H563ZI 不能完全照搬因为 PHY 的位置变了。在 CubeMX 里配置 Ethernet 时要注意两个关键点外设选择H563ZI 的以太网外设一般叫 ETH1不是老工程里的 ETH。如果你选了别的型号模板可能根本没把 ETH1 纳入时钟树。PHY 接口模式如果你的板子用片内 PHY通常按 RMII 模式配置并且由 MCU 给 PHY 提供 50MHz 参考时钟。CubeMX 时钟树里要把 ETH1 需要的时钟源拉出来否则 PHY 永远无法完成 auto-negotiation网口灯不会亮。我见过太多人把 H7 工程直接改成 H5 芯片结果 ETH 的外设名、引脚复用全不对编译能过跑起来就是没 Link。这时候不要急着查 UDP先去 CubeMX 里重新看 ETH1 的 Mode 和引脚分配。2.2 怎么确认 PHY/Link 状态在 NetX Duo 里IP 实例创建后有一段等待 Link Up 的过程。官方例程通常会打印类似Link Up的日志但如果你不确定最简单直接的办法是看板子 RJ45 网口的指示灯是否亮用串口打印heth-gState或 HAL_ETH_GetState 的返回值在调试器 Watch 窗口看 ETH 的 PHY 状态寄存器。如果 Link 都没起来后面所有 UDP 排查都是白费。顺带提一个经验不要把网线插在只有百兆口的差交换机上H563ZI 的以太网是 10/100M交换机如果自动协商出 10M full duplex 也没问题但劣质网线会导致大量 CRC 错误表现为 ping 忽通忽不通UDP 更没法看。3. NetX Duo 协议栈层IP、ARP、UDP socket 的配置检查3.1 检查 IP 地址和掩码是否真的写进去了例程里常见的是静态 IP比如板子设成192.168.0.10PC 设成192.168.0.2。看起来没错但有两个隐藏坑IP 写错位置有人把地址填到了nx_ip_create的第一个参数但之后又用 DHCP 或者nx_ip_interface_address_set改了接口地址导致实际地址跟打印出来的不一样PC 在另一个网段PC 是192.168.1.x板子是192.168.0.x两者不经过路由器UDP 当然到不了。先用 ping 确认网络层通不通。注意ping 用的是 ICMP它也要经过 ARP。如果 ping 通了说明 IP 和 ARP 基本没问题可以继续查 UDP socket如果 ping 不通先别纠结 UDP去查 IP 和链路。3.2 UDP socket 创建/绑定/接收线程四个必查项到了 NetX Duo 这一层代码本身不复杂但这几个位置特别容易出错nx_udp_socket_create是否成功如果返回 NX_PTR_ERROR 或 NX_NOT_ENABLED说明nx_udp_enable没调用或者 socket 指针没定义。nx_udp_socket_bind端口是否被占用如果例程 bind 的是 6000但你在调试时另一个任务也 bind 了 6000会返回 NX_ALREADY_BOUND。很多“UDP 不回”其实是 bind 失败了但例程没打印错误码。接收线程是否创建有些简化工程把nx_udp_socket_receive放到了主循环里但主循环可能被其他任务卡住或者根本没有启动调度器线程跑不起来。nx_udp_socket_receive返回值有没有被人为忽略如果它返回 NX_NO_PACKET说明 Packet Pool 里没有可用包数据被协议栈丢了如果返回 NX_NOT_BOUND说明 socket 没有 bind 成功。很多人一看到“UDP 不回显”就去改回显逻辑其实隐患早在 socket 初始化就埋下了。所以在跑任何例程前我强烈建议把所有 NetX Duo API 的返回值都打印出来不要用“肯定成功”的心态去看待这些函数。3.3 UDP 与 TCP 的差别为什么 ping 通但 UDP 没反应这里顺便说一个容易被忽略的基础问题TCP 和 UDP 在调试体验上差异巨大。TCP 有三次握手、确认和重传机制如果连接建不起来你很快能发现UDP 是无连接、无确认的发出去就发出去丢了就丢了。回显任务本身没有“这包没收到请重发”的能力所以只要协议栈里丢了一个包你的调试工具就永远等不到回显。还有一个常被误解的点很多人问“TCP 比 UDP 更省流量吗”。单看协议头UDP 头只有 8 字节TCP 头至少 20 字节UDP 通常更省但实际应用里 TCP 的确认和重传会增加额外流量。不过这不是 UD Echo Server 的主要矛盾关键是你要理解UDP 调试靠的不是连接状态而是抓包和日志。4. 从 ping 到 iperf3一套能定位 UDP 丢在哪里的排查命令4.1 环境准备和发送端工具选择我建议桌面准备这几样东西串口终端PuTTY 或 STM32CubeMonitor看板子日志Wireshark用来抓 PC 网卡上的实际数据包一个能自定义源端口和目标端口的 UDP 调试工具比如 SocketTool、NetAssist或者你自己写的 Python UDP 脚本电脑有线网口不建议用 Wi-Fi 做这种局域网调试。先用最简单的静态 IP板子192.168.0.10PC192.168.0.2子网掩码255.255.255.0。然后按顺序做ping 192.168.0.10 -tping 通了之后再用 UDP 工具向192.168.0.10:6000发送一串 ASCII 字符。如果板子回显问题早就解决了如果没回显立刻打开 Wireshark 看。4.2 用 iperf3 的 UDP 模式做压力测试如果你手头有另一个能跑 iperf3 的 PC 或者已经在板上移植了 iperf 服务可以用iperf3的 UDP 模式打流iperf3 -u -c 192.168.0.10 -b 10M -t 10但这里要非常注意iperf3的 UDP 模式不是普通 Echo它有自己的协议板子必须也跑对应的 iperf server 才能回包。如果板子只有 UDP Echo Server直接拿 iperf3 打板子数据会被当普通 UDP payload 处理除非你的 Echo 程序恰好能回否则很容易误判。我更推荐的做法是先用两台 PC 跑iperf3 -u确认 PC 网卡、交换机和防火墙没有限制 UDP再切换到板子做 Echo 测试。这样能先把“PC 端环境”和“板子端协议栈”隔离开。另外做 UDP 打流时一定要限速。UDP 没有流控发太快会把板子的 Packet Pool 打爆出现“串口日志里有大量 NX_NO_PACKET”的现象。这不代表你的 Echo 逻辑有问题而是内存池容量不够属于资源问题。4.3 抓包判断丢在哪个环节Wireshark 抓包是整个排查的分水岭。打开 Wireshark选择连接板子的有线网卡过滤器先写udp.port 6000 || arp || icmp然后从 PC 发一个 UDP 包到板子观察如果 Wireshark 里根本看不到发出的 UDP 包说明 PC 的工具、防火墙或网卡已经把包拦了根本没发到网线上。优先检查 Windows 防火墙很多 UDP 调试工具第一次运行时会被默认拦截。如果 Wireshark 能看到 PC 发出的包但板子没有串口日志说明板子要么没收到以太网帧要么 NetX Duo 协议栈在 IP/UDP 层丢了包。继续看 PHY link、ETH 中断、Packet Pool。如果板子回包但 PC 收不到注意 Wireshark 是否能看到回包。如果回包被 PC 防火墙丢弃调试工具收不到但 Wireshark 能看到。这种情况不是板子问题。用 Wireshark 还能直接区分 TCP 和 UDP协议列会明确写 UDP不要靠端口号猜。局域网 UDP 调试里很多“匹配失败”和“无响应”其实是 PC 防火墙把 UDP 丢掉了而不是板子没回。5. STM32H563ZI 移植 NetX Duo 时的经典坑5.1 CubeMX 生成代码少了 Ethernet 中断或 DMA 描述符STM32CubeMX 生成的 NetX Duo 例程一般会帮你配好 ETH 中断和 DMA 描述符但如果你是在自己的工程里手动移植很容易漏掉ETH_IRQHandler里调用HAL_ETH_IRQHandler或者没把 DMA 描述符内存放到合适的位置。ETH 的 DMA 描述符有对齐要求CubeMX 默认放在特定的 RAM 段。如果描述符被定义在普通变量区而且编译器做了优化可能导致 DMA 写完描述符后 CPU 读到的还是旧值。最典型的症状是ping 偶尔通UDP 完全没反应或者收包长度全乱。解决办法是检查链接脚本里 ETH DMA 描述符所在内存区保证对齐和 cache 一致性。如果你在带 D-Cache 的 MCU 上遇到同样问题还要检查 MPU 是否把 DMA 描述符和 buffer 区域配成了 non-cacheable避免 cache 和 DMA 数据不一致。5.2 线程栈和 Packet Pool 大小不够NetX Duo 的默认配置适合跑通例程但如果你改了 MTU、加了 TLS 或其他协议内存需求会涨。Echo Server 看似占用小但 UDP 接收时要为每个包分配一个NX_PACKET结构Packet Pool 里如果都是小包遇到大 UDP 载荷就会分配失败。我习惯把 Packet Pool 按这个思路配每个包至少能放下一个完整 IP 包也就是 1518 字节左右再加上NX_PACKET结构头。如果测试工具一次发几 KB 的 UDP payload那还要确保nx_ip_packet_pool_create的 packet size 和数量都够否则数据会直接被丢掉。线程栈也别忘了。NetX Duo 的回调、ARP、ICMP 都会用到当前线程栈如果把接收线程栈配成 512 字节看起来能跑但栈一深就溢出。调试时可以在线程入口打印tx_thread_info_get的栈使用峰值确认剩余空间。5.3 PHY 地址、时钟、GPIO 参数不对如果你的板子是外置 PHY或者你改了板子PHY 地址、复位引脚这些必须和 HAL_ETH_Init 里的参数一致。H563ZI 自带 PHY 时有些 CubeMX 版本会自动填好 PHY 地址但手动创建工程时可能要自己读 PHY ID 来确认。RMII 模式还要重点看参考时钟PHY 需要 50MHz REF_CLK而以太网 MAC 本身用的时钟可能来自 PLL。CubeMX 时钟树里如果没把ETH1REF_CLK正确输出PHY 的 auto-negotiation 一直失败Link 灯不亮UDP 自然没有任何反应。遇到这种问题先拿示波器量 RMII REF_CLK 引脚有没有 50MHz 方波没有就回到时钟树改配置。5.4 初始化顺序和中断优先级NetX Duo 的初始化顺序也很讲究。官例一般是tx_kernel_enter之前先做好硬件初始化然后在tx_application_define里创建 Packet Pool 和 IP 实例。如果你把nx_udp_socket_create放到nx_ip_create之前或者nx_udp_enable没有调用socket 创建会直接报错。中断优先级也容易被忽略。ETH 中断优先级如果和系统节拍定时器中断冲突可能造成丢包或死锁。NetX Duo 文档里建议把网络中断优先级配置为允许被系统调度器管理但不同 RTOS 移植有所差异。我调试时习惯先用默认优先级跑通再逐步优化不要在第一步就动中断优先级。6. 一些我已经踩过的经验救命的调试顺序6.1 串口日志要加够NetX Duo 例程自带的日志通常很简洁像我这种喜欢“看日志猜问题”的人建议在以下位置补上日志nx_udp_socket_bind的返回值接收线程里nx_udp_socket_receive的返回值和收到的字节数回显发送nx_udp_socket_send的返回值每次收到包时的源 IP 和源端口。加了日志之后你至少能判断是“没收到”还是“收到了但发不出去”。很多人在协议栈回调里一收到包就忙着处理但又不打印长度结果问题和现象完全对不上。6.2 尽量先跑官方例程原包不要先改上位机在板子不工作的时候我强烈建议先不要自己写复杂的上位机也不要急着改例程逻辑。先用官方编译好的 UDP Echo Server 工程配好静态 IP用最简单的 UDP 工具发一条短消息。如果官方例程正常那问题大概率出在你后来加的代码上如果官方例程都不正常再按前面步骤从 PHY、IP、socket 一层层查。尤其注意“板子是板子PC 是 PC”的隔离。我遇到过有人在 PC 上开了两个 UDP 工具一个绑定了同一个端口导致另一个工具收不到回包还跑过来改板子代码。遇到这种问题先把 PC 上的所有 UDP 相关程序关掉再重测。6.3 判断 UDP 请求还是 TCP 请求别靠猜还有一个小技巧在 Wireshark 里过滤器写udp看 Protocol 列如果是 UDP就可以继续看udp.srcport和udp.dstport。不要看到端口号是 80 就认为是 TCP现在 QUIC 也在 UDP 上跑 80/443。局域网调试里虽然少见但养成看协议列的习惯能少走很多弯路。最后再说一个我个人的习惯每次调试 UDP我都会先强制发一个固定内容、固定长度的包比如 32 字节的ABCDEFGHIJKLMNOPQRSTUVWXYZ123456。因为 UDP 是面向报文的回显必须是“一次发送一次原样返回”。如果用 TCP 的习惯去 think UDP总想靠连接状态来判断问题方向就错了。如果你也遇到 STM32H563ZI 上 NetX Duo UDP Echo Server 不回显建议照这个顺序走一遍先看 Link再 ping再抓包最后查 socket 和内存。这个顺序能解决 90% 以上的 UDP 问题。