
简介本资源是一套面向嵌入式开发工程师与STM32进阶学习者的完整以太网通信实践方案聚焦STM32F4系列尤其F429驱动LAN8720A PHY芯片并实现TCP数据通信的核心技术链。涵盖硬件RMII接口连接、HAL库级ETH/MAC初始化、MDIO寄存器配置、lwIP协议栈移植、TCP客户端连接及收发逻辑等关键环节适用于IoT终端、工业网关等需稳定有线网络接入的场景。压缩包含240个文件主体为125个.h头文件与111个.c源码文件支撑底层驱动、中间件适配与应用层通信逻辑另有Keil工程文件uvprojx/uvoptx、可执行hex固件及汇编启动文件总大小1.74MB。已有1820人学习下载代码结构清晰直接基于stm32f4xx_hal_eth.c等官方驱动模块扩展集成PHY状态轮询、中断响应、DMA收发及错误日志输出等实用机制具备即编译、可调试、易迁移的工程化特性。 做嵌入式以太网通信STM32F4系列里自带MAC的型号不少但真正把成本、功耗和稳定性权衡下来F429配LAN8720A是我目前用过最舒服的组合。前阵子要给一个工业采集设备加网口板子上主控是STM32F429IGT6外设资源不算紧张项目要求能通过标准网线接入局域网和上位机做TCP数据交互。这个方案不用外扩MAC芯片STM32F429内置的以太网MAC加上一颗便宜的PHY芯片LAN8720A就能跑起来成本低、调试资料多整体落地很快。整个项目做完硬件连线和软件驱动都踩了不少坑趁热记录下来希望能帮到正在搞F4系列以太网的朋友。不管是刚接触以太网的新手还是手头有成熟项目想换PHY方案的老人这篇文章里的连接方式、寄存器配置和调试思路都能直接拿过去参考。1. 方案选型与整体思路1.1 为什么是STM32F429加LAN8720ASTM32F4系列里面带以太网MAC的单片机其实就那几款F407、F417、F429、F439这些。F429相比F407多了LCD控制器和SDRAM控制器很多时候选它不是因为要跑界面而是因为它货量充足、主频能到180MHz拿来跑以太网应用绰绰有余。以太网通信的完整链路是MAC层加PHY层STM32F429内部已经集成了MAC控制器负责数据帧的封装、校验、流控这些脏活但物理层的信号编解码、载波侦听、脉冲变换这些事必须交给PHY芯片来完成。LAN8720A就是一颗非常典型的10/100M以太网PHY芯片通过SMI接口用MDIO和MDC两根线就能跟STM32F429的MAC对接数据通路走RMII接口整体占用的引脚非常少。我最开始也考虑过STM32F429搭配DP83848的方案那款PHY芯片同样经典TI的资料也很全但算了一下成本LAN8720A的单价大概是DP83848的一半还不到而且封装更小QFN24封装只有4mm乘4mm在空间受限的板子上特别友好。不过LAN8720A有个要注意的地方它的供电是3.3V单电源但I/O电平可以兼容1.8V到3.3V跟STM32F429直接连完全没问题。这颗芯片平时工作电流只有几十毫安对电源的要求也不苛刻很适合单片机系统。还需要考虑的一点是供货稳定性。LAN8720A在市面上流通了很多年引脚定义没变过国产替代型号也出了不少万一原厂缺货换一个兼容芯片也能快速顶上。做产品选型的时间越长越明白芯片不一定要最新最强的关键是生命周期够长、用的人够多、踩坑资料够丰富LAN8720A恰好都满足。1.2 用STM32F429标准库还是HAL库这个项目我选用的是ST标准库而不是HAL库。有人说都202X年了还抱着标准库不放但说实话对于以太网这种对外设寄存器操作要求精细的场景标准库的逻辑更直白。你想看某个寄存器的赋值直接进库文件里搜就行不像HAL那样绕了好几层而且网上大量现成的STM32F429以太网工程模板都是标准库写的我这次正好接手一套延续多年的老代码技术栈定的就是标准库整个工程顺势就沿用下来了。标准库驱动以太网的思路就是三步配置GPIO复用、配置MAC控制器、配置DMA描述符。ETH外设的库函数并不多关键的初始化函数是ETH_Init里面要填充一个ETH_InitTypeDef结构体把MAC地址、双工模式、速度、自动协商这些参数都定义好。相比HAL库标准库少了多层抽象遇到问题直接对照参考手册查寄存器排错效率高不少。HAL库的以太网部分也不是不能用但它引入了LinkState回调、MspInit这种机制新手容易被底层细节绕晕。标准库也有一个不算缺点的缺点官方已经停止更新了。但以太网这部分代码非常稳定多年没变过所以不存在库太老导致用不了的问题。反而是一些HAL库版本升级后API频繁变化老代码迁移起来更麻烦。如果你纠结选哪个我就一句话别纠结手里有哪个顺手就先用哪个以太网通信卡住进度的一般都不是库的版本而是原理图和寄存器配置。2. 硬件设计与LAN8720A原理图核心模块2.1 LAN8720A原理图核心模块拆解先说LAN8720A原理图很多人第一次画这个芯片的电路会觉得引脚多实际上拆开看就四块供电和滤波、时钟电路、网络变压器接口、LED状态指示。供电部分LAN8720A有VDDCR和VDD两个供电域VDD是3.3V主电源VDDCR是内部数字核心电源。原理图上通常会用磁珠或0欧电阻把3.3V引到VDDCR再加一个1uF和一个0.1uF去耦电容。数据手册明确要求VDDCR必须有电压接法上很多参考设计是直接跟VDD连在一起但我个人习惯串一个磁珠方便调试时测量核心功耗还能滤掉一些高频噪声。LED指示灯的电源也要单独滤波避免LED开关瞬间的电流波动影响到PHY内部。时钟电路是重点。LAN8720A需要一个50MHz的参考时钟来源有两种一种是用外部50MHz有源晶振直接接到XI引脚另一种是用25MHz无源晶振配合内部PLL倍频到50MHz。大多数参考设计用的是25MHz晶振加两个18pF负载电容的方案因为这样时钟可以从XCLKOUT引脚再引出来给MAC用省一颗有源晶振。我用的就是25MHz无源晶振加两个18pF电容配合内部PLL把50MHz参考时钟输出到XCLKOUT引脚给STM32F429。这个方案成本低但晶振的负载电容必须根据芯片手册选准偏差太大会导致起振困难或者频率不准。网络变压器接口LAN8720A的TXP、TXN和RXP、RXN引脚要通过网络变压器接到RJ45座。变压器的主要作用是隔离防止共模干扰和浪涌损坏PHY芯片同时实现电平转换。RJ45座如果自带变压器就省事了直接把差分对连过去如果是分离式变压器比如HR911105A就要多留几个电阻电容的位置。我在原理图里给TXP、TXN和RXP、RXN差分对加了终端电阻和滤波电容PCB走线尽量等长保证信号完整性。LED状态指示就两路nLED1和nLED2可以配置成link和activity指示。原理图上一般串联1k限流电阻到LED再拉到3.3V方便观察链路状态。调试的时候网口指示灯能直接告诉你PHY是否连上了对端设备这是最快速的一级故障判断手段。2.2 RMII接口与STM32F429的引脚分配STM32F429的以太网MAC支持两种接口模式MII和RMII。MII需要16根数据线占用引脚多但理论上吞吐更高RMII只需要7根信号线数据线只有2位但需要外部提供50MHz的REF_CLK。我的板子最终选RMII模式因为省引脚而且100M速率下RMII完全够用没必要为了理论上的带宽多占用9根IO。RMII模式下STM32F429需要用到这9个引脚PA1 - ETH_RMII_REF_CLKPA2 - ETH_MDIOPA7 - ETH_RMII_CRS_DVPC1 - ETH_MDCPC4 - ETH_RMII_RXD0PC5 - ETH_RMII_RXD1PB11 - ETH_RMII_TX_ENPB12 - ETH_RMII_TXD0PB13 - ETH_RMII_TXD1这9个引脚必须严格按复用功能配置。曾经有位同事把RXD0和RXD1接反了结果MAC一直收到乱包排查了大半天。所以画原理图时一定要把引脚对应关系反复核对数据手册上怎么标就怎么接。尤其注意CRS_DV信号它在RMII模式下由PHY芯片驱动给MAC把载波侦听和数据有效合并到一个引脚上接错就会导致链路起不来。REF_CLK的接法有两种一种是LAN8720A的XCLKOUT引脚输出50MHz时钟到STM32F429的PA1另一种是STM32F429用MCO引脚输出50MHz给PHY芯片。我强烈建议用前者也就是用PHY自己的时钟来驱动MAC这样两边时钟同步软件上少配置一层MCO排查问题的时候也少一个变量。标准库初始化时如果采用外部PHY时钟方案就不需要在RCC配置里开启MCO输出直接配置GPIO复用即可。3. 底层驱动的实现细节3.1 通过MDIO总线读写PHY寄存器在初始化LAN8720A之前首先得确认MCU和PHY之间的MDIO通信正常。MDIO总线就两根线MDC是管理时钟由MAC输出频率不能超过2.5MHzMDIO是管理数据线双向传输用来读写PHY寄存器。读PHY寄存器的方法很简单标准库里已经封装好了ETH_ReadPHYRegister和ETH_WritePHYRegister这两个函数。但有个小坑是这两个函数依赖GPIO和MDC时钟先配置好。实际项目里我先写了一个PHY_Init函数第一步就是读PHY的ID寄存器也就是寄存器2和寄存器3。LAN8720A的PHY ID是0x0007C0F1读出来能对上说明PHY复位和MDIO通道都正常再进行后续配置。读取PHY ID这一步特别值得做因为很多以太网问题其实是PHY芯片根本没有正常工作这时候MAC层什么都收不到光在应用层浪费时间。我每次调新板子拿到手第一件事就是写一段简单的读ID代码通过串口打印出来看到0x0007C0F1才敢往下走。如果读到的全是0xFFFF那基本可以断定硬件有问题要么供电不对要么复位没拉起来要么MDIO引脚接错。MDIO总线本身是慢速接口操作时不需要太高的时序性能但要注意MDC时钟频率不能太高STM32F429默认分频出来的时钟一般在2.5MHz以内符合PHY规范。写寄存器时还要注意MDIO前导码和数据格式这些标准库底层都已经处理好了不需要自己实现但了解原理有助于排查信号完整性或者上拉电阻缺失引起的偶发读写失败。3.2 LAN8720A的复位与初始化序列LAN8720A的复位方式有两种硬件复位和软件复位。硬件复位就是把nRST引脚拉低至少1ms再释放这个时序很重要复位时间不够会导致PHY内部状态不稳定后续自协商一直失败。我的板子把nRST接到了STM32F429的一个GPIO上用软件控制复位时机而不是简单的上电RC复位这样MCU初始化完成后再去控制PHY复位时序完全可控。软件复位是通过BMCR寄存器地址0的第15位实现的。置1后PHY会执行完整的复位流程然后该位自动清零。实际操作时我建议硬件复位和软件复位都做一遍MCU上电先把nRST拉低等20ms再拉高然后通过MDIO写BMCR的复位位轮询等待复位完成这样最稳妥。复位完成后不要立刻开始自协商等个100ms左右让PHY内部稳定。初始化序列里有一个关键操作把PHY配置为RMII模式。LAN8720A有一个专门的寄存器叫PHY Special Control/Status Register地址是31Bit0就是RMII模式选择。默认值是0表示MII模式必须写1切换成RMII。这一步漏掉的后果就是PHY和MAC速率协商不一致表现出来是物理链路能建立但数据完全不通ping包全丢。还要办的事是配置自协商。默认情况下LAN8720A上电后会自动协商速率和双工模式一般建议开启因为我接的是普通交换机。如果是点对点连接两台设备自协商有可能会失败此时可以强制设置速度和双工。具体操作是BMCR寄存器的Bit13和Bit8组合强制100M全双工就设0x2100。我的应用场景是接交换机所以开启了自协商。3.3 ETH外设的RMII配置与DMA描述符STM32F429的ETH模块初始化关键是配置好MAC控制器、DMA控制器和描述符链表。标准库的ETH_BSP_Config函数看起来很长但核心就几件事。首先是MAC地址配置。用一个6字节数组把MAC地址填进去注意高位在前。MAC地址别用太随机的值建议从MCU的96位唯一ID芯片UID派生一个48位MAC地址避免多台设备同时上网时MAC冲突。比如把UID的低3字节和高3字节做一次异或生成一个大概率不重复的地址虽然不保证全球唯一但在局域网里足够用了。然后是DMA描述符。以太网DMA的收发是典型的描述符环结构。STM32F429的ETH_DMA外设维护两组链表一组接收描述符一组发送描述符。每个描述符指向一个数据缓冲区接收到的帧由DMA自动写入缓冲区处理完后再回收描述符。标准库工程一般给四个接收描述符和四个发送描述符缓冲区大小可以配置。我的接收缓冲区用了1524字节因为要容纳最大1518字节的以太网帧加4字节CRC虽说CRC是硬件计算的但缓冲区留点余量没坏处。初始化配置里还有一个参数容易让人忽略ETH_InitStructure.ETH_DMAArbitration。这个参数是DMA收发优先级配置在高流量场景下会影响吞吐我的应用没有高并发需求保持默认即可。一旦ETH_Init返回ETH_SUCCESSETH DMA就开始接收数据了。之后要做的是开启ETH_DMA_IT_RX中断在接收中断里把数据从描述符缓冲区拷贝到应用层然后释放描述符回到接收状态。描述符的使用是环形结构检查描述符的OwnBit是否为0来判断是否有新数据这是整个收包流程的核心。如果没有及时释放描述符DMA缓冲区很快会被占满新数据帧直接丢弃这是以太网丢包的一个隐藏原因。4. TCP数据通路的搭建4.1 协议栈选择裸机轮询还是lwIP有了MAC和PHY以太网链路层算是通了但要做TCP数据通信必须在这个基础上再跑一套TCP/IP协议栈。这里有两个方向要么自己实现一个精简的TCP/IP协议栈手写UDP、ARP、ICMP这些协议要么移植lwIP这种现成的开源协议栈。我毫不犹豫选了lwIP因为自己写协议栈的周期非常长而且稳定性很难保证这种成熟方案没有必要重复造轮子。lwIP的全称是lightweight IP是为嵌入式系统设计的轻量级TCP/IP协议栈内存占用比Linux内核里的协议栈小一个数量级非常适合STM32F429这种资源还算充裕但不算丰富的MCU。STM32F429有256KB RAM跑一个带TCP Server功能的lwIPRAM开销大概在40KB到50KB左右完全负担得起。lwIP的移植有一个固定套路底层以太网驱动调用low_level_output函数发送数据接收的数据通过low_level_input函数进入协议栈。协议栈本身与硬件无关只要把netif结构体注册好再把收发函数绑定进去基本就能跑起来。如果是带操作系统的环境lwIP可以跑在一个单独的线程里用信号量和邮箱做进程间通信如果是裸机需要周期调用tcpip_thread和ethernetif_input保证协议栈及时处理数据包。我这次工程里加了FreeRTOSlwIP跑在独立线程里这样TCP Server线程不会被底层收包拖累代码结构也更清晰。如果你不想上操作系统裸机跑lwIP也能实现TCP通信只要保证中断回调里不阻塞、主循环足够快就行但多任务扩展起来会麻烦一些。4.2 实现TCP Server的核心代码TCP Server的实现逻辑并不复杂。在lwIP里先用netconn_new创建连接然后netconn_bind绑定本地端口比如8080再netconn_listen进入监听状态之后一直循环netconn_accept接收客户端连接。建立连接后netconn_recv接收数据、netconn_write发送数据。这套API是lwIP的netconn接口比raw API好理解得多适合快速开发。下面是我在项目中实际使用的TCP Server线程核心片段static void tcp_server_thread(void *arg) { struct netconn *conn, *newconn; struct netbuf *buf; err_t err; conn netconn_new(NETCONN_TCP); netconn_bind(conn, IP_ADDR_ANY, 8080); netconn_listen(conn); while (1) { err netconn_accept(conn, newconn); if (err ERR_OK) { while ((err netconn_recv(newconn, buf)) ERR_OK) { void *data; u16_t len; netbuf_data(buf, data, len); // 这里调用应用层处理函数 process_recv_data(data, len); netconn_write(newconn, ack_data, strlen(ack_data), NETCONN_NOCOPY); netbuf_delete(buf); } } netconn_close(newconn); netconn_delete(newconn); } }这个线程是死循环只处理一个监听连接。如果项目要求支持多个客户端那就要在netconn_accept成功之后创建新线程处理每个连接。但多线程在MCU上开销比较大我的场景是单上位机对单设备单连接循环就够了。实际测试时电脑上的TCP调试助手能稳定连接、收发数据连续跑24小时没有断连。lwIP在STM32F429上还有一个细节系统时钟基准用的是SysTick在lwip_init之前要把tcpip_init和网卡初始化配好。如果开了DHCP客户端还要在收到DHCP应答之后更新netif的IP地址和掩码否则获取到的IP不会生效。我这边为了调试方便直接用静态IP例如192.168.1.100避免依赖路由器DHCP服务。5. 调试记录与常见问题排查5.1 我实际踩过的几个坑排第一的坑是PHY地址问题。LAN8720A的PHY地址由PHYAD0引脚的上下拉决定默认是0我的原理图上PHYAD0脚是悬空的理论上也应该是0。可实际焊上芯片后MDIO读地址0一直失败返回0xFFFF。排查后发现是贴片时PHY芯片旁边的去耦电容虚焊导致芯片内部电源不稳定MDIO通信时序乱掉了。重新补焊之后读取ID立刻正常。这个教训告诉我MDIO读失败时优先怀疑硬件电源和焊接不要一上来就改软件。排第二的坑是REF_CLK没起来。用25MHz晶振给LAN8720A提供时钟但XCLKOUT引脚始终没有50MHz输出。用示波器一量晶振根本没有起振。查了一圈发现晶振的负载电容焊成了15pF而LAN8720A数据手册要求的是18pF。这种几皮法的差异平时可能影响不大但整个系统对50MHz参考时钟的稳定性要求很高负载不匹配导致起振困难。换电容后一切正常。后来我在画原理图时都会特意在晶振附近标注推荐电容值防止产线换料时焊错。排第三的坑是TCP连接建立后很快断开。这个问题查了很久最终发现是lwIP的重传超时时间配置得太短丢一个包就触发超时重传重传几次失败后连接就被关闭。把lwipopts.h里的TCP_SYNMAXRTX和TCP_MSS适当调大重新编译烧录连接就稳定了。要注意lwIP的默认参数是给通用场景的用在低带宽的嵌入式环境里经常需要微调别直接用默认值就以为万事大吉。还有一个容易忽视的问题是DMA描述符缓冲区与Cache的一致性。F429没有D-Cache所以不用考虑这个问题。但如果你换成带D-Cache的芯片比如H7系列需要处理Cache和DMA缓冲区的一致性问题否则收到的数据会出现随机错乱。5.2 常见问题速查表现象可能原因排查与解决办法MDIO读PHY ID返回0xFFFFPHY供电或复位异常、MDIO引脚接错量3.3V和VDDCR电压检查复位引脚时序核对引脚连接无法建立TCP连接但串口有打印IP或网段配置错误确认静态IP和子网掩码先Ping通设备再连TCP能Ping通但收发数据丢包DMA描述符不足或缓冲区溢出增加描述符数量接收缓冲区调到1524字节速率协商只有10M无法到100MRMII模式没配置设置PHY寄存器31的Bit0为1切换RMII模式网口指示灯亮但数据不通TXD/RXD引脚接反或REF_CLK异常用示波器核对CRS_DV和TXD信号检查引脚复用连接建立后反复断连lwIP重传超时参数不合理调整TCP_SYNMAXRTX和TCP_MSS通信正常但偶尔卡死中断优先级和临界区处理不当确保ETH中断优先级合理避免长时间关闭中断5.3 快速定位问题的几个技巧调以太网比调串口难的地方在于数据是双向的出了问题不好判断是软件还是硬件。我的经验是先把问题分层物理层看PHY的连接状态寄存器链路层看MAC的收发统计寄存器网络层再看ARP和Ping结果。第一步永远是确认物理层。读取PHY寄存器1BMSRBit2是链路状态为1说明物理链路建立了。再读寄存器31的Bit2这是LAN8720A报告的速度0是10M1是100M可以确认自协商结果。这两个寄存器值读出来物理层有没有问题基本就有定论了。第二步确认MAC层。STM32F429的ETH_MAC有统计寄存器能从寄存器层面看到收发帧数、CRC错误数、对齐错误数。我在调试时直接读取ETH_GetDMAFlagStatus检查DMA的接收错误标志位。如果MAC层有错误计数持续增长说明物理层信号质量可能不好要检查PCB走线、网络变压器和RJ45座。第三步才轮到协议栈。如果物理层和链路层都好但TCP连不上那就用电脑上的Wireshark抓包看设备有没有发出ARP响应有没有SYN包回来。Wireshark能直接判断是设备没发包还是PC端没收到把问题范围快速缩小到某一侧。这个方法比瞎猜高效太多我在嵌入式网络调试中强烈推荐。6. 实测效果与后续扩展6.1 数据吞吐做一个初步估算在稳定建立TCP连接之后我做了简单的吞吐测试。PC端通过网线直连设备和通过交换机接入两种方式都测过。直连时自协商结果是100M全双工TCP单连接单向上传速度实测在85Mbps左右这个数据对大多数工业通信场景都够用了。如果对端是交换机速率会受交换机背板带宽影响但也在合理范围。实际上对于这个采集设备来说每秒上报的数据量也就几百KB根本跑不到线速吞吐余量非常充足。所以做以太网设计时不用一味追求极限吞吐把稳定性做好才是关键。在连续跑了两天的高低温箱测试后TCP连接没有断过链路层零丢包这个结果给了我很大信心。做吞吐测试时有一个小细节电脑端的网卡高级设置里最好关闭节能以太网和流控否则实测速率会受影响。另外用iperf测试比用TCP调试助手发大文件更准确因为iperf能排除应用层瓶颈直接测协议栈的吞吐上限。6.2 这个方案还可以怎么扩展STM32F429加LAN8720A这个平台的可扩展性很强简单列几个可以继续做的方向。一是从单客户端改成多客户端支持把netconn_accept后的处理逻辑拆成多线程就能同时服务多台上位机。配合FreeRTOS的信号量和队列可以把接收到的数据分发给不同的任务处理。不过要注意每增加一个TCP连接lwIP的PBUF和线程栈都要跟着增加RAM占用会明显上升需要预留充足空间。二是加Modbus TCP协议把采集到的传感器数据通过Modbus寄存器方式暴露给上位机这样不需要自定义协议直接用现成的Modbus调试工具就能读取数据。Modbus TCP的报文基于TCP承载在lwIP上调通TCP后再套一层Modbus解析并不难。我目前正在做这个扩展主要工作量在协议解析和寄存器映射底层TCP通路已经很稳定。三是结合F429自带的SDRAM和LCD控制器在本地做数据可视化界面以太网收到的数据直接显示在屏幕上调试的时候不用外接上位机一台设备就能完成全链路验证。这就属于项目后期锦上添花的部分了前提是底层通信已经足够可靠。做这类项目越到后面越能体会到硬件方案选型的第一颗螺丝拧对了后面所有工作都水到渠成。STM32F429内置MAC加LAN8720A的组合是当前F4系列里做以太网最稳的一条路调试工具成熟、参考代码多、成本还低值得推荐给做工业数据采集和物联网网关的朋友。本文还有配套的精品资源点击获取