☰
STM32以太网ETH外设从原理到实战:硬件设计、CubeMX配置与lwIP调试指南
2026/10/5 5:24:32 网站建设 项目流程

做嵌入式这些年,我接过的项目里十有八九会碰到“给设备加个网口”的需求。客户一句“直接连路由器不就行了”,背后其实藏着一整套东西:MAC、PHY、协议栈、DMA、中断,哪一个环节没处理好,设备就连不上网。STM32以太网外设(ETH)是很多朋友入门嵌入式联网的第一站,STM32内部集成了一个带DMA的以太网MAC控制器,配上一颗外部PHY芯片,就能让单片机接入标准的以太网络。这篇文章我打算把STM32 ETH从硬件到软件、从原理到实操完整拆一遍,重点讲讲CubeMX怎么配置ETH和lwIP、PHY芯片怎么选、调试的时候那些坑怎么绕过去。适合正准备用STM32做联网设备、做工业采集器、做毕业设计的同学参考,纯新手也能按步骤跑通。

1. 先把STM32的ETH外设看明白:它是MAC,不是网口

1.1 以太网外设在芯片内部到底干了什么

STM32的ETH外设,准确说是一个符合IEEE 802.3协议的以太网MAC控制器。它负责的事情可以理解成“快递分拣中心”:网络数据从网口进来,MAC负责把帧头解析出来、校验数据完整性、然后交给DMA(直接存储器访问)搬到内存里;发送的时候反过来,DMA把内存里的数据搬到MAC,MAC加上帧头帧尾和CRC校验,再通过MII或RMII接口送给外面的PHY芯片。

这里有个关键点要搞清楚:ETH外设不负责电信号处理,也不负责把数据变成网线上的差分信号。这些脏活累活是PHY芯片干的,它会把MAC传来的并行数据编码成适合双绞线传输的物理信号,同时处理载波侦听、冲突检测这些底层机制。STM32内部集成的SMI接口(MDIO/MDC)专门用来配置PHY芯片的内部寄存器,比如查链路是否通、协商速率和双工模式。

所以你在芯片手册里会看到ETH这个外设有三部分:MAC核心、DMA控制器、SMI管理接口。DMA让CPU在收发数据时基本不用参与,SMI让单片机可以“指挥”外部PHY。理解了这个架构,后面配置的时候你就知道每个选项是干什么的了。

1.2 哪些STM32带ETH,哪些只是“看起来很接近”

不是所有STM32都带ETH外设,很多朋友在选型时踩过这个坑。整体来说,F1、F0、L0、L4这些系列基本都没有以太网MAC,你要用它们做联网,只能外挂SPI接口的W5500或者串口转以太网模块。而F407、F429、F767、H743这些中高端型号,内部才集成了ETH外设。

即便带ETH的型号,细节上也有差异。F407和F429的ETH是同一代设计,支持MII和RMII两种接口,速率最高100Mbps。F7和H7系列在DMA和缓存一致性上做了改进,ETH的收发描述符管理更复杂,尤其H7有Cache问题需要额外处理。选型建议很直接:如果只是做简单的数据采集上传,F407完全够用;如果追求性能、打算跑复杂协议甚至做网关,直接上H7。

还有一个容易忽略的点:ETH需要一定数量的引脚,F407用RMII接口大约是9个引脚,用MII接口要16个引脚以上,加上PHY芯片,PCB面积和走线难度都要考虑进去。别选完型发现引脚冲突或者布线太痛苦,那就被动了。

1.3 它与“SPI转以太网”方案的本质区别

市面上很火的W5500这类芯片,内部是MAC+PHY合封在一起的,MCU通过SPI口读写它,对单片机来说它更像一个“外部外设”,协议栈也固化在芯片内部。而STM32的ETH只是一个MAC,PHY在外面,协议栈(比如lwIP)跑在MCU自己身上。

这两种方案没有绝对的谁好谁坏,区别主要看场景。W5500方案开发快、MCU负载低,但成本高、带宽有限(SPI瓶颈),而且难以应对大流量和复杂协议。STM32 ETH方案的成本低,H7这类芯片跑满100Mbps完全没问题,灵活性也高,协议栈随便改,功能扩展空间大。代价就是开发难度高,你要稍微懂一点PHY的原理,还要会调协议栈参数。

我自己给客户的建议是:如果只是采集几个传感器数据上传到PLC,W5500省事;如果要做网关、做视频传输、做较大流量的边缘计算设备,老老实实选STM32 ETH方案,后面扩展不会想哭。

2. 硬件电路设计与PHY选型:一半的调试错误都出在这里

2.1 RMII还是MII:选型背后的信号与时序逻辑

STM32的ETH外设支持MII和RMII两种与PHY连接的接口标准。MII是标准接口,16根信号线,速率10/100Mbps自适应;RMII是简化版,只要7根信号线,但参考时钟从25MHz提到了50MHz,数据线从4位降到了2位。

很多新手会觉得“信号线越少越好”,直接选了RMII,这个方向没错,但要注意RMII对时钟的要求更严格。RMII模式下,MAC和PHY必须使用同一个50MHz参考时钟,不管是MAC提供还是PHY提供,两侧的时钟必须同步,否则收发时序会乱。MII的时钟则分别由PHY提供,发送和接收各自独立,虽然信号多,但时序容错更强。

实话说,在100Mbps的速度下,RMII是完全够用的,而且能省一半引脚。PCB布线也更轻松,我建议大多数项目直接用RMII。更关键的是,现在市面上大多数低成本PHY芯片(比如LAN8720A)主打的就是RMII模式,资料和参考设计一抓一大把。MII模式更适合那些对时序有特殊要求或者想兼容其他MAC的场景,一般项目用不上。

这里给个信号对照表,方便画原理图的时候核对:

功能MIIRMII
发送数据TXD[3:0] 4位TXD[1:0] 2位
发送时钟TX_CLK(25MHz/2.5MHz)共用REF_CLK(50MHz)
接收数据RXD[3:0] 4位RXD[1:0] 2位
接收时钟RX_CLK共用REF_CLK
控制信号TX_EN、RX_DV、CRS、COLTX_EN、CRS_DV
管理接口MDC、MDIOMDC、MDIO

2.2 PHY芯片怎么选:地址、时钟、LED这些“配置引脚”别想当然

PHY芯片是你的设备能不能上网的第一道关口,选型时除了看速率和接口,更要关注那些“悄悄起作用”的配置引脚。我常用的是LAN8720A、DP83848和KSZ8081这三款,它们的共同点是都支持RMII,价格也不贵。不同点在于内部寄存器的默认值、复位时序、参考时钟产生方式各有差异。

先说PHY地址。STM32通过SMI接口(MDIO/MDC)访问PHY芯片,而总线上每个PHY芯片必须有一个唯一的地址。这个地址不是软件里随便写的,而是由PHY芯片的PHYAD引脚在上电时的电平状态决定。LAN8720A的PHYAD0引脚内部有下拉,默认地址是0;DP83848有一组PHYAD[4:0]引脚,默认值要看板子上的上下拉电阻配置。我见过太多人把CubeMX里的PHY Address填错了导致PHY初始化的寄存器读出来全是0xFFFF,网口灯都不亮。所以画原理图的时候,一定要确认PHYAD引脚接法,然后按手册算出实际地址,再填到CubeMX里。

再说时钟。RMII模式需要50MHz参考时钟,这个时钟的来源有三种常见做法:一是把50MHz有源晶振直接接到PHY的REF_CLK引脚;二是用25MHz晶振接到PHY,由PHY内部倍频后把50MHz输出给MCU;三是从STM32的MCO引脚输出50MHz给PHY。具体用哪种,要配合PHY芯片的参考设计来定,LAN8720A常用第二种,DP83848常用第一种或第三种。时钟接错了,现象很典型:PHY能读到ID,但Link状态永远起不来。

2.3 硬件布线上的经验与常见坑

硬件上的坑,比软件难查得多,我吃过不少亏,把最容易出问题的几个点列一下:

网络变压器的接法不能省。STM32的ETH接口与外接RJ45座子之间,必须接网络隔离变压器。现在很多RJ45座子自带变压器,选型时要注意区分。变压器的作用是隔离电平、抑制共模干扰,省掉它的后果是通信不稳定,丢包严重,而且静电防护一塌糊涂。

差分信号线要尽量等长、阻抗匹配。RMII虽然数据线是单端信号,但PHY到RJ45之间网口的TX±和RX±差分线要控制100欧姆差分阻抗,走线尽量短,不要打过孔,不要在中间跨分割。要是布线实在没办法保证,至少也要保证TX±和RX±各自等长。

PHY芯片的复位电路要靠谱。很多PHY芯片要求复位信号至少有几百微秒的低电平时间,而且上电后要等时钟稳定再释放复位。如果只是简单接个RC复位,很容易出现PHY芯片“半醒不醒”的状态,表现为MDIO能读到寄存器,但Link状态灯死活不亮,或者隔一段时间就死掉。建议用MCU的一个GPIO来控制PHY复位,软件里严格按时序操作。

LED指示灯的限流电阻别省。PHY的Link/Activity指示灯引脚通常需要外部限流电阻,有些精简电路直接把它挂到3.3V,结果就是指示灯常亮不灭或者根本不亮,干扰我们排查问题。

3. CubeMX配置ETH + lwIP:从建工程到跑通TCP回环

3.1 建工程前的准备工作

用CubeMX配置ETH之前,有几件事先确认好,不然生成的代码会花很多时间收拾。

确认选型。打开CubeMX的MCU选择器,在搜索框里输入你手里的芯片型号,然后看它的Peripherals列表里有没有ETH。没有ETH的芯片,后面步骤直接免谈。

确认引脚冲突。ETH用的是GPIO复用功能,RMII模式下至少要占用PA1、PA2、PA7、PC4、PC5等引脚,这些引脚常常和SDIO、USART、定时器等功能复用。我的建议是先把原理图里ETH相关引脚固定下来,再排其他外设,让CubeMX的引脚冲突检查和自动分配帮你规避问题。我试过好几次让CubeMX自己分配引脚,结果它把ETH引脚和调试串口的引脚挤在了一起,最后只能重新画板子,很被动。

准备一个固定的MAC地址。虽然MAC地址可以写成任何不冲突的值,但严谨的产品要申请企业唯一的MAC段,开发阶段随便填一个也不会影响功能。比如我常用02:00:11:22:33:44这类本地管理地址,这种地址第二位是02,属于本地管理地址范围,不会和公网设备冲突。

3.2 一步一步配置ETH与lwIP

CubeMX里的操作流程,我一步步拆开讲。

第一步,打开ETH外设。在Pinout & Configuration界面找到Connectivity,展开ETH,勾选Activate。这时候会出现MII和RMII的选择,按你硬件设计选。RMII模式下,还需要在ETH配置里把PHY Address填成你PHY芯片对应的地址。LAN8720A默认0,那就填0。

第二步,设置RMII参考时钟。在ETH配置的Parameter Settings里,有个PHY Clock选择。这里选哪一项,取决于你的时钟方案。如果你用MCO输出50MHz给PHY,需要先去RCC配置里打开MCO2,选择50MHz输出;如果你用外部晶振给PHY、由PHY输出时钟给MCU,那软件里不需要额外配置,但要在原理图上把这个时钟方向画对。

第三步,配置MAC地址和DMA描述符。ETH配置界面里会有MAC Address、DMA Descriptor Count这些选项。MAC地址填你定好的值,描述符数量我一般设为4到8个。描述符数量影响收发缓冲的深度,数量太少容易在突发数据时丢帧,数量太多会占用RAM。128KB RAM以上的芯片建议直接给8个接收描述符和8个发送描述符,性能更好。

第四步,添加lwIP。在Middleware组里双击LWIP,勾选Activate。这里有几个关键的参数要设置:

  • IP Address、Netmask、Gateway:调试阶段建议先用静态IP,比如192.168.1.10、255.255.255.0、192.168.1.1。开启DHCP反而容易因为网络环境问题干扰判断。
  • Memory settings:lwIP会根据你设定的内存池大小分配RAM,默认值在内存小的芯片上可能偏大,要根据芯片RAM容量调整。
  • Protocol:默认启用TCP和UDP。如果只是做TCP通信,可以去掉UDP,还能省一点内存。
  • TCP:开启TCP,设置好TCP_MSS(最大报文段长度)和TCP_WND(接收窗口)。这两个参数直接决定吞吐率,我建议TCP_MSS设为1460(以太网最大报文1500字节减去IP头20字节再减去TCP头20字节),TCP_WND设为4个MSS左右,也就是5840到8192字节之间。

配置完成后,点击Project菜单,选择Generate Code,生成的代码里会自动包含ETH和lwIP的初始化函数。这里有个细节:生成的main函数里会调用MX_LWIP_Init(),它会完成所有协议栈初始化,你不需要自己再另外初始化。

3.3 代码验证:先Ping通,再跑TCP Server

生成完代码,编译下载到板子,第一步先做链路测试。用网线把开发板直接连到电脑网口,把电脑的网卡IP设成和开发板同一网段,比如192.168.1.2,然后从电脑上ping开发板的IP。能ping通,说明MAC、PHY、lwIP基本都正常了。

ping通之后,就可以试着跑TCP通信了。我用lwIP的netconn API写一个最简单的TCP回环服务器,把收到的数据原样返回。核心代码大致这样:

#include "lwip/netconn.h" void tcp_echo_server(void) { struct netconn *conn, *newconn; struct netbuf *buf; err_t err; conn = netconn_new(NETCONN_TCP); if (conn == NULL) { return; } err = netconn_bind(conn, IP_ADDR_ANY, 8080); if (err != ERR_OK) { netconn_delete(conn); return; } err = netconn_listen(conn); if (err != ERR_OK) { netconn_delete(conn); return; } while (1) { err = netconn_accept(conn, &newconn); if (err != ERR_OK) { continue; } while (netconn_recv(newconn, &buf) == ERR_OK) { netconn_write(newconn, buf->payload, buf->len, NETCONN_COPY); netbuf_delete(buf); } netconn_close(newconn); netconn_delete(newconn); } }

这个代码放在lwIP启动之后循环执行就行。需要注意的是netconn API在调用accept和recv时会阻塞当前线程,所以最好把TCP服务器逻辑放到一个独立的RTOS任务里,或者用lwIP自带的tcpip_thread上下文中运行。我最早是直接在main的主循环里死等netconn_accept,结果发现整个系统都卡住了,后来才意识到问题在于netconn API会阻塞,不适合放在主循环里裸奔。

3.4 给新手的建议:先用官方板或开发板起步

我见过太多人拿着自己画的板子,第一次通电就出问题,然后调了一整天也不知道是硬件问题还是软件问题。新手起步,强烈建议先买一块现成的开发板,比如正点原子、野火那些带网口的高性能STM32开发板,最便宜的带LAN8720A的也就一两百块。先用开发板把软件流程跑通,确认你对ETH和lwIP的理解是对的,再回过头来折腾自己的板子。

如果一定要直接做自己的板子,第一版回来之后,先别急着下载程序。第一步先量PHY芯片的供电、复位波形、时钟波形,确认这几个基本条件正常了再写软件。至少先写一个小程序,通过SMI接口读取PHY芯片的ID寄存器,如果读出来的值和数据手册对不上,说明硬件有问题或者PHY地址配置错误,这时候就不要继续往下调了。

4. 常见问题与排查技巧实录

4.1 Link Up了,但Ping不通:从ARP查起

这是最常遇到的问题:PHY的Link灯已经亮起来了,说明物理层已经协商成功,但电脑Ping开发板就是不通。排查顺序按我的经验这样来:

第一步,用wireshark抓包。抓包工具能看到物理网卡上所有接收和发送的报文,这是定位网络问题最重要的工具。具体做法是电脑连着开发板,在电脑的网卡上抓包。如果电脑持续发出ARP请求,询问“192.168.1.10的MAC地址是多少”,而开发板没有任何回复,说明问题出在开发板接收路径或者协议栈回复逻辑上。

第二步,检查PHY的中断和轮询状态。lwIP通过一个ethernetif_input线程不断轮询PHY,当ETH_DMA收到数据后,会触发中断或者被轮询线程读取。如果你的工程里没有正确使能ETH中断,或者轮询线程没有运行,数据就会一直堆在DMA描述符里,ARP请求自然没有回复。

第三步,查看MAC地址过滤。STM32的ETH外设有接收地址过滤功能,如果你的MAC地址配置错了,或者过滤器设置得过严,MAC层会把发往本机的帧直接丢弃。调试时可以把过滤配置放宽,允许接收所有帧(配置成Promiscuous模式),确认协议栈能正常收到数据再说。

4.2 初始化卡死在ETH或lwIP:DMA描述符与内存对齐

STM32的ETH DMA要求收发描述符和缓冲区必须按特定边界对齐。在F4上,要求4字节对齐;在F7和H7上,由于有Cache的存在,还涉及Cache一致性问题。如果初始化时程序跑飞或者卡在HardFault,多半就是内存对齐或者Cache配置出了问题。

F4系列比较简单,CubeMX生成的代码会自动分配对齐的内存,默认不用操心。但如果你自己移植代码、自己定义描述符数组,就要确保数组的起始地址是按32字节对齐的(STM32的DMA对描述符有对齐要求)。

F7和H7系列就要小心Cache了。我在H743上调以太网时,遇到过一种诡异现象:程序正常运行一段时间,突然收发数据开始乱,表现为Ping时断续,TCP连接极不稳定。查了好久才发现是DMA写入内存后,CPU读到的却是Cache里的旧数据,因为DMA不经过CPU Cache。解决办法有两个:一是把ETH的DMA描述符和缓冲区所在内存区域配置成Non-cacheable(用MPU),二是在每次收发完数据后手动做Cache clean和invalid操作。我刚接触H7时忽略了这个问题,浪费了一整天。CubeMX的默认工程在H7上已经会配置MPU,但如果你自己裁剪代码,很容易就把这部分弄丢。

4.3 吞吐率上不去、频繁丢包:协议栈参数与双缓冲

能ping通,TCP也能连上,但传大文件时速度很慢,或者传输过程中丢包严重。这个问题要从两方面排查。

先看DMA描述符的数量。接收描述符如果只有2个,网络流量一大,DMA还没处理完一个数据帧,新的帧已经到了,描述符队列就满了,后面的帧会被硬件直接丢弃。解决办法是增大描述符数量,我建议至少配置8个接收描述符。

再看lwIP的内存池大小。lwIP内部使用pbuf管理报文缓冲区,如果内存池太小,协议栈处理不过来时会把数据包丢弃。TCP_MSS和TCP_WND这两个参数尤其重要。TCP_WND控制发送方在没有收到确认之前最多可以发送多少数据,它太小会严重影响吞吐率。我建议TCP_WND至少是TCP_MSS的4倍,如果内存充足,可以更大。我调试过的一个项目,默认TCP_WND是4KB,传文件速度只有几百KB/s,改成16KB后直接跑到了5MB/s以上。

还可以打开lwIP的统计功能,在lwipopts.h里把LWIP_STATS设为1,然后通过串口打印协议栈的各项统计信息,看看哪些数据包被丢弃了,一目了然。这个功能在定版前的性能调优阶段特别有用,最终产品里可以关掉以节省RAM。

4.4 问题速查表:以太网调试现场记录

把这些年遇到的典型问题整理成一张表,方便大家照着排查:

现象可能原因排查方法
PHY Link灯不亮供电问题、差分线接反、网线质量问题、PHY地址错误测量PHY供电、检查TX±/RX±方向、读PHY ID寄存器
Link灯亮,但Ping不通MAC过滤、ARP未回复、IP地址冲突、中断未使能wireshark抓包、检查MAC地址、确认lwIP线程运行
数据时通时断时钟不稳定、差分走线太长、PHY复位时序不对示波器看时钟波形、缩短走线、软件复位PHY
初始化HardFault描述符未对齐、内存不足、PHY初始化失败检查地址对齐、增大RAM分配、打印PHY ID
H7上数据错乱Cache一致性问题配置MPU为Non-cacheable、做Cache清理
传输速度极低DMA描述符太少、TCP_WND太小、内存池不足增大描述符数量、调大TCP_WND、调整内存池
串口打印乱码且网络不通MCU时钟配置错误检查系统时钟、确认是否稳定输出50MHz给PHY

排查以太网问题,最忌讳的就是瞎猜。很多人一看到Ping不通,就怀疑是协议栈没配好,把lwIP的配置改来改去,结果发现其实是网线质量差或者PHY供电不干净。我的习惯是:先物理层、后协议层,从底层往上查。指示灯、示波器、wireshark,这三个工具能解决90%的问题。

最后再分享一个小技巧:调试以太网功能之前,先花十分钟把PHY芯片的数据手册和你的原理图对照看一遍,确认PHY地址、时钟源、复位脚、LED指示脚的接法全部正确,再开始写代码。很多人以为这类信息不重要,结果恰恰是这些细节让整个调试过程变得痛苦万分。STM32的ETH外设一旦掌握了这套调试方法,后续做车载以太网测试或者扩展更多网络协议的时候,思路都会清晰很多,你也有了自己的一套排查逻辑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询