拿到STM32H743这块板子,很多人第一件事是点灯,第二件事就是想折腾以太网。H743内置10/100M MAC,外部配一颗YT8512C PHY,工程用CubeMX生成,上层再叠LWIP协议栈——这套组合在工业网关、数据采集盒子、运动控制设备里非常常见。链路看起来不复杂,但真正踩过坑的都懂:PHY地址、RMII参考时钟、DMA与D-Cache一致性,任何一个环节没对齐,板子就是ping不通。这篇文章就围绕STM32H743+YT8512C这个组合,把从CubeMX配置到LWIP成功跑通的完整过程拆开来讲,包括我认为最容易被忽略的几个细节。
1. 项目全貌:这块组合解决什么问题
1.1 为什么是H743配YT8512C
先说说芯片选型。STM32H743属于意法半导体H7高性能系列,Cortex-M7内核,主频最高能到480MHz,带双精度硬浮点、1MB RAM,最关键的是内部集成了以太网MAC控制器。这意味着我们只需要外接一颗物理层芯片,也就是PHY,就能把网络接口做出来。
YT8512C是裕太微电子推出的一款国产10/100M自适应以太网PHY,功能和早年流行的LAN8720A基本对标,支持RMII和MII两种接口模式。这几年在国内工业板卡里用量很大,一方面供货稳定,另一方面价格和交期比进口PHY更有优势。很多开发者一提到“国产PHY配STM32H7”第一反应就是它。
我之所以推荐这个组合,主要是因为H743的RAM足够大,跑LWIP协议栈非常从容,甚至可以在跑着FreeRTOS、USB、多路串口的同时,再开几个TCP连接。YT8512C的体积小、外围电路简单,一颗25MHz晶振加上几颗电阻电容就能工作,非常适合做低成本网络接口。
1.2 从MAC到PHY再到LWIP,一条完整链路
很多人把“以太网通信”想得很玄乎,其实拆开来看就是一条清晰的链路:
- STM32H743内部有MAC(媒体访问控制层),负责把IP数据包封装成以太网帧,并处理MAC地址、VLAN、流控等。
- MAC本身不直接连接网线,它通过RMII接口和YT8512C连接。RMII相比MII最大的优势是引脚少,数据线只需2位收发,总共7根信号线加2根管理线就能跑100Mbps。
- YT8512C负责物理层编码,比如把数字信号转换成差分信号放到网线上,同时处理载波检测、链路状态、自动协商等。
- LWIP是跑在MAC之上的轻量级TCP/IP协议栈,负责IP、TCP、UDP、ARP、ICMP这些网络层和传输层的事情。
数据包的实际流向是:业务代码生成数据 → LWIP打包成TCP/IP包 → H743的MAC封装成以太网帧 → 经RMII送到YT8512C → PHY编码后上到网线。接收方向正好反过来。
这里有一个常见误区:有人觉得只要CubeMX里勾选了LWIP,通信就能通。其实LWIP只是协议栈,它依赖HAL库正确驱动MAC,而MAC又依赖PHY正常协商出链路,任何一层出了问题,上面都是白搭。所以我在调试时习惯按物理层→链路层→网络层的顺序来排查,遇到ping不通先别看代码,先确认PHY有没有link。
2. 开工前的硬件要点:YT8512C外围设计没那么简单
2.1 先确认PHY地址和复位电路
很多人拿到板子就打开CubeMX开始配,结果PHY始终读不到ID,最后发现是PHY地址不对。YT8512C的PHY地址是通过PHYAD引脚的电平组合决定的,默认值不是固定的,不同厂家的模块设计也不一样。常见的有0x00和0x01两种,具体以板子原理图为准。
怎么确认?两个办法。一是看原理图,找到PHYAD[2:0]或者类似命名的引脚,看它们接高电平还是低电平,然后算地址。二是写个小程序通过MDIO接口读取PHY寄存器0x02和0x03,读到的就是PHY的ID。如果读出来全是0xFFFF,那基本就是地址不对或者MDC时序有问题。
复位电路也很关键。YT8512C的复位脚nRST通常需要外部接上拉电阻和RC延时电路,保证上电后PHY能正常退出复位状态。如果这个引脚悬空,或者被低电平一直拉着,PHY会处于持续复位状态,MDIO怎么都读不到数据。我吃过的亏是:引脚在原理图上看着有下拉,实际焊接时虚焊,结果排查了两天才发现是复位没释放。
2.2 RMII参考时钟的两种接法,别在这里翻车
这是整个项目里最容易被忽视、但影响最大的硬件细节。RMII要求有一个50MHz的参考时钟,方向有两种可能:
- 方案A:PHY自身带25MHz晶振,内部PLL倍频到50MHz,然后通过REF_CLK引脚输出给STM32的PA1。这种方式最省事,只要板子上有25MHz晶振,CubeMX里启用ETH的RMII模式即可。
- 方案B:由STM32的MCO2引脚输出50MHz时钟,接到PHY的REF_CLK输入,同时PA1也取同一个时钟源。这种方式需要CubeMX里额外配置MCO2,并且确认板子的时钟连接。
关键问题在于:时钟方向必须和板子设计一致。如果板子方案A但你在代码里把MCO2也打开了,两个时钟源可能会互相干扰;如果板子方案B但你只按方案A配置,PHY可能完全收不到参考时钟,网口灯都不亮。
我在实际调试时拿到一个新板子,一定先用示波器量PHY的REF_CLK引脚对GND的波形,确认频率是不是稳定的50MHz。如果频率不对或者压根没波形,后面的调试全是浪费时间。
2.3 MDIO/MDC和RMII信号完整性问题
YT8512C和MCU之间还有一组管理总线:MDIO和MDC。MDC是时钟,由MCU输出,MDIO是双向数据线。MDC的频率不能太高,很多PHY手册建议最高2.5MHz左右,CubeMX里可以通过PHY Clock Divisor来分频。
另外,RMII的数据线虽然只有7根信号,但它们在100Mbps模式下翻转频率不低,尤其是REF_CLK。PCB布线时最好把RMII信号线做成等长、短走线,避免过孔过多。如果是在开发板上跑,一般不用太担心,但如果是自己做板,这点要留意。我见过一块DIY板子,RMII的TXD1和TXD0走线长度差了3厘米,结果100M模式下频繁丢包,降到10M模式反而正常,这就是典型信号完整性问题。
3. CubeMX一步步配置:H743+YT8512C工程搭建
3.1 新建工程与时钟树配置
打开CubeMX,新建工程,芯片型号选STM32H743VIT6(或者其他H743封装)。第一步先把基础外设配好:
- SYS:Debug选择Serial Wire,Timebase Source改成TIM6。这里很关键,LWIP的HAL驱动会依赖系统tick,如果Timebase还是SysTick,运行到LWIP轮询时容易出奇怪问题。
- RCC:HSE选择Crystal/Ceramic Resonator,外部晶振按自己板子的实际频率填,常见的是25MHz。
- 时钟树:H743最高可以跑到480MHz,一般配法是HSE 25MHz进入PLL1,输出SYSCLK=480MHz,AHB分频为240MHz,APB1和APB2为120MHz。
如果你的板子是方案B需要在PHY的REF_CLK端由MCU供50MHz,那么还要在时钟树里打开MCO2,并配置为50MHz输出。注意MCO2对应的引脚是PC9,这个引脚在CubeMX里通常不会自动分配到ETH相关配置里,要手动确认。
3.2 使能ETH外设并配置RMII
在Connectivity里找到ETH,模式选择RMII。CubeMX会自动分配RMII相关的引脚,包括PA1(ETH_RMII_REF_CLK)、PA2(ETH_MDIO)、PC1(ETH_MDC)等,具体引脚映射看CubeMX的Pinout视图即可。
在ETH参数配置页面里,有几个选项需要根据实际板子设置:
- PHY Address:填前面确认过的PHY地址,常见0x00或0x01。
- PHY Clock Divisor:如果H743的AHB时钟是240MHz,而MDC想要2.5MHz,分频系数就要选大一点,保证读PHY寄存器时稳定。
- 如果CubeMX支持选择PHY Device型号,很多国产板子选择LAN8720A就能兼容。YT8512C和LAN8720A的寄存器布局相似度很高,实际项目中不少人直接选LAN8720A跑通了。但这属于经验之谈,严谨起见建议读一下自己在YT8512C上的PHY ID寄存器,确认无误再信任这个选择。
3.3 勾选LWIP并调整内存参数
在Middleware and Software Packs里找到LWIP,勾选Enable。然后在LWIP配置界面里做几件事:
- IP模式:建议先用Static IP做调试,把IP设为192.168.1.10,子网掩码255.255.255.0,网关192.168.1.1。等调试通了再考虑DHCP。
- 关闭DHCP:调试阶段DHCP不好排查问题,先关掉。
- 调整内存参数:H743 RAM够大,可以给LWIP多分一点。我把MEM_SIZE开到1MB左右,PBUF_POOL_SIZE设为16,PBUF_POOL_BUFSIZE保持1518,TCP_MSS设为1460,TCP_WND设48KB。
这些参数不是越大越好,但太小了确实容易出问题。比如PBUF池不足时,高负载下LWIP会直接丢包,表现就是ping通但TCP下载速度上不去。
3.4 串口预留调试通道
强烈建议在工程里加一路串口,专门打印PHY状态和LWIP诊断信息。H743的串口资源很多,随便拿一个UART出来,波特率115200,后面就可以在代码里打印PHY ID、link状态、收到的中断计数等。没有这路日志,出了bug只能干瞪眼。
4. 代码层的几个关键修改与联动
4.1 生成代码后,先验证PHY能被正常读取
CubeMX生成代码后,第一次编译下载到板子上,先不要急着跑LWIP。我建议在主循环前加一段测试代码:通过HAL提供的MDIO接口读取YT8512C的PHY ID和基本状态寄存器,用串口打印出来。
正常情况下,如果PHY地址正确、时钟正常、复位释放成功,读到的ID不会是0xFFFF。如果读出来是全0或者全F,先回去查PHY地址和MDC分频。这一步能帮你把硬件问题在协议栈之前就暴露出来。
另外,YT8512C和LAN8720A的ID值大概率不同,读到的ID和代码里预设的PHY ID不匹配不要慌。很多HAL层的PHY初始化会做ID匹配检查,如果不通过,可以注释掉检查或者把常量改成自己读到的ID值。
4.2 H7的D-Cache和DMA一致性,必须处理
这是H7系列和F1/F4最不一样的地方,也是新手最容易掉进去的坑。H743的Cortex-M7内核带D-Cache,而以太网DMA直接把数据搬运到内存。如果没有做缓存一致性处理,会出现很奇怪的现象:收到的数据在DMA中断里看起来是对的,但到LWIP处理时已经变成脏数据,甚至一帧数据出现前面几个字节正确、后面全是乱码。
解决办法有三种档次:
- 最简单:在CubeMX里配置MPU,把ETH的DMA描述符和缓冲区所在的内存区域设置为Non-cacheable。这是最推荐的做法,性能损失很小,而且一劳永逸。
- 麻烦一点:在代码里手动维护缓存,接收时调用SCB_InvalidateDCache,发送时调用SCB_CleanDCache。
- 最粗暴:直接关闭D-Cache。这样能跑通,但H743的性能优势会大打折扣,不太推荐。
我当时第一次跑H7网络,没做任何缓存处理,结果是ping的请求能收到,但响应发不出去。后来查了大量资料,才意识到是D-Cache一致性导致DMA发送缓冲区里的数据被CPU缓存遮住了,DMA根本读不到最新内容。所以这里真的要重视。
4.3 裸机主循环与ETH中断回调
H743配LWIP,在裸机环境下,可以两种方式驱动:
- 轮询方式:在主循环里调用ethernetif_input,反复检查DMA描述符有没有新收到的包。这种方式代码简单,CPU占用率会高一点。
- 中断方式:ETH全局中断使能后,在HAL_ETH_RxCpltCallback回调里把接收事件通知给LWIP,比如置一个标志位,主循环检测到标志位后再去调用ethernetif_input。这种方式实时性更好。
我这里给一个简化版的框架:
extern ETH_HandleTypeDef heth; volatile uint8_t eth_rx_flag = 0; void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth) { eth_rx_flag = 1; } int main(void) { // 初始化等操作 while (1) { if (eth_rx_flag) { eth_rx_flag = 0; ethernetif_input(&heth); } MX_LWIP_Process(); // 其他业务 } }在实际项目中,中断里我只是置标志位,不做复杂处理,尽量缩短中断时间。主循环里再去消费接收事件,这样避免和其他实时任务打架。
5. 上板实测:从ping通到TCP Server跑起来
5.1 硬件检查:先看PHY的link状态
插上网线,接通电源,第一步看YT8512C的LED。很多模块的LED会直接指示link状态和活动状态。如果LED不亮,先查网线、对端设备、PHY供电,以及前面说的参考时钟。
同时通过串口打印PHY状态寄存器,可以看到link位的值。正常情况下插上网线后,link位应该变为1,自动协商出100M全双工或者10M全双工。
这步是分水岭:如果link都没有,后面LWIP肯定跑不通,不用浪费时间。
5.2 静态IP ping通
确认link正常后,把PC网卡IP设置成和板子同一网段,比如PC是192.168.1.100,板子是192.168.1.10,然后在PC上ping。
第一次ping通时,会有一种“终于能通”的释然感。但要注意,如果第一次超时,第二次开始通,通常是因为ARP表还没建立,属于正常现象。真正有问题的是连续丢包或者完全ping不通。
ping通了之后,我一般会持续ping几百个包,看看有没有丢包。如果稳定在0%丢包、时延在1ms以内,说明硬件链路和LWIP基础都已经正常。
5.3 一个最简单的TCP Server验证
ping通只能证明IP层通了,想要验证传输层,还得跑一个TCP或UDP应用。LWIP提供了raw API,适合裸机或者简单裸核场景。下面是一个最小TCP Server的代码思路:
#include "lwip/tcp.h" static err_t echo_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (p != NULL) { tcp_write(tpcb, p->payload, p->len, 1); tcp_output(tpcb); pbuf_free(p); } return ERR_OK; } static err_t echo_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { tcp_recv(newpcb, echo_recv); return ERR_OK; } void my_tcp_server_init(void) { struct tcp_pcb *pcb = tcp_new(); tcp_bind(pcb, IP_ADDR_ANY, 8080); pcb = tcp_listen(pcb); tcp_accept(pcb, echo_accept); }这段代码创建一个监听8080端口的TCP Server,收到什么就原文返回什么。用PC上的网络调试助手连接板子的IP和8080端口,随便发一段字符串,如果能收到回显,说明整个以太网通信链路已经完整打通。从这个基础出发,再往上面加自己的应用协议就很容易了。
5.4 如果想上FreeRTOS
CubeMX支持一键把LWIP和FreeRTOS组合在一起。勾选FreeRTOS后,LWIP的任务、信号量、互斥锁都会自动适配。需要注意几个点:
- 中断优先级:ETH中断的优先级要低于FreeRTOS可管理中断的最高优先级,不然容易触发断言。
- 任务栈大小:跑LWIP的任务栈建议给到1024字以上,太小会导致程序跑飞。
- 内存策略:FreeRTOS的堆和LWIP的内存池是分开的,要分别预留空间。
6. 常见问题速查与避坑实录
6.1 PHY读不到ID或者读出来全0xFFFF
这是最经典的上电首坑。排查顺序:
- 量PHY的电源引脚,确认供电正常。
- 量复位引脚,确认不是一直被拉低。
- 查PHY地址,读不到就试着把PHY地址改成0x01看看。
- 降低MDC时钟频率,比如把分频调大。
- 用示波器看PHY的时钟引脚,确认25MHz晶振起振。
如果以上都没问题,还是读不到,检查MDIO/MDC引脚是否接对,有些板子MDIO和MDC会标反。
6.2 网口灯不亮
先分清楚是哪个灯不亮。YT8512C一般有两个LED,一个指示link/速度,一个指示活动。如果完全不亮,多半是供电或者时钟问题。如果只亮一个,可能是速度配置不对或者网线是交叉线而不是直通线。调试阶段建议用直通线连接交换机或PC。
6.3 link正常但ping不通
这个情况最让人抓狂。物理层看起来是好的,但就是网络不通。排查方向:
- 检查PC和板子的IP、子网掩码是否同一网段。
- 检查PC防火墙,很多Windows防火墙默认会拦截ping。
- 用Wireshark在PC上抓包,看看有没有发出ARP请求,板子有没有回ARP响应。如果ARP有来有回但ping请求无响应,问题出在ICMP处理或LWIP配置。
- 确认D-Cache一致性处理了,H7上这是高频原因。
- 确认LWIP的IP地址、网关、掩码这些参数实际生效。
6.4 高负载下丢包严重
丢包先看是不是内存池不足。把LWIP的MEMP_NUM_TCP_SEG、PBUF_POOL_SIZE这些参数调大,观察是否有改善。其次看中断处理,如果主循环里处理以太网太慢,缓冲区的包会被新包覆盖。再就是RMII的时钟质量问题,REF_CLK抖动太大也会导致PHY在100M模式下误码率上升。
6.5 排查以太网问题的心法
每次调网络问题,我都习惯按这个顺序来,能省很多时间:
- 物理层:网线、灯、PHY供电、PHY时钟。
- 管理通道:MDIO能不能正常读PHY寄存器。
- 链路状态:PHY有没有协商出速度和双工。
- MAC层:用串口打印ETH DMA的错误标志,看有没有溢出、CRC错误。
- 网络层:ping和ARP,判断IP协议栈是否工作。
- 传输层:TCP握手是否完成。
每一层验证通过之后,再往上一层走。千万不要在物理层都没确认的情况下,就去翻LWIP源码,那样只会越调越乱。
就我自己的习惯来说,不管最终项目跑不跑FreeRTOS,我都会先保留一个裸机最小工程,只做三件事:读PHY ID、看link状态、ping通。这三件事通过了,再往LWIP上叠加应用逻辑。很多朋友一上来就开FreeRTOS加LWIP,出了bug根本分不清是协议栈的问题,还是任务调度和缓存一致性的问题。先把地基打稳,后面就会顺利很多。YT8512C这个PHY虽然文档没有国际大厂那么丰富,但实际用下来性能稳定,寄存器兼容性也不错,配合H743做网络通信是完全可靠的方案。