一上来先把结论说清楚:如果你也在用STM32H743这颗片子,并且想用LAN8720A这颗PHY芯片跑LWIP协议栈,配上FreeRTOS做多任务处理,那这套组合其实是目前最多人走的一条路。CubeMX从v6.5.0开始对H7系列的ETH外设配置支持得已经非常成熟,但成熟不代表能一路绿灯。我前后在这块板子上折腾了差不多一个周末,踩了五六类坑,有的是CubeMX配置界面上看不出来的,有的是芯片参考手册没有细讲的,最后全靠LA抓波形和反复看勘误手册才理清。
这篇东西不是教程复读机,而是把你可能遇到的坑按顺序排好,我把当时实际操作的流程、配置界面里每个可能误导你的地方、以及最终跑通的参数组合全部写下来。无论你是第一次接触H7以太网,还是之前用F4系列做惯了想迁移过来,这篇应该能帮你省下大量查手册的时间。
1. 整体设计与方案选型
1.1 为什么是H743 + LAN8720这套组合
先给出当时做选型时的完整考量框架。市面上STM32搭配以太网的方案大致可以分成三类:第一类是STM32内部自带MAC控制器,外接一颗PHY芯片完成物理层收发,典型组合是STM32F4/CubeMX默认的DP83848,还有现在很多板子喜欢配的LAN8720A;第二类是MAC和PHY一起外挂,用SPI或者并口接一个完整的网络接口芯片,比如W5500、DM9000;第三类是用STM32H7系列里带以太网MAC的高配型号直接连光口或者外接PHY。
选第一类的核心原因是成本和性能的平衡点最舒服。STM32H743这颗料本身自带10/100M以太网MAC,支持MII和RMII两种接口模式,H7系列的MAC还加入了VLAN过滤、多队列、时间戳等高级功能,这些是F4系列没有的。LAN8720A这颗PHY价格低、封装小(QFN-24,4x4mm)、外围器件少,百兆场景下性能完全够。如果选MII接口需要25MHz参考时钟并且要16根数据线,RMII接口只需要50MHz参考时钟和7根信号线,PCB好画非常多。从实际项目看,H743的MAC加上一颗LAN8720A,整体BOM成本能控制在非常合理的范围内,而且H7的FMC和DMA带宽足够,在跑TCP传输时CPU占用率能压得很低。
1.2 版本选型:CubeMX v6.5.0、HAL库版本与LwIP源码版本
CubeMX的版本选择直接决定了你后面少踩多少坑。v6.5.0这个版本对H7系列的ETH驱动更新得比较到位,具体来说:HAL库版本是1.11.x以上的话,HAL_ETH_Start和HAL_ETH_ReadData这套新API已经替换掉了老版本里的ETH_MACDMA_Config遗留接口,中断回调函数的注册方式也变成了HAL_ETH_MACAddressFilterConfig这类新写法。如果你还在用很老的HAL库,直接打开CubeMX生成工程后会看到很多deprecated告警,虽说不影响编译,但对后续调试干扰很大。
LwIP协议栈版本建议锁定到2.1.2或者2.1.3。CubeMX允许你在Middleware and Software Packs里选择LwIP版本,默认可能是2.0.3,我建议手动改成2.1.2以上。2.1.x相比2.0.x在内存池管理、TCP序列号处理、以及tcpip_thread和ethernetif_input的配合上改进了不少,而且市面上能找到的排查资料也基本以2.1.x为主。另外要明确一点:CubeMX生成的LwIP代码是做了HAL层适配的,但它的内存分配策略默认是静态池(MEM_SIZE和PBUF_POOL_SIZE在lwipopts.h里配置),这个后面单独讲。
FreeRTOS版本建议用CMSIS_V1接口,CubeMX生成的代码会创建defaultTask和tcpip_thread,并且把socket线程安全相关配置自动处理好。不推荐在H7上使用CMSIS_V2,不是不能用,而是CubeMX生成的osMessageQueuePut和LwIP的sys_mbox在配合上偶尔会出现优先级反转的边界问题,排查起来很麻烦。
2. CubeMX配置要点全拆解
2.1 引脚和时钟的预配置
新建工程选好STM32H743VIT6或者ZIT6之后,别急着去开启ETH,先把RCC时钟树配置好。H743的ETH外设挂在AHB1总线上,需要开启的时钟是ETHMAC、ETH TX、ETH RX、ETH TX、ETH RX这些时钟(在RCC设置里的Ethernet MAC和Ethernet TX/RX都要勾选)。然后最关键的一步:LAN8720A的REF_CLK必须由外部提供50MHz时钟,这颗PHY不支持内部时钟输出模式。
这里有个非常容易踩的坑:H743的ETH_RMII_REF_CLK可以作为输入也可以作为输出。如果CubeMX的RMII配置里你选择了RMII_REF_CLK作为输出,意味着由STM32提供50MHz时钟给PHY。但LAN8720A在硬件设计上如果要让STM32输出REF_CLK,必须把LAN8720的XI引脚接25MHz晶振、CLKOUT引脚悬空,并用STM32的PA1输出50MHz。更简单的做法是让PHY自己接一颗50MHz有源晶振,然后把REF_CLK输入给STM32。大部分开发板走的就是后一种方案。判断方法是看原理图上LAN8720A的XI脚接的是25MHz无源晶振还是50MHz有源晶振。如果是25MHz无源晶振,则需要STM32提供50MHz给PHY的CLKIN,同时RMII模式下外部时钟由MCU提供。
我调试过程中发现,如果这里选错,现象是PHY的寄存器能通过MDIO读取(比如读取ID寄存器能读到0x0007C0F1),但是链接状态寄存器(Basic Mode Status Register,地址1)的bit2一直为0,也就是说PHY始终检测不到链路。后面会再详细说这个问题。
2.2 ETH参数配置界面的正确设置
CubeMX的Connectivity -> ETH配置界面里,有几个参数需要逐个确认:
Mode选择RMII。H743的ETH支持MII和RMII,如果你在MII模式下把信号线全部连出来,CubeMX会自动占用大量引脚。RMII只占7根线,PA1是时钟,PA2是MDIO,PC1是MDC,PA7是CRS_DV,PC4是RXD0,PC5是RXD1,PB11是TXD0,PB12是TXD1,PB13是TX_EN。这是标准接法。PHY Address填0。这里是LAN8720A的大坑:LAN8720A的PHY地址由RXER/PHYAD0引脚的电平决定,默认是0x00,不是某些PHY的0x01或者0x04。很多教程里写填1是因为他们用的是DP83848或者KSZ8081。如果你的原理图上PHYAD0没有做特殊处理,这一定是0。填错之后MDIO通信能读到数据但地址对不上,Link状态永远起不来。MAC Address随意填一个合法的单播地址即可,后面可以在代码里改。DMA Descriptor相关参数:Descriptor Count建议设为4~6个,默认4个在百兆环境下跑满速TCP时可能不足,后文会详细说明原因。
2.3 使能LwIP并配置协议栈参数
在Middleware and Software Packs -> LWIP里面,关键配置如下:
Mode选Ethernet,Data选No RTOS或者RTOS。因为我们最终要用FreeRTOS,这里选RTOS,但注意选RTOS模式后,CubeMX要求你在前面先配置好FreeRTOS,否则会生成失败。LwIP version选2.1.2以上。Memory Settings里面,MEM_SIZE默认给的1600字节对于TCP通信来说太小,我改成10240。PBUF_POOL_SIZE是接收和发送缓冲区池的数量,默认给的是16,在百兆全双工跑TCP下载或大量UDP收发时会丢包,我改成32。PBUF_POOL_BUFSIZE默认给的是1518,刚好是一个标准以太网帧大小,保持默认即可。TCP部分,TCP_WND是TCP接收窗口大小,默认好像是4096,也就是你是一次最多能接收4KB的数据,这在跑HTTP下载的时候明显不够,导致吞吐量上不去,改成65535。TCP_SND_BUF是发送缓冲区,默认4096,同样改成65535。DHCP建议开启,LWIP_DHCP选Enabled。H7跑DHCP是完全没有性能压力的,而且在调试时能自动获取IP,省去手动配置的麻烦。
这些参数看着挺绕,但它们决定了LwIP的可用内存和TCP窗口,直接决定了你后续的传输速度。这些参数对应的宏定义,CubeMX会把它们写到lwipopts.h里,你可以在生成的工程里找到并进一步修改。
2.4 FreeRTOS集成:任务划分和堆栈大小
FreeRTOS的集成在CubeMX里也是图形化配置:Middleware and Software Packs -> FREERTOS里选择CMSIS_V1接口,然后会生成一个defaultTask。LwIP选RTOS模式后,CubeMX会自动帮你在ethernetif.c里创建tcpip_thread,所以FreeRTOS配置里你只需要保证两件事:TOTAL_HEAP_SIZE给足够大,以及configMINIMAL_STACK_SIZE不要太小。
TOTAL_HEAP_SIZE是FreeRTOS所有任务栈、队列、信号量、互斥量的总内存池。H743内部有1MB RAM,分配上可以阔绰一点,我给到50 * 1024。defaultTask栈大小给1024字(word),也就是4KB,这个是CubeMX的默认值,对于要处理TCP数据的业务任务来说偏小,如果你在任务里声明了一个大数组,很容易触发栈溢出检查。建议把业务任务分成两个:tcpServerTask(给2048字)和netifLinkTask(给1024字)。关于任务划分的重要性,在第四部分会专门展开。
3. 硬件设计和LAN8720A关键引脚解析
3.1 LAN8720A的引脚功能与硬连接要求
LAN8720A的引脚不多,但有几个引脚直接决定了软件配置的成败:
- Pin 2(
nINT/REFCLKO):这个引脚的双重功能要注意。如果LAN8720A的XI脚接的是25MHz晶振,那么REFCLKO会输出50MHz参考时钟,这个时钟可以直接接到STM32H743的PA1(ETH_RMII_REF_CLK)引脚上。如果XI脚接的是50MHz有源晶振,REFCLKO不会输出时钟,此时需要外部时钟直接接到STM32的PA1。 - Pin 5(
RXER/PHYAD0):作为PHY地址引脚。硬件上这个脚如果悬空或者下拉,PHY地址就是0;如果上拉到3.3V,则是1。我之前用的一个开发板这里做了上拉,我还傻乎乎地填了0,结果是读ID能读到,但MII管理接口访问的一直是另一个PHY地址,状态完全对不上。最后用万用表量了这个引脚的电压,才意识到是1。
还有一个特殊点:LAN8720A的nINT引脚在默认配置下是低有效中断输出,如果你把它接到了MCU的GPIO上用于PHY中断检测,那么LwIP的ethernetif.c里面的链路状态检测逻辑可能会变得复杂。CubeMX默认生成的代码里面,链路状态是靠轮询PHY寄存器实现的,没有用到中断,所以这个引脚悬空即可。
3.2 RMII接口信号完整性的几个关键点
RMII接口虽然信号数少,但因为是50MHz的DDR(实际上是25MHz时钟的双沿采样,等效50Mbps,但REF_CLK本身是50MHz),布线时必须注意:
REF_CLK信号要短而直,尽量少打过孔,并且远离其他高速开关信号。实测发现如果REF_CLK走线绕了很大一圈,PHY的接收数据会出现偶发错位,导致CRC错误包的占比很高(可以从ETH外设的DMACSR寄存器的RCE位看到)。TXD0/TXD1和TX_EN信号是一组,要保证等长,差个几百mil问题不大,但如果和REF_CLK之间偏差太大,在温度变化时会出现偶发断流。CRS_DV(PA7)信号上不要加太长走线,它用来指示载波和有效数据。如果板上其他信号干扰到这个脚,会把无效帧也送进MAC,严重时LwIP的接收队列会被垃圾包塞满。
当然,如果你的板子是直接用官方开发板改的,这些走线问题基本不用太担心。如果是自己画板,建议严格遵守以上几点,并且网口变压器(RJ45带隔离)的中心抽头要正确接3.3V或者根据PHY数据手册要求接。
3.3 PHY芯片复位电路和注意事项
LAN8720A的复位脚(Pin 14,nRST)低电平有效,有的开发板直接用一个RC电路上电复位,有的接到了MCU的GPIO上。这里要注意:如果CubeMX生成的HAL代码里没有对PHY进行硬复位,然后PHY内部状态在上电后不稳定,可能出现读ID正常但配置寄存器失败的情况。
最稳的做法是:在main()里、调用MX_LWIP_Init()之前,先用GPIO拉低PHY的复位脚至少10ms再释放,然后再初始化。CubeMX的ETH配置界面如果在GPIO Settings里没有看到PHY的复位引脚,需要你自己额外配置一个GPIO输出,并在代码里加一段复位逻辑。我后来看了STM32H743的官方示例,他们的做法也是在MX_LWIP_Init之前做了这个操作。
顺带提一句:如果硬件把nRST直接接到3.3V(不经过MCU),也是能工作的,因为PHY上电后自己会完成初始化,但偶尔会出现第一次上电后MII管理接口访问超时的情况,这时候断电重来又好了。如果想彻底解决,建议还是用GPIO控制复位。
4. 核心流程实现:从CubeMX生成到代码联调
4.1 生成工程和初始代码改动
CubeMX里全部配置好之后,点GENERATE CODE,生成的工程里重点需要看的文件有三个:eth.c、ethernetif.c、lwip.c、lwipopts.h。打开lwip.c可以看到MX_LWIP_Init(),这个函数会调用ethernetif_init()完成底层网卡初始化,然后调用netif_add()把网卡注册到LwIP协议栈。
第一个要改的地方:ethernetif.c里的low_level_init()函数。这个函数中有一个netif->hwaddr_len = 6;和netif->hwaddr[0] = 0x02;之类的默认MAC地址配置,你可以改成自己想用的MAC地址。注意第一个字节的低两位有一个要求:bit0是组播位,必须为0;bit1是本地管理位,建议为1,比如0x02:00:11:22:33:44这种是合法的本地管理地址。
第二个要改的地方:ethernetif.c里的low_level_output()函数。H7的HAL库对DMA描述符的管理和F4不完全一样,在HAL_ETH_TransmitFrame返回HAL_OK之后,buffer并不一定马上被释放。如果你在这之后直接修改了p->payload指向的内存,有可能会造成数据覆盖。正确的做法是把netif->transmit回调里传入的struct pbuf的生命周期管理交还给LwIP,在low_level_output里调用HAL_ETH_TransmitFrame之后,不要立刻pbuf_free,而是等DMA传输完成中断标志位出现后再释放。CubeMX默认代码里其实已经做了一部分处理,但如果用的HAL库版本较老,需要自己补上HAL_ETH_TransmitFrame_IT和回调函数的注册。
4.2 主循环和tcpip_thread的配合
当LwIP配成RTOS模式后,每个以太网数据包的接收流程是这样走的:
- PHY把模拟信号转成RMII的数字信号,MAC通过DMA把数据放到内存里的DMA描述符指向的缓冲区。
- HAL库在
HAL_ETH_RxCpltCallback回调里收到数据完成中断。 - 这个回调函数由CubeMX生成的
HAL_ETH_ReadData系列函数调用,把DMA缓冲区里的数据拷贝到LwIP的pbuf中,tcpip_input()将数据投递给tcpip_thread。 tcpip_thread根据数据包的协议类型,交给TCP/IP协议栈处理。
关键点:tcpip_thread的优先级必须比defaultTask高。因为如果业务任务一直占着CPU,tcpip_thread得不到调度,TCP的ACK不能及时发出,对端会一直重传。CubeMX默认生成的tcpip_thread优先级是osPriorityNormal,如果你的业务任务优先级也是Normal,就会出现调度顺序不定的问题。实际调的时候把defaultTask的优先级改成osPriorityLow,或者在defaultTask里使用osDelay主动让出CPU。
CubeMX生成的lwip.c里有一个MX_LWIP_Process()函数,这是用来在轮询模式下驱动LwIP的。你在RTOS模式下,千万不要在while(1)里调用它之后又调用vTaskDelay,这样会导致接收流程重复处理,出现pbuf泄漏。
4.3 动态IP和静态IP的选择与实现
CubeMX里使能了DHCP后,MX_LWIP_Init()会调用dhcp_start(&gnetif),这时候板子上电后会自动向路由器申请IP。调试阶段这个很方便,但有一个细节:H743的LwIP中DHCP启动后,netif的IP地址会显示为0.0.0.0,直到DHCP完成。如果你想在固件里知道IP是否分配成功,可以在defaultTask里定期查询:
if (gnetif.ip_addr.addr != 0) { // DHCP got an address }需要注意的是,gnetif.ip_addr是ip4_addr_t类型,addr如果是0,说明还没获取成功。实际使用中发现,LAN8720A从复位到DHCP能拉到IP,一般需要3秒到10秒不等,取决于路由器响应速度。如果超过15秒还没拿到IP,优先怀疑PHY没有link上:看LAN8720A的Basic Mode Status Register(地址1)bit2是不是1,如果不是,说明物理链路有问题,DHCP肯定起不来。
静态IP的配置就更直接了,把MX_LWIP_Init()里dhcp_start(&gnetif)换成:
IP4_ADDR(&gnetif.ip_addr, 192, 168, 1, 100); IP4_ADDR(&gnetif.netmask, 255, 255, 255, 0); IP4_ADDR(&gnetif.gw, 192, 168, 1, 1); netif_set_up(&gnetif); netif_set_link_up(&gnetif);这是很多量产项目的做法,缺点是不能自动适应网络环境。
4.4 一个最小可用的TCP Server实现思路
跑通LwIP之后,大多数人第一件事是先做一个TCP Server,用电脑上的网络调试助手连上来看看能不能收发数据。这里分享一个最简洁的实现模板,放在defaultTask里:
static void tcp_server_thread(void *arg) { struct netconn *conn, *newconn; struct net_buf *buf; err_t err; conn = netconn_new(NETCONN_TCP); netconn_bind(conn, NULL, 8080); netconn_listen(conn); while (1) { err = netconn_accept(conn, &newconn); if (err == ERR_OK) { while ((err = netconn_recv(newconn, &buf)) == ERR_OK) { // process buf->p, send echo maybe netconn_write(newconn, buf->p->payload, buf->p->len, NETCONN_COPY); netbuf_delete(buf); } netconn_close(newconn); netconn_delete(newconn); } vTaskDelay(10); } }这是一个阻塞式TCP服务器,只用netconnAPI,不需要手动管理多个连接。实际项目里性能要求高一些的话,建议用RAW API配合tcp_accept、tcp_recv回调,但这套netconnAPI足够跑通整个链路了。你要验证的只是MAC有没有正常工作,TCP协议栈有没有在跑,CPU和中断有没有冲突。
5. 常见问题与排查技巧实录
5.1 PHY读不到ID或Link灯不亮
这个问题排在所有问题的第一位,如果PHY的ID都读不到,后面的LwIP配置全是白搭。排查步骤:
- 先确认PHY的供电,LAN8720A有多个供电引脚:
VDDCR(1.2V内部核心,一般由内部LDO产生,只需接退耦电容)、VDDIO(3.3V)和VDD33(3.3V)。如果VDDIO没接3.3V,MDIO接口是读不到ID的。 - 确认MDIO的上拉电阻。LAN8720A的MDIO引脚内部有上拉,但如果外部电路把它接地了,那MDIO通信就会异常。
- 用逻辑分析仪抓MDC和MDIO时序。正确的读操作:先发送
0x01(帧头)加0x01(读操作码)加PHY地址(5 bit)加寄存器地址(5 bit),然后是2个时钟周期的 turnaround,之后PHY在MDIO上输出16位数据。如果时序完全没输出,那就是MCU压根没有发起MII管理读操作;如果有输出但数据是0xFFFF,说明PHY没有应答,大概率是PHY地址改变了或者PHY没上电成功。
实际调试经验:我曾经在一批板子上遇到大概5%的PHY读ID偶尔读到0x00000000,但重新上电就好了。后来发现是PHY复位时间不足,上电后立刻就去读寄存器导致PHY还没准备好。解决办法是在读取前延时至少50ms,同时检查复位引脚的RC时间常数是否达标。
5.2 PING不通:链路状态和IP配置的排查顺序
PING不通是最常见的现象,但原因可能藏在不同层面。按照下到上的顺序排查:
- 第一层:物理链路。看PHY的
BMSR寄存器的bit2,也就是Link Status。这个位为1表示链路已建立。如果为0,检查网线、对端设备、变压器和PHY之间的匹配电阻。LAN8720A的典型接法里,TX/RX差分对各需要串联电阻,通常板子上已经做好,但如果用的是廉价的网络变压器,输出电平可能不对。 - 第二层:MAC层。用抓包工具(Wireshark)看电脑端有没有收到来自板子的ARP请求或ARP应答。如果板子在DHCP模式下,上电后会发送DHCP Discover报文,电脑上如果开了Wireshark并且接了同一交换机/路由器,应该能看到这些广播包。如果完全看不到任何报文,说明MAC没有正常发出数据。这时去查ETH外设的中断标志位,尤其
HAL_ETH_GetError返回的错误。 - 第三层:IPv4层。如果能看到ARP广播但PING不通,多半是IP地址配置问题,比如板子的IP和对端不在同一子网,或者网关配置错误。
很多时候卡在第二层,因为RMII时钟相位错了。有个很隐蔽的问题:LAN8720A的REF_CLK如果由外部有源晶振提供,晶振出来的50MHz时钟接到PHY的XI脚和STM32的PA1。如果PCB走线上面存在比较大的寄生电容,时钟上升沿可能会变缓,导致MAC采样数据时正好落在数据变化沿上。这时候现象就是杂散的错误包时有时无。解决方法是调整PHY的PHY控制寄存器(地址0)的RMII/10Mbps相关位,或者更简单,换一个时钟源。
5.3 能PING通但TCP吞吐量极低或频繁断连
能PING通说明链路层和IP层都OK,TCP层吞吐量上不去通常是内存窗口和DMA描述符数量设置不合理。具体的排查方向:
TCP_WND如果还是默认的4096,那么发送方一次最多只能发4KB数据,之后就等你ACK。在百兆环境,这个约束直接导致吞吐量只有几Mbps。改到65535之后能明显改善。- DMA描述符数量过少。H743的ETH外设描述符默认是4个接收描述符,如果网络包比较多,而你的应用层处理不够快,描述符被用完后新到的包直接丢弃。这个可以从
HAL_ETH_GetRxDataBuffer返回的错误计数看出来。 tcpip_thread的栈空间不足。2.1.x的LwIP中,TCP协议栈在处理大块数据时需要的栈比较大,如果configMINIMAL_STACK_SIZE还是默认的128字,跑TCP下载时很可能触发栈溢出,现象就是频繁断连。- 最容易被忽略的是网卡的中断优先级。
ETH_IRQn的中断优先级如果设置得比其他外设低,会在系统繁忙时无法及时处理接收中断,导致DMA描述符来不及回收。CubeMX生成的默认优先级是5,我建议改成5以下(数字越小优先级越高),同时不要和SysTick冲突。
5.4 FreeRTOS+LwIP内存不足导致HardFault
这个问题我几乎可以断定每个用H7的人都遇到过:跑一会儿或者一上电就进HardFault。原因基本有三个:
TOTAL_HEAP_SIZE太小,LwIP分配pbuf或者任务创建时,pvPortMalloc返回NULL,然后某些代码没有做空指针检查就直接解引用,崩了。解决办法是把TOTAL_HEAP_SIZE设置成至少60 * 1024,同时启动FreeRTOS的栈溢出检测功能(configCHECK_FOR_STACK_OVERFLOW设为2)。- LwIP的
MEM_SIZE和PBUF_POOL_SIZE分配不够。注意,这组内存分配走的是LwIP自己的mem_malloc,不走FreeRTOS的堆分配器。如果你把PBUF_POOL_SIZE设得很小,在突发流量下分配失败,也会导致丢包甚至断言错误。安全起见,在调试阶段可以开启LWIP_DEBUG,直接看是哪个模块报错。 - 不小心在中断里调用了LwIP或者FreeRTOS API。比如在
HAL_ETH_RxCpltCallback里直接调用netconn_recv,这在RTOS模式下是非法操作,一定概率会崩。正确做法是把接收到的数据投递到消息队列,由业务任务去处理。
5.5 首包慢、偶发超时的排查策略
如果你的整体链路是通的,但TCP建连或者首包响应有时候会慢到几百毫秒甚至几秒,多半是ARP缓存或者DHCP续期导致的。调试时注意几个点:
- 板子的MAC地址是否和另一块板子重复,如果有两个设备用了相同MAC,交换机或者路由器的ARP表会不停抖动。
- 路由器如果开启了端口隔离,板子的UDP广播包可能无法穿过路由器到达电脑,导致你抓不到包但实际板子在工作。
- LwIP的
ARP_TABLE_SIZE如果太小,在广播包多的网络里ARP缓存频繁失效,每次通信前都要重新解析,会显得很卡。把这个值改到10以上会好很多。
6. 几个增强小技巧和经验总结
6.1 用CubeMX的引脚冲突检查提前发现隐患
CubeMX里当你开启ETH和FDCAN时,H743的引脚冲突就会报出来。H743的FDCAN1的RX和TX引脚如果想用PD0/PD1,会和ETH的RMII_MDC/MDIO冲突;如果改用其他引脚,可能又不冲突。最好在画PCB之前就把这些外设全部在CubeMX里试配一遍,否则画完板发现引脚打架,就只能飞线或者改版了。
6.2 系统时钟配置:H743的以太网时钟不必追求太高主频
很多人会纠结H743跑以太网是不是一定要上400MHz以上。实际上,ETH外设的时钟来源于AHB1,而AHB1时钟从SYSCLK分频而来,你跑240MHz也完全可以,网速不会因此变慢。百兆以太网的数据处理瓶颈更多在内存带宽和LwIP的事务处理效率上。我试过用280MHz主频,TCP单向吞吐能到70~80Mbps,再往上涨就没意义了,因为百兆物理层的上限是100Mbps。
6.3 调试阶段的日志和断言配置
强烈建议在调试阶段把下面几个宏打开:
#define LWIP_DEBUG 1 #define ETHARP_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF #define UDP_DEBUG LWIP_DBG_OFFLWIP_DEBUG作为总开关打开,具体调试哪个模块再单独开对应模块的DEBUG。全都打开的话,串口会被日志刷爆,反而看不清关键信息。TCP调试时只开TCP_DEBUG和ETHARP_DEBUG,其他关掉,性能也还能接受。
FreeRTOS的栈溢出检测平时可以先关掉,等系统稳定后再打开。因为栈溢出检测本身会消耗CPU时间,在H7上虽然不算什么,但如果开启了configCHECK_FOR_STACK_OVERFLOW为2,每一次任务切换都会做额外的检查。实测在百兆满速传输时,开和不开这个检查,吞吐率会有几个百分点的差别。
6.4 后续扩展建议:从TCP Echo到文件服务器
当整个TCP链路稳定之后,实际上你已经拥有了一套可以继续往上扩展的完整底座。常见扩展方向包括:用netconnAPI封装HTTP服务器,在板上跑一个简单的Web配置页面;使用lwip/apps里的httpd模块做嵌入式Web服务器;或者进一步接入MQTT客户端,把H743采集的数据往云端推。如果要做MQTT,记得开启LWIP_ALTCP和LWIP_ALTCP_TLS相关选项,配合mbedTLS做加密通信,但这些属于另一个深水区了,可以等基本网络功能稳定后再去尝试。
我在实际使用中还有一个小习惯:每次改完LwIP配置重新生成工程后,先把lwipopts.h和上一版diff一下,确认哪些宏被CubeMX偷偷改回去了。CubeMX更新配置时很有可能把你的手动修改覆盖掉,具体来说,lwip.c和lwipopts.h是每次生成都会重写的文件,而ethernetif.c在第一次生成之后,CubeMX默认不会再次覆盖。如果发现改动丢了,别急着重新改,先找到CubeMX的User Code Section区域,把你的自定义配置放在那两个/* USER CODE BEGIN */和/* USER CODE END */注释之间,这样不管怎么重新生成,代码都不会被覆盖。这也是之后能持续基于CubeMX迭代而不翻车的核心习惯。