ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F407移植lwIP并搭建HTTPD服务器:从底层驱动到动态网页

STM32F407移植lwIP并搭建HTTPD服务器:从底层驱动到动态网页 没写过lwIP回帖就别急着往下看这章是系列第二篇上篇我把STM32F407的以太网底层外设、PHY芯片和RMII接口捋了一遍今天就直接干正事把lwIP协议栈移植到工程里再启动内置的HTTPD服务器让板子能像路由器后台一样被浏览器访问。做完这一篇你就能在浏览器里打开一个网页控制LED、看传感器数值甚至通过HTTP接口下发指令——这是绝大多数联网嵌入式设备的基础能力。这篇内容更适合手里有块F407开发板、已经能用HAL库跑通点灯和串口打印的读者。协议栈移植听着玄乎其实核心就四件事底层网卡驱动、内存池配置、协议栈初始化、应用层服务挂载。把它拆开看每步都不复杂难的是细节对齐。我尽量把每个环节的关键参数和为什么这么配讲清楚你照着做也能跑起来。1. 移植前的全局规划先把这三个问题想清楚1.1 硬件链路F407的以太网MAC与PHY是什么关系STM32F407内部集成了以太网MAC控制器支持10/100M速率但它不带物理层收发器。所谓PHY芯片比如最常见的LAN8720A、DP83848、RTL8201负责把MAC的数字信号转换成网线上的模拟差分信号。MAC和PHY之间通过MII或RMII接口通信F407两种都支持但绝大多数评估板和项目都用RMII为什么省引脚。MII需要16根数据线RMII只要7根对引脚紧张的MCU来说太关键了。我在规划阶段最关注的其实是时钟也就是MAC的RMII参考时钟REF_CLK。很多第一次用F407LAN8720A的人在这里被坑。关键点在于F407的RMII接口里50MHz的REF_CLK是输入给MAC的不是由F407输出给PHY的。LAN8720A可以通过外接25MHz无源晶振由内部PLL倍频后从CLKOUT引脚输出50MHz给F407这样MCU端就不需要额外折腾时钟。而DP83848的方案则需要在F407的MCO引脚上输出50MHz或者用带50MHz输出的有源晶振。我画过一张对比表帮助选型这里直接贴出来PHY方案RMII参考时钟来源晶振配置适用场景LAN8720APHY内部PLL倍频后输出25MHz无源晶振接PHY大多数低价开发板电路最简单DP83848MCU的MCO引脚输出50MHz25MHz晶振接MCU部分工控板卡兼容性好外部有源晶振有源晶振直接输出50MHz50MHz有源晶振对稳定性要求高的场合别小看这个选择我见过有人把LAN8720A的25MHz晶振接到F407主晶振引脚上结果整个系统时钟全乱串口打印全是乱码。硬件上一定要确认PHY的时钟树。1.2 软件层面lwIP的分层结构和HAL库如何配合lwIP全称lightweight IP是一个开源的轻量级TCP/IP协议栈专门为嵌入式系统设计。它内部结构大致分成三层netif层网卡抽象层需要你自己实现驱动函数对应F407的MACDMA外设。core层协议栈核心包括IP、ICMP、UDP、TCP协议处理这部分你基本不用动只需要配置宏定义。api层提供给应用使用的接口包括netconn类似BSD socket的简化版、socket API以及像HTTPD这样的应用协议服务。HAL库做的事情是把F407的MAC控制器、DMA描述符、PHY寄存器读写封装成了函数。实际移植时你在驱动回调里调用HAL_ETH_TransmitFrame、HAL_ETH_ReadPHYRegister这类HAL函数再把收到的数据包交给lwIP的netif-input。逻辑上其实就一条线网卡收到数据 → DMA搬进内存 → 驱动把内存指针交给lwIP → 协议栈解析 → 应用层处理。我在真正写代码前建议先把这条数据流画在纸上标清楚每个环节谁调用谁。只有搞清了数据从网线到浏览器的路径后续找问题时才知道该在哪个断点观察。1.3 裸机还是RTOSNO_SYS这个宏决定整个工程形态lwIP里面有个非常关键的配置宏NO_SYS。名字有点误导它其实问的是“协议栈要不要跑在操作系统上”如果你的工程没有RTOS也就是裸机跑那配置NO_SYS1。协议栈不需要创建线程也不需要信号量和邮箱程序主循环里反复调用几个处理函数就行。优点是简单缺点是如果同时要处理多个网络连接或任务多了主循环忙不过来实时性差。如果上了FreeRTOS或RT-Thread那就配置NO_SYS0。lwIP会在初始化时创建tcpip_thread专用线程用邮箱机制接收来自其他线程的网络请求同时ETH中断可以通过信号量通知tcpip_thread去读取网卡数据。这套机制更接近Linux下的网络架构稳定性和并发能力都好不少。以我自己的习惯如果只是做功能验证、快速出结果先裸机打通流程网页能开了再决定要不要套RTOS。如果一开始就上RTOS同时排错lwIP和任务调度的问题排查面会一下子大很多。这篇后续的HTTPD部分我会给裸机的写法但它往FreeRTOS上迁移并不复杂核心驱动不受影响。2. 基于STM32CubeMX的以太网外设配置2.1 引脚复用与时钟树设置用CubeMX配置F407的以太网外设非常省事。我建议直接在Pinout Configuration视图里找到ETH勾选RMII接口把引脚让CubeMX自动分配。RMII模式需要用到的引脚包括ETH_RMII_REF_CLK、ETH_RMII_CRS_DV、ETH_RMII_RXD0、ETH_RMII_RXD1、ETH_RMII_TX_EN、ETH_RMII_TXD0、ETH_RMII_TXD1再加上MDIO和MDC两根管理总线一共9根。时钟树方面RCC里HSE要选Crystal/Ceramic Resonator注意F407的HSE推荐用25MHz无源晶振。大多数开发板也是这么干即STM32F407的PHY时钟方案是25MHz主晶振用于MCU同时通过MCO或PHY本身给以太网提供RMII参考时钟。使用LAN8720A时PHY侧接自己的25MHz晶振MCU的ETH_RMII_REF_CLK引脚直接连到PHY的CLKOUT即可。CubeMX里不需要额外配置任何时钟输出。比较坑的地方是有的开发板原理图上MCU和PHY的时钟是同一个25MHz晶振通过MCO1输出25MHzLAN8720A作为输入源内部倍频成50MHz再输出给MAC。这种设计也稳定但要保证PHY芯片的电源去耦良好不然在网络负载大时偶尔会丢包。2.2 PHY地址与初始化细节以LAN8720A为例PHY芯片挂在MDIO总线上有自己的总线地址。大多数是硬件引脚上下拉决定的。LAN8720A的PHY地址通常由PHYAD0引脚决定默认一般是0x00。DP83848则常用地址0x01。CubeMX的ETH配置里有个PHY Address参数你得在代码里和实际板子对应上。CubeMX生成代码后在ethernetif.c里会有一段MX_ETH_Init()它会调用HAL_ETH_Init()。HAL库初始化时会通过MDIO总线读PHY的ID寄存器来判断PHY是否存在对应代码是HAL_ETH_ReadPHYRegister。如果PHY地址不对这一步就会失败网络自然起不来。我曾在一个板子上发现PHY地址是1但CubeMX默认是0结果初始化直接挂掉查了半天。PHY初始化完成之后还要做一件事等待PHY的自动协商完成。建议在初始化后轮询PHY的Basic Status Register寄存器0x01看bit5的Auto-Negotiation Complete位是否置1。常见写法是设置一个超时时间比如500ms超时仍未完成就继续往下跑因为有些PHY启动很慢。我给你的建议是别在这里死等先继续初始化lwIP真正的连通性判断在应用层通过netif_is_link_up来查。2.3 DMA描述符与缓冲区一个容易被忽视的坑F407的以太网DMA使用描述符环形队列管理数据包缓冲区。CubeMX默认生成4个发送描述符和4个接收描述符每个描述符指向一个缓冲区。这些描述符和缓冲区都是全局数组。/* 描述符数组注意这里的对齐要求 */ ETH_DMADescTypeDef htd[ETH_TXBUFNB] __ALIGNED(32); ETH_DMADescTypeDef hrd[ETH_RXBUFNB] __ALIGNED(32); uint8_t tx_buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __ALIGNED(32); uint8_t rx_buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __ALIGNED(32);一个我反复强调的坑这些数组绝对不要放在F407的CCM RAM0x10000000地址段里。CCM RAM虽然和内核直连访问快但DMA外设访问不到它数据一旦分配到那边网卡收包时就会时而正常时而全零表现非常诡异。我建议把网络缓冲区放到普通SRAM1/SRAM2并且用__ALIGNED(32)保证32字节对齐这是以太网DMA描述符的最低对齐要求。另外建议把RX描述符数量从默认的4个提高到6个甚至8个。原因是接收方向如果描述符不够在突发流量下容易丢包。F407的SRAM足够应付这点开销没必要在内存上抠。3. lwIP协议栈移植的四个关键对接点3.1 源码目录与工程文件组织lwIP目前的版本已经比较稳定我常用2.1.x分支。从GitHub拉下来的源码里真正要参与编译的主要目录src/core协议栈核心ip4.c、tcp.c、udp.c、mem.c、memp.c等。src/core/ipv4IPv4协议相关。src/netif自带的以太网接口通用驱动ethernetif.c。src/apisocket API、netconn API如果不用socket层可以选择不编译部分文件。src/apps/httpdHTTPD服务器源码。工程里还要包含一个lwipopts.h配置文件或者使用lwip自带的lwip/opt.h默认配置。我强烈建议你自己建一个lwipopts.h把不用的功能关掉、把缓冲区大小调好而不是用默认值直接跑。默认配置是为通用场景设计的跑在F407上会遇到内存不足或TCP窗口太小的问题。如果懒一点可以用CubeMX的MiddleWare组件直接勾选lwIP它会生成一套代码配置界面里可以调内存参数。但我觉得手写引入源码更能理解协议栈结构出了错也容易定位。CubeMX生成的有时候魔改过网上资料对不上反而麻烦。3.2 lwipopts.h核心配置内存、缓冲、套接字接口lwipopts.h是整个移植过程中最影响性能的文件。下面是我在F407工程里常用的一组配置也是很多开源项目的基础设置#define NO_SYS 1 #define LWIP_SOCKET 0 #define LWIP_NETCONN 0 #define MEM_ALIGNMENT 4 #define MEM_SIZE (10 * 1024 * 1024) /* 堆内存池大小 */ #define MEMP_NUM_PBUF 16 #define PBUF_POOL_SIZE 20 #define PBUF_POOL_BUFSIZE 1512 #define LWIP_ARP 1 #define LWIP_ICMP 1 #define LWIP_DHCP 0 /* 调试阶段建议静态IP */ #define LWIP_TCP 1 #define LWIP_UDP 1 #define TCP_SND_BUF (6 * 1024) #define TCP_WND (6 * 1024) #define MEMP_NUM_TCP_SEG 32 #define TCPIP_THREAD_STACKSIZE 0 #define LWIP_STATS 1我把LWIP_SOCKET设成0是因为NO_SYS1时标准socket层基本用不了应用层直接走raw API或netconn API。HTTPD服务器走的也是raw API所以不影响。MEM_SIZE定10KBPBUF_POOL_SIZE定20个左右F407有192KB SRAM完全带得动。TCP_SND_BUF和TCP_WND我习惯定6KB这是TCP吞吐的关键太小的话传大文件时速度上不去。MEMP_NUM_TCP_SEG如果太小大量并发的HTTP请求时会提示memory allocation failure。还有几个宏不常被注意但很重要#define LWIP_NETIF_STATUS_CALLBACK 1 #define LWIP_NETIF_LINK_CALLBACK 1打开这两个可以注册回调在网络断开/恢复时收到通知非常适合做状态指示比如LED翻转提示网络连接状态。3.3 netif驱动low_level_output与low_level_input实现要点lwIP的标准驱动放在lwip-contrib里文件是ethernetif.c。在NO_SYS模式下这个驱动里的low_level_init、low_level_output、low_level_input三个函数就是你的主要战场。low_level_init要做的事初始化PHY、设置MAC地址、把netif-hwaddr_len和netif-hwaddr填好然后配置描述符最后设置netif-flags包括NETIF_FLAG_BROADCAST、NETIF_FLAG_ETHARP、NETIF_FLAG_LINK_UP等。MAC地址我建议用一个固定的数组比如{0x02,0x00,0x00,0x00,0x00,0x01}第一位是02表示本地管理的单播地址不会和真实设备冲突。low_level_output的流程是从netif-output传来的PBUF链表中取出数据拷贝到DMA发送缓冲区然后调用HAL_ETH_TransmitFrame发送。发送完成后的DMA中断里要做清理把已经发送完的描述符释放。这里有个优化点可以使用HAL_ETH_TransmitFrame_IT配合中断回调释放PBUF而不是轮询等待发送完成否则CPU在主循环里会被长时间阻塞。low_level_input的流程是查询接收描述符如果有数据把DMA缓冲区里的数据封装成PBUF交给netif-input后者会进入lwIP的协议栈。static err_t low_level_input(struct netif *netif, struct pbuf **p) { struct pbuf *q; uint32_t len; if (HAL_ETH_GetReceivedFrame_IT(heth) ! HAL_OK) { return ERR_IF; } len heth.RxFrameInfos.length; *p pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (*p NULL) { // 没内存了这里必须释放DMA描述符否则网卡停摆 HAL_ETH_ReleaseRxBuffer(heth); return ERR_MEM; } // 拷贝DMA数据到PBUF ... }很多细节不能在文字里全展开但核心原则就一条无论接收成功还是失败都要保证DMA描述符被释放让网卡继续收下一包。我在调试时遇到过一个情况内存不够时忘记释放接收缓冲区结果几个小时后网卡彻底不收到任何包只能复位。这就是典型的低级但可恶的bug。3.4 超时机制裸机下靠周期调用RTOS下靠信号量lwIP协议栈内部有很多定时任务比如ARP表项老化、TCP重传计时、DHCP客户端超时重试。这些在NO_SYS模式下不会自动运行你必须周期性地调用sys_check_timeouts()。我常用的做法是在main函数的while循环里每毫秒执行一次uint32_t last_timeout HAL_GetTick(); while (1) { /* 轮询网卡接收数据 */ while (ethernetif_input(g_netif) 0) { ; } /* 周期处理协议栈超时 */ if (HAL_GetTick() - last_timeout 1) { sys_check_timeouts(); last_timeout HAL_GetTick(); } }ethernetif_input每次查询网卡是否收到数据有包就推进协议栈处理。sys_check_timeouts的频率1ms就足够lwIP内部的超时粒度一般也是毫秒级。如果用的是RTOS可以把ethernetif_input放在一个专门的任务里ETH中断通过SemaphoreGive给它发信号这样CPU利用率低且实时性好。我在裸机调试阶段会特意在主循环里放一个计数器每100ms翻转一次LED用来观察主循环是否被某个网络操作卡住。如果LED翻转频率不稳定说明某个网络调用耗时太久需要优化。4. HTTPD服务器与动态网页的实现细节4.1 httpd_init到TCP监听协议栈内置HTTPD的工作方式lwIP的HTTPD是一个专门跑在协议栈上的HTTP服务器应用。它内部会创建TCP控制块绑定80端口然后开始监听连接。你只要在初始化协议栈之后调用httpd_init()它就会自动完成这些。ip_addr_t ip, mask, gw; IP4_ADDR(ip, 192, 168, 1, 100); IP4_ADDR(mask, 255, 255, 255, 0); IP4_ADDR(gw, 192, 168, 1, 1); netif_add(g_netif, ip, mask, gw, NULL, ethernetif_init, ethernet_input); netif_set_default(g_netif); netif_set_up(g_netif); httpd_init();做HTTPD的前提是TCP能正常工作所以先用静态IP把网络链路调通再考虑DHCP。默认的httpd_init不带CGI和SSI只会发送静态文件。要让浏览器能看到你自定义的网页需要处理好静态页面数据。4.2 静态页面fsdata.c与makefsdata工具的用法lwIP的HTTPD访问网页文件并不是从Flash文件系统读取的而是把一个网页文件转成C语言数组存在程序里。这个数组默认在lwip/apps/httpd/fs/fsdata.c里。你会看到里面有一个巨大的const unsigned char的数组那其实就是你网页内容的编码。手动去改16进制数组太痛苦了lwIP-contrib里提供了一个工具叫makefsdata它可以扫描一个目录里的所有网页文件生成新的fsdata.c。我当时的做法是makefsdata html/html目录里放入index.html、style.css、app.js等资源运行后会在当前目录生成fsdata.c把这个文件替换到工程里重新编译即可。要注意的是文件名尽量用短名称并且不要用中文lwIP内部的文件索引是哈希实现的文件名太长或太复杂可能出问题。默认的首页文件名应该是index.htmlHTTPD收到/请求时自动映射到它。我在首次试验时犯过一个错用了一个很大的HTML文件里面引了好几个外部的CSS和JS每个资源请求都占一个TCP连接。F407的HTTPD虽然支持并发连接但连接多了之后的处理压力会反映在内存占用上。更建议的做法是把CSS和JS尽量内联在HTML里减少请求数这样页面加载会更快也减少服务器的压力。如果想更省事可以让HTTPD直接返回带HTTP头的数据完全不用文件系统。4.3 用CGI实现动态参数控制与JSON接口静态页面满足不了设备控制的需求这时候就要用CGICommon Gateway Interface。lwIP的HTTPD会对URL路径匹配CGI处理器。比如你在浏览器里访问 http://192.168.1.100/cgi/led?state1HTTPD就会调用你注册的CGI回调函数并把参数传进去。注册CGI处理器的方式#include lwip/apps/httpd.h static const char *cgi_led_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) { if (iNumParams 1 strcmp(pcParam[0], state) 0) { if (strcmp(pcValue[0], 1) 0) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); } } return /index.html; // 处理完成后跳转的页面 } static const tCGI cgi_handlers[] { { /cgi/led, cgi_led_handler }, }; void user_httpd_init(void) { httpd_set_cgi_handlers(cgi_handlers); httpd_init(); }CGI回调的返回值决定HTTPD发送哪个页面给浏览器。如果我想做一个纯粹的JSON接口可以直接在回调里构造一个JSON字符串然后返回。实际上还有一个更干净的方式用httpd_post_begin和httpd_post_receive_data处理POST请求但我用GETCGI的写法已经能满足大多数设备控制需求代码量也更少。我在实际做设备调试接口时经常会在CGI里塞一个类似“/cgi/sysinfo”的处理器返回一段JSON内容包含固件版本、开机时长、内存余量、当前IP地址等。浏览器端用fetch接口定时拉取就能做出一个相当好看的状态监控面板。手机在没有专业调试工具时这个JSON接口也是验证网络通信是否正常的最快手段。4.4 用SSI在HTML模板中插入实时数据CGI适合做双向交互但如果只是想往HTML页面里插入几个动态数值比如温度、电压、连接状态SSIServer Side Include是更轻量的方案。在HTML文件里你可以这么写html body 当前温度!--#tempvalue-- ℃ 运行时间!--#uptime-- 秒 /body /html把这文件用makefsdata打包之后在代码里注册SSI标签和对应的处理器static const char *ssi_tags[] { tempvalue, uptime }; static u16_t ssi_handler(int iIndex, char *pcInsert, int iInsertLen) { switch (iIndex) { case 0: return (u16_t)snprintf(pcInsert, iInsertLen, 25.6); case 1: return (u16_t)snprintf(pcInsert, iInsertLen, %lu, (unsigned long)(HAL_GetTick() / 1000)); } return 0; } httpd_set_ssi_handler(ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));lwIP的HTTPD在发送HTML文件时会扫描这些特殊标签匹配到后调用对应的回调用返回值替换标签。这比每次请求都重新生成整个HTML页面要快得多。需要注意两点一是标签不要重名且长度不要超过LWIP_HTTPD_MAX_TAG_NAME_LEN默认好像只有8个字符写长了会被截断二是ssi_handler里填充的字符串长度不能超过pcInsert指向的缓冲区长度缓冲区大小由LWIP_HTTPD_MAX_TAG_INSERT_LEN控制我一般把它调到32字节以上否则长字符串会被截断。还有一个SSI和CGI的搭配技巧CGI处理完表单提交后返回的是静态页面页面里再用SSI显示最新的状态。这样表单提交和数据刷新就可以同时实现用户体验很接近完整的Web管理系统。5. 调试方法与常见问题排查5.1 网络不通时先问三层PHY层、链路层、传输层移植完成后最怕的情况就是代码编译通过但浏览器就是打不开页面。我的排查顺序非常固定先用万用表确认PHY芯片的供电和时钟引脚再读取PHY的寄存器确认link状态最后才用抓包工具看协议栈报文。读取PHY寄存器可以直接在调试器里调用HAL_ETH_ReadPHYRegister。重点看两个寄存器寄存器0x00Basic Control Registerbit13是软复位位。寄存器0x01Basic Status Registerbit2是链路建立状态位bit5是自动协商完成位。如果链路始终断开需要检查网络变压器、RJ45的灯是否亮以及RMII的REF_CLK引脚是否有50MHz方波。示波器查一下这个频率几乎能排除80%的硬件问题。链路层通了之后用Wireshark抓包看ARP。如果你从电脑上ping板子电脑会先发ARP请求板子收到后要回ARP应答。这个阶段如果只有请求没有应答问题多半在lwIP的low_level_input数据没有进入协议栈。可以在low_level_input入口加调试打印或者设置断点。如果ARP正常但ICMP echo request没有回复那就是IP层或者netif配置问题检查IP地址、子网掩码是否设置正确。5.2 页面打不开、卡顿和重启的排查方向页面能ping通但浏览器打不开这种情况最有趣也最麻烦。通常有几个典型方向第一是HTTPD没跑起来。确认httpd_init确实被调用了并且tcp_bind_abort之类的错误被正确处理。建议在HTTPD回调里加断点浏览器一访问看会不会触发。第二是内存池不够。HTTPD处理请求时需要分配PBUF和TCP段内存如果MEMP_NUM_TCP_SEG太小请求一多就会分配失败。可以打开LWIP_STATS通过mib2统计值看到tcp内存分配失败的次数或打印memp_stats来定位。第三是DMA描述符耗尽。RX描述符如果全部被占用而没有被low_level_input和HAL_ETH_ReleaseRxBuffer释放网卡就不再接收新数据表现就是网页第一次能打开刷新后越来越慢直到彻底卡住。出现这个现象时先查接收描述符的环形索引是否循环回来。我在代码里会加一个计数器记录HAL_ETH_GetReceivedFrame_IT返回HAL_ERROR的次数一旦发现异常累计立刻复位网卡并重建描述符保证设备能自恢复。我强烈建议在调试阶段打开lwIP的LWIP_DEBUG针对TCP、HTTPD、netif开启调试输出。串口上能看到连接建立、请求到达、数据发送的完整日志对分析卡顿非常有效。但要注意DEBUG输出本身会占用不少MCU时间和串口带宽所以正式版记得关掉。5.3 常见问题速查表我从多个项目里攒了一张速查表每次网口出问题都先对着查一遍省下很多时间现象可能原因排查方法网口灯不亮PHY供电异常、25MHz晶振不起振、RJ45变压器接反测量PHY电源和时钟查看原理图能ping通但网页打不开HTTPD未初始化、静态页面数组为空、端口被占用检查httpd_init调用位置和fsdata.c内容网页打开一次后卡死接收描述符没有释放、内存池耗尽看DMA描述符环形索引打印memp_stats传输大文件超时TCP发送缓冲区太小、MEMP_NUM_TCP_SEG不足增大TCP_SND_BUF适当提升TCP_SEG数量ARP一直请求无应答low_level_input未正确调用、netif-input注册错误断点调试或串口打印确认received帧是否触发每次重启后MAC相同但ARP异常有多个设备使用相同MAC地址修改MAC地址后两位避开冲突调试过程中损失的睡眠时间越多这套表的价值就越大。它背后是我踩过的一个个坑写出来真希望你能直接绕过。6. 一点个人经验移植lwIP这件事真正耗时间的往往不是lwIP本身而是底层驱动和内存管理的边界条件。很多项目死在DMA描述符没释放、内存池耗尽、CCM RAM放错位置这些地方。我的建议是首次移植别急着写应用层先把ARP、PING调通每调通一层再往下走一层。等TCP连接能建立、HTTPD能返回静态页面你的框架就已经稳定了剩下的CGI和SSI不过是往这个框架上填业务。最后再分享一个习惯我在F407工程里同时跑了两个调试通道串口打印lwIP的统计数据和日志网页JSON接口提供运行时状态。这两套输出在联调时帮了大忙尤其是当设备接入到一个已有网络的局域网里别人拿着手机去访问你的板子出现问题时你能立刻从串口日志里定位到是协议栈的问题还是访问者设备的问题。串口日志加上Wireshark抓包基本能覆盖绝大多数的移植疑难杂症。希望这篇能让你从“逻辑上通了”到“实际跑起来”少走几步弯路。
返回列表