STM32F407+lwIP+HTTPD嵌入式Web服务器搭建实战
2026/9/6 10:08:08 网站建设 项目流程

做嵌入式开发久了就会发现一个特别现实的问题:很多设备根本不需要跑完整操作系统,但非常需要上网。我自己在STM32F407上折腾网络功能时,首先想到的就是lwIP,这个圈子里版权友好、资源占用可控的轻量级协议栈。协议栈能跑通只是第一步,真正让项目出效果的是挂在它上面的应用,所以我这次直接在lwIP上把自带的HTTPD服务器也搭了起来,让浏览器输入板子的IP就能看到实时数据,还能远程控制IO口。

这篇是这个系列的第二篇,上一篇把开发环境、时钟、串口这些基础底子打好了,这一节就专门讲协议栈移植和HTTPD服务器搭建。如果你手里是F407或者同系列带以太网MAC的芯片,跟着这个流程走基本能复现,不管你是刚开始接触网络协议栈,还是已经在裸机上写过几个TCP样例,这里面的经验和坑应该都能用得着。

1. 整体方案:为什么是“F407+lwIP+HTTPD”

1.1 硬件底子:F407自带MAC,PHY要外挂

STM32F407这颗芯片最大的优势之一就是内置了10/100M以太网MAC控制器,配合DMA可以自动完成网络报文和内存之间的搬运,CPU只在必要的时候参与处理。但MCU内部只有MAC,物理层的PHY芯片还是需要外部挂一颗,常见搭配是LAN8720、DP83848、KSZ8081这几种。

选PHY的时候有一个特别容易忽略的问题:RMII接口的50MHz参考时钟到底由谁产生。以LAN8720为例,它可以用外部25MHz晶振,内部PLL把时钟倍频到50MHz后从REF_CLK引脚输出给MCU,这种方案完全不用动MCU的MCO。而有些PHY则要求MCU的MCO1直接供给50MHz时钟,这时候去调STM32F407的PLL配置会很痛苦,因为系统主频168MHz和精确50MHz的MCO输出在普通分频比下很难同时满足。我做板子的时候干脆选了LAN8720这种自带REFCLK输出的方案,少了一个时钟源绕来绕去的麻烦。

引脚分配上,RMII接口只需要TXD0/1、RXD0/1、TX_EN、MDC、MDIO和REF_CLK这七个信号,相比MII接口省了一半多的引脚,对F407这种100pin封装非常友好。具体引脚号要根据你的板子原理图来,CubeMX里面配置时会自动检查冲突,但要确认板上MCU和PHY的实际连接。

1.2 软件分层:CubeMX生成底层、lwIP跑协议、HTTPD管交互

软件上我把它想成三层结构。最底层是STM32CubeMX生成的HAL以太网驱动,负责处理MAC、DMA描述符、中断这些寄存器级别的操作;中间层是lwIP协议栈,负责把TCP/IP报文解析、重传、路由这些复杂逻辑包装成简单接口;最上层才是我们真正要写的应用代码,比如HTTPD服务器、MQTT客户端,或者自定义的TCP/UDPSocket逻辑。

CubeMX的好处在于它会帮你生成ethernetif.c和lwip.c,前者是以太网驱动的接口适配层,后者是协议栈的初始化流程。HTTPD则属于lwIP官方applications目录下的一个组件,它的核心是一个小型的Web服务器,支持静态页面、CGI接口和SSI动态注入。有了这层东西之后,浏览器访问板子上的HTML页面就和访问一个小网站没有本质区别。

这种三层结构最舒服的地方是每一层都可以独立调试。如果你发现自己改的HTTPD代码崩了,可以先确认不是底层驱动的问题;如果ping不通,也不用怀疑是应用层写错了。分层清晰之后,定位Bug的时间能省一半。

1.3 方案对比:裸写协议栈、Linux和lwIP怎么选

谈嵌入式网络方案,我见过最多的三条路:一是自己写TCP/IP协议栈,二是跑Linux系统,三是在MCU上移植lwIP这样的开源轻量协议栈。

自己写TCP/IP协议栈这种事,写个UDP收发还算简单,但要做到TCP的可靠传输、超时重传、滑动窗口、状态机管理,工作量直接爆炸,而且很难保证兼容性和稳定性。除非你是为了教学或者去面试秀肌肉,否则真没必要重复造这个轮子。Linux倒是功能强大,但F407没有MMU,跑完整的Linux内核非常别扭,外设驱动适配也是个大工程,最基本的Flash和RAM容量就不太够。相比之下,lwIP专为资源受限的嵌入式设备设计,几KB内存就能跑起来,而且它提供RAW API、netconn API和BSD Socket API三种开发接口,从裸机到RTOS都能用。F407跑lwIP属于杀鸡用牛刀,空间很充裕。

另外说一句,lwIP的BSD Socket接口写起来几乎和PC端socket编程一样,这一点对很多从上位机转过来的开发者非常友好。你要是以后想把代码迁移到Linux或Windows平台,逻辑部分改动很小,这就是选成熟协议栈的红利。

2. CubeMX工程配置:关键参数与避坑点

2.1 使能ETH外设:RMII接口与PHY地址

打开CubeMX工程,在Pinout & Configuration面板里找到Connectivity下的ETH,勾选激活。接口模式选RMII,然后ETH的底层配置里最关键的两个参数:一个是PHY地址,一个是MAC地址。

PHY地址这个坑我踩过好几次。LAN8720的地址通常是0x00,DP83848在官方评估板上是0x01甚至0x1F,具体要看PHY芯片的地址引脚怎么接。如果地址填错,HAL_ETH_Init阶段可能看似成功,但后面读PHY的链接状态寄存器永远失败,表现就是网线插上去了、link灯也亮了,但ping死活不通。建议在初始化后主动读一次PHY的ID寄存器来验证,比如LAN8720读到的ID应该是0x0007,读不到就说明地址不对或者MDIO时序有问题。

MAC地址不能全是00,只要不跟局域网内其他设备冲突,可以随便设一个自己局域网内的唯一地址。CubeMX默认有一个值,我一般改成00:80:E1:00:00:01这种格式,方便后面做MAC过滤和调试。

配置完成后打开System Core里的RCC,确认外部高速晶振HSE已经启用,然后把时钟树自动解算一遍。F407的PCLK1和PCLK2不直接影响以太网,但DMA和RAM的时钟要正常,主频尽量跑在168MHz,这样整体系统性能才够。

2.2 lwIP中间件参数:IP、内存和协议特性

在Middleware and Software Packs里找到LWIP,先Enabled,再设置OS Mode。这个选项我强烈建议选FreeRTOS,只要你没有特殊原因必须裸奔。裸机模式下lwIP的定时器需要自己在主循环调用MX_LWIP_Process()来驱动,否则TCP超时重传、ARP老化这些功能都不会工作,做HTTPD这种多并发任务会很吃力。选了FreeRTOS之后,lwIP会创建一个tcpip_thread,协议栈的收发处理都在这个线程里,应用层用Socket接口就能直接阻塞等待数据。

IP地址配置上,刚开始调试建议用静态IP,比如192.168.1.10,子网掩码255.255.255.0,网关192.168.1.1。DHCP虽然也能用,但每次板子上电可能拿到不同IP,串口打印IP又搞不好,容易在第一步就卡住排查方向。

内存相关参数我通常会这样改:

  • MEM_SIZE(协议栈堆大小):CubeMX默认可能给得比较小,跑HTTPD加上多个TCP连接会吃紧,建议加到16000以上。
  • TCP_SND_BUF(发送缓冲)和TCP_WND(接收窗口):直接决定了单条TCP连接传输速度,HTTPD页面大的话,窗口太小会导致页面加载明显变慢,我一般放到2048甚至更大。
  • TCP_MSS:以太网标准MTU是1500字节,减去TCP/IP头,MSS默认1460就行,不要乱加。

LWIP_HTTPD相关选项在Middleware配置页里,把HTTPD使能,并且打开SSI和CGI支持。这些开关最终会反映到lwipopts.h的宏定义里,比如LWIP_HTTPD_CGI、LWIP_HTTPD_SSI。如果你用的是lwIP 1.x,这部分源码路径和API命名会有些差异,建议直接上2.x,用起来顺手得多。

2.3 时钟、中断和FreeRTOS的协同问题

ETH外设本身不依赖系统主频做协议计算,但RMII的时钟要求必须满足。如果你用的是LAN8720这类PHY输出50MHz REF_CLK给MCU的方案,只需要在CubeMX里确认ETH_RMII_REF_CLK引脚配置正确。如果你用的PHY需要MCU输出50MHz,那就要查MCO1的分频配置,而这颗料在168MHz主频下要精确得到50MHz并不容易,所以我前面才反复强调选PHY时优先考虑能够独立输出REFCLK的型号。

中断方面,ETH的中断开启后,我建议把它的抢占优先级设置在5左右,不要设成比FreeRTOS的configMAX_SYSCALL_INTERRUPT_PRIORITY更高。原理其实很简单:FreeRTOS的API在临界区受保护,高优先级中断如果调用了会触发系统错误的函数,莫名其妙死机就是从这里来的。CubeMX生成代码后,手工检查一下NVIC配置,把ETH和ETH_WKUP的中断优先级设到一个合理的值。

还有一个很多人忽略的点:如果工程里同时跑FreeRTOS,需要在CubeMX默认生成的FreeRTOS配置里给TCP/IP操作留下足够的内存,比如在FreeRTOS的heap size上适当加大。lwIP的pbuf、TCP连接控制块都是动态分配的,每个连接会吃几十字节到上百字节,页面并发连接一多,内存耗尽的表现很诡异,可能不是崩溃而是响应变慢,排查起来很折磨人。

3. 底层驱动对接:ethernetif.c与数据通路

3.1 生成代码里必须调整的PHY驱动逻辑

CubeMX生成工程后会有一个ethernetif.c,它把lwIP的netif操作函数和HAL库的ETH驱动桥接起来。这个文件里low_level_init()函数是整个以太网驱动初始化的核心,里面有MAC地址设置、DMA描述符初始化和PHY地址配置。生成代码默认的PHY处理逻辑比较通用,但实际用起来要动几个地方。

首先确认PhyAddress填了正确的值,这个前面已经强调过。然后是PHY芯片的自动协商处理,LAN8720和DP83848对寄存器地址的定义基本兼容,但有些PHY需要额外配置芯片的工作模式,比如RMII模式选择寄存器。拿LAN8720来说,它有一个PHY Special Control/Status寄存器,bit15是RMII模式选择位,如果芯片被误配成MII模式,RMII接口的通信就会失败。

我在low_level_init末尾加了一段PHY自检逻辑,读取PHY的基础状态寄存器,打印出link状态和速度模式。调试信息用printf发到串口,这样一上电就能立刻看到PHY有没有正常协商成千兆百兆、双工是否正常。这个动作花不了几行代码,但它能让你在后续排查网络问题时省下大量时间。

3.2 数据收发路径:从浏览器到PHY再到协议栈

理解lwIP的收发路径能帮你少踩很多坑。发送方向是:应用层写数据到Socket、lwIP把数据切包封装成TCP/IP头,然后调用netif->linkoutput函数,也就是ethernetif.c里的low_level_output()和HAL_ETH_TransmitFrame()。发送完成后HAL库通过发送描述符知道数据已经被DMA拿走,lwIP就会释放对应的pbuf内存。

接收方向是:PHY收包后通过RMII接口给MAC,DMA自动把数据搬运到内存里的接收描述符缓冲区,然后触发接收中断或轮询。ethernetif.c在eth_rx()里调用HAL_ETH_GetReceivedFrame和HAL_ETH_ReadData,把DMA缓冲区里的数据拷贝到lwIP的pbuf里,最后通过netif->input交给tcpip_thread处理。

这里有个很重要的性能点:以太网DMA描述符和缓冲区必须放在4字节对齐、并且能被DMA访问的内存区域。CubeMX生成的代码一般默认放在内部SRAM,但如果后续你用了外部SDRAM,需要特别注意这个DMA地址范围的问题。零拷贝这个事儿在lwIP里主要说的是驱动层尽量直接操作pbuf,避免不必要的拷贝,但HAL库默认实现还是有一份拷贝,对大多数嵌入式Web服务器来说性能完全够用,不追求极限速率的话不用过度优化。

3.3 lwIP与FreeRTOS线程模型的配合

当lwIP的OS Mode设为FreeRTOS之后,协议栈会启动几个核心线程。tcpip_thread负责处理协议栈的输入输出,tcpip_timeout线程管理各种超时定时器。应用程序发起的Socket读写最终就是通过邮箱IPC机制投递到tcpip_thread执行的。这种模型的好处是协议栈内部所有数据结构都有锁保护,多个线程可以安全地同时访问Socket。

实际使用时要注意,你自己创建的多个业务线程如果频繁创建和关闭TCP连接,可能因为内存池耗尽或者长时间占用锁导致tcpip_thread处理不及时。最简单的规避方式就是别在一秒内并发创建几十个连接,HTTPD的默认并发连接数在8个左右,足够日常页面轮询使用,比这个高反而容易出问题。如果遇到网络卡死,80%的情况是某个线程在回调里做了阻断操作,比如在CGI回调里直接等待串口打印完成或者延时几百毫秒,这会卡住整个TCP/IP线程,页面当然就刷不出来了。

4. HTTPD服务器落地:网页、SSI与CGI

4.1 fsdata静态网页打包:makefsdata的使用

lwIP的HTTPD不像PC服务器那样依赖文件系统和磁盘,它默认的页面存储方式叫fsdata机制,简单说就是把HTML页面、CSS文件这些静态资源通过工具转成C语言数组,编译进单片机固件里。这么做的好处是零文件系统开销、不会碎片化、也不存在Flash磨损问题。

转换工具叫makefsdata,位于lwIP的contrib源码目录下的apps/httpd/makefsdata。使用方式也很粗暴:把一个文件夹里的所有网页文件都放进去,在命令行执行makefsdata,它会自动扫描文件夹并生成fsdata.c和fsdata.h。生成的fsdata.c里每一个文件都对应一个HTTPD_FS_DATA_ENTRY结构体,里面保存了文件名、内容指针和长度。

我在工程里新建一个fs文件夹,把index.shtml、style.css这些文件都放进去,然后用工具生成fsdata.c,替换掉CubeMX默认生成的同名文件,再重新编译就可以了。有一点要特别注意:HTTPD处理文件名是区分大小写的,而且CGI重定向要跳转的页面名必须和fsdata里的文件名完全一致,否则会404。我第一次就栽在这个问题上,页面文件名写成了“Index.shtml”,FS里存的是“index.shtml”,调试了半天才反应过来。

4.2 设计一个Led控制台页面:HTML骨架

为了让页面不只是显示“hello world”,我设计了一个简单的设备控制台页面,功能包括显示ADC采样值、显示LED当前状态、两个按钮控制LED开关。页面代码如下:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>F407 Web Console</title> <style> body { font-family: Arial, sans-serif; margin: 40px; } button { padding: 10px 24px; margin-right: 12px; font-size: 16px; } .status { background: #eee; padding: 12px; display: inline-block; min-width: 200px; } </style> <script> function refresh() { var xhr = new XMLHttpRequest(); xhr.open("GET", "/data.shtml", true); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { document.getElementById("adc").innerHTML = xhr.responseText; } }; xhr.send(); } setInterval(refresh, 1000); </script> </head> <body> <h2>STM32F407 Web Console</h2> <p>ADC Value: <span id="adc" class="status">--</span></p> <p>LED State: <!--#led_state--></p> <p> <a href="/led?state=on"><button>LED ON</button></a> <a href="/led?state=off"><button>LED OFF</button></a> </p> </body> </html>

这个页面里有两个动态点:<!--#led_state-->是SSI占位符,服务器返回页面时会替换成LED当前状态;data.shtml是一个极简的动态接口,它只有一个数字,JavaScript定时器每秒去拉一次,更新到页面上。这样不用整页刷新就能拿到实时数据,交互体验比刷新整个页面好很多,流量上也更友好。

4.3 SSI动态数据注入:让ADC值“活”起来

SSI全称是Server Side Include,lwIP实现里就是扫描HTML模板中的特殊注释标签,例如<!--#tag_name-->,发现之后调用我们注册的SSI回调函数生成内容。它的触发点在服务端发送页面之前,所以浏览器拿到的已经是替换后的最终HTML。

注册SSI标签和处理函数,代码如下:

#include "lwip/apps/httpd.h" /* SSI标签列表,顺序要和回调里case对应 */ static const char *ssi_tags[] = { "adc_value", "led_state" }; static uint16_t read_adc_value(void) { /* 这里换成你自己读取ADC的函数即可 */ return 1234; } u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { uint16_t len = 0; switch (iIndex) { case 0: /* adc_value */ len = (uint16_t)snprintf(pcInsert, iInsertLen, "%u", read_adc_value()); break; case 1: /* led_state */ if (get_led_status()) { len = (uint16_t)snprintf(pcInsert, iInsertLen, "ON"); } else { len = (uint16_t)snprintf(pcInsert, iInsertLen, "OFF"); } break; default: break; } return len; } void httpd_custom_init(void) { http_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags)); }

注意这里的iIndex对应的是ssi_tags数组的下标,所以回调里switch的case顺序必须和标签数组一一对应。pcInsert缓冲区的大小受限于HTTPD内部配置,建议单次注入内容不要超过几百字节。snprintf一定要带上长度参数,防止溢出踩踏内存。

注册这个回调函数的时机是httpd_init()之后,我在main函数里做了类似的事情:

int main(void) { /* HAL初始化、MX_LWIP_Init等省略 */ httpd_init(); httpd_custom_init(); /* 启动RTOS后, osKernelStart(); */ }

不同lwIP版本API名称会变,旧的1.4里可能是httpd_set_ssi_handler,2.x以后是http_set_ssi_handler,如果编译报错,去httpd.h里确认一下当前版本的函数原型就行。

4.4 CGI命令处理:浏览器远程控制LED

CGI是lwIP HTTPD处理动态请求的主要方式,通常用于处理URL带参数的GET请求。点击页面上的LED ON按钮其实就是在浏览器地址栏发起/led?state=on。HTTPD会把URL路径匹配到我们注册的CGI表,然后调用对应的处理函数。

注册CGI处理器如下:

static const char *cgi_led(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { uint8_t i; for (i = 0; i < iNumParams; i++) { if (strcmp(pcParam[i], "state") == 0) { if (strcmp(pcValue[i], "on") == 0) { set_led_status(1); } else if (strcmp(pcValue[i], "off") == 0) { set_led_status(0); } } } /* 返回的字符串是HTTP重定向页面名,告诉浏览器跳回主页面 */ return "/index.shtml"; } static const tCGI cgi_handlers[] = { { "/led", cgi_led }, }; void httpd_custom_init(void) { /* ...SSI注册代码... */ http_set_cgi_handlers(cgi_handlers, LWIP_ARRAYSIZE(cgi_handlers)); }

iNumParams是参数个数,pcParam[i]pcValue[i]分别是对应的参数名和参数值。我这里只解析了state参数。需要注意的是,CGI处理函数返回值是一个字符串,它代表跳转的页面名,注意千万不能用局部字符串数组去返回,因为函数结束后那段内存就失效了,处理函数返回的字符串默认是静态的页面路径常量。如果你需要在CGI里生成动态响应内容,就得用我前面设计的data.shtml接口思路,用SSI标签生成。

4.5 用“假接口”实现AJAX轮询:每秒刷新实时数据

真正的Web服务器很轻松就能做一个返回JSON的API接口,但lwIP HTTPD默认框架下返回动态纯文本内容比较绕。如果你直接用CGI返回一段字符串,它只会被当成页面名去查找FS里的文件,并不会把字符串内容直接发送给浏览器。所以不做FS层深度定制的话,最优雅的取巧方案就是建一个只有一个SSI标签的页面,叫它data.shtml,内容就一行:

<!--#adc_value-->

当JavaScript请求/data.shtml时,HTTPD会正常返回这个页面,并且在返回之前把SSI标签替换成实际的ADC值,于是浏览器拿到的响应正文就是类似1234这样的纯文本。响应头里虽然带着text/html,但XMLHttpRequest读取responseText完全没问题。

这个方案我用了很多次,效果很稳定,而且不用改lwIP底层逻辑。浏览器端代码就是前面HTML里的refresh()函数,setInterval(refresh, 1000)定时每秒拉取一次数据。如果你需要更复杂的数据结构,可以让这个data页面里包含多个SSI标签,然后用文本处理去解析,或者调整FS层做真正的动态JSON输出,但大多数内部调试和Demo场景这样已经足够。

5. 现场排查与调优记录

5.1 ping不通时的五步检查法

HTTPD页面打不开,第一个动作永远是Ping,而不是打开浏览器。Ping不通就说明网络层都有问题,HTTPD肯定也好不了。我给自己整理了一套固定的排错顺序:

  1. 看PHY的link状态:串口打印读到的PHY基本状态寄存器,bit0为1表示有网线连接且协商完成。如果link都不通,优先查网线、对方交换机端口、PHY供电。
  2. 确认RMII时钟:LAN8720的REF_CLK引脚有没有50MHz波形,没有就查晶振和芯片供电,有才继续。
  3. 确认IP配置:板子和电脑是否在同一网段,网卡IP和板子IP不能冲突。
  4. 检查PHY地址:用读PHY ID寄存器的方式确认MDIO总线能正常访问PHY,这一步不会骗人。
  5. 关掉防火墙:PC端防火墙偶尔会拦ICMP,裸板调试时直接关掉或者加一个Ping放行规则。

有一次我卡在第四步,原因是LAN8720的地址引脚有个上拉电阻没焊,导致地址变来变去,MDIO通信时好时坏。这个例子说明硬件细节和软件配置必须放在一起查。

5.2 页面打开但数据异常的处理思路

Chip能通、页面也能出来,但显示的数据不对,这是另一个典型的坑。首先是SSI标签没有替换,页面上直接显示原始的<!--#adc_value-->。这种情况多半是LWIP_HTTPD_SSI这个宏没开,或者SSI标签字符串和回调里注册的不一致。

其次是CGI点击后跳转404,这个大概率是CGI返回的页面名在fsdata里不存在,可以对比一下返回字符串和FS中实际文件名。另外我遇到过一种很隐蔽的情况:FS里页面内容被压缩了,HTTPD对某些类型的文件默认启用了压缩特性,而页面文件里又包含中文字符,解码后出现乱码。简单做法是避免在FS里存大体积文件,以及确保文件编码和Content-Type匹配。

最后还有一种情况是页面打开很慢,极有可能是DHCP没生效而你又用了自动获取IP,或者ARP缓存没刷新。固定IP情况下通常是TCP窗口太小,MTU设置又偏大导致分片重传,可以按照2.2节里的参数调整。如果还是慢,检查一下全局中断是否被人为关闭了,那会拖累整个协议栈的响应。

5.3 常见问题速查表

我把实际项目中遇到比较多的现象和原因整理成了表格,方便你遇到问题时快速对号入座。

现象可能原因处理办法
PING不通,PHY link灯灭网线/PHY供电/变压器问题查PHY寄存器link bit,短接测试网口
PING不通,link灯亮PHY地址配置错误读PHY ID寄存器确认地址
PING不通,串口无打印lwIP初始化失败或MAC地址全0检查ethernetif.c和硬件复位
页面打开404fsdata缺少对应文件或文件名不匹配用makefsdata重新生成,检查大小写
页面显示原始SSI标签未使能LWIP_HTTPD_SSI或标签不匹配检查lwipopts.h和ssi_tags
CGI点击无反应CGI表未注册或URL路径不匹配对比tCGI表中的路径和实际URL
页面奇慢无比TCP窗口太小或内存不足调大TCP_WND、MEM_SIZE
运行一段时间断网内存泄漏或定时器没跑开启lwIP stats统计,检查超时线程
FreeRTOS下偶尔死机中断优先级问题导致API不安全降低ETH中断优先级,释放锁检查

这个表格是我在调试新板子时的第一参考,很多问题其实不需要看代码,对照现象就能缩小范围。

5.4 让HTTPD更稳的几点调优

调试阶段跑通和产品阶段稳定运行之间还有一段路,我通常会在功能正常后做这几件事。

第一,打开lwIP自带的统计功能,LWIP_STATS_DISPLAY宏置1,状态页或者串口就能输出内存池使用量、TCP连接状态、丢包重传统计。这个能帮你直观看出内存够不够用、有没有泄漏。第二,固定IP改为DHCP获取,然后在串口打印分配到的IP,这样生产环境插上路由就能用,不用每台改配置。第三,HTTPD并发连接数默认8个左右,一般够用,但如果你在页面上开了多个定时轮询任务,可以考虑适当调高HTTPD_MAX_CONNECTIONS,同时注意内存开销。

最后一件事,如果你决定在HTTPD之外再挂MQTT或者TLS这类更重的应用,一定要提前规划内存分配,调大MEM_SIZE之外,还要看FreeRTOS的堆大小。协议栈吃内存很凶,别等到功能叠加到一半才想起来看内存余量,那会儿查起来会很痛苦。

我个人在实际操作中最深的体会是,lwIP移植这件事,配置和代码只占三成,剩下的七成都在排查连接、时钟、内存这些“看不到”的环节。把ethernetif.c和lwipopts.h这些关键文件里的每个参数吃透,再遇到问题就会从容很多。做完HTTPD这个Demo之后,下一步可以往MQTT、CoAP或者TLS方向扩展,思路和这次完全一样,希望这篇能让你少走几段弯路。

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

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

立即咨询