资讯中心

STM32裸机编程指南-12:用Mongoose在裸机上跑通以太网网络服务器

📅 2026/9/26 11:13:02
STM32裸机编程指南-12:用Mongoose在裸机上跑通以太网网络服务器
1. 从点灯到联网裸机以太网服务器到底难在哪STM32 裸机编程走到第 12 篇前面我们已经把时钟、串口、SysTick、CMSIS 头文件这些基础打好了。现在要面对一个更刺激的目标在没有 RTOS、没有 LwIP 协议栈、没有文件系统的裸机环境里跑通一个能返回网页的以太网服务器。听起来像是要写几千行代码但实际上用 Mongoose 这个单文件网络库核心逻辑可以压缩到几百行以内。先说清楚这件事能做什么。你手上如果有一块带板载以太网的 STM32 开发板比如 Nucleo-F429ZI烧录完成后板子会通过 DHCP 自动获取 IP然后你在浏览器里输入这个 IP就能看到一个设备仪表盘页面上面有 LED 状态、实时数据推送甚至还能通过 WebSocket 做双向通信。适合谁适合已经能独立完成 STM32 裸机工程搭建、想进一步理解网络协议栈如何与硬件驱动对接的嵌入式开发者。难点在哪以太网硬件需要两个组件协同PHY 负责向铜缆发送和接收电信号MAC 负责驱动 PHY 控制器。在 Nucleo-F429ZI 上MAC 是 MCU 内置的PHY 是外部的 LAN8720a两者通过 RMII 接口通信。这意味着你需要配置一堆引脚为复用功能使能以太网时钟处理中断向量表还要让网络库知道怎么跟这个硬件打交道。Mongoose 把这些都封装好了但前提是你得把配置骨架搭对。我试过在裸机上从零手写 TCP/IP 协议栈光是 ARP 缓存和 TCP 重传就够折腾一周。Mongoose 的价值在于它把网络驱动、协议栈、HTTP 库全塞进一个 mongoose.c 文件里你只需要告诉它用哪个驱动、开哪些功能剩下的它自己处理。下面我会把整个流程拆成可复制的步骤从 PHY 初始化到 curl 验证每一步都给出具体代码和参数。2. TaoToken 前置为什么网络调试需要它在裸机上跑网络服务器最头疼的不是写代码而是调试。板子通过 DHCP 拿到 IP 后你怎么确认它真的在监听 80 端口浏览器打不开的时候是 PHY 没初始化好还是 TCP 握手失败还是 HTTP 响应格式不对这时候需要一个能直接跟模型对话、帮你分析日志和报错的工具。TaoToken 在这里的角色是提供模型对话和 API 接入能力。你可以把串口打印的 Mongoose 调试日志贴进去让模型帮你判断是 ARP 缓存没建立还是 DHCP 超时。它的模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 适合在排障阶段快速定位问题。如果你打算长期做嵌入式网络开发需要频繁调用 API 做自动化测试可以看看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。这些前置准备不影响裸机代码本身但能让你在遇到 ETH_IRQHandler 不触发、DHCP 拿不到地址这类问题时有个能快速查资料和验证思路的通道。3. 可复制配置Mongoose 骨架与 STM32 以太网初始化3.1 拷贝文件与 mongoose_custom.h 配置先把 mongoose.c 和 mongoose.h 拷到工程目录。Mongoose 提供了设备仪表盘示例我们直接复用它的 net.c仪表盘功能和 packed_fs.c内嵌 HTML/CSS/JS 文件系统。这四个文件加上你的 main.c 和 startup.c就是全部源码。接下来创建 mongoose_custom.h告诉 Mongoose 开启哪些功能#pragma once #define MG_ARCH MG_ARCH_NEWLIB #define MG_ENABLE_MIP 1 #define MG_ENABLE_PACKED_FS 1 #define MG_IO_SIZE 512 #define MG_ENABLE_CUSTOM_MILLIS 1MG_ARCH_NEWLIB 指定用 newlib 作为 C 库MG_ENABLE_MIP 启用内置 TCP/IP 协议栈MG_ENABLE_PACKED_FS 启用打包文件系统MG_IO_SIZE 设成 512 是为了适配嵌入式内存限制MG_ENABLE_CUSTOM_MILLIS 表示我们自己提供毫秒计时函数。3.2 RMII 引脚初始化与时钟使能在 main.c 里加上以太网初始化代码。Nucleo-F429ZI 的 RMII 引脚分布是固定的直接照抄uint16_t pins[] { PIN(A, 1), PIN(A, 2), PIN(A, 7), PIN(B, 13), PIN(C, 1), PIN(C, 4), PIN(C, 5), PIN(G, 11), PIN(G, 13) }; for (size_t i 0; i sizeof(pins) / sizeof(pins[0]); i) { gpio_init(pins[i], GPIO_MODE_AF, GPIO_OTYPE_PUSH_PULL, GPIO_SPEED_INSANE, GPIO_PULL_NONE, 11); } nvic_enable_irq(61); // 以太网中断 RCC-APB2ENR | BIT(14); // 使能 SYSCFG SYSCFG-PMC | BIT(23); // 切换为 RMII 模式必须先于时钟使能 RCC-AHB1ENR | BIT(25) | BIT(26) | BIT(27); // 以太网时钟 RCC-AHB1RSTR | BIT(25); // 强制复位 ETHMAC RCC-AHB1RSTR ~BIT(25); // 释放复位注意 SYSCFG-PMC 那行必须在时钟使能之前执行否则 RMII 模式切换不生效PHY 会一直处于错误状态。3.3 弱符号向量表与 ETH_IRQHandlerMongoose 的 STM32 驱动内部定义了 ETH_IRQHandler我们需要让 startup.c 的向量表支持覆盖。用弱符号别名的方式改造void __attribute__((weak)) DefaultIRQHandler(void) { for (;;) (void) 0; } #define WEAK_ALIAS __attribute__((weak, alias(DefaultIRQHandler))) WEAK_ALIAS void NMI_Handler(void); WEAK_ALIAS void HardFault_Handler(void); WEAK_ALIAS void MemManage_Handler(void); // ... 其他中断同理 __attribute__((section(.vectors))) void (*tab[16 91])(void) { 0, _reset, NMI_Handler, HardFault_Handler, MemManage_Handler, // ... 其余向量 };这样当 Mongoose 驱动里定义 ETH_IRQHandler 时链接器会自动选择非弱符号版本中断就能正确进入驱动处理函数。3.4 Mongoose 事件管理器与 HTTP 监听最后初始化 Mongoose 并启动监听struct mg_mgr mgr; mg_mgr_init(mgr); mg_log_set(MG_LL_DEBUG); struct mip_driver_stm32 driver_data {.mdc_cr 4}; struct mip_if mif { .mac {2, 0, 1, 2, 3, 5}, .use_dhcp true, .driver mip_driver_stm32, .driver_data driver_data, }; mip_init(mgr, mif); extern void device_dashboard_fn(struct mg_connection *, int, void *, void *); mg_http_listen(mgr, http://0.0.0.0, device_dashboard_fn, mgr); MG_INFO((Init done, starting main loop));主循环里调用 mg_mgr_poll(mgr, 1) 即可。mdc_cr 4 是 MDC 时钟分频对应 RMII 参考时钟 50MHz 下的标准值。4. 验证请求从串口日志到 curl 实测把 mongoose.c、net.c、packed_fs.c 加入 Makefile重新构建烧录。连接串口控制台你应该能看到类似输出847 3 mongoose.c:6784:arp_cache_add ARP cache: added 0xc0a80001 90:5c:44:55:19:8b 84e 2 mongoose.c:6817:onstatechange READY, IP: 192.168.0.24 854 2 mongoose.c:6818:onstatechange GW: 192.168.0.1 859 2 mongoose.c:6819:onstatechange Lease: 86363 sec LED: 1, tick: 2262 LED: 0, tick: 2512看到 READY 和 IP 地址就说明 DHCP 成功了。先用 ping 确认链路ping 192.168.0.24然后 curl 验证 HTTP 响应curl -v http://192.168.0.24/正常情况会返回 200 OK 和一段 HTML。如果只想看头部curl -I http://192.168.0.24/浏览器打开这个 IP 就能看到仪表盘页面上面有 LED 状态和实时数据。WebSocket 连接也会自动建立页面上的数值会动态刷新。5. 本篇常见错排查5.1 ETH_IRQHandler 不触发现象是串口没有任何 DHCP 日志或者一直停在 link down。先检查 SYSCFG-PMC 的 BIT(23) 是否在 RCC 时钟使能之前设置。其次确认 nvic_enable_irq(61) 中的中断号是否正确F429 的以太网中断号是 61。最后检查 startup.c 的向量表是否用了弱符号别名如果 ETH_IRQHandler 被默认死循环占用中断永远不会进驱动。5.2 DHCP 超时拿不到 IP串口打印 READY 但 IP 是 0.0.0.0或者一直显示 DHCP 请求中。先确认网线插在板子的以太网口而不是调试口。然后检查 mif.mac 地址是否合法示例里的 {2,0,1,2,3,5} 是本地管理地址不会冲突。如果路由器开了 MAC 过滤换一个地址试试。另外 mdc_cr 的值如果设错PHY 寄存器读写会失败表现为 link 一直 down。5.3 curl 返回空响应或连接被拒ping 能通但 curl 没反应通常是 HTTP 监听没启动。检查 mg_http_listen 的第二个参数是不是 http://0.0.0.0端口默认 80。如果板子 IP 是 192.168.0.24curl 时不要加端口号。还有一种情况是 packed_fs.c 没有正确链接导致 device_dashboard_fn 找不到文件系统返回 404。确认 Makefile 里包含了 packed_fs.c 并且 MG_ENABLE_PACKED_FS 设为 1。5.4 浏览器打开页面空白串口日志显示 HTTP 请求进来了但页面空白。大概率是 packed_fs.c 里的文件路径和 net.c 中请求的路径不匹配。Mongoose 的 packed 文件系统在编译时把文件打包成 C 数组路径是大小写敏感的。检查 net.c 里 mg_http_reply 或 mg_serve_http 用的路径和 packed_fs.c 生成时的路径保持一致。6. 继续深入与工具链建议裸机以太网服务器跑通后下一步可以尝试把仪表盘数据接到真实传感器或者用 WebSocket 做远程控制。调试阶段如果遇到协议栈层面的怪问题把 Mongoose 的调试日志贴到模型对话里分析比翻源码快得多。需要批量验证不同 HTTP 路径的响应可以用 API 接入写个简单的测试脚本API Keys 在控制台生成即可。完整工程源码在 step-7-webserver 文件夹对照着改引脚和时钟配置就能移植到其他 STM32 型号。记住一个原则PHY 初始化顺序和 RMII 模式切换是硬件层最容易翻车的地方先把这两步确认无误再往上调协议栈。

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案