那批板子拿回来那两天我盯着串口日志上的 link 始终为 down人麻了大半。明明是照着老熟人 LAN8720A 的流程在 STM32CubeMX 6.4.0 里配置 STM32F407ZGT6 的以太网外设换成国产 YT8512C PHY 之后却怎么都不通。后来把 REF_CLK 来源、PHY 地址、复位时序和 lwIP 状态判断挨个过了一遍才搞清楚坑不是单点的是一串。现在回头看这套组合只要捋顺了其实很稳所以把这个实战过程完整记录下来给准备用国产 PHY 替换开发的同行省点时间。文章基于 STM32F407ZGT6 YT8512C开发环境是 STM32CubeMX 6.4.0中间件用 lwIP从原理图确认、CubeMX 配置、代码适配一直讲到上电排障。1. 整体方案与硬件架构1.1 方案选型与整体链路F407 这颗芯片自带 10/100M 以太网 MAC外部只要再接一颗 PHY 芯片就能把网口做出来。以往最常用的是 LAN8720A这颗芯片资料多、教程烂大街但在批量成本敏感的场景里国产的 YT8512C 越来越常见。我这次接手的是用 YT8512C 的定板属于典型的外置 PHY 方案MAC 在 MCU 内部物理层收发器和网络变压器在板子上。整个数据通路大概是这样的应用程序把数据交给 lwIP 协议栈lwIP 往下走到底层网卡驱动驱动通过 DMA 把数据送入 STM32 内部的以太网 MACMAC 再通过 RMII 接口把帧送给 YT8512C最后由 YT8512C 转换成差分信号经网络变压器上到网线。反向收包就是逆过程。这里特别要拎清楚的是 MAC 和 PHY 的职责边界。MAC 管的是帧的分包、寻址、校验和 DMAPHY 管的是物理信号的编码、电平转换和链路协商两者之间通过 MDC/MDIO 等管理接口和 RMII/MII 数据接口通信。很多调试到后面发现 ping 不通问题其实不是 lwIP而是 PHY 物理层根本没把链路建立起来或者 MAC 口的时钟根本没跑对。1.2 MAC 层面的 RMII 协议STM32F407ZGT6 的以太网 MAC 支持两种对外接口MII 和 RMII。MII 需要 16 根数据相关引脚RMII 只需要 9 根对于管脚紧张的板子来说RMII 几乎是唯一选择。RMII 的工作频率是 50MHz数据总线从 MII 的 4bit 变成 2bit也就是一个时钟周期传 2bit通过 50MHz 时钟等效出 100Mbps 的速率。F407 的 RMII 引脚是固定的不是随便拿几个 GPIO 复用就能成具体引脚映射要做到心中有数别在 CubeMX 里看到了 RMII 选项就以为硬件随便接都行。RMII 里最核心的信号是 REF_CLK这个 50MHz 参考时钟必须稳定。我的理解是PHY 和 MAC 在做 RMII 传输时所有数据信号都以这个 50MHz 时钟为基准对齐。所以这个时钟的相位和幅度一旦有问题收到的帧基本是乱码表现就是能检测到载波但 MAC 收不到有效帧。1.3 YT8512C 与常见 PHY 的差异YT8512C 本身是一颗 10/100M 自适应 PHY支持 RMII 和 MII 两种接口模式寄存器大体上是兼容 IEEE 802.3 标准的但很多厂商寄存器位定义跟 LAN8720A 不一样。这就会造成一个很现实的麻烦很多人拿 CubeMX 自动生成的 ethernetif.c里面默认 link 状态检测依赖的是 ST 官方适配过的 PHY 型号如果直接套到 YT8512C 上可能会读不到真实的 link 状态位或者读到的 bit 语义完全不同。对比项YT8512CLAN8720ADP83848接口RMII / MIIRMIIMII / RMIIPHY 地址原理图引脚决定常见 0x01原理图引脚决定常见 0x00原理图引脚决定常见 0x01标准寄存器基本兼容基本兼容基本兼容厂商寄存器需按手册核对资料多资料多综合成本有优势偏高偏高所以用 YT8512C 的第一原则不要假设它跟 LAN8720A 完全一样。基础控制寄存器 0x00 和状态寄存器 0x01 一般是兼容的但速度、双工、自协商状态的细节判定最好拿到数据手册后对着位定义确认。CubeMX 里如果找不到 YT8512C 型号就选默认 PHY 或者通用 PHY地址按原理图填后续在驱动里自己适配 link 检测逻辑这才是正路。2. 硬件连接与关键信号设计2.1 F407 的 RMII 引脚映射确认先摆一张 F407ZGT6 上 RMII 对应的固定引脚表画原理图和拉线时直接对着这个查信号引脚方向说明ETH_RMII_REF_CLKPA1输入50MHz 参考时钟ETH_RMII_CRS_DVPA7输入载波侦听/数据有效ETH_RMII_RXD0PC4输入接收数据 0ETH_RMII_RXD1PC5输入接收数据 1ETH_RMII_TX_ENPB11输出发送使能ETH_RMII_TXD0PB12输出发送数据 0ETH_RMII_TXD1PB13输出发送数据 1ETH_MDCPC1输出管理时钟ETH_MDIOPA2双向管理数据这组引脚是 F407 的 RMII 硬件映射PA1 必须接 PHY 输出的 REF_CLK 或者和 PHY 共用一个 50MHz 时钟源。MDC 和 MDIO 是管理通道MDIO 通常是开漏结构硬件上最好加上拉电阻一般 2.2k 到 3.3k 都行。如果没有上拉MDIO 上的信号极容易读写成乱码我在一块自研板上栽过这个现象是 HAL_ETH_Init 返回正常但读取 PHY 寄存器偶尔成功偶尔失败。2.2 PHY 地址、复位与上电时序YT8512C 的 PHY 地址不是固定的看型号定的而是由芯片的 PHYAD 引脚电平决定的。多数模块原理图会把地址设成 0x01但也有设成 0x00 或者 0x10 的具体必须看原理图上 PHYAD 相关引脚的上拉下拉情况。CubeMX 的 ETH 配置里有一个 PHY Address 项务必和硬件一致否则 MDIO 访问的 PHY 就是个错误的地址后面 lwIP 怎么收包都是空的。上电时序是另一个容易被忽略的点。PHY 芯片上电后需要一段稳定时间一般数据手册会给出上电到 MDIO 可访问的最小延时。有的设计里 MCU 复位后立刻初始化 PHY结果读回寄存器全是 0xFFFF这就是 PHY 还没 ready。通常做法是在初始化里加一次至少 10ms 到几十毫秒的延时必要时把 PHY 的硬件复位引脚由 MCU 拉低再拉高手动触发一次完整的复位过程。STM32CubeMX 生成的代码默认不会帮你控制 PHY 的硬件复位所以这一块要自己在用户代码里补。2.3 50MHz REF_CLK 三种来源怎么选RMII 能不能通REF_CLK 是命门。常见的有三种接法。第一种是板子上放一颗 50MHz 有源晶振输出同时接到 YT8512C 的 REF_CLK 和 STM32 的 PA1这种方案最省心PHY 和 MAC 同源时钟稳定CPU 主频随便配不需要刻意跟 MCO 联动。第二种是 STM32 通过 MCO2 引脚输出 50MHz 给 PHY 的 REF_CLK同时 PHY 的 REF_CLK 再反馈给 PA1这种方案省一颗晶振但时钟树配置有讲究。第三种是 PHY 自己内部产生 REF_CLK 输出给 MAC部分 PHY 支持这个模式具体要看 YT8512C 手册里 RMII reference clock 的方向配置不是所有芯片都支持。如果板子设计选的是第二种 MCO2 方案那你在 CubeMX 时钟树里会遇到一个很经典的取舍想输出真正的 50MHzPLLP 输出频率必须是 100MHz而 F407 的 MCO2 在常用配置中输出的是 PLLP 的二分频。也就是说系统时钟如果跑 168MHzPLLP 是 168MHzMCO2 分频后是 84MHz根本出不了 50MHz。想拿到 50MHz就得让 SYSCLK 定在 100MHz这种情况下 USB 的 48MHz 又很难同时精确满足。所以我的建议很直接如果板子上有 USB 需求不要指望用 MCO2 出 50MHz 同时还要 USB 48MHz优先改用有源晶振或者外置时钟。如果只是纯以太网应用100MHz 主频完全够用用 MCO2 方案也能稳定跑。2.4 原理图核对几个容易漏的关键点画完板子或者拿到别人板子时别急着配软件先把这几个点核一圈。PHY 供电脚上有没有足够的去耦电容YT8512C 的电源引脚如果滤波不到位link 状态会来回跳。网络变压器是外置的还是 RJ45 connector 内部集成的10/100M 只用一对发送差分和一对接收差分变压器中心抽头接法要参照数据手册抽头接电容和终端电阻的取值会影响信号质量。MDI 差分走线应该做等长和阻抗控制虽然低速下要求没那么苛刻但线拉太长或者跨分割自协商会不稳定。YT8512C 的 LED 引脚接法也要确认如果软件想通过 GPIO 读 link 状态而不是靠 MDIO 轮询那 LED 引脚就得引出到 MCU否则只能老老实实走 MDIO 寄存器。3. STM32CubeMX 6.4.0 逐项配置与代码生成3.1 先建工程芯片、调试口与时钟树打开 STM32CubeMX 6.4.0新建工程芯片型号选 STM32F407ZGT6。在 System Core 里把 SYS 的 Debug 选成 Serial Wire不然烧录器二次连接容易出问题。RCC 选 HSE 的 Crystal/Ceramic Resonator注意这里一定要填对板子实际晶振频率CubeMX 如果默认给你按 25MHz 算而板子上实际是 8MHz那最终的波特率、以太网时钟全都会偏掉。时钟树的配置取决于 REF_CLK 方案。如果板子是外置 50MHz 有源晶振独立给 PHY那么 MCU 的主频按你的应用需求配不需要管 MCO2。如果板子是从 STM32 MCO2 取 50MHz假设 HSE 是 25MHz 晶振我会把 PLL_M 设 25、PLL_N 设 200、PLL_P 设 2这样 VCO 是 200MHzSYSCLK 是 100MHzMCO2 选择 PLLP 二分频后正好 50MHz。这种配置下如果还能接受 USB 时钟是 50MHz 而不是标准 48MHz那就把 PLL_Q 设 4跑普通非 USB 或者不做紧时序的应用问题不大但严格要过 USB 认证就别这么干。3.2 开启 ETH 外设选择 RMII 并设置 PHY 地址在 Connectivity 里启用 ETH模式选 RMII。配置界面里有个 PHY Address按你在原理图上查到的地址填比如常见的是 0x01。这里还要留意 CubeMX 可能让你选择一个 PHY 型号如果下拉列表里没有 YT8512C选默认或者 Generic 的 PHY 模型不要硬选 LAN8720A因为不同的 PHY 驱动在协议栈里的 link 检测逻辑可能不完全一致硬选会让后面排查时心里没底。ETH 的 MAC 地址建议在这里就设好。随便填一个 02:00:00:00:00:01 这种本地管理地址也没问题但注意不要用全 0也不要用广播地址。MAC 地址写错或者全 0ARP 阶段就可能出问题表现是电脑 ping 板子时有 ARP 请求板子就是不回。3.3 挂上 lwIP 中间件关键参数与初始配置中间件里勾选 LWIPlwIP 版本在 6.4.0 里一般是 2.1.x这套配置直接照着用就行。先别折腾太多高级参数目标是把链路跑通。关键参数我按自己习惯列一下参数推荐值说明IP VersionIPv4先用最简网络ModeStatic 优先测试通了再换 DHCPLocal IP192.168.1.10和电脑同一网段Netmask255.255.255.0常规 C 类掩码Gateway192.168.1.1没有路由器也先填同网段地址LWIP_MEM_SIZE保持默认够 ping 通测试PBUF_POOL_SIZE保持默认收发小流量够用TCP_WND保持默认跑业务再调如果选择了带 FreeRTOS 模式lwIP 会以独立的线程方式运行主循环里不需要手动轮询如果选择 NO_SYS 裸机模式CubeMX 会在 main 的 while 循环里生成 MX_LWIP_Process 的调用这个调用非常重要它驱动底层网卡轮询和协议栈定时处理。很多人把这段删了或者被自己的代码覆盖结果就是网口收发都不动。3.4 生成工程后别急着编译确认自动代码工程生成后先不要急着改业务代码打开几个关键文件扫一眼。eth.c 里是 ETH 外设的 HAL 初始化lwip.c 里有 MX_LWIP_Initmain.c 里确认初始化顺序先是 HAL_ETH_Init 再是 MX_LWIP_Init。ethernetif.c 是底层网卡适配层里面会有 PHY 地址的宏定义、MAC 地址赋值和 link 状态检测函数。若生成代码里 PHY 地址不是我需要的 0x01先在这里用全局搜索改掉再往下排查。这个文件不建议整体手改但 PHY 相关那几个宏和函数是需要动手的。4. YT8512C 驱动适配与 lwIP 联动4.1 先确认 YT8512C 的寄存器语义拿到 YT8512C 数据手册后第一件事不是看跑多少速率而是看地址为 0x00 到 0x03 的寄存器定义。0x00 是基本控制寄存器0x01 是基本状态寄存器这两个是 IEEE 标准规定的基础寄存器大部分功能位在各家 PHY 上是一致的。0x02 和 0x03 是 PHY ID 寄存器可以用来验证 MDIO 通道是否通。调试时我会在初始化后直接读一遍 0x02 和 0x03如果能读到非 0xFFFF、非 0x0000 的具体值说明 MDIO 链路、PHY 地址、上电复位这三件事都对了。如果读回来全是 0xFFFF那就把排查重心放到 PHY 地址和复位时序上。link 状态检测最常用的是 0x01 寄存器的 bit2这个位为 1 表示链路已经建立为 0 表示链路断开。STM32CubeMX 生成的 ethernetif.c 里默认 link 检测逻辑可能用的是某个预设 PHY 的寄存器位如果没被适配它可能读出来的状态永远不对。最简单的适配办法是找到这个函数把读取的寄存器改成 0x01把判断位改成 bit2先让链路状态出来再考虑速度双工的细节。4.2 修改 PHY 地址与 link 状态读取逻辑在 STM32CubeMX 生成的 ethernetif.c 里通常会有一个类似 PHY 地址宏定义的地方#define PHY_ADDRESS 0x01U如果原理图上是 0x01那这个值就对了。如果不是改成实际值。link 状态读取函数示意如下注意不同 CubeMX 版本生成的结构略有差异我只写关键逻辑uint32_t reg 0; if (HAL_ETH_ReadPHYRegister(heth, 0x01U, reg) HAL_OK) { if ((reg (1U 2)) ! 0U) { /* 链路已建立 */ link_status 1U; } else { /* 链路断开 */ link_status 0U; } }实际 ST 生成的代码里很多时候是通过一个 ReadPHYRegister 函数配合封装宏来做的。我不建议在不知道函数签名的情况下硬改更稳妥的方式是先搜生成文件中是否有0x00、0x01、PHY_BSR之类的关键字再根据上下文替换成 YT8512C 对应的定义。改完以后重新编译烧录串口打印或调试器里观察链路状态是否能从 0 变 1。这一步通了后面 lwIP 才有意义。4.3 裸机模式下的 lwIP 初始化与轮询裸机模式也叫 NO_SYS 模式所有协议栈工作都在 super loop 里跑。CubeMX 生成的 main 里大概是这样MX_ETH_Init(); MX_LWIP_Init(); while (1) { MX_LWIP_Process(); }MX_LWIP_Process 里做的事情等效于不断调用底层的网卡轮询函数把接收到的帧送给 lwIP 处理同时推进 ARP、ICMP、TCP 等协议栈的定时事件。如果你在 while 循环里有像延时 1 秒之类的阻塞逻辑一定要让 MX_LWIP_Process 保持被频繁调用。实测里最常见的现象是初始化完成后没问题但主循环里加了别的阻塞延时ping 的响应时间就忽高忽低甚至完全不通根源就是协议栈饿死了。另外裸机模式下 lwIP 的内存管理用的是静态内存池和内存堆CubeMX 给的是默认值测试 ping 完全够用不建议一开始就调大。等到后续真要跑大流量 TCP 传输了再去根据吞吐需求调整 MEM_SIZE、PBUF_POOL_SIZE 和 TCP_SND_BUF 这些参数。4.4 如果切到 FreeRTOS 模式如果工程里有实时操作系统需求CubeMX 里 lwIP 配置可以打开 FreeRTOS 集成。两者的最大区别在于协议栈线程化和底层网卡驱动轮询的实现方式。带 FreeRTOS 时lwIP 会创建 tcpip_thread 和相关线程你来管理自己的应用线程即可。切到这个模式需要注意两点一是堆大小要够FreeRTOS 的任务栈和 lwIP 的内存池会叠加占用 RAM二是中断回调里不要直接调用 lwIP API跨线程调用 API 需要走 tcpip_callback 机制否则在不算高的网络压力下就可能出现死锁或内存踩踏。5. 上电调试、问题定位与排障实录5.1 调试环境与预期结果调试前把环境准备好。板子网口用网线接电脑的 RJ45或者接同一个交换机。电脑的有线网卡手动设成 192.168.1.2掩码 255.255.255.0网关 192.168.1.1。板子这边配置的静态 IP 是 192.168.1.10。串口用 115200 波特率打开方便打印 lwIP 初始化和链路状态信息。启动后预期能看到 PHY 的 link 状态从 down 变 up串口打印出 IP 地址配置然后在电脑上执行 ping 192.168.1.10正常情况是每 1 秒左右返回一次 Reply延迟在毫秒级。如果 ping 不通先别急着怀疑代码按下面的层次一层一层看。物理层看网口指示灯和链路状态寄存器链路层看 ARP 请求有没有回应网络层看 ICMP 报文能否回包。我前面整理了一张排障顺序基本能覆盖我这段时间遇到的百分之八十的问题。5.2 从物理层到协议栈逐层排查先看物理层。测量 YT8512C 的电源是否正常REF_CLK 引脚有没有 50MHz 稳定时钟PHY 复位引脚是否被拉到了正确电平。然后读 PHY 寄存器 0x01bit2 是否为 1。如果 bit2 是 1说明链路已经建立物理层基本没问题。如果 bit2 一直是 0检查网线、对端网口、PHY 的自协商使能位以及是否有人把网线接到了不支持百兆的老设备上。再看 MAC 层。HAL_ETH_Init 返回值要等于 HAL_OK初始化完以后看 STM32 的 ETH DMA 有没有使能。有的调试场景里 PHY link 已 up但无法收包这时候要检查 MAC 的 RMII 时钟是否和 PHY 一致检查时钟树是否有误MCO2 配置是否真正生效PA1 上能否测到 50MHz 信号。如果 MAC 时钟和 PHY 时钟不是同源或者相位差太大会出现收发偶尔成功但大部分时间失败的诡异现象。最后才是协议栈层。确认 lwIP 初始化返回的 IP、掩码、网关和板子实际需要的一致。如果 lwIP 里是 DHCP 模式但局域网内没有 DHCP 服务器板子会一直拿不到 IP表现就是 ping 不通。确认模式下优先静态 IP 排除这个变量。然后再看电脑端防火墙Windows 某些状态下会拦截 ICMP 回显这个跟板子没关系先把防火墙临时关了再测。5.3 常见问题速查表现象大概率原因处理办法ETH 初始化返回 HAL_ERRORPHY 地址不对 / MDIO 上拉缺失 / REF_CLK 无输出核对原理图地址、检查 MDC/MDIO 上拉、测量 PA1 时钟PHY 寄存器读出全是 0xFFFFPHY 未上电 / 复位未释放 / PHY 地址错误检查供电与使能脚加快 PHY 稳定延时link 状态始终 down自协商失败 / 网线质量差 / 对端类型不匹配读 BSR bit2强制 100M 全双工测试link up 但 ping 超时IP 不在同网段 / 防火墙 / lwIP 未轮询改用静态 IP、关闭防火墙、确认 MX_LWIP_Process 被调用能收到 ARP 请求但不回 ARPMAC 地址不对 / 驱动收包路径异常 / DMA 配置问题检查 MAC 地址配置确认 RX 描述符链表和 DMA 中断偶发丢包或者时延抖动时钟源不稳定 / 双工不匹配 / 内存池不足检查 REF_CLK 波形、强制双工、调整 lwIP 内存参数这张表是我把几个项目里的问题合并之后整理的不一定每条都对应你的场景但排查顺序是通用的先物理层后 MAC 层再协议栈层。不要一上来就怀疑 lwIP 的 TCP 窗口太小。5.4 用 Wireshark 验证数据流动ping 不通的时候Wireshark 比串口日志更直观。电脑上打开 Wireshark选择有线网卡过滤条件填arp || icmp然后开始 ping 板子。正常流程应该是电脑先发一个 ARP 请求询问 192.168.1.10 的 MAC 地址板子回 ARP 响应然后电脑发 ICMP Echo Request板子回 ICMP Echo Reply。如果看不到板子的 ARP 响应说明板子接收正常但发送路径或者 lwIP 协议栈有问题重点查 MAC 地址和 DMA 发送。如果 ARP 响应能看到但 ICMP Reply 看不到重点查 lwIP 的 IP 地址、网关和 checksum 配置。还有一个检查技巧电脑端看 ARP 表命令是 arp -a。如果板子重启后电脑还保留旧的 MAC 映射可以先 arp -d 清一下缓存再测。很多人栽在这里板子已经换了一版程序、MAC 地址变了电脑 ARP 缓存没更新看起来就像板子没回包。这种问题和你写的代码毫无关系浪费一晚上不值得。6. 最后说点实操体会这一套 STM32CubeMX 6.4.0 STM32F407ZGT6 YT8512C lwIP 的组合做下来最大的感受是国产 PHY 本身没多大问题真正的问题是资料少、参考工程少导致很多细节要靠自己踩。如果你也打算移植到别的 PHY 上我建议一开始就留出读 PHY ID 和 link 状态的功能随时能确认物理层状态后面能省大量时间。还有一点CubeMX 生成的默认代码尽量少改尤其是底层 HAL_ETH 初始化部分——需要改的是 PHY 地址、link 检测逻辑和复位时序这些写在用户区和 ethernetif.c 的适配层里就行。后面如果再想加 TCP Server、MQTT 或者上 FreeRTOS 做多任务这套底子都不用动直接在应用层往上叠就行。
