尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32以太网开发实战:从硬件设计到LWIP协议栈移植与调试

STM32以太网开发实战:从硬件设计到LWIP协议栈移植与调试 做嵌入式开发这么多年碰过不少通信接口串口、SPI、I2C基本属于家常便饭但真正让我觉得“这玩意儿有点门槛”的是以太网。尤其是STM32接以太网涉及的不只是简单的寄存器配置还有硬件布线、PHY芯片选型、协议栈移植、内存管理、实时性调优等等一串问题。最近整理旧硬盘翻出2018年3月那批STM32以太网进阶培训资料重新看了一遍发现很多细节当时没吃透现在结合这几年的实际项目经验再梳理一遍把真正能落地的东西写出来希望对正在做STM32以太网开发的同学有帮助。这篇分享不是把培训PPT搬过来复述而是站在“工程师做完一个以太网产品后回看”的角度讲清楚从硬件设计到LWIP协议栈移植、再到帧校验和调试抓包的完整链路以及那些文档里一般不写、但实际项目里一定会踩的坑。适合的对象是用STM32做网关、数据采集器、工业控制板、智能家居中控已经开始接触以太网但还没完全跑通或者跑通了但想搞清楚原理、想优化性能的开发者。1. 整体设计思路与硬件架构拆解1.1 STM32以太网资源的真实全貌先说个容易被误解的点STM32本身并不带以太网PHY收发器它带的是MAC控制器。MAC负责数据链路层的逻辑比如帧的封装、地址过滤、CRC校验但真正的物理信号收发要靠外接的PHY芯片完成。PHY承担的是物理层工作把MAC送来的并行数据转换成串行的差分信号通过双绞线发出去同时接收对端信号并解码。这两者的分工可以类比成“打包快递”和“实际运输”的关系。MAC是快递公司的分拣中心负责贴单、分拣、把关PHY是运输车队负责把包裹从一个城市送到另一个城市。你光有分拣中心没有车队货是发不出去的。STM32家族里带以太网MAC的系列主要是F407、F417、F427、F437等F4系列还有F7系列和H7系列。以最常见的STM32F407VET6为例它内部集成了一个10/100M Ethernet MAC支持MII和RMII两种接口模式但对外必须挂一颗PHY芯片比如LAN8720A、DP83848、KSZ8081这些。这里就引出一个核心的前提如果你手上是STM32F103系列那很遗憾它内部没有以太网MAC硬件上就死了这条心要么换MCU要么外扩SPI接口的以太网控制器比如W5500。W5500方案其实对新手更友好因为它内部直接集成了TCP/IP协议栈MCU只需要通过SPI读写数据不做协议解析。但如果你想真正掌握以太网原理后续做复杂应用还是得走MACPHY软件协议栈这条路线。1.2 为什么选STM32做以太网设备很多项目纠结要不要为了以太网单独加一颗Linux芯片比如全志、瑞芯微这些跑Linux的SoC开发门槛高、成本高、功耗也高。而STM32做以太网设备的核心优势在于在不跑操作系统的前提下用裸机或者RTOS配合LWIP就能处理多路TCP连接和数据转发满足绝大多数工业数据采集和物联网网关场景。举个实际案例我之前做一个环境监测网关采集温湿度、PM2.5等传感器数据通过Modbus TCP上报给上位机软件同时还要预留一个简单的Web页面用于本地配置。整机功耗控制在2W以内BOM成本压得非常低一颗F407加一颗LAN8720搞定网络部分跑FreeRTOS加LWIP稳定运行几个月不用重启。换Linux方案光启动时间就得论秒算成本至少翻一倍在这个场景下完全是浪费。但这里有个前提得说清楚STM32跑以太网适合的是中小数据量和中等并发连接数的场景。如果你要处理大文件传输、高并发Web服务或者复杂的路由转发那STM32的资源和性能很快就会见底。选不选STM32做以太网核心看需求做数据采集和轻量级服务STM32很合适做重负载网络服务趁早换平台。1.3 MII接口和RMII接口怎么选这是硬件设计开始前就必须定下来的关键选择。MII和RMII都是MAC和PHY之间的数据接口标准区别主要在于引脚数量和时钟频率。MII接口需要16根数据线TX和RX各8根加上控制信号一共二十多根引脚。数据时钟是25MHz对应100Mbps速率4位并行每个时钟周期传4位就是4×25M100M。RMII接口则把数据线精简到7根TX和RX各2根数据线时钟固定为50MHz2位并行2×50M100M。引脚节省了大半对MCU封装和PCB布线都友好很多。在实际项目中我绝大多数情况下都选RMII。原因是STM32F407的引脚资源本身就很紧张MII模式要占掉差不多30个IO很多复用功能都会被顶掉。RMII只剩7根线省下的IO可以留给传感器、显示屏、按键这些功能。代价是RMII需要一个50MHz的参考时钟源这个时钟要求精度比较高后面时钟部分会详细说。还有个细节要注意选RMII模式时PHY芯片的地址配置要提前确认。100M以太网中PHY地址通常通过引脚的电平组合来确定比如LAN8720A的PHYAD0引脚接下拉电阻地址就是0DP83848的地址引脚更多可以配置1到31。MAC通过MDIO接口去读PHY寄存器时必须先知道PHY地址否则初始化会失败。这个地址一旦因为硬件设计错误写死后面排查起来特别痛苦。2. 硬件设计关键环节与实操避坑2.1 PHY芯片选型对比与实测体会市面上适合STM32的10/100M PHY芯片不少但真正用得多的、资料全的我觉得就这几颗PHY芯片接口类型RMII时钟来源特点常见问题LAN8720AMII/RMII外部50MHz OSC或MAC提供50M时钟价格便宜体积小应用极广泛抗静电能力一般时钟要求严格DP83848MII/RMII支持25MHz晶振倍频灵活性强鲁棒性好工业级兼容性强价格稍高电路稍复杂KSZ8081MII/RMII支持多种时钟源功耗低连接稳定市面上假货多需要注意渠道LAN8720A在我项目里用得最多价格、功耗、封装尺寸都有优势而且ST官方的评估板上大量使用这颗芯片参考电路好找。但它对RMII的50MHz时钟要求很高时钟抖动大会直接导致网络不通或者丢包严重所以布线时必须重视时钟信号的处理。DP83848则适合对稳定性要求更高的工业场景比如工作温度范围宽、电源波动大的环境。它耐造一些但BOM成本上去了对成本敏感的产品压力大。还有一点提醒PHY芯片的功耗虽然不高但电源设计不能含糊。PHY的数字电源和模拟电源通常要分开用磁珠或者0欧电阻做单点连接模拟电源的滤波电容要靠近PHY的电源引脚放置。我在一个项目里图省事把模拟和数字电源直接并用结果网络通断不稳定折腾了很久才发现是电源纹波耦合导致的。2.2 网络变压器与RJ45设计的硬性要求PHY芯片出来之后信号要经过网络变压器比如HR911105A这类集成RJ45变压器的一体化连接器再连到网线。很多新手会问PHY直接连RJ45行不行答案是行但绝对不建议。网络变压器在以太网链路里承担着几个重要任务一是隔离把PHY芯片和外部网线之间的直流电平隔开防止外部浪涌和共模干扰损坏PHY二是信号耦合通过中心抽头的偏置电压配合PHY的电流驱动实现差分信号的正规传输三是阻抗匹配将PHY侧和线缆侧的特性阻抗匹配到100欧姆减少反射。实际项目中我强烈建议直接用集成变压器的一体化RJ45座比如HR911105A、JK025等。这类连接器已经把变压器、共模电感、终端电阻全做进去了硬件工程师省心很多。如果选分立式变压器加普通RJ45座要自己搞定差分走线和变压器外围电路PCB面积和设计难度都增加不少。RJ45金属外壳的接地问题也要注意。按照一般工业惯例RJ45的金属屏蔽壳通过一个高压电容通常1nF/2kV和一个大阻值电阻并联接到大地这样既能泄放静电又不至于让噪声顺着地线跑进系统。如果你做的是双绞线测试仪或者要精准测量信号质量的设备这个接地设计更是不能省。2.3 PCB布线中关于差分对和时钟的硬核细节以太网走线的核心是差分对TX_P和TX_N是一对RX_P和RX_N是一对。差分对布线的关键要求是等长、等距、同层同时要保持100欧姆的差分阻抗。实际布线时我一般把差分对线宽设为6~8mil间距按阻抗计算工具算出的数值来通常也在6mil左右。每一对差分线的长度差要控制在5mil以内越长越容易产生共模噪声影响信号质量。除了差分对RMII的50MHz参考时钟也要特别对待。它是一根单端信号但频率高、要求低抖动所以走线要尽量短靠近PHY和MAC的时钟引脚并且远离其他高速信号。如果使用外部有源晶振提供50MHz晶振输出要靠近PHY的CLKIN引脚如果由MCU的MCO引脚输出时钟给PHY则MCO到PHY的走线要短串接一个22欧姆或33欧姆的电阻用来抑制反射。关于隔离以太网区域的地和主系统数字地通常建议做分割或者至少在布局上把以太网部分单独放一角底下铺独立的地平面再通过一个磁珠或者0欧电阻与主地相连。这么做不是为了炫技是真的能有效降低以太网信号对MCU其他部分的干扰。2.4 时钟方案的三种典型做法的选择RMII模式下50MHz时钟的来源市面上常见的做法有三种各有优缺点选型时要权衡成本、可靠性和引脚占用。第一种用50MHz有源晶振直接给PHY提供时钟PHY再把恢复后的参考时钟反馈给MAC。这种方式时钟质量高但需要多一颗有源晶振成本高一些。第二种用25MHz无源晶振接PHYPHY内部通过PLL倍频到50MHz再通过CLK_OUT引脚输出给MAC。这种方式最常用因为25MHz无源晶振便宜而且PHY内部PLL质量可控。我在LAN8720A上就是这么接的。第三种STM32的MCO引脚输出50MHz时钟给PHY。这种方式省了晶振但MCO引脚的时钟抖动通常比专用晶振大对高速网络稳定性有潜在影响。要求不高的应用能跑但建议慎用。时钟方案选完要在代码里相应配置。如果使用CubeMX需要在ETH配置里选择RMII并且配置MCO或者时钟引脚系统初始化时保证时钟先起来PHY才能正常工作。这是一个“硬件先于软件”的依赖顺序初始化顺序错了PHY就没法和MAC握手成功网络就起不来。3. LWIP协议栈移植与配置细节3.1 裸机跑LWIP还是配合RTOSLWIP是一个轻量级TCP/IP协议栈设计初衷就是给资源受限的嵌入式设备用的。LWIP本身支持三种运行模式noopts裸机轮询、copt带操作系统模拟层和multi-threaded多线程模式。在STM32上我最推荐配合FreeRTOS跑多线程模式理由有三个。第一LWIP的TCP/IP处理需要阻塞、超时等机制配合RTOS的信号量和邮箱能更自然地处理数据到达、连接断开等异步事件。第二以太网接收中断里只做“把数据从DMA拷贝到PBUF”的工作具体协议解析放在tcpip_thread线程中能保证中断处理时间极短避免中断嵌套带来的实时性问题。第三应用层如果有多个任务同时使用网络比如一个任务跑Modbus TCP另一个任务做Web服务器多线程才能让代码结构清晰不互相卡死。当然裸机也完全能跑LWIPIO用轮询方式处理代码简单适合极简应用。但一旦并发连接数增加或者数据吞吐量上来裸机模式的CPU占用率会明显升高实时任务被拖死的风险大。所以只要条件允许我建议直接上RTOS。3.2 lwipopts.h里那些不能乱调的参数LWIP的配置文件是lwipopts.h里面的参数决定了协议栈的行为和内存占用。新手最容易犯的错是直接复制默认配置不管或者随便改大内存参数以为能提升性能结果内存爆了跑一会儿就死机。我把自己常用的关键参数整理了一下MEM_ALIGNMENT内存对齐字节数STM32一般是4字节这个保持默认即可。MEM_SIZE堆内存池大小用于PBUF等动态分配。一般4KB起步如果并发连接多可以设到16KB或更大。PBUF_POOL_SIZEPBUF池数量每个PBUF默认长度PBUF_POOL_BUFSIZE。这个决定了同时能处理多少数据包我一般设16到32个。MEMP_NUM_TCP_SEGTCP同时能缓存的报文段数量一般设16到32。MEMP_NUM_NETBUF网络缓冲区数量TCP传输时每个连接会用到netbuf设16个左右。MEMP_NUM_TCP_PCB同时打开的TCP连接数量上限默认值可能只有4到8个如果设备需要支持多个客户端同时连接必须调大比如16或者32。TCP_WNDTCP接收窗口大小影响吞吐量。如果想跑满100M窗口要调到64KB以上但内存也吃得多需要权衡。TCP_SND_BUFTCP发送缓冲区大小一般设16KB左右。调这些参数的原则是按实际应用需求来不要盲目调大。我见过很多开发者一上来就把MEM_SIZE调到几兆STM32F407总共才192KB RAM代码还没跑就先爆内存系统直接HardFault。我的习惯是先按默认值跑通功能然后通过抓包和查看内存统计宏逐步调参。3.3 netif接口与PHY状态轮询的实现细节LWIP中每个网络接口对应一个struct netif结构体。在STM32上初始化流程大致是先初始化MAC和PHY然后调用netif_add注册接口设置netif-name和netif-mtu再调用netif_set_up把接口置为UP状态。这里有个容易疏漏的点PHY的链路状态检测。网线插上、拔下时PHY的Link Status寄存器会变化MAC和协议栈需要感知这个变化。实际项目中我一般在主循环里调用一个PHY状态读取函数周期性地读PHY的Basic Status寄存器寄存器地址1bit2是Link Status当检测到链路从Down变为Up时调用tcpip_callback配合netif_set_link_up反之则调用netif_set_link_down。这么做的原因是如果网线没插协议栈却把接口置为UPTCP连接必然失败应用层会一直卡在连接超时里。如果代码里实现了link状态管理插上网线后协议栈能立刻感知恢复通信体验好很多。以太网中断的处理也值得一提。STM32的ETH模块支持接收中断和发送完成中断。在HAL库里主要关注ETH_WAKEUP、ETH_RX、ETH_TX这几个事件。接收中断中要做的事尽可能少我只做一件事调用HAL_ETH_GetReceivedFrame如果成功就把接收到的PBUF交给协议栈返回错误就直接释放PBUF。这样中断占用时间极短不会影响其他实时任务。3.4 性能调优校验和卸载和零拷贝校验和计算是一个可以通过硬件加速的点。以太网帧里的IP校验和、TCP/UDP校验和在协议栈里默认用软件计算占CPU时间。STM32的MAC支持发送时硬件自动计算和插入IP/TCP/UDP校验和通过在DMA描述符中设置CHECKSUM_INSERT的配置硬件会在发送时自动填好校验和省掉软件计算的开销。我在高吞吐应用里会打开这个功能大概能省出5%到10%的CPU占用。零拷贝则是指在接收数据时DMA直接把收到的以太网帧数据写入LWIP分配的PBUF内存中避免一次额外的内存拷贝。STM32的HAL库在初始化ETH时通过HAL_ETH_Init和HAL_ETH_ConfigDMA配置了DMA描述符和接收缓冲区配合LWIP的PBUF池可以实现接收路径的零拷贝。要点是lwipopts.h中的PBUF_LIBRARY使用PBUF_POOL并且ETH_RX_BUFFER_SIZE要设置为PBUF_POOL_BUFSIZE的整数倍关系。性能优化的目标是跑通后用CubeMX和MDK的周期统计工具看一下CPU占用率。一个优化到位的F407LWIP方案在20Mbps以内的UDP收发时CPU占用率应该能控制在30%以下超过这个数就要检查是不是校验和没有卸载、中断处理太频繁或者内存分配效率太低了。4. 以太网报文结构与帧校验原理4.1 从网线到数据包以太网帧到底长什么样学习以太网绕不开的就是帧结构。以太网帧遵循IEEE 802.3标准我在培训资料里花了很大篇幅讲这块因为后面做抓包分析、写底层驱动、排查通信问题全都依赖对整个帧格式的理解。一个标准的以太网帧由以下几个部分组成前导码Preamble和帧起始定界符SFD、目的MAC地址、源MAC地址、长度/类型字段、数据载荷、帧校验序列FCS。具体到字节数前导码7字节SFD 1字节目的地址6字节源地址6字节类型字段2字节数据载荷最小46字节、最大1500字节FCS 4字节。所以一个帧的最小长度是66246464字节最大是1518字节不算前导码和SFD。这里有一个应用层容易忽略的点以太网最小帧长64字节的原因是为了支持CSMA/CD碰撞检测机制保证任何站点在发送完一个帧之前都能检测到远端是否有碰撞。虽然现在全双工交换网络里基本用不到CSMA/CD了但帧长的下限依然保留着如果应用层要发送的数据不足46字节MAC层会自动填充0到46字节。所以网上有些教程让你自己填Padding实际在STM32里HAL库已经自动处理了不用自己操心。类型字段也就是EtherType标识上层协议类型。0x0800表示IPv40x0806表示ARP0x86DD表示IPv6。这个字段特别重要因为MAC层靠它决定把收到的帧交给哪个协议模块处理。STM32的MAC硬件支持类型过滤可以配置成只接收特定EtherType的帧减少主控负担但这个功能我一般不开因为应用场景多变用软件过滤更灵活。4.2 FCS和校验和两个特别容易搞混的概念以太网帧的FCS字段是CRC32校验由MAC硬件自动生成和校验覆盖从目的MAC地址到数据载荷末尾的全部内容。CRC32的算法是多项式0x04C11DB7初始值和输出处理方式有规定。STM32的MAC在发送时自动计算并填充FCS在接收时自动校验如果校验不通过会丢弃或标记错误。所以应用层基本接触不到CRC32的计算你用逻辑分析仪或者抓包工具看到的以太网帧FCS都是正常值。真正需要应用层自己计算的是IP/TCP/UDP协议头中的校验和这个校验和用的是另一种算法叫Internet校验和和CRC32完全是两码事。Internet校验和是16位反码求和把IP头或TCP头按16位一组累加进位回卷最后取反。LWIP的软件实现里通过一个查表或者分支展开函数来计算速度很快。为什么会有“以太网帧校验和计算器”这种工具因为它处理的通常不是以太网FCS而是IP/TCP/UDP校验和。调试中经常出现一种情况你自己构造一个TCP报文发出去上位机Wireshark提示TCP校验和错误。原因大概率是硬件卸载校验和没有正确打开或者你在构造报文时手动填了错误的校验和走了两种机制。排查思路是先看Wireshark里TCP checksum那栏如果显示“incorrect”优先检查发送路径的校验和是否由MAC硬件自动填写如果已经打开则检查填充的校验和字段是否被强制覆盖了。4.3 IP与TCP/UDP校验和的增量更新技巧增量更新Incremental Update是一个比较进阶但实际工程很有用的技巧。应用场景是你在TCP连接上持续发送数据但每次发送时可能需要改掉TCP头里的某个字段比如序列号或者IP头里的总长度字段。这时候如果每次都重新计算整个头部的校验和消耗的CPU时间是一致的但增量更新可以直接基于旧的校验和和变化的字段在O(1)时间内算出新的校验和。原理很简单Internet校验和是线性的符合“先加的校验和 后加的新校验和”。所以当头部某个字段变化时新的校验和等于旧校验和减去字段旧值加上字段新值再做回卷。实际代码里就是RFC 1624里的公式~C ~C ~m m即新校验和的反码等于旧校验和的反码加上字段旧值的反码再加上字段新值。这个技巧最典型的应用是在做UDP/TCP透明转发或者打洞时不重新封包只改IP和端口字段就能高性能地完成报文改写。我在做一个UDP数据转发网关时就是靠这个技巧把CPU占用率降了整整一倍。4.4 抓包分析一条网线看透数据流学以太网调试最核心的工具是Wireshark配合一个USB转以太网的抓包工具或者交换机镜像口就能看到线上数据。实际调试时我通常关注四类报文ARP报文设备上电后首先发出的是ARP请求询问网关MAC地址。如果连不上服务器先看ARP能不能通。Wireshark里ARP请求不停重发说明IP配置有问题或者网关不可达。DHCP报文DHCP Discover、Offer、Request、ACK四个过程缺一不可。DHCP失败多半是网线/交换机问题或者DHCP服务器的池子满了。ICMP报文ping用的是ICMP Echo Request和Reply。能ping通说明IP层没问题但如果TCP连不上重点查TCP握手。TCP报文TCP三次握手是关键。如果看到客户端发了SYN但没有SYN-ACK回来通常是服务器端协议栈没有监听端口或者连接数满了如果SYN总是重传大概率是网络不通。抓包建议是从设备上电到连接建立完整地抓一遍对照帧结构逐层看。很多问题不是出在最后一层而是底层协议就没走通。比如我遇到过DHCP一直获取不到IP抓包发现ARP请求根本没人回应排查半天发现是PHY工作在100M而交换机端口强制10M导致链路状态一直不稳定这种问题从Wireshark中一眼就能看出来是底层层面的异常。5. 实操流程从CubeMX配置到TCP通信打通5.1 用CubeMX完成ETH、RMII和LWIP的初始化配置现在跑通STM32以太网已经比2018年那会儿省心太多了ST官方提供的STM32CubeMX直接支持ETH和LWIP的图形化配置。我以STM32F407VET6加LAN8720A为例说一遍实操流程。首先在CubeMX里选择芯片型号配置时钟树。F407最高主频168MHz外部晶振一般25MHz通过PLL倍频到168MHz。RMII的50MHz时钟如果由MCO输出需要在时钟树里把MCO1引脚配置成50MHz输出连接到PHY的REF_CLK。第二步配置ETH外设。把ETH的RMII模式开启引脚会自动分配确认一下接线ETH_MDC、ETH_MDIO、ETH_RMII_REF_CLK、ETH_RMII_CRS_DV、ETH_RMII_RXD0、ETH_RMII_RXD1、ETH_RMII_TX_EN、ETH_RMII_TXD0、ETH_RMII_TXD1。这里有一个容易踩坑的点在CubeMX里ETH外设引脚和某些复用功能可能冲突比如PHY的MDIO引脚占用了某个调试口的引脚编译没问题但实际硬件上就冲突了。用之前先核对原理图确保引脚没有被其他外设占用。第三步中间件选择LWIP。在Middleware and Software Packs中勾选LwIP配置选项里建议选“FreeRTOS LwIP”的组合这样CubeMX会自动生成FreeRTOS的配置文件并且默认以多线程模式运行LWIP。协议栈版本选择最新的2.x版本默认支持IPv4和IPv6实际用IPv4就够则可以把IPv6关掉节约内存。第四步配置LWIP参数。在LwIP配置页里可以设置本地IP、子网掩码、网关地址。调试阶段我一般设静态IP192.168.1.10255.255.255.0网关192.168.1.1。同时把TCP监听端口之类的参数按需配置。注意这里的IP地址是给netif的如果后续要用DHCP可以在代码里修改。5.2 HAL库中ETH和LWIP代码结构解读CubeMX生成的项目里LWIP的初始化代码在lwip.c里核心函数是MX_LWIP_Init()。它会调用lwip_init()进行协议栈初始化然后配置netif接口设置IP地址、网关、子网掩码最后调用netif_set_up()把接口置为UP。ETH的DMA配置在HAL_ETH_Init()里完成初始化代码里会分配DMA描述符和接收缓冲区这些缓冲区一般定义为静态数组放在RAM里。如果选择RMII模式CubeMX会在SystemClock_Config中配置好MCO输出确保PHY时钟先就绪。使用FreeRTOS时MX_LWIP_Init()内部会根据配置启动tcpip_thread协议栈处理线程、eth_rx_thread以太网接收线程和eth_link_thread链路状态检测线程这些线程的优先级和栈大小都在lwipopts.h和FreeRTOSConfig.h里配置好了。实际项目中我发现eth_rx_thread的栈大小默认值可能偏小如果应用收到的包很大或者突发流量高容易栈溢出建议把它的栈大小调到1024字节以上并且打开FreeRTOS的栈溢出检测功能。5.3 快速实现一个TCP Server并测试协议栈跑通之后就要在应用层写TCP服务。我以一个最简单的TCP Server为例监听8080端口客户端连上来之后发一串欢迎字符串同时实现回显功能。在main.c或者单独的任务文件里我们创建一个TCP Server任务调用netconn_new创建一个TCP连接控制块netconn_bind绑定端口netconn_listen进入监听状态然后进入循环netconn_accept接受客户端连接netconn_recv接收数据netconn_write发送数据最后netconn_close关闭连接。使用netconn API比直接操作raw API简单很多适合应用层开发。在FreeRTOS任务里如果用户长时间无操作netconn_recv会阻塞等待数据不会占CPU资源。实际测试时电脑上我一般用Python写一个简单的socket客户端import socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((192.168.1.10, 8080)) s.sendall(bhello stm32) data s.recv(1024) print(data) s.close()如果设备端调试正常电脑端会收到设备返回的欢迎字符串和回显内容。测试通过后再进一步做压力测试和断线重连测试。5.4 LWIP裸机移植到自定义板卡的三步定位法如果你没有用CubeMX而是自己写启动代码和驱动或者从旧工程迁移到新板卡我推荐一个三步定位法来排查“网络完全不通”的问题。第一步检查PHY是否工作。上电后读PHY的ID寄存器寄存器2和3LAN8720A应该读到0x0007DP83848应该读到0x2000KSZ8081应该读到0x0022。如果ID读不到或者读出全0说明MDIO通信异常重点查PHY地址、电源和复位引脚。第二步检查链路层。在代码里循环读PHY的Basic Status寄存器1看bit2链路状态是否置1。网线插上后如果这个位始终是0可能是网线问题、变压器问题或者PHY的自动协商配置不对。第三步检查协议栈是否收到帧。在LWIP的接收路径上打断点或者放一个计数器看是否进入接收中断是否收到ARP请求。如果计数器增加说明底层数据通路是通的问题在协议栈配置如果计数器没有增加返回第二步继续查链路。这套方法在旧工程维护中帮了我大忙每次换板子只需要花十几分钟定位问题不用再对着寄存器手册一头雾水。6. 常见问题排查与实战经验总结6.1 以太网模块常见问题速查表问题现象可能原因排查步骤与解法上电后PHY ID读不到PHY地址错误、复位引脚没有释放、MDIO引脚被复用查原理图PHY_AD0~AD4的上拉下拉用万用表确认复位电平核对CubeMX引脚复用网线插上后Link状态不稳定PHY电源纹波大、RJ45虚焊、网线质量问题示波器看PHY电源纹波补焊RJ45换一根短网线交叉验证能获取IP但ping不通netif没有置UP、网关失配、ARP不通检查netif_set_up是否调用PC端arp -d清缓存抓包看ARP回应能ping通但TCP连接失败监听端口未启动、TCP PCB数量上限见底确认app层TCP server任务在跑调大MEMP_NUM_TCP_PCB传输速度异常低LWIP窗口太小、校验和卸载未开启、中断频繁调大TCP_WND和TCP_SND_BUF开启硬件校验和减少中断处理跑一段时间系统死机LWIP内存耗尽、堆栈溢出、DMA描述符配置错误检查内存分配函数返回值打开FreeRTOS栈溢出检测增大DMA描述符数量这张表基本覆盖了我做STM32以太网开发这五年遇到的大部分问题的排查入口。真出问题的时候不要盲猜先判断问题是硬件层、链路层、协议栈层还是应用层每层有对应的检查手段。6.2 STM32引脚复用与JTAG冲突问题详解这个坑在以太网项目里出现频率特别高因为ETH的RMII引脚比如PA8、PB11、PC4等常常和JTAG调试口引脚重合。我早期做的一块板子上PHY的MDC引脚正好和PA13/PA14的SWDIO/SWCLK在同一组IO上程序一跑MDC操作就把调试口搞挂了连ST-Link都连不上只能把SWD重新配置才能连接非常痛苦。解决办法是在开发阶段用软件把不用或冲突的调试口禁用。具体做法是调用__HAL_AFIO_REMAP_SWJ_NOJTAG()标准库或者__HAL_RCC_AFIO_CLK_ENABLE()后配置AFIO的SWJ_CFG寄存器把JTAG禁用只保留SWD两线调试。注意只禁JTAG不禁SWD这样程序还能下载调试。另一种做法是硬件上调整PHY地址或模块引脚避开与调试口的冲突。但我更推荐软件禁用JTAG因为不动硬件、改动可控。HAL库环境下的写法是__HAL_AFIO_REMAP_SWJ_NOJTAG();放在GPIO初始化之前执行。这样既保留SWD下载又把JTAG相关IO释放出来给以太网用。6.3 为什么能ping通但TCP数据传一半就卡住这个是最常被问到的场景之一。一种原因是LWIP的TCP发送缓冲区不足数据量大时发送失败应用层没有处理重试导致传输中断。另一种原因是接收端的TCP窗口太小导致吞吐量极低甚至一直阻塞。第三种常见原因是中断处理函数里做了过多耗时操作比如在以太网接收中断里直接调用printf输出日志中断被长时间打断导致DMA描述符来不及处理数据包丢失。我的排查套路是先在Wireshark里看TCP流如果出现大量Zero Window说明接收端缓冲区满了应用层消费速度跟不上需要调大接收窗口加快速率如果出现大量Dup ACK和Retransmission说明网络丢包先查硬件链路质量。代码层面我还会在应用层加一个发送结果检查如果netconn_write返回内存不足错误就稍作延时再重发这样能显著提高传输的健壮性。6.4 LWIP内存泄漏与协议栈断言的调试思路LWIP维护了一套内存统计机制可以在lwipopts.h里打开LWIP_STATS和MEMP_STATS宏编译后程序运行时会生成内存使用统计。如果怀疑内存泄漏可以在主循环周期性地调用stats_display_mem()观察MEM_SIZE和PBUF_POOL的剩余量。如果空闲量持续下降直到0那一定有泄漏常见的泄漏点是接收路径上忘记释放PBUF或者TCP连接关闭时回调没有正确处理。LWIP自带assert机制出错时会调用LWIP_ASSERT打印错误信息。调试时把这些assert信息保留不要为了省空间关掉。实际踩过的坑是某个版本的LWIP在TCP连接数达到上限时会触发未定义行为assert信息提示TCP_PCB list的错误最后定位到是MEMP_NUM_TCP_PCB设太小连接一多就出问题。调大之后稳定运行。在FreeRTOS环境下还需要注意协议栈每层使用的栈大小。LWIP的tcpip_thread默认栈大小1024字节如果应用程序处理数据量大或者使用API在tcpip_thread上下文执行长时间操作很容易栈溢出。我的经验是tcpip_thread栈配到2048字节eth_rx_thread配到1536字节应用层任务按需分配尽量给网络任务留足余量。6.5 从2018到现在的几点实操体会回看2018年3月那份培训资料现在很多工具和细节都变了但底层原理和排查思路依然有效。比如说PCB布局时PHY芯片底下要铺完整的地平面差分线要等长这些现在依然是在做的关键点。又比如LWIP的配置虽然CubeMX能自动生成但真正理解MEM_SIZE这些参数背后的内存模型才能在产品出问题时快速定位。个人体会最深的一点是以太网调试慢就是快。遇到网络不通不要急着改这改那先按“硬件PHY——链路层——协议栈——应用层”这个顺序一层层排查每层都有明确的验证手段PHY层看ID寄存器和Link状态链路层看中断和DMA描述符协议栈层看ARP和ICMP应用层看TCP连接和数据吞吐。按这个流程走绝大多数问题半小时内可以定位。最后再分享一个小技巧。很多项目的以太网mac地址是写死在代码里的这在量产时会被客户抓到因为同一批次设备MAC地址相同一旦接入同一局域网会造成ARP混乱。建议在批量生产时从STM32内部Flash的某个特定区域读取烧录的MAC地址配合产线烧录工具唯一化。如果没有产线工具也可以从外部EEPROM读取。这个细节虽然跟纯技术关系不大但在实际产品化时是绝对不能忽略的。
返回列表