
如果你捣鼓过带网络接口的嵌入式设备大概率遇到过这种情形现场设备明明有网口想改个参数却还要电脑装上专用上位机版本一换就找不到安装包。最近在做STM32F407项目时我发现直接把它内部自带的以太网MAC接口配上外置PHY芯片再跑一层lwIP协议栈就能在STM32F407上实现一个Web服务器——浏览器打开页面设备状态、参数配置、固件升级都能用网页搞定。这篇是系列第一篇我先讲清楚三件事为什么要在F407上做Web服务器、以太网硬件到底怎么搭、lwIP和HTTP之间是怎么分工的。这些基本概念通了后面写CubeMX工程配置和实际代码时才不会一头雾水。1. 为什么要在STM32F407上开一个Web服务器应用场景与选型思路很多搞单片机出身的人对Web服务器有种错觉觉得那是Linux设备或者服务器领域的事STM32这种Cortex-M4芯片跑个TCP通信还行做网页服务是不是太勉强了。实际上这个想法早就过时了F407主频168MHz内置1MB Flash和192KB RAM硬件带CRC和随机数模块跑一个轻量级HTTP服务绰绰有余。关键在于你能不能把需求范围控制住不在单片机上跑PHP和数据库而是老老实实提供“够用”的动态页面。1.1 传统上位机的维护成本比你想的高先回忆一下以前给嵌入式设备做交互界面普遍怎么搞。最土的办法是串口屏设备接一块带液晶的串口屏做几页菜单让用户按键操作讲究一点的在PC上用C#、Qt或者LabVIEW写上位机走串口、CAN或者以太网和板卡通信再新潮一些的会做手机App走Wi-Fi模块和云端对接。这些方案不是不能用但维护成本确实高。串口屏每改一个菜单图片、字库、页面工程都要重新烧一遍而且屏幕一旦出问题只能整块换上位机最大的坑是环境依赖现场电脑换了系统、缺了.NET库、杀毒软件把exe隔离了技术人员就得跑一趟手机App更麻烦Android和iOS要分别开发还要考虑各机型权限差异小团队根本耗不起。换种思路如果设备本身自带一个Web服务器用户只需要在浏览器地址栏输入设备的IP就能看到状态页面、修改参数、导出日志甚至上传固件。浏览器人人都有不需要装任何额外软件也不涉及跨平台适配。这个需求在工业设备配置、实验室仪器仪表、科研数据采集、楼宇自动化这类场景里特别强烈。STM32F407作为应用比较广的MCU有内置以太网MAC做这件事的硬件成本可以压得很低。1.2 内置MAC和外挂方案比到底选哪条路提到给单片机加网络功能市面上常见的路线有好几条我列一下自己接触过的方案外挂串口转以太网模块比如USR-TCP232模块单片机只要发串口数据模块帮你完成TCP/IP协议转换。优点是开发最快缺点是每个模块都要钱而且灵活性差动态页面逻辑不好写。外挂SPI接口以太网控制器比如W5500内部硬件实现了TCP/IP协议栈单片机只需要操作SPI寄存器就能收发TCP数据。这个方案在Arduino生态里很火但W5500的RAM有限并发连接数一多就容易丢包价格也不算便宜。外挂ESP8266/ESP32等Wi-Fi模组走AT指令或者MQTT协议。Wi-Fi确实方便但有些设备处于强电磁干扰环境或需要长期稳定有线连接Wi-Fi并不可靠。使用内置以太网MAC的MCU外围只加一颗PHY芯片加网络变压器跑lwIP软件协议栈。STM32F407就是这条路。F407内置的以太网外设是完整的10/100M以太网MAC带DMA控制器数据收发不需要CPU一个字节一个字节去搬。你要做的只是外面接一个PHY芯片负责把MAC传过来的数字信号变成网线上的模拟差分信号。从成本上看一颗百兆PHY几块钱加上网络变压器和RJ45座子整个以太网硬件成本能控制在二三十块以内。从灵活性上看TCP/IP协议栈lwIP是开源的HTTP服务逻辑完全自己写想做什么页面就做什么页面。这个系列我选F407而不是其他芯片主要是因为它资料多、例程完善、CubeMX支持得好踩坑时能找到的参考特别多适合做入门和深度定制。而且F407在电赛、毕业设计、工业产品里出镜率非常高读者覆盖面广。2. 先把网络硬件搞明白F407的MAC、PHY和时钟很多人第一次在F407上跑网络程序直接卡在硬件配置上。不是代码逻辑有问题而是对“MAC和PHY到底是干什么的”完全没有概念照着网线顺序接了一堆引脚发现link灯不亮或者ping不通就开始怀疑人生。所以这一步必须先拆清楚。2.1 MAC/PHY分工一个管打包一个管开口说话打个比方MAC层像是快递分拣中心负责把应用层的数据封装成以太网帧添加上目标MAC地址、源MAC地址、类型字段做完校验之后交给下层PHY层则是快递员负责把这一帧数据按位发出去同时监听网线上有没有其他人在说话避免碰撞。MAC是数字逻辑PHY是数模混合电路两者之间通过MIIMedia Independent Interface介质无关接口连接。这也是“介质无关”的含义MAC不关心底层是双绞线、光纤还是其他介质这些都是PHY的事。一个MAC可以搭配多种PHY只要接口兼容就能换。F407内置的就是MAC这一层寄存器数量多、配置复杂但好处是CPU可以通过DMA直接把数据包搬到内存不用逐字节参与。PHY芯片外面一般跟着网络变压器然后才是RJ45插座。F407的MAC和PHY之间有两种标准接口MII和RMII。MII需要16根左右的数据和控制线RMII可以砍到7根代价是时钟频率从25MHz提高到50MHz而且TX和RX的数据线各只有2位。STM32F407的引脚数量有限做产品时几乎都用RMII省下来的GPIO可以干别的。2.2 RMII接线的细节与常见踩坑RMII虽然线少但脚位关系必须搞对。最常用的几根信号线是信号名方向作用ETH_RMII_REF_CLK输入/输出50MHz参考时钟ETH_RMII_CRS_DVPHY→MAC载波监听/数据有效ETH_RMII_RXD0/RXD1PHY→MAC接收数据ETH_RMII_TX_ENMAC→PHY发送使能ETH_RMII_TXD0/TXD1MAC→PHY发送数据ETH_MDCMAC→PHY管理接口时钟ETH_MDIOMAC↔PHY管理接口数据这里面最容易出问题的是REF_CLK由谁提供。很多RMII PHY模块要求外部提供一个50MHz时钟传统做法是用F407的MCO引脚输出时钟给PHY但MCO能不能直接输出50MHz取决于你板子的HSE晶振频率和PLL配置。如果外部晶振是25MHzMCO只能输出25MHz或经过分频的值凑不出50MHz。更稳妥的做法是选一个板上带25MHz晶振、由PHY自己生成50MHz REF_CLK的模块让PHY的CLK_OUT引脚把50MHz反馈给F407的ETH_RMII_REF_CLK。也有模块要求MCU提供时钟接法完全反过来。提示做硬件设计之前先确认你手里的PHY模块是“由PHY提供50MHz时钟”还是“由外部输入50MHz时钟”。两种方案在CubeMX里的时钟配置入口完全不同接错的话网口link状态会非常不稳定时通时断。第二个常见问题是PHY地址。PHY芯片一般通过外部引脚配置一个0~31之间的地址常见的是0x0、0x1、0x4。lwIP初始化时通过MDIO/MDC管理接口读PHY的寄存器0和寄存器1来获取芯片型号和链路状态。如果你的代码写的是读地址0但板子上PHY地址是1那么读出来的寄存器全是对不上号的这个时候会出现一种诡异现象电平看起来都正常但网卡初始化一直报超时。所以拿到板子第一件事就是查PHY地址然后看例程里读的是哪个地址。第三个坑是RMII的CRS_DV和RXD0/RXD1引脚是否被其他外设占用。F407上ETH引脚往往和一部分GPIO复用用了网络就不能同时用那些脚做普通IO。设计PCB或者用开发板时要注意否则功能冲突的时候你查半天不知道是哪边的错。2.3 常用PHY芯片怎么选LAN8720A、DP83848、KSZ8081市面上能和F407搭配的百兆PHY很多我实际用过三颗LAN8720A、DP83848、KSZ8081。简单对比一下芯片接口特点参考价格典型应用LAN8720ARMII功耗低、外围电路少、集成度高便宜大量低成本的STM32开发板DP83848MII/RMII老牌、抗干扰能力强、温度范围宽偏贵工业级设备KSZ8081MII/RMIIMicrochip出品、兼容性不错中等商业产品LAN8720A之所以能在开发板里大行其道是因为它的外围电路实在太简单只需要一个50MHz时钟源、几个去耦电容和电阻就能工作占板面积小成本低。但它的功耗也相对低对应的抗静电和抗浪涌能力比工业级PHY弱一些。如果是做产品尤其是走485、电机控制这些干扰大的环境我建议用DP83848或者KSZ8081并且在网口变压器和PHY之间加共模电感、TVS管。DP83848是很多老工程师用得顺手的老芯片寄存器定义和驱动资料非常全F407官方评估板用的就是它。缺点就是价格高、封装大、外围的退耦要求也高一些。KSZ8081算是一个中间选择雷同寄存器做得还算规范ST官方也提供针对它的驱动板级支持包。注意不管你选哪颗PHYlwIP裸机移植时最重要的就是三个文件——ethernetif.c的底层发送函数、底层接收函数以及PHY初始化里读取芯片ID和协商速率的那段逻辑。这颗芯片型号一变往往只改管理接口读写地址和极个别寄存器位不需要把整个协议栈推倒重来。3. lwIP帮我们省掉了什么它内部又干了什么硬件链路打通之后接下来的问题是谁来处理TCP/IP协议总不能让应用层代码自己去回应ARP请求、处理TCP三次握手和重传吧这些网络协议琐碎到足以耗尽一个小团队的开发周期。于是lwIP进入视线。lwIP全称Lightweight IP是瑞士计算机科学院开发的轻量级TCP/IP协议栈专门面向资源受限的嵌入式系统。它的目标不是替代Linux内核里的完整网络栈而是在节省内存的前提下保留最核心的TCP/IP能力。对STM32F407这种级别的MCU来说lwIP是实用性最高的选择之一。3.1 从零实现TCP/IP协议栈为什么不可取有人可能会说STM32的Web服务器不就收个HTTP请求、回个网页吗自己写个TCP分帧解析行不行这个问题我以前也想过但试过之后会发现完全不是那么回事。TCP/IP协议栈不是只有TCP收发还有ARP地址解析、IP分片重组、ICMP响应、TCP状态机、滑动窗口、超时重传、校验和计算。光是TCP状态机从LISTEN到SYN_RCVD、ESTABLISHED、FIN_WAIT_1、TIME_WAIT这些状态迁移就能写晕一堆人。况且Web服务器要做到能多客户端访问连接并发管理就是个复杂工程。lwIP把这些都封装成了现成的模块你只需要告诉它“这个数据包从网卡收到了”“系统有一个1ms的时基Tick”它就自动帮你完成ARP缓存、路由查表、TCP段重组、校验和验证然后通过回调或队列把净载荷交给应用层。所以说lwIP帮我们省掉的是以太网之上、HTTP之下那一大坨“网络脏活”让我们能集中精力写业务页面和数据处理逻辑。3.2 三种API模式的使用场景raw、netconn、socketlwIP为上层提供了三种主要API模式很多人一上来就被这几种模式搞晕。实际上它们只是封装的层次不同由底层到高层排列如下RAW API回调式API协议栈直接调用你注册的回调函数。效率和内存占用最优可以在无操作系统的裸机上跑但代码逻辑较绕需要手动管理状态。Netconn API在协议栈内部封装了线程和邮箱机制采用类似阻塞读写的API编程直观得多。它需要有操作系统支撑建议配合FreeRTOS使用。Socket API基于Netconn API再封一层接口向BSD Socket看齐几乎可以把Linux/Windows上写的网络代码迁移过来。适合从PC端开发转过来的工程师但多封装一层也会带来一点额外开销。API模式是否依赖OS易用性性能和资源占用典型场景RAW API否差最优裸机环境、对RAM要求极致Netconn API是较好较好FreeRTOS lwIP的主流组合Socket API是好一般从PC开发转MCU/复杂网络逻辑做STM32F407的Web服务器我最推荐的是FreeRTOS Netconn/Socket API的组合。原因很简单Web服务天然可能同时处理多个连接还有可能一边响应HTTP请求一边保持和设备主逻辑的通信。裸机大循环里做多任务切换一旦某个页面处理耗时整个系统响应就会卡顿。用FreeRTOS把lwIP放到单独的任务里网页请求不会阻塞电机控制、数据采集这些实时逻辑。CubeMX现在已经能一键生成FreeRTOS lwIP的工程底层的任务栈、信号量、队列都配置好了非常省心。3.3 pbuf和内存管理lwIP的高效底子lwIP在内存使用上有一套专门机制核心叫pbufpacket buffer它本质上是一个数据包缓冲区的管理结构。为什么不是直接malloc一块内存传数据因为网络数据在协议栈每一层都要加头部应用层数据下去要加TCP头加IP头再往前还要加以太网头。如果每层都做一次数据拷贝CPU和内存开销就上去了。pbuf通过链表的方式把不同段的缓冲区串起来各层可以只操作自己关心的那一节甚至共享同一块数据的内存从而减少拷贝次数。pbuf本身分几种类型PBUF_RAM表示内存从RAM堆中分配收发数据时经常用PBUF_ROM/PBUF_REF表示数据本身不在pbuf管理范围内协议栈只引用外部数据。理解这个会让你在调lwIP内存时少走弯路。写Web服务器时如果给lwIP分配的内存池太小高并发或大数据量页面刷新时会直接丢包现象就是浏览器转圈很久偶尔能打开偶尔打不开。这种问题通过检查PBUF数量和TCP_MSS、TCP_WND配置就能找到方向。lwIP不需要你熟练掌握每一个宏但至少要清楚这些宏影响着什么。4. 嵌入式Web服务器的HTTP基本功和服务形态硬件通了协议栈有了下一个问题HTTP服务到底怎么写。HTTP是一个基于TCP的请求-响应协议说人话就是客户端浏览器发起一个请求服务器处理完后返回一段内容然后浏览器把内容渲染成页面。Web服务器里面其实没有太多神秘的东西。4.1 HTTP请求与响应的最小结构一个例子看懂从浏览器地址栏输入http://192.168.1.100浏览器会向服务器发送一个类似下面的请求GET / HTTP/1.1\r\n Host: 192.168.1.100\r\n Connection: keep-alive\r\n Upgrade-Insecure-Requests: 1\r\n \r\n这个请求非常直白第一行是请求方法、路径和HTTP版本后面是若干请求头最后空一行表示请求头结束。如果路径是/一般约定返回默认页面。服务器解析出“这个客户端想要根路径”就把存储在Flash里的HTML内容组织好再按HTTP响应格式返回HTTP/1.1 200 OK\r\n Content-Type: text/html\r\n Content-Length: 233\r\n Connection: close\r\n \r\n !DOCTYPE html html ... /html关键是中间那个空行之前是响应头之后是正文。浏览器根据Content-Length字段知道正文有多长接收完就渲染页面。如果你想要动态数据比如实时温度和电流最简单的办法是让设备在HTML里填充特殊的占位符服务器在返回前用实时数值替换。如果想要页面自动刷新在HTML的head里加一个meta http-equivrefresh content2设备就会每隔2秒重新请求一次实现最简单的“实时监控”。4.2 页面资源藏在哪里代码内嵌、SPI Flash还是文件系统既然F407自带1MB Flash有人习惯直接把HTML做成C语言字符串常量编译进程序访问时直接从内部Flash读出来回给浏览器。这个方式对于纯静态页面确实最简单不用额外文件系统唯一的风险是页面一旦变大尤其嵌入了图标、图片或者CSS/JS库的base64编码Flash占用会迅速飙升。1MB看着不小但程序本身代码、协议栈、驱动也不小能留给页面的空间需要仔细计算。如果要做资源较多的Web服务比如需要存多张历史曲线、上传日志文件建议给F407外挂一个SPI接口的Flash芯片比如W25Q64、W25Q128跑一个轻量级文件系统。比较常见的方案是LittleFS或者SPIFFS。还有一个思路用SD卡存页面文件虽然做法更接近传统Web服务器但是F407的SDIO接口和以太网同时用的话要确认引脚不冲突另外SD卡在设备运行时不能随意拔插否则容易引发读写异常。简单总结一下三种资源存储方式的取舍存储方式优点缺点适用场景内部Flash字符串数组无需额外硬件简单直接页面更新必须刷固件空间有限页面固定、规模小外挂SPI Flash 文件系统可远程更新页面容量大需要文件系统移植和Flash磨损平衡需要较多页面资源SD卡容量巨大方便调试可靠性和引脚冲突风险开发调试期间4.3 在F407上通过Web最常做的四件事理解HTTP之后还得知道Web服务器在F407里通常用来干什么方便你在设计页面时心里有数参数配置。把串口波特率、PID参数、设备IP、校准系数放到网页表单里管理员填好数字点保存设备收到POST请求后解析参数并写入Flash。这样就不用再跑现场按按键或者发串口命令了。状态监控。通过定时刷新或AJAX请求把CPU利用率、电压、电流、温度、开关量等实时展示在网页上省去专门制作组态软件的成本。固件升级。在工业设备里Web升级是最受欢迎的用户上传一个固件文件F407接收后写入内部Flash并在BootLoader配合下完成重启切换。实现时需要用到HTTP/1.1的分块上传或者multipart/form-data格式。日志浏览。设备把运行日志写到外挂Flash或SD卡用户点一下网页上的按钮就能下载或者在线查看。出问题时现场工程师把页面截图发给开发人员问题定位效率大幅提升。5. 开始动手前我建议你先准备好这些很多新人会在看完原理的当天就打开CubeMX试图一口气把Web服务器全部搞定结果被一堆配置项劝退。我在折腾这个系列之前吃过类似的亏。在这篇概念介绍收尾之前我把准备工作清单列出来照着准备能少走很多弯路。5.1 硬件清单和工具链对齐开发板方面最好直接买带网口的STM32F407核心板或最小系统板。市面上很多板子默认带的是LAN8720A模块优点就是资料多。注意看板上PHY芯片型号、RMII时钟来源是板上晶振还是MCU的MCO引出这直接决定你在CubeMX里RMII时钟配置选哪个模式。除了板子本身还需要一根普通的网线先不用交叉线现在的网卡都支持自动翻转直连电脑或路由器都行。路由器或者交换机。如果条件不允许也可以用网线直接连接电脑网口然后把电脑的有线网卡IP设为静态同网段地址。这个做法在野外调试时很管用不依赖路由器。USB转TTL串口工具用来打印调试信息。lwIP初始化日志、PHY芯片ID、DHCP分配结果这些关键信息都靠串口输出来确认不要指望着插着调试器用IDE看变量很多时候现场没电脑跑IDE。杜邦线和万用表量一下RMII接口各引脚有没有虚焊网络故障排查中很多问题最后都出在物理连接上。软件开发环境建议使用STM32CubeMX生成初始化工程配上Keil MDK或者STM32CubeIDE。CubeMX的好处是它能自动完成GPIO复用、时钟树、DMA配置的关联手动写这些配置会让人崩溃。注意CubeMX版本尽量新一点旧版本对lwIP配置项支持不完整。5.2 网络调试三板斧静态IP、Ping、抓包代码写完后调试顺序应该是“先物理层再链路层再协议栈再应用层”。物理层是否正常看PHY芯片link状态寄存器进一步就是看以太网连接状态电脑和板子都插好插头看RJ45绿灯/黄灯是否亮。网线插上灯不亮后面代码全是白折腾。然后给板子配置一个静态IP比如192.168.1.100开发阶段不要轻易用DHCP。DHCP虽然方便但依赖路由器而且等待DHCP租约的时间会掩盖很多问题。静态IP在局域网内直接pingping 192.168.1.100ping通了说明MAC、PHY、lwIP的ARP和ICMP模块都能正常工作。如果ping不通先用串口看板子打印的PHY ID确认是否能读到。读不到PHY ID基本是RMII时钟问题或PHY地址问题此时不要怀疑上层协议栈。应用层调试时手边放一个Wireshark抓包工具务必让电脑有线网卡处在混杂模式去抓网口报文。用浏览器访问板子IP在Wireshark里看TCP握手有没有完成、HTTP请求和响应有无异常。如果能看到TCP三次握手但HTTP请求一直重传多半是lwIP的TCP窗口和内存配置不合理如果能看到SYN包没有回ACK问题大概率在lwIP的接收路径上。5.3 这几种情况会在后续工程里反复折磨你最后分享一些我在准备阶段就发现的坑提前了解能省很多时间。第一个是malloc和lwIP内存池冲突的问题。用FreeRTOS时如果堆配置过小lwIP可能在实际发数据时申请不到pbuf表现为刚开始ping是通的一开网页请求就卡死。解决方法是检查CubeMX生成的FreeRTOS heap大小以及lwIP的MEM_SIZE宏是否合理通常把它调到一个能容纳几个完整TCP段的值以上会稳妥很多。第二个是HTTP页面刷新时的TCP连接占用。部分浏览器的行为是打开页面时建立多个TCP连接如果服务器端没有正确关闭或超时回收空闲连接连接表会被占满。lwIP里涉及的MEMP_NUM_TCP_PCB等宏需要合理设置。Web服务器应用最好让每个HTTP响应都带上Connection: close处理完立即关闭这个TCP连接避免长时间占据资源。第三个是中断优先级问题。F407的以太网MAC发送和接收都依赖DMA中断在FreeRTOS工程中如果以太网中断优先级设置得比某个系统调用高有可能会触发FreeRTOS的断言机制。CubeMX生成代码时一般会给一个默认值但如果你的工程里还有其他优先级较高的外设中断要统一规划别让两个中断互相打断到系统调度异常。下一篇文章我会从CubeMX工程开始把芯片选型、时钟树配置、RMII引脚和LAN8720A驱动一步步拆开带你把F407的网口调到一个能稳定ping通的状态。在那之后再写HTTP解析和动态页面逻辑就会顺畅得多——Web服务器不是堆代码堆出来的而是一层一层把通信链路捋顺之后自然长出来的应用。