ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F407+LAN8720以太网调试实战:RMII接口配置与常见坑解析

STM32F407+LAN8720以太网调试实战:RMII接口配置与常见坑解析 做嵌入式这几年凡是涉及网络功能我脑子里第一个蹦出来的 MCU 永远是 STM32F407搭配一颗 LAN8720A 就能组出标准的 10M/100M 以太网口成本低、资料多、性能也够用。但这套组合有个绕不开的门槛就是 RMII 接口的配置与调试。很多人第一次点亮网口面对的往往是 PHY ID 读不出来、网线插上灯不亮、或者 Link 起来了却 ping 不通之类的问题。这篇文章算是我自己踩过几轮坑之后把 CubeMX 生成配置、HAL 库初始化、LAN8720 驱动移植以及现场排查思路整理成的一份完整操作记录。如果你是刚接触 F407 以太网或者板子到手后网口死活不通按这套流程走一遍大概率能把问题范围缩小到具体哪一个环节。1. 为什么 RMII 会让 F407 和 LAN8720 的组合变成老大难1.1 这网口到底由哪几段组成以太网链路从大的方向看是 MAC、PHY、网络变压器和 RJ45 座子共同组成。STM32F407 内部自带的是一个 MAC 控制器负责封装、解析以太网帧LAN8720 是物理层收发器负责把数字信号转换成差分电平送上网线。MCU 的 MAC 和 LAN8720 之间通过 RMII 接口连接然后再由 LAN8720 连接到带隔离变压器的 RJ45 座。这样一拆就能明白网口不通可能发生在任何一个环节。硬件上有供电、时钟、复位、差分线的问题软件上有引脚复用、PHY 寄存器、DMA 描述符、协议栈配置的问题。调试时如果一上来就翻协议栈的代码很容易把方向带偏。RMII 的意思是简化媒体独立接口相比传统的 MII 少了一堆信号线只保留了 TXD、RXD、TX_EN、CRS_DV、REF_CLK、MDC、MDIO 这几组尤其是数据线只有 2 位引脚占用大幅减少。但少一根引脚意味着每一位数据都要在更高的速率下工作因此 RMII 模式强制要求外部提供一个 50MHz 的参考时钟这个时钟是所有收发时序的基准。如果说 MII 像一条宽敞的大马路那 RMII 就是一条单车道路上所有车都必须严格按同一个时钟节拍运行时钟一旦乱了整个链路就会崩掉。1.2 为什么很多人都卡在 REF_CLK 上F407 的 ETH_RMII_REF_CLK 引脚是 PA1这个引脚在 RMII 模式下必须输入 50MHzMAC 侧本身不能直接产生这个时钟它必须由 PHY 或者外部有源晶振供给。这一点和很多人的直觉完全相反。有的板子是 LAN8720 外接 25MHz 晶振PHY 内部 PLL 倍频到 50MHz再由 REF_CLK 引脚输出给 PA1有的板子则是在 PC9MCO2上输出 50MHz 直接连到 LAN8720 和 PA1还有的板子干脆用一颗 50MHz 有源晶振同时给 PHY 和 MAC 提供时钟。三种方案在 CubeMX 里的配置基本一样但硬件上的区别很大这直接导致同样的代码放在不同板子上效果完全不同。所以拿到任何一块 F407LAN8720 的板子第一件事不是看代码也不是打开 CubeMX而是去看原理图。确认板上 LAN8720 用的是 25MHz 晶振还是外部 50MHz 时钟PA1 有没有接过去复位引脚 RC 电路时间常数大概多长PHY 地址是 0x00 还是 0x01这几项不确定后面怎么查都是盲人摸象。2. 动手配置前的硬件核查与原理图阅读2.1 LAN8720 的引脚与复位时序LAN8720A 的 RMII 信号有 ETH_RMII_TX_ENPA11 或 PB11、ETH_RMII_TXD0PA12 或 PB12、ETH_RMII_TXD1PA13 或 PB13、ETH_RMII_RXD0PA14 或 PB14、ETH_RMII_RXD1PA15 或 PB15、ETH_RMII_CRS_DVPA7 或 PB10加上 PA1 的 REF_CLK、PC1 的 MDC、PA2 的 MDIO。我劝大家不要死记因为同一颗芯片在不同开发板上的引脚映射完全不同有些板子用 PA 组有些板子为了避开调试器引脚而改用 PB 组。最靠谱的办法是打开 CubeMX根据原理图把对应引脚逐个勾选然后让工具自动生成代码。LAN8720 的复位时序也特别容易踩坑。很多板子在 nRST 上接了一个简单的 RC 延时电路上电后 PHY 的复位解除时间可能高达几十毫秒甚至上百毫秒。而 MCU 启动速度比 PHY 快得多所以在主函数里如果上电后立刻发起 MDIO 读写、立刻读 PHY ID很容易读回 0xFFFF。我的习惯是在初始化以太网外设之前先对复位引脚做一次明确的软件复位低电平保持 100ms 以上再拉高然后再延时 200ms 左右等 PHY 内部 PLL 稳定最后才去访问 PHY 寄存器。HAL_GPIO_WritePin(LAN8720_RESET_GPIO_Port, LAN8720_RESET_Pin, GPIO_PIN_RESET); HAL_Delay(120); HAL_GPIO_WritePin(LAN8720_RESET_GPIO_Port, LAN8720_RESET_Pin, GPIO_PIN_SET); HAL_Delay(200);如果板子复位引脚没有引出到 MCU那就只能靠上电 RC 延时但要注意软件里必须留足延时不要一上电就急着操作。2.2 PHY 地址和时钟拓扑的确认方法LAN8720 的 PHY 地址由 PHYAD0 引脚决定默认内部下拉所以绝大多数设计地址是 0x00。但有些厂商的评估板会把 PHYAD0 上拉地址变成 0x01。CubeMX 的 ETH 配置界面里有一个 PHY Address 下拉框很多人直接让它保持默认值这就埋下了一个隐患。正确做法是在原理图上搜 PHYAD 相关字眼或者在 CubeMX 的 ETH 配置中反复测试 0x00 和 0x01 哪个能读回正确的 PHY ID。时钟拓扑的确认稍微复杂一些。如果板上 LAN8720 附近有 25MHz 的无源晶振并且 PA1 确实连到了 LAN8720 的 REF_CLK 输出那就是 PHY 倍频给 MAC 供时钟的方案这时 PA1 是输入不需要配置任何时钟输出。如果板上只有 50MHz 有源晶振通常这个晶振会同时接到 LAN8720 的 XTAL1/CLKIN 和 PA1这种方案 PA1 同样是输入。还有一种方案是 STM32F407 的 PC9 用 MCO2 输出 50MHz 给 PHY这种情况下除了把 PC9 配置成 MCO 输出还要确认这个 50MHz 是否同时回馈到了 PA1。搞清楚这三者再去分析为什么网口不工作就不会一头雾水。3. CubeMX 配置实操从新建工程到产生初始化代码3.1 工程建立与外设时钟树设置打开 STM32CubeMX先选择 F407 对应的具体型号一般常用的是 STM32F407VGT6 或者 STM32F407ZGT6。时钟配置这里我直接说结论系统主频做到 168MHz即 HCLK168MHz、APB142MHz、APB284MHz这是 F407 最常规的配置。以太网 MAC 的外设时钟挂在 AHB1 上所以 APB 分频不影响 MAC 主干但如果你要用 MCO2 输出 50MHz 给 LAN8720就需要注意 PLL 的配置MCO2 可以选择输出 PLLCLK/2也就是 168MHz 除以 2 等于 84MHz并不是 50MHz这一点很多参考例程里并没有说清楚。如果是靠 PHY 自身倍频的时钟方案CubeMX 时钟树基本不用为以太网做特别处理只要保证主频和 USB 需要的 48MHz如果用到 USB 的话正确即可。我给的建议是除非你的原理图明确把 PC9 连到了 PHY 的时钟输入否则不要轻易去动 MCO2。很多时候网口不工作不是没有时钟而是调试者为了让 PA1 有时钟特意去开了 MCO2结果输出频率又不是 50MHz反而把问题搞复杂。3.2 ETH 外设的模式和参数设置在 Connectivity 分类下找到 ETH把它使能为 RMII。这里有两个地方需要重点检查一个是 PHY Address 的值要根据原理图填 0x00 或 0x01另一个是 MAC Address 的默认值CubeMX 会随机给一个我建议把它改成自己规划的地址比如 02:00:00:12:34:56避免和局域网内其他设备冲突。剩下的 Advanced Parameters 里自动协商、全双工、100Mbps 这些默认配置一般不需要改但如果你是在测试裸机回环可以关闭自动协商强制设置成 100M 全双工这样更容易定位问题。ETH 的 DMA 参数也很关键但 CubeMX 里这几个参数的暴露程度依赖版本。新版 CubeMX 的 ETH 配置页在高级参数里提供了 Tx、Rx Descriptor 数量默认值 4 一般够用。DMA 描述符和缓冲区最终会分配在哪块内存CubeMX 不会替你决定这需要在 HAL 初始化代码里手动指定稍后我会详细展开。把这些配好之后记得给 ETH 的全局中断使能打钩如果你打算用中断方式接收然后生成工程。3.3 引脚自动生成与冲突排查CubeMX 生成代码后打开 gpio.c 检查 PA1、PA7、PA11、PA12、PA13、PA14、PA15、PA2、PC1 等引脚是否都正确配置成了复用功能。这里要特别提醒PA13 和 PA14 默认是 SWD 调试引脚如果你的板子把 RMII 信号放在 PA13/PA14 上而调试器又占用了这两脚在调试时可能会发生冲突。我实际碰到过一种情况MDIO 和 MDC 默认复用没问题但某个引脚的 GPIO 速度没有调到 High导致高速时钟沿变缓偶尔丢掉一帧。所以生成代码后建议把 ETH 相关引脚的速度统一改成 Very High。PA8 这个引脚和 RMII 没有任何关系但很多 F407 核心板会用 PA8 做 USB Type-C 的 VBUS 检测。如果你在音频、USB 或者自定义代码里也初始化了 PA8并且它的模式被覆盖成了输入或输出那只是影响 USB 检测逻辑不会造成网口不通。不过排查问题时还是要注意有时代码一多某个外设的初始化函数把别的引脚状态改了造成奇怪的副作用。最稳妥的做法是在 main 函数里一层层注释确定只有 ETH 初始化执行的时候网口相关引脚的状态才是正确的。4. HAL 库里的 PHY 驱动到底要怎么改4.1 CubeMX 生成的 ETH 初始化代码能直接跑吗坏消息是F407 的 HAL 库虽然把 MAC、DMA、MDIO 的底层初始化都做好了但并没有把 LAN8720 这种具体 PHY 芯片的驱动全部封装好。生成代码后MX_ETH_Init 函数做的事情是初始化 MAC 控制器和 DMA 描述符但它不会去配置 LAN8720 的寄存器更不会帮你自动协商。所以你需要手动写一个简单的 PHY 驱动至少包含四个功能复位 PHY、读取 PHY ID、获取 Link 状态、配置 PHY 工作模式。在较新的 CubeF4 固件包里旧式 HAL_ETH_ReadPHYRegister 和 HAL_ETH_WritePHYRegister 仍然可用但在更早一点的版本里这两个函数需要你先调用 HAL_ETH_ReadPHYRegister而且参数顺序稍有不同。为了避免版本差异带来的困惑我一般直接在自己的驱动文件里封装一层函数内部统一调用 HAL 库的读写接口这样以后换芯片或者换库版本只需要改这个地方。uint8_t LAN8720_ReadReg(uint16_t reg, uint32_t *value) { return HAL_ETH_ReadPHYRegister(heth, reg, value) HAL_OK; } uint8_t LAN8720_WriteReg(uint16_t reg, uint32_t value) { return HAL_ETH_WritePHYRegister(heth, reg, value) HAL_OK; }4.2 自定义 LAN8720 驱动读取 PHY ID 和 Link 状态上电复位完成后第一件事是读 PHY 的标识寄存器。LAN8720A 的 PHY ID 由寄存器 2 和寄存器 3 组成读回来应该是 0x0007 和 0xC0F1。如果读到的不是这个值说明 MDIO/MDC 通路有问题或者 PHY 根本没工作。如果读到的是 0x0000可能是 PHY 地址没设对如果读到的是 0xFFFF大概率是复位没完成或者 MDIO 引脚配置有问题。Link 状态的判断用寄存器 1Basic Status Register的 bit 2这一位置 1 表示链路已建立。实际项目中我不建议只查一次就做出判断因为网线插拔瞬间这一位会有延迟我会写一个循环等待函数设定超时时间在超时时间内反复读取直到这一位稳定为 1。这样在应用层才能减少误判。uint8_t LAN8720_WaitLinkUp(uint32_t timeout_ms) { uint32_t reg 0; uint32_t tick HAL_GetTick(); while (HAL_GetTick() - tick timeout_ms) { LAN8720_ReadReg(0x01, reg); if (reg 0x04) return 1; HAL_Delay(10); } return 0; }除了 Link 状态你还可以通过寄存器 0Basic Control Register强制 PHY 工作在半双工、全双工、10M、100M 等模式。但常规用途下建议就用自动协商让 PHY 自己和交换机去协商速率。这里有一个细节LAN8720 的自动协商需要一点时间polling 等待不能太激进否则容易误判为协商失败。4.3 DMA 描述符发送缓冲区和接收缓冲区怎么分配HAL 库提供的收发接口基于 DMA 描述符描述符实际上是一组结构体数组每个描述符指向一块缓冲区。CubeMX 生成的代码里这些描述符和缓冲区要么默认分配在某个数组里要么需要你手动指定。我见过很多程序编译没问题跑起来却收不到数据原因就是描述符数组没有 4 字节对齐。虽然 F407 没有 D-Cache不存在 Cache 一致性问题但 DMA 控制器对内存对齐有要求描述符尤其如此。我习惯把一个完整的 DMA 缓冲区结构定义成 4 字节对齐并且缓冲区大小留足一整帧比如 1524 字节的空闲接收缓冲区防止超大帧把后续内存踩掉。__attribute__((aligned(4))) ETH_DMADescTypeDef TxDesc[ETH_TX_DESC_CNT]; __attribute__((aligned(4))) ETH_DMADescTypeDef RxDesc[ETH_RX_DESC_CNT]; __attribute__((aligned(4))) uint8_t TxBuf[ETH_TX_DESC_CNT][ETH_MAX_PACKET_SIZE]; __attribute__((aligned(4))) uint8_t RxBuf[ETH_RX_DESC_CNT][ETH_MAX_PACKET_SIZE];CubeMX 生成的 ETH 初始化函数里有一个 HAL_ETH_DescAssignMemory 或者通过 HAL_ETH_Init 之后的显式赋值过程。不同固件包差异比较大但只要描述符地址和缓冲地址与上面的数组对应上底层收发就能正常工作。我在裸机程序里推荐使用轮询方式接收也就是在主循环中不断调用 HAL_ETH_GetRxDataBuffer、HAL_ETH_Receive 和 HAL_ETH_ReleaseRxBuffer这样不依赖中断的优先级配置逻辑也容易理解。5. 调试实录网口不通常见的几个坑怎么排查5.1 读 PHY ID 失败的排查顺序如果 LAN8720_WaitLinkUp 调用后连接不上或者读 ID 返回 0xFFFF先不要急着改寄存器配置。我一般按顺序检查四项第一是量 LAN8720 的供电VDD 要 3.3VVDDCR 输出要 1.2V 左右如果 VDDCR 异常PHY 内部数字逻辑可能完全没起来第二是量复位引脚的波形确认复位解除后是不是高电平稳定第三是用示波器量 PA1 或者 REF_CLK 引脚有没有 50MHz 方波如果没有说明时钟通路有问题第四是核对 MDIO 和 MDC 的 GPIO 复用配置确认没被其他初始化覆盖。这一套下来基本能定位是硬件问题还是软件问题。有一个经验值得单独说MDIO 和 MDC 上因为需要和 PHY 通信如果外部上拉电阻没接或者接的电阻值过大在高速通信时波形沿会变差偶尔出现 CRC 错误或寄存器读回乱码。我遇到过一块板子读 PHY ID 时好时坏最后发现是 MDIO 上拉电阻焊错了改成 10kΩ 上拉后立刻稳定。5.2 Link 起不来网口灯不亮怎么办Link 灯不亮首先确认网线另一头是不是可靠设备比如交换机、路由器、直连电脑的网卡。然后检查 LAN8720 的差分信号是否正常RX/RX- 和 TX/TX- 要成对出现在 RJ45 座子上中间还要经过网络变压器。如果你用的是一块现成开发板这一步大概率没问题但如果是自己画的板子就需要仔细核对 RJ45 座和变压器之间的绕线接反或者断开都会导致 Link 起不来。还有一个容易忽略的问题是 LAN8720 和对端设备的协商能力。有些老交换机只支持 10M 半双工而 PHY 默认自动协商又没有回退机制可能导致灯不亮。此时可以用寄存器把 PHY 强制设成 10M 全双工试试如果强制后灯亮了说明变压器和网口座没问题问题在协商过程。另一个隐蔽点是 LAN8720 的 CLK/PHYAD1 引脚如果这个引脚的电平决定了 PHY 使用外部时钟还是内部晶振当 PHYAD1 拉高时 PHY 默认从 XTAL1/CLKIN 输入时钟如果你板子上实际使用 25MHz 晶振并且 PHYAD1 恰好拉高PHY 就无法正常工作。这个引脚很容易被忽略原理图上看一眼就能排除。5.3 能 Link 上但 ping 不通该从哪里切Link 上来之后问题就进入软件协议栈层面了。如果用的是 lwIP第一件事是确认 MAC 地址被正确设置不能全 0。第二件事是确认 IP 地址和电脑的 IP 在同一个网段比如开发板配置 192.168.1.10电脑手动设置 192.168.1.2掩码都是 255.255.255.0。第三件事是关闭电脑防火墙很多第一次调试的人会发现 ping 请求发出去没回应其实包到了电脑网卡但被系统防火墙拦了。裸机环境下没有协议栈可以先做一个最简单的回环测试。把发送缓冲区的目的 MAC 和源 MAC 都填成本机的 MAC 地址让 MAC 处于回环模式然后看能不能在接收缓冲区里收到自己发的帧。如果回环能收到说明 MAC 到 DMA 的收发送通路是通的剩下的问题就在 PHY 或协议栈上。F407 的 HAL 库没有直接提供一键回环 API但你可以通过设置 MAC 配置寄存器的相关位来实现这在验证底层时非常有帮助。5.4 能收到数据但 CRC 错误频繁可能是什么原因CRC 错误频繁大多数情况集中在两个原因一是时钟抖动REF_CLK 不是标准的 50MHz或者占空比严重偏差这会让 PHY 在采样数据时出现位错误二是 PCB 走线导致信号完整性问题RMII 数据线虽然只有几根但布线时如果和高速时钟靠太近串扰会造成偶发误码。遇到这类问题不要急着改代码先用示波器看 REF_CLK 波形再看数据线波形50MHz 时钟如果幅度不足或者边沿过缓就先把时钟通路处理好。软件上也有一点要注意DMA 描述符的环形链表必须正确初始化如果接收描述符被意外改写DMA 可能会把数据写到错误的缓冲区然后 CRC 校验也相当于完全错位。我遇到过一次诡异的现象接收到的前几帧正常后面每帧 CRC 都错最后发现是我的代码在中断里修改了同一个缓冲区而 HAL_ETH_ReleaseRxBuffer 还没被调用导致描述符指向的缓冲区被上层覆盖。先释放再处理数据顺序千万不能反。5.5 插上网线开机程序跑飞或者卡死在以太网初始化怎么办有一种典型现象是代码里在 MX_ETH_Init 之后立即开始读 PHY 寄存器但板子上电顺序导致 PHY 还没完成内部初始化软件就一直卡在等待循环里。这个问题在带 PoE 供电的设计中尤其常见PHY 的供电由网线供电模块输出和 MCU 上电时序不一样往往 MCU 已经跑起来几秒钟PHY 才刚开始上电复位。我的建议是 PHY 驱动里的所有等待都带超时超时后即使没有 Link 也要返回错误码而不是死循环否则整个系统会在初始化阶段被卡住。另一个原因是 MPU 配置。很多从 F7 或 H7 平台转过来的同事会习惯性地配上 MPU 和 Cache但在 F407 上根本没有这些模块CubeMX 也不会生成相关代码。如果你在网上看到 F4 的例程里有人写了 SCB_EnableDCache 之类的函数那大概率是拿别的平台代码硬改的直接忽略就好。F407 只需要关注对齐不需要处理 Cache 一致性问题。6. 调试工具与最后的经验清单6.1 一个有用的串口调试手段调试以太网最痛苦的地方在于你看不到 PHY 和 MAC 内部状态所以我习惯在串口上把关键信息全部打出来。上电后打印 PHY ID、Link 状态、自动协商结果、收到的帧计数、CRC 错误计数这样程序跑到任何位置我都能从串口日志里判断是底层的哪一环出了问题。很多人觉得 printf 太土但实际项目里最快定位问题的往往是这些简单的辅助输出。有些 HAL 版本里ETH 外设会提供一些统计寄存器比如接收错误计数、CRC 错误计数直接把对应寄存器的值读出来打印就能判断误码来自物理层还是 DMA 层。配合 Wireshark 抓包如果电脑端能收到开发板发出的 ARP 请求说明发送链路基本通了如果抓不到任何包但 MDIO 和 Link 都正常那就回到 DMA 描述符和缓冲区分配的逻辑上去查。6.2 我在实际操作里的几条经验第一不要相信网上任何一份现成代码能直接在你的板子上跑通F407 以太网的引脚映射、PHY 地址、时钟方案在不同板子之间差异太大最可靠的依据永远是原理图。第二调试顺序应该是先确认 PHY ID再确认 Link再做回环测试最后才跑协议栈跳过任何一步都可能导致问题放大让你在协议栈里找出根本不在协议栈里的问题。第三HW 上的坑远比软件多LM8720 这种 PHY 芯片对电源去耦和时钟质量很敏感如果软件排查到 PHY ID 都正常但仍然偶发丢包回头去量电源纹波往往比盯代码效率高得多。我之前做的一块板子就是因为在 LAN8720 的 VDDCR 引脚上少放了一颗 0.1uF 电容导致 PHY 核心电压不稳定现象是冷启动偶尔 Link 不上跑几分钟后网络自动恢复查了整整两天才发现是器件布局的问题。所以说FLAN8720 F407 这套方案只要硬件是正常的软件层面按部就班就能调通硬件有问题的时候再优秀的协议栈也撑不住。希望这份记录能让你少走一点弯路。
返回列表