多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

STM32H7内置MAC+LAN8720A以太网通信实战:从CubeMX配置到LWIP调优与ping通排查

STM32H7内置MAC+LAN8720A以太网通信实战:从CubeMX配置到LWIP调优与ping通排查 1. 项目缘起与整体方案拆解STM32H7 系列 MCU 主频能跑到 480MHz带 Ethernet MAC 控制器配合外置 PHY 芯片 LAN8720A 就能搭出一路 10/100M 自协商以太网口。这个组合在工业网关、数据采集器、边缘计算节点里非常常见。但真正上手的人都知道从 CubeMX 点完配置到ping通中间隔着一堆坑RMII 时钟没配对、PHY 地址扫描不到、LWIP 内存池给少了、中断优先级冲突导致收包丢帧……每一个都能让你对着串口打印发呆半天。这篇内容就是把我自己从零搭通这套链路的过程完整拆开包括 CubeMX 里每一项配置背后的原因、LWIP 参数怎么算、ping 不通时按什么顺序排查。适合正在用 STM32H7 做以太网通信的嵌入式开发者也适合之前只用过 F103ENC28J60 这类 SPI 网卡、第一次接触内置 MACRMII 方案的朋友。读完你应该能独立完成一套可 ping 通的以太网底层并且知道出问题时该往哪个方向查。1.1 为什么选 H7 内置 MAC LAN8720A 而不是 SPI 网卡早些年做以太网很多人用 ENC28J60 或 W5500 这类 SPI 接口的网卡芯片。好处是接线简单、驱动成熟但瓶颈很明显SPI 速率有限W5500 撑死也就几 Mbps 的实际吞吐而且硬件 TCP/IP 栈虽然省 MCU 资源但灵活性差遇到自定义协议或者需要精细控制收发时序的场景就很别扭。STM32H7 内置的 Ethernet MAC 是 IEEE 802.3 兼容的支持 RMII 和 MII 两种接口。RMII 只需要 7 根信号线REF_CLK、TXD0、TXD1、TX_EN、RXD0、RXD1、CRS_DV比 MII 少一半PCB 布线压力小很多。LAN8720A 是一颗性价比很高的 10/100M PHYRMII 接口支持自协商和 HP Auto-MDIX也就是说你网线直连或者交叉连都能通不用纠结线序。两者配合理论吞吐能到 100Mbps实际跑 TCP 也能有 40-60Mbps比 SPI 方案高一个数量级。代价就是配置复杂度上来了。RMII 需要外部提供 50MHz 参考时钟LAN8720A 自己可以输出 50MHz 时钟给 MCU也可以由 MCU 的 MCO 引脚输出给 PHY。这两种方式各有讲究后面细说。另外 LWIP 协议栈的内存管理、PBUF 分配、中断处理都需要手动调不像 W5500 那样开箱即用。1.2 整体数据流与关键环节从网线进来的数据先到 LAN8720A 做物理层解码通过 RMII 接口送到 STM32H7 的 Ethernet MACMAC 做 CRC 校验和地址过滤后通过 DMA 把数据包搬到内存里的 RX Buffer然后触发中断LWIP 在中断或轮询中取走 PBUF逐层解析到应用层。发送方向反过来应用层数据封装成 PBUFLWIP 调用 MAC 驱动发送DMA 从内存搬到 MAC FIFO再经 RMII 到 PHY最后上线。这条链路里最容易出问题的环节有三个一是 RMII 时钟REF_CLK 必须是 50MHz偏差大了 PHY 直接不工作二是 DMA 描述符和 Buffer 的对齐H7 的 Ethernet DMA 对地址对齐有要求没对齐会静默丢包三是 LWIP 的内存配置PBUF_POOL_SIZE和MEM_SIZE给少了ping 小包能通一大包就崩。2. 硬件设计与 CubeMX 配置要点2.1 LAN8720A 参考电路的关键细节LAN8720A 的参考电路网上能搜到很多但有几个地方容易被忽略。首先是 REF_CLK 的方向选择。LAN8720A 的 PIN 16 是nINT/REFCLKO通过内部寄存器可以配置成 50MHz 时钟输出。如果你让 PHY 输出时钟给 MCU那这个引脚要接到 STM32H7 的 PA1ETH_REF_CLK同时 PHY 的 XTAL1/XTAL2 接 25MHz 晶振内部 PLL 倍频到 50MHz 输出。这种方案省一个 MCU 引脚但要求 PHY 先启动。另一种方案是 STM32H7 的 PA8MCO1输出 50MHz 给 PHY 的 XTAL1PHY 工作在外部时钟模式。这种方案的好处是时钟源由 MCU 控制启动顺序更可控但 PA8 要配置成 MCO 输出且时钟树要保证能分出 50MHz。我实际用的是第一种PHY 输出时钟。原因是 H7 的 MCO 输出 50MHz 需要 PLL 配置配合有时候为了其他外设的时钟会打架不如让 PHY 自己管自己。但要注意LAN8720A 的nINT/REFCLKO引脚在复位后的默认状态是中断输出需要在初始化代码里通过 SMI 接口写寄存器0x1F的 bit 7 来切换到时钟输出模式。这一步如果漏了MCU 收不到 REF_CLKMAC 直接不工作。复位电路也不能省。LAN8720A 的 nRST 引脚建议接一个 10k 上拉到 3.3V再加一个 100nF 到地同时接到 MCU 的一个 GPIO 上方便软件控制复位时序。我见过有人直接把 nRST 接 VCC结果 PHY 上电时序不对偶尔能通偶尔不通查了半天。RMII 信号线要等长吗严格来说 RMII 的 50MHz 时钟和数据线最好做等长偏差控制在 5mm 以内。但实际在 10cm 以内的短距离走线上不做等长也能跑只是眼图会差一些。如果 PCB 空间允许还是尽量让 TXD0/TXD1/TX_EN 和 RXD0/RXD1/CRS_DV 这六根线长度接近。2.2 CubeMX 里 Ethernet 外设的配置逻辑打开 CubeMX选好 STM32H723 或 H743 之后在 Connectivity 里找到 ETH。Mode 选 RMII下面的参数会自动带出来。这里有几个关键项Carrier sense signal: 选CRS_DV这是 RMII 标准。PHY address: 默认是 0但 LAN8720A 的 PHY 地址由 PHYAD0 引脚决定接地下拉就是 0接上拉就是 1。我一般硬件上直接下拉到 0软件里也填 0。Auto negotiation: 勾上让 PHY 自己协商速率和双工模式。Advanced Parameters 里Rx Buffers和Tx Buffers的数量建议都设成 4 以上。H7 的 Ethernet DMA 支持多缓冲设少了在高流量下容易丢包。Rx Buffer Length默认 1524 字节够用。Checksum offload可以都勾上让硬件算 IP/TCP/UDP 校验和省 CPU。中断方面ETH 的中断优先级要设得比一般外设高但不要高过 FreeRTOS 的configMAX_SYSCALL_INTERRUPT_PRIORITY。我一般设成 5 或 6数值越小优先级越高确保收包中断能及时响应。2.3 时钟树配置与 50MHz 的来源确认H7 的时钟树比较复杂但 Ethernet 需要的时钟有两个一个是 MAC 的ETH1TX和ETH1RX时钟来自ETH1CLK另一个是 RMII 的 REF_CLK来自外部 PHY 或 MCO。如果 PHY 提供 REF_CLK那 CubeMX 里ETH1CLK的时钟源要选External PHY或者对应的 RMII 参考时钟输入。具体在 Clock Configuration 页面找到ETH1CLK那一栏确认它显示的是 50MHz。如果显示 0 或者别的值说明时钟源没配对。我遇到过一种情况CubeMX 里 ETH 配置好了但时钟树里ETH1CLK显示 25MHz原因是 PHY 的时钟输出还没使能CubeMX 不知道。这种情况下先生成代码在MX_ETH_Init()之前手动调用 PHY 初始化把时钟输出打开再初始化 ETH。或者干脆在 CubeMX 里先把时钟源设成 MCO等 PHY 通了再改回来。3. LWIP 协议栈配置与内存规划3.1 LwIP 的两种模式裸机与 FreeRTOSCubeMX 里配置 LWIP 时第一个要选的就是LWIP_NETIF_API和LWIP_SOCKET这些选项但更关键的是NO_SYS这个宏。NO_SYS1表示裸机模式LWIP 靠主循环轮询和中断驱动NO_SYS0表示配合 RTOSLWIP 有自己的线程tcpip_thread应用层通过消息队列和它通信。裸机模式简单但所有网络处理都在主循环里实时性差。FreeRTOS 模式复杂一些但收包在中断里触发处理在独立线程应用层可以用 socket API开发效率高。我建议只要用了 RTOS就选NO_SYS0后面调起来省心。在 CubeMX 的 Middleware 里选 LWIP然后General Settings里把NO_SYS设成 0LWIP_NETCONN和LWIP_SOCKET都使能。MEM_SIZE默认是 1600 字节这个太小了后面会讲怎么算。3.2 内存池参数的计算与设置LWIP 的内存分几块MEM_SIZE是堆内存给mem_malloc用PBUF_POOL_SIZE是 PBUF 池的数量每个 PBUF 默认PBUF_POOL_BUFSIZE字节还有MEMP_NUM_*系列是各种结构体的池。MEM_SIZE怎么算如果你用 socket API每个 socket 连接会占用一些内存TCP 发送和接收缓冲区也从这里出。一般建议至少 4KB跑 TCP 服务端的话给到 8KB 或 16KB。我一般设MEM_SIZE8192留够余量。PBUF_POOL_SIZE和PBUF_POOL_BUFSIZE的关系要搞清楚。每个 PBUF 能存PBUF_POOL_BUFSIZE字节的数据如果来的包比这个大LWIP 会用多个 PBUF 链接起来。以太网最大帧 1518 字节去掉 14 字节头payload 最多 1500 字节。如果PBUF_POOL_BUFSIZE设成 1524那一个 PBUF 就能装下一个完整帧效率最高。但这样每个 PBUF 占 1524 字节池子大了内存吃不消。我的做法是PBUF_POOL_BUFSIZE1524PBUF_POOL_SIZE8。这样池子占 12KB 左右能同时缓存 8 个满帧应付一般流量够了。如果做视频流或者高速数据采集可以加到 16 甚至 32。MEMP_NUM_PBUF、MEMP_NUM_TCP_PCB、MEMP_NUM_TCP_SEG这些也要相应调大。MEMP_NUM_TCP_SEG尤其重要它决定能同时排队的 TCP 段数量设小了 TCP 吞吐上不去。我一般设MEMP_NUM_TCP_SEG32。3.3 PHY 驱动与自协商流程CubeMX 生成的代码里ethernetif.c里的low_level_init会调用LAN8720_Init之类的函数但 CubeMX 默认可能没有针对 LAN8720A 的驱动需要自己补。核心是通过 SMIMDIO/MDC接口读写 PHY 寄存器。自协商的流程是先读 BMSR寄存器 1确认链路状态然后写 BMCR寄存器 0的 bit 12 启动自协商等待 BMSR 的 bit 5 变成 1 表示协商完成再读 BMSR 和 BMSR 的扩展寄存器确认速率和双工模式。LAN8720A 有个特殊的地方它的 BSR寄存器 1的 bit 2 是 Link Status但这个位是锁存的读一次之后会清零需要读两次才能拿到真实状态。我见过有人只读一次结果一直显示链路断开其实是读法不对。uint32_t phy_read(uint32_t reg) { return HAL_ETH_ReadPHYRegister(heth, PHY_ADDR, reg); } void phy_write(uint32_t reg, uint32_t val) { HAL_ETH_WritePHYRegister(heth, PHY_ADDR, reg, val); } uint8_t phy_link_status(void) { uint32_t bsr phy_read(0x01); bsr phy_read(0x01); // 读两次 return (bsr 0x04) ? 1 : 0; }4. Ping 不通的排查路径与实战记录4.1 从 PHY 寄存器开始逐层确认Ping 不通的时候不要一上来就怀疑 LWIP。按物理层到应用层的顺序查效率最高。第一步确认 PHY 的 SMI 通信正常。读 PHY 的 ID 寄存器寄存器 2 和 3LAN8720A 的 ID 应该是0x0007和0xC0F1。如果读出来是0xFFFF或者0x0000说明 MDIO/MDC 有问题检查接线和 PHY 地址。第二步确认链路状态。读 BSR 两次看 bit 2 是否为 1。如果是 0说明网线没插好或者对端没通电。也可以读 LAN8720A 的特殊状态寄存器寄存器 31它的 bit 2 是 Link Statusbit 3 是 Speedbit 4 是 Duplex读一次就能拿到完整状态。第三步确认 REF_CLK。用示波器量 PA1 引脚应该有 50MHz 方波。如果没有检查 PHY 的时钟输出配置。LAN8720A 的寄存器 31 的 bit 7 是REFCLKO使能位写 1 才输出时钟。第四步确认 MAC 的收发计数。H7 的 Ethernet MAC 有统计寄存器读ETH_MMCRGUFCR接收帧计数和ETH_MMCTGFSCCR发送帧计数看有没有在动。如果发送计数在涨但接收不涨说明发送通路 OK接收有问题重点查 RXD0/RXD1/CRS_DV 和 RX DMA 描述符。4.2 常见问题速查表现象可能原因排查方法PHY ID 读不到MDIO/MDC 接线错、PHY 地址不对、PHY 没复位量 MDC 有无时钟换 PHY 地址试链路状态始终断开网线问题、对端设备没开、自协商没启动换网线读 BMSR 两次手动启动自协商REF_CLK 没有 50MHzPHY 时钟输出没使能、晶振没起振写寄存器 31 bit7量晶振引脚发送计数涨、接收不涨RX DMA 描述符没对齐、RX Buffer 地址错检查ETH_DMARxDesc对齐看 DMA 状态寄存器ping 小包通、大包不通PBUF_POOL_BUFSIZE 太小、MEM_SIZE 不够加大 PBUF 池和堆内存ping 通但丢包严重中断优先级太低、DMA 缓冲太少提高 ETH 中断优先级增加 Rx Buffers上电偶尔不通PHY 复位时序不对、时钟稳定慢加长复位延时等时钟稳定再初始化 MAC4.3 一个真实的排查案例我最近做的一块 H723 板子PHY 用 LAN8720ACubeMX 配置看起来都对但就是 ping 不通。串口打印显示 PHY ID 读到了链路状态也是 Link Up100Mbps 全双工但就是收不到包。先量 REF_CLK有 50MHz。再读 MAC 统计寄存器发送计数在涨接收计数不动。说明 MAC 在往外发但收不到。查 RX DMA 描述符发现ETH_DMARxDesc的地址是0x30000000开头而 H7 的 Ethernet DMA 要求描述符地址 4 字节对齐Buffer 地址也要对齐。我检查了链接脚本发现.RxDecripSection和.RxArraySection被放在了 DTCM 里而 DTCM 的地址是0x20000000虽然也是 4 字节对齐但 H7 的 Ethernet DMA 不能访问 DTCM只能访问 AXI SRAM 或 D2 SRAM。把这两个段移到 AXI SRAM0x24000000之后接收计数立刻开始涨ping 也通了。这个问题很隐蔽因为编译不报错运行时也不报错就是静默丢包。CubeMX 生成的链接脚本默认可能把 Ethernet 描述符放在 DTCM需要手动改。/* 在链接脚本里把 Ethernet 描述符和 Buffer 放到 AXI SRAM */ .ethernet_data : { . ALIGN(4); *(.RxDecripSection) *(.TxDecripSection) *(.RxArraySection) *(.TxArraySection) } RAM_D15. 性能调优与稳定性加固5.1 中断与 DMA 的配合优化H7 的 Ethernet DMA 支持中断和轮询两种模式。默认是中断模式每收到一帧触发一次中断。在高流量下中断太频繁会吃满 CPU。可以改成轮询模式在主循环或定时器里定期检查 DMA 的接收描述符有数据就处理。但轮询模式实时性差适合低优先级的大流量场景。我一般用中断模式但把中断处理函数做薄只做 DMA 描述符的回收和 PBUF 的提交具体协议处理交给 LWIP 线程。这样中断里停留时间短不会阻塞其他中断。DMA 描述符的数量也要调。CubeMX 里Rx Buffers设成 4 是保守值可以加到 8 或 16。每个描述符对应一个 BufferBuffer 多了能缓存的帧就多突发流量下不容易丢包。但 Buffer 占内存Rx Buffer Length是 1524 字节的话16 个 Buffer 就是 24KB要确认内存够。5.2 LWIP 参数微调与吞吐测试Ping 通了只是第一步实际跑 TCP 的时候还要调 LWIP 参数。TCP_MSS默认是 1460这是以太网 MTU 1500 减去 IP 头 20 和 TCP 头 20 的结果。如果网络环境有 PPPoE 之类的额外封装MSS 要相应减小。TCP_WND是 TCP 窗口大小决定一次能发多少未确认数据。默认可能只有 2048 或 4096太小了吞吐上不去。我一般设成4 * TCP_MSS也就是 5840 左右。TCP_SND_BUF也要相应调大至少等于TCP_WND。测试吞吐可以用 iperf 或者自己写个 TCP 回环服务。我实测 H723 LAN8720A 在 FreeRTOS 下TCP 发送能到 45Mbps 左右接收 60Mbps 左右。再往上受限于 PHY 和 MCU 的处理能力提升空间不大。5.3 长时间运行的稳定性注意事项以太网长时间运行最容易出两个问题一是 PHY 偶尔失联二是 LWIP 内存泄漏。PHY 失联一般是电源波动或者 ESD 导致的可以在软件里加个心跳检测定期读 PHY 的链路状态发现断了就重新初始化 PHY 和 MAC。LWIP 内存泄漏多半是应用层没正确释放 PBUF 或者 socket。用mem_malloc分配的内存一定要mem_freesocket 用完要close。可以在 LWIP 的配置里打开LWIP_STATS和MEMP_STATS定期打印内存使用情况发现异常及时排查。还有一个小技巧在ethernetif.c的low_level_input里加个计数器统计收到的帧数和丢弃的帧数。如果丢弃数持续增长说明 PBUF 池不够或者处理太慢需要调参数。6. 写在最后的几点实操体会这套 H7 LAN8720A LWIP 的组合我从第一次点亮到稳定跑通前后折腾了差不多两周。大部分时间不是花在写代码上而是花在查手册和量信号上。H7 的参考手册有几千页Ethernet 那一章就上百页但真正关键的就那么几个寄存器。我的建议是先把 PHY 的 SMI 通信调通能读到 ID 和链路状态再搞 MAC 和 DMA最后上 LWIP。顺序反了容易一头雾水。另外CubeMX 生成的代码只是起点不是终点。它帮你把外设初始化框架搭好但 PHY 驱动、内存配置、中断处理这些都要自己补。不要指望点几下鼠标就能 ping 通该看的寄存器一个都不能少。最后分享一个我常用的调试手段在MX_ETH_Init()之后手动读一遍 MAC 的配置寄存器确认ETH_MACCR的 bit 14FES和 bit 11DM跟 PHY 协商的结果一致。如果不一致说明 MAC 和 PHY 的双工模式没对上这种状态下 ping 可能通但大流量会丢包。这个检查花不了两分钟但能省掉后面几个小时的抓包分析。
返回列表