GD32F4移植YT8512 PHY驱动:RMII接口与FreeRTOS实战
1. 项目缘起与整体方案拆解GD32F4 系列作为国产 Cortex-M4 阵营里性价比相当能打的一颗料主频能跑到 200MHz自带以太网 MAC 控制器做工业网关、数据采集器、边缘计算节点这类带网口的设备非常合适。但真正上手做项目的时候很多人会卡在同一个地方官方例程用的是自家的 PHY 或者常见的 LAN8720而手头板子上焊的偏偏是 YT8512 这颗国产 PHY。这时候就面临一个很现实的问题——驱动怎么移植RMII 模式下的时钟和引脚怎么配FreeRTOS 下收发数据怎么不丢包。我这次做的项目就是基于 GD32F427 加 YT8512跑 FreeRTOS实现一个稳定的以太网通信节点。整个方案的核心思路其实不复杂GD32F4 内部集成了以太网 MAC媒体访问控制层它负责数据链路层的帧组装、CRC 校验、地址过滤这些活而 PHY物理层芯片负责把 MAC 送来的数字信号变成差分信号发到网线上同时把网线上的信号还原成数字信号送回 MAC。两者之间通过 RMII 接口连接MAC 和 PHY 之间再通过 SMI站管理接口本质就是 MDIO 和 MDC 两根线来读写 PHY 的寄存器完成协商、状态查询、速率双工配置这些操作。为什么选 RMII 而不是 MII这是第一个要讲清楚的选择。MII 接口需要 16 根数据和控制线RMII 精简到 7 根两根时钟、两根发送数据、两根接收数据、一根 CRS_DV引脚占用少了一大半。代价是 RMII 的参考时钟必须是 50MHz而 MII 是 25MHz。对于 GD32F427 这种引脚资源紧张、又要跑 FreeRTOS 加一堆外设的场景RMII 几乎是唯一合理的选择。YT8512 支持 RMII 模式而且它有一个很实用的特性——可以输出 50MHz 参考时钟给 MAC 用这样整个系统只需要一个晶振就能同时喂饱 PHY 和 MAC省料省事。FreeRTOS 在这个项目里的角色是任务调度和资源管理。以太网收发如果裸机轮询CPU 会被大量占用响应其他任务就慢了。用 FreeRTOS 之后可以把网络接收放到一个独立任务里阻塞等待信号量发送放到另一个任务主控任务该干嘛干嘛。但这里有个坑FreeRTOS 的中断优先级配置和以太网中断的优先级必须匹配否则会出现中断里调用 FreeRTOS API 导致断言失败的问题。这个后面会详细说。整个方案适合谁参考如果你手上有 GD32F4 的板子焊了 YT8512 或者类似的国产 PHY想跑 FreeRTOS 做网络通信那这篇内容基本可以照着抄。如果你用的是 STM32F407 加 LAN8720 或者 83848接口逻辑是相通的RMII 的配置思路和 PHY 寄存器操作也大同小异同样有参考价值。甚至你只是想知道 RMII 接口到底怎么接线、50MHz 时钟从哪来、FreeRTOS 下网络任务怎么划分这里都有答案。2. 硬件连接与 RMII 接口配置细节2.1 RMII 接口的引脚映射与时钟来源RMII 接口的信号线不多但每一根都不能接错。先把 GD32F427 这边以太网 MAC 的 RMII 引脚列出来再对照 YT8512 的引脚定义一根一根对。GD32F4 的以太网 RMII 模式用到以下引脚以常见封装为例信号名GD32F4 引脚方向说明REF_CLKPA1输入/输出50MHz 参考时钟MDIOPA2双向站管理数据MDCPC1输出站管理时钟CRS_DVPA7输入载波侦听/数据有效RXD0PC4输入接收数据位0RXD1PC5输入接收数据位1TX_ENPB11输出发送使能TXD0PB12输出发送数据位0TXD1PB13输出发送数据位1这里最关键的是 REF_CLK 这根线。RMII 协议规定 MAC 和 PHY 必须共用同一个 50MHz 参考时钟。有两种方案第一种是外部放一个 50MHz 有源晶振同时接到 MAC 和 PHY 的 REF_CLK 引脚第二种是 PHY 外接 25MHz 晶振内部 PLL 倍频到 50MHz然后从 PHY 的 REF_CLK 引脚输出给 MAC。YT8512 支持第二种方案这也是我推荐的做法因为省一个晶振而且 PHY 输出的时钟和 PHY 内部工作时钟同源相位关系更稳定。具体到 YT8512 的电路连接几个要点必须注意。YT8512 的 XTAL1 和 XTAL2 之间接 25MHz 无源晶振负载电容根据晶振规格选一般 12pF 到 20pF 之间。PHY 的 REF_CLK 输出引脚接到 GD32F4 的 PA1。这里有个细节GD32F4 的 PA1 在 RMII 模式下是复用功能需要配置成对应的 AF 模式而且要注意它的输入输出方向——当 PHY 提供时钟时PA1 配置为输入模式接收 PHY 送来的 50MHz 时钟。MDIO 和 MDC 这两根线需要上拉。MDIO 是双向线通常接一个 1.5k 到 10k 的上拉电阻到 3.3V具体阻值看 PHY 手册推荐。MDC 是主机输出一般不需要上拉但有些设计为了信号完整性会加一个弱上拉。MDC 的频率不能超过 PHY 支持的最大值YT8512 的 MDC 最高支持 25MHz但实际用的时候建议分频到 2.5MHz 左右就够了太快没必要还容易受干扰。2.2 YT8512 的地址配置与复位电路YT8512 的 SMI 地址由几个配置引脚在上电复位时锁存决定。具体是哪几个引脚、怎么组合得看 YT8512 的数据手册。一般来说PHY 地址范围是 0 到 31GD32F4 的 MAC 支持通过 MDIO 访问 32 个 PHY 地址。如果板子上只挂了一颗 PHY地址通常配成 0 或者 1。配置引脚一般内部有弱上拉或弱下拉但为了可靠建议外部加明确的上下拉电阻避免上电时引脚悬空导致地址锁存错误。复位电路这块YT8512 有一个 RST_N 引脚低电平复位。标准做法是用一个 RC 电路RST_N 接一个 10k 上拉电阻到 3.3V同时接一个 100nF 电容到地。上电时电容充电RST_N 保持低电平一段时间保证 PHY 完成内部初始化。这个时间不能太短YT8512 手册里一般要求复位低电平至少保持 10ms 以上。如果 MCU 有多余的 GPIO也可以直接用 GPIO 控制复位这样软件可以主动复位 PHY调试的时候很方便。我这次就是用了一个 GPIO 来控制 RST_N上电后先拉低 20ms 再拉高确保 PHY 状态干净。还有一点容易被忽略YT8512 的某些配置引脚在复位释放的瞬间会被采样用来决定 PHY 的工作模式比如 RMII 还是 MII、全双工还是半双工、是否开启自动协商。这些引脚在复位期间必须保持稳定不能有毛刺。如果板子上这些引脚还接了其他外设要确保上电过程中不会误触发。我踩过一次坑某个配置引脚复用了 LED 驱动上电时 LED 的驱动电路把引脚拉低了导致 PHY 进入了错误的模式后来把 LED 驱动改到别的引脚才解决。2.3 GD32F4 以太网 MAC 的时钟配置GD32F4 的以太网 MAC 需要两个时钟一个是 RMII 参考时钟50MHz从 PA1 输入另一个是 MAC 自身的时钟通常由系统时钟经过分频得到。在 GD32F4 的时钟树里以太网 MAC 的时钟源可以选择 HCLK 或者 PLL 输出具体看参考手册的时钟树章节。RMII 模式下MAC 的 TX 和 RX 逻辑都依赖 50MHz 参考时钟。GD32F4 的以太网外设有一个 ETH_RMII_REF_CLK 的选择位需要配置成从外部引脚输入。这个配置在以太网 MAC 的控制寄存器里具体是哪个位、怎么设置后面代码部分会讲。系统时钟这边GD32F427 跑 200MHz 的时候HCLK 是 200MHz以太网 MAC 时钟如果从 HCLK 分频需要确保分频后的频率在 MAC 支持范围内。GD32F4 的以太网 MAC 时钟最高可以到 150MHz 左右但实际用的时候一般分到 100MHz 或者 50MHz 就够了。分频系数在 RCU 模块里配置具体寄存器是 RCU_CFG0 或者 RCU_CFG1 里的 ETH 时钟选择位。这里有个实操经验如果 RMII 参考时钟不稳定或者频率不对MAC 初始化会卡在等待 PHY 协商完成的循环里或者能初始化但收发数据全是错的。用示波器量 PA1 上的波形确认是干净的 50MHz 方波幅度在 3.3V 左右。如果波形幅度不够或者频率偏差大先查 PHY 的 25MHz 晶振是否起振再查 PHY 的 PLL 配置是否正确。3. YT8512 驱动移植与 PHY 寄存器操作3.1 PHY 标准寄存器与 YT8512 扩展寄存器YT8512 的寄存器分为两块一块是 IEEE 802.3 标准定义的通用寄存器地址 0 到 15另一块是厂商自定义的扩展寄存器地址 16 以上。标准寄存器里最常用的几个寄存器地址名称关键位说明0x00BMCRbit15 复位, bit12 自动协商使能, bit8 双工模式基本控制0x01BMSRbit5 自动协商完成, bit2 链路状态基本状态0x02PHYID1高16位ID厂商ID高字节0x03PHYID2低16位ID厂商ID低字节0x04ANAR自动协商通告本端能力0x05ANLPAR链路伙伴能力对端能力YT8512 的 PHY ID 可以通过读寄存器 2 和 3 得到。YT8512 的 ID 一般是 0x0000 开头或者 0x4c00 开头具体值查手册。读 ID 是验证 SMI 通信是否正常的第一步——如果读出来的 ID 全是 0xFFFF 或者 0x0000说明 MDIO/MDC 时序有问题或者 PHY 地址配错了。YT8512 的扩展寄存器里有一些很实用的功能比如寄存器 0x11PHY 特定状态可以读到当前协商的速率和双工模式寄存器 0x1E一些测试和配置位寄存器 0x1FRMII 模式配置有些 PHY 需要在这里设置 RMII 还是 MII具体到 YT8512它的 RMII 模式通常是通过硬件配置引脚决定的不需要软件写寄存器。但有些批次的芯片可能需要软件确认一下。我建议在初始化的时候读一下相关的配置寄存器确认 RMII 模式已经生效。3.2 SMI 读写时序与代码实现SMI 接口的时序不复杂但时序参数必须满足 PHY 的要求。MDC 是时钟MDIO 是数据。写操作时MAC 在 MDC 上升沿之前把数据放到 MDIO 上PHY 在 MDC 上升沿采样读操作时PHY 在 MDC 上升沿之前把数据放到 MDIO 上MAC 在上升沿采样。GD32F4 的以太网 MAC 硬件自动处理 SMI 时序软件只需要配置好 MDC 时钟分频然后往 MAC 的 SMI 数据寄存器和地址寄存器写值再触发读写操作。GD32F4 的 SMI 时钟分频在 MACMIIAR 寄存器里配置分频系数从 HCLK 分频得到 MDC。假设 HCLK 是 200MHzMDC 目标频率是 2.5MHz分频系数就是 200/2.5/2 40实际配置的时候要查手册确认分频公式。下面是一段 SMI 读写的核心代码逻辑基于 GD32 的标准外设库风格#define PHY_ADDR 0x00 uint16_t phy_read(uint16_t reg) { uint32_t timeout 0; /* 等待 SMI 空闲 */ while (ETH_MAC-MACMIIAR MACMIIAR_MB) { if (timeout 100000) return 0xFFFF; } /* 配置 PHY 地址、寄存器地址、读操作、时钟分频 */ ETH_MAC-MACMIIAR (PHY_ADDR 11) | (reg 6) | MACMIIAR_CR_DIV42 | MACMIIAR_MB; timeout 0; while (ETH_MAC-MACMIIAR MACMIIAR_MB) { if (timeout 100000) return 0xFFFF; } return (uint16_t)(ETH_MAC-MACMIIDR 0xFFFF); } void phy_write(uint16_t reg, uint16_t data) { uint32_t timeout 0; while (ETH_MAC-MACMIIAR MACMIIAR_MB) { if (timeout 100000) return; } ETH_MAC-MACMIIDR data; ETH_MAC-MACMIIAR (PHY_ADDR 11) | (reg 6) | MACMIIAR_CR_DIV42 | MACMIIAR_MW | MACMIIAR_MB; timeout 0; while (ETH_MAC-MACMIIAR MACMIIAR_MB) { if (timeout 100000) return; } }这段代码里加了超时保护这是实际项目里必须的。如果 PHY 没焊好或者 MDIO 线断了没有超时的话程序会死循环在这里整个系统就卡死了。超时值设 100000 次循环在 200MHz 主频下大概几毫秒足够完成一次 SMI 操作。3.3 PHY 初始化流程与自动协商PHY 初始化的标准流程是这样的硬件复位 PHY拉低 RST_N 至少 10ms读 PHY ID确认 SMI 通信正常软复位 PHY写 BMCR 的 bit15等待软复位完成读 BMCRbit15 自动清零配置自动协商通告寄存器ANAR设置支持的速率和双工模式启动自动协商写 BMCR 的 bit12 和 bit9等待自动协商完成轮询 BMSR 的 bit5读 PHY 特定状态寄存器确认最终协商结果根据协商结果配置 MAC 的速率和双工模式YT8512 支持 10M/100M 自适应支持全双工和半双工。在 RMII 模式下100M 全双工是最常见的配置。自动协商通告寄存器里bit8 和 bit7 分别对应 100M 全双工和 100M 半双工bit6 和 bit5 对应 10M 全双工和 10M 半双工。一般把 100M 全双工和 10M 全双工都置上让 PHY 和对端协商出最佳结果。这里有个实操细节自动协商完成之后不要只看 BMSR 的 bit5还要读一下 PHY 特定状态寄存器YT8512 是 0x11确认实际协商的速率和双工。有些交换机或者路由器的自动协商行为不太标准BMSR 显示协商完成但实际链路是半双工或者 10M如果不查这个寄存器MAC 配置成 100M 全双工链路就会大量丢包。还有一个坑GD32F4 的 MAC 在初始化的时候如果 PHY 还没协商完成MAC 的发送和接收使能位不要急着打开。正确的顺序是先初始化 MAC 的基本配置地址、过滤模式等然后等 PHY 协商完成再根据协商结果配置 MAC 的速率和双工最后使能收发。顺序反了的话MAC 可能以错误的速率工作导致链路不通。4. FreeRTOS 下的网络任务设计与中断配置4.1 以太网中断优先级与 FreeRTOS 的兼容性FreeRTOS 在 Cortex-M4 上运行时会配置一个最低中断优先级阈值通常是 configMAX_SYSCALL_INTERRUPT_PRIORITY。只有优先级数值大于等于这个阈值的中断才允许在中断服务函数里调用 FreeRTOS 的 API比如 xSemaphoreGiveFromISR。优先级数值小于这个阈值的中断FreeRTOS 内核不会屏蔽它们但它们也不能调用 FreeRTOS API。GD32F4 的中断优先级寄存器是 4 位有效数值越小优先级越高。FreeRTOS 默认把 configMAX_SYSCALL_INTERRUPT_PRIORITY 设成 5 或者 6具体看配置。以太网中断的优先级必须设置成大于等于这个值比如设成 6 或者 7这样才能在以太网中断里安全地给信号量。我见过有人把以太网中断优先级设成 0 或者 1然后在中断里调用 xSemaphoreGiveFromISR结果 FreeRTOS 直接触发断言程序卡在 vAssertCalled 里。原因就是优先级太高FreeRTOS 认为这个中断不受它管理不允许调用它的 API。具体配置的时候在 FreeRTOSConfig.h 里确认这两个宏#define configPRIO_BITS 4 #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 #define configMAX_SYSCALL_INTERRUPT_PRIORITY \ (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY (8 - configPRIO_BITS))然后在以太网中断初始化的时候把抢占优先级设成 6 或者 7nvic_irq_enable(ENET_IRQn, 6, 0);这样以太网中断就能安全地调用 FreeRTOS 的 FromISR 系列 API 了。4.2 网络接收任务与信号量同步FreeRTOS 下以太网接收的典型架构是这样的以太网中断服务函数里当收到一帧数据时给一个二值信号量或者任务通知接收任务阻塞等待这个信号量一旦等到就去读取 MAC 的接收 FIFO处理数据。为什么用二值信号量而不是队列因为以太网接收是“有数据就处理”的模式不需要缓存多帧。二值信号量足够表达“有数据待处理”这个状态。如果数据量大、需要缓冲多帧可以用计数信号量或者消息队列但那样内存开销更大。接收任务的伪代码void eth_rx_task(void *param) { while (1) { if (xSemaphoreTake(eth_rx_sem, portMAX_DELAY) pdTRUE) { /* 读取 MAC 接收 FIFO */ uint32_t len eth_read_frame(rx_buf, sizeof(rx_buf)); if (len 0) { /* 处理数据帧 */ process_frame(rx_buf, len); } /* 重新使能接收中断 */ eth_rx_enable(); } } }这里有个关键点GD32F4 的以太网 MAC 在收到一帧数据后如果不及时读取 FIFO后续的数据帧会被丢弃。所以中断里给完信号量之后要尽快让接收任务运行。如果系统里还有其他高优先级任务接收任务的优先级要设得足够高避免数据丢失。另外MAC 的接收中断有两种模式一种是每收到一帧就中断一次另一种是接收 FIFO 达到一定阈值才中断。前者实时性好但中断频繁后者中断少但延迟大。实际项目里如果网络流量不大用每帧中断就行如果流量大建议用 FIFO 阈值中断减少中断次数。4.3 发送任务与零拷贝优化发送这边相对简单但有一个性能优化点值得说。GD32F4 的 MAC 发送 FIFO 支持两种模式一种是软件把数据拷到 MAC 的发送 FIFO 里另一种是 DMA 模式MAC 直接从内存读数据。DMA 模式就是所谓的“零拷贝”数据不需要在内存和 FIFO 之间搬来搬去CPU 占用低吞吐量高。在 FreeRTOS 下用 DMA 发送需要注意内存对齐和缓存一致性。如果开了 D-CacheDMA 读的内存区域必须保证 Cache 里的数据和内存里的一致否则 DMA 可能读到旧数据。GD32F4 的 Cortex-M4 有 Cache处理方法是发送前对数据缓冲区做 Clean 操作接收后对缓冲区做 Invalidate 操作。发送任务的伪代码void eth_tx_task(void *param) { while (1) { if (xQueueReceive(eth_tx_queue, tx_msg, portMAX_DELAY) pdTRUE) { /* 等待 MAC 发送 FIFO 有空闲 */ while (!eth_tx_ready()); /* 把数据写入发送 FIFO 或配置 DMA 描述符 */ eth_write_frame(tx_msg.buf, tx_msg.len); /* 启动发送 */ eth_tx_start(); } } }发送队列用 FreeRTOS 的消息队列队列项里存数据指针和长度。这样发送任务和产生数据的任务解耦产生数据的任务只管往队列里丢发送任务负责实际发送。队列深度根据实际流量定一般 4 到 8 就够了。4.4 中断服务函数里的注意事项以太网中断服务函数里要做的事情尽量少。标准做法是读 MAC 的中断状态寄存器判断是接收中断还是发送中断如果是接收中断清除中断标志给接收信号量如果是发送中断清除中断标志给发送信号量或者直接处理如果是错误中断记录错误计数必要时复位 MAC不要在中断里做数据拷贝、协议解析这些耗时操作。中断里只做状态判断和信号量通知具体处理交给任务。还有一个细节GD32F4 的以太网中断标志清除有顺序要求。有些标志必须先读某个寄存器再写另一个寄存器才能清除顺序错了标志清不掉中断会反复触发。具体顺序查 GD32F4 的用户手册以太网章节。5. 调试过程中踩过的坑与排查方法5.1 常见问题速查表现象可能原因排查方法PHY ID 读出来是 0xFFFFMDIO/MDC 接线错误、PHY 地址不对、PHY 未复位示波器看 MDC 有无波形检查 PHY 地址配置引脚PHY ID 读出来是 0x0000MDIO 被拉低、PHY 供电异常量 MDIO 电压检查 PHY 电源自动协商一直不完成网线没插、对端设备未上电、PHY 晶振未起振换网线量晶振波形读 BMSR 看链路状态协商完成但 ping 不通MAC 速率双工配置错误、IP 地址冲突读 PHY 特定状态寄存器确认协商结果检查 MAC 配置能 ping 通但大量丢包中断优先级配置错误、接收 FIFO 溢出检查 FreeRTOS 中断优先级降低网络流量测试程序卡在 PHY 初始化SMI 读写没有超时保护、PHY 硬件故障加超时换 PHY 芯片测试发送正常接收异常RMII 接收时钟相位问题、CRS_DV 接线错误量 REF_CLK 波形检查 CRS_DV 连接5.2 几个印象深刻的调试案例第一个坑是 JTAG 引脚冲突。GD32F4 的 PA13、PA14、PA15、PB3、PB4 这些引脚默认是 JTAG 功能。如果以太网 RMII 用到了其中某些引脚比如 PB11、PB12、PB13 虽然不在 JTAG 默认引脚里但有些封装会复用或者板子上其他外设用了这些引脚就需要先关闭 JTAG保留 SWD。关闭 JTAG 的代码rcu_periph_clock_enable(RCU_AF); gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE);这行代码把 JTAG 禁用只保留 SWD 调试接口。如果不做这一步那些被 JTAG 占用的引脚没法用作普通 GPIO 或者复用功能以太网引脚配置会失败。第二个坑是 RMII 参考时钟的相位。YT8512 输出的 50MHz 时钟和 GD32F4 MAC 期望的时钟相位可能有偏差。表现是链路能建立但收发数据 CRC 错误率高。解决方法是调整 PHY 的时钟输出相位配置YT8512 有相关的配置寄存器可以微调输出时钟的相位。或者检查 PCB 走线REF_CLK 的走线尽量短避免过孔和分支。第三个坑是 FreeRTOS 堆栈溢出。网络任务里用了比较大的局部数组来存数据帧比如uint8_t rx_buf[1524]如果任务堆栈设小了直接溢出程序跑飞。排查方法是开启 FreeRTOS 的堆栈溢出检测#define configCHECK_FOR_STACK_OVERFLOW 2然后在 vApplicationStackOverflowHook 里打印出问题的任务名。网络任务的堆栈建议至少 1024 字不是字节是 StackType_t 的个数如果协议栈复杂给到 2048 字。第四个坑是 PHY 复位不彻底。有些板子上 PHY 的复位电路只有 RC没有 GPIO 控制。上电时如果电源上升慢RC 复位时间可能不够PHY 内部状态机没初始化好。表现是偶尔能工作偶尔不行。解决方法是加一个 GPIO 控制复位或者加大复位电容。我后来改成 GPIO 控制上电后主动拉低 50ms问题彻底解决。5.3 实测性能数据在 GD32F427 跑 200MHz、FreeRTOS 跑 1000Hz tick、YT8512 协商成 100M 全双工的条件下实测 TCP 吞吐量大概在 60 到 80Mbps 之间。UDP 吞吐量稍高能到 90Mbps 左右。这个性能对于大多数工业数据采集和网关应用足够了。如果要做性能优化几个方向一是用 DMA 收发代替 FIFO 拷贝减少 CPU 占用二是增大 FreeRTOS 的 tick 频率到 1000Hz 以上减少任务切换延迟三是把网络任务的优先级设到合适的位置既不能太低导致丢包也不能太高影响其他关键任务。CPU 占用方面满速收发的时候网络任务大概占用 30% 到 40% 的 CPU。如果加上协议栈处理比如 LwIP占用会更高。所以如果系统里还有其他重负载任务网络性能需要适当妥协或者换更高主频的芯片。6. 移植过程中的经验总结与扩展思路YT8512 在 GD32F4 上的移植核心就三件事RMII 引脚接对、SMI 时序配对、FreeRTOS 中断优先级设对。这三件事任何一件出问题表现都是“网络不通”但排查方向完全不同。我的经验是先量 REF_CLK 有没有 50MHz再读 PHY ID 确认 SMI 通不通最后看自动协商结果和 MAC 配置是否匹配。按这个顺序排查基本能覆盖 90% 的问题。代码层面PHY 驱动其实可以做成一个通用的框架。把 SMI 读写、PHY ID 识别、自动协商、链路状态查询这些操作抽象成函数指针针对不同的 PHY 芯片只需要实现差异部分。这样以后换 PHY 芯片比如换成 LAN8720 或者 83848只需要改少量代码。我在这个项目里就是这么做的YT8512 的驱动和 LAN8720 的驱动共用同一套上层逻辑切换的时候只改 PHY 地址和几个特定寄存器的定义。后续如果要扩展几个方向比较实用。一是加上 LwIP 协议栈把裸帧收发升级成 TCP/UDP 通信这样能直接对接上层应用。LwIP 在 FreeRTOS 下的移植资料很多关键是配置好内存池大小和邮箱数量。二是加上网络状态监控定期读 PHY 的链路状态寄存器链路断了自动重连并且通过 LED 或者日志输出状态。三是做固件远程升级通过以太网接收新固件写入 Flash这个在工业现场非常实用。最后分享一个调试小技巧如果手头没有示波器可以用 GD32F4 的定时器输入捕获功能来测 REF_CLK 的频率。把 PA1 配置成定时器输入捕获测出来的频率如果是 50MHz 左右说明时钟基本正常。虽然精度不如示波器但至少能判断有没有时钟、频率对不对。这个方法在没有仪器的紧急情况下很管用。