简介:本资源面向基于正点原子STM32H743阿波罗开发板进行网络协议开发的嵌入式工程师与学习者,解决在H7平台上将FreeRTOS、LWIP与FreeModbus-TCP/IP三者协同移植的问题。资源以正点原子《lwIP例程11 lwIP_SOCKET_TCP客户端实验》为基础,结合《基于STM32的ModBus实现(二)移植FreeMODBUSTCP》的移植思路,整合出一套可运行的工程。压缩包共1040个文件,约57.73MB,以375个h头文件、306个c源文件为核心,辅以大量o、d、crf编译中间文件,以及uvprojx、uvoptx工程配置、sct分散加载、hex与axf固件、map映射等,完整保留Keil工程结构。已有1400人学习下载。读者可从中获取FreeRTOS任务调度、LWIP协议栈与Modbus-TCP从站/主站融合的完整代码框架,理解Socket通信与寄存器映射的衔接方式,并借助工程配置与固件文件快速复现调试,适合具备一定STM32与网络协议基础的中高级开发者参考。
1. 从一块 H743 板子说起:为什么网络协议栈移植总在“最后一公里”翻车
手里这块 STM32H743 板子,跑裸机点灯、串口打印都顺得不行,可一旦要把 FreeRTOS、LWIP、FreeModbus-TCP 三个东西塞进同一个工程,事情就开始变得玄学。我见过太多人卡在“PHY 能识别、ping 不通”“TCP 能连上、Modbus 一读就断”“单线程跑得好好的,一上 RTOS 就死锁”这类问题上。STM32 网络协议移植这件事,难的不是某一个库本身,而是三者的接口边界:FreeRTOS 管任务调度和信号量,LWIP 管 pbuf 和网卡收发,FreeModbus 管 TCP 端口监听和寄存器映射,任何一层的配置错位都会让整条链路崩掉。这篇笔记面向的是正在 H743 上做以太网通信、准备把 Modbus-TCP 从机跑起来的开发者,我会把版本选择、CubeMX 配置、三个中间件的对接顺序、以及装完怎么验证,按我实际踩过的顺序写清楚。
2. 环境准备与版本选型:H743 上这三个库到底该拿哪一版
2.1 芯片与工具链的硬性前提
STM32H743 属于 H7 系列,Cortex-M7 内核,主频能到 480MHz,带 2MB Flash 和 1MB RAM,以太网外设是自带的 MAC,需要外挂 PHY。我用的板子 PHY 是 LAN8720A,RMII 接口,25MHz 晶振由 PHY 自己提供。工具链方面,我习惯用 STM32CubeIDE 1.13 以上版本,配套的 HAL 库选 STM32CubeH7 固件包 V1.11.0 或更新。这里有个容易忽略的点:H7 的 Cache 和 MPU 配置会直接影响以太网 DMA 描述符的访问,如果 D-Cache 没处理好,LWIP 收包会出现数据错乱,这个后面避坑章节会细说。
FreeRTOS 版本我选的是 FreeRTOS-Kernel V10.4.6,这个版本对 Cortex-M7 的支持稳定,且和 CubeMX 里 FreeRTOS 中间件的 CMSIS-RTOS v2 封装兼容性好。LWIP 用 2.1.2 版本,这是目前和 STM32 以太网驱动配合最成熟的版本,2.2.0 虽然新,但部分 H7 的驱动适配还没跟上。FreeModbus 选 freemodbus V1.6.0,注意要拿带 TCP 支持的版本,很多老包只带 RTU 和 ASCII。
| 组件 | 推荐版本 | 获取方式 |
|---|---|---|
| STM32CubeH7 | V1.11.0+ | CubeMX 内下载 |
| FreeRTOS-Kernel | V10.4.6 | CubeMX 中间件勾选 |
| LWIP | 2.1.2 | CubeMX 中间件勾选 |
| FreeModbus | V1.6.0 | 独立源码包 |
2.2 CubeMX 里先把“地基”打对
打开 CubeMX 新建 H743 工程后,第一步不是急着勾中间件,而是把时钟树和引脚配好。H743 的以太网 RMII 引脚分布在 PA1、PA2、PA7、PC1、PC4、PC5、PG11、PG13、PG14 这几组,CubeMX 里选 ETH 并设为 RMII 模式后会自动分配。时钟树里注意 ETH 的时钟源要选 PLL1Q 或 PLL2Q,我一般用 PLL1Q 出 50MHz 给 MAC。
# CubeMX 时钟配置参考(手动核对) HSE = 25MHz PLL1: M=5, N=192, P=2, Q=4 SYSCLK = 480MHz ETH_TX_CLK / ETH_RX_CLK 由 PHY 提供 25MHz这段配置的逻辑是:HSE 25MHz 经过 PLL1 倍频到 480MHz 给内核,Q 分频出 50MHz 给以太网 MAC 的 REF_CLK。如果 Q 分频算错,PHY 能识别但收发包会大量丢,现象是 ping 通几包就断。参数上 M/N/P/Q 要根据自己板子的晶振改,不是所有板子都用 25MHz。
2.3 中间件的勾选顺序有讲究
CubeMX 里中间件勾选顺序建议是:先 FreeRTOS,再 LWIP,最后手动加 FreeModbus。因为 LWIP 在 CubeMX 里会问你要不要绑定到 FreeRTOS,如果 FreeRTOS 没先勾,LWIP 的NO_SYS选项会默认成 1,生成出来就是裸机版,后面再改很麻烦。
FreeRTOS 配置里,TOTAL_HEAP_SIZE我一般给到 30KB 以上,因为 LWIP 的 pbuf 池和 FreeModbus 的任务栈都要从这里出。MINIMAL_STACK_SIZE保持默认 128 字即可,但 LWIP 的 tcpip 线程栈要单独在 LWIP 配置里给到 1024 字以上。LWIP 配置里关键几项:LWIP_NETCONN和LWIP_SOCKET都使能,MEM_SIZE给 16KB,PBUF_POOL_SIZE给 16,TCP_SND_BUF给 4KB 以上,Modbus-TCP 一次读多个寄存器时缓冲区不够会直接断连。
3. 移植实操:从 CubeMX 生成到 FreeModbus 接入的完整链路
3.1 生成工程后先跑通 LWIP 裸 ping
CubeMX 生成代码后,别急着加 FreeModbus,先确认 LWIP 本身能通。在main.c的MX_LWIP_Init()之后,确认MX_ETH_Init()已经被调用,然后编译下载。用网线把板子和电脑直连,电脑 IP 设成 192.168.1.100,板子默认 IP 在lwip.c里是 192.168.1.10,掩码 255.255.255.0。
/* lwip.c 中确认默认 IP 配置 */ IP4_ADDR(&ipaddr, 192, 168, 1, 10); IP4_ADDR(&netmask, 255, 255, 255, 0); IP4_ADDR(&gw, 192, 168, 1, 1);这段代码在MX_LWIP_Init()里,如果这里 IP 没改对,后面 Modbus 怎么调都连不上。参数说明:ipaddr是板子自己的地址,gw网关在同网段直连时可以随便填,但不要和电脑 IP 冲突。ping 通的标准是连续 100 包不丢,如果丢包超过 5%,先回去查 RMII 时钟和 Cache 配置,不要往下走。
3.2 把 FreeModbus 源码挂进工程
FreeModbus V1.6.0 的源码目录结构是modbus/下分rtu、ascii、tcp三个子目录,我们只需要tcp和core两部分。在 CubeIDE 里新建一个Middlewares/FreeModbus分组,把modbus/core/下的mb.c、mbfunc*.c和modbus/tcp/mbtcp.c加进来,头文件路径加上modbus/include和modbus/tcp。
/* porttcp.c 中需要实现的回调,这是移植的核心 */ BOOL xMBTCPPortInit(USHORT usTCPPort) { struct sockaddr_in addr; int opt = 1; addr.sin_family = AF_INET; addr.sin_port = htons(usTCPPort); addr.sin_addr.s_addr = INADDR_ANY; /* 创建 socket 并绑定 502 端口 */ ... }这个xMBTCPPortInit是 FreeModbus 留给用户的移植接口,必须自己实现。逻辑是创建一个 LWIP socket,绑定到 502 端口,然后监听。参数usTCPPort一般传 502,如果被占用可以改成 5020 测试。注意这里用的是 LWIP 的 socket API,所以前面LWIP_SOCKET必须使能,否则编译会报找不到lwip/sockets.h。
3.3 在 FreeRTOS 里起 Modbus 轮询任务
FreeModbus 的 TCP 模式需要一个任务周期性调用eMBPoll(),我一般单独起一个任务,优先级比 tcpip 线程低一级,栈给 512 字。
void ModbusTask(void *argument) { /* 初始化 Modbus TCP 从机,地址设为 1 */ eMBInit(MB_TCP, 0x01, 0, 502, MB_PAR_NONE); eMBEnable(); for (;;) { /* 轮询处理 Modbus 请求,内部会阻塞在 socket recv */ (void)eMBPoll(); osDelay(1); } }eMBInit的第二个参数是从机地址,TCP 模式下其实用不到,但必须传一个合法值。第四个参数是端口,和xMBTCPPortInit里绑定的端口要一致。eMBPoll()内部会调用xMBTCPPortGetRequest去 recv 数据,如果 socket 是阻塞模式,这个任务会卡在 recv 上,所以osDelay(1)其实很少执行到,这是正常的。如果发现任务卡死导致看门狗复位,检查 socket 是否设了超时。
4. 避坑与排查:H743 网络移植里最容易翻车的五个点
4.1 ping 不通但 PHY 灯亮
现象:网口灯正常闪烁,电脑显示网线已连接,但 ping 板子超时。原因通常是 RMII 的 REF_CLK 方向配错。H743 的 RMII 参考时钟可以由 PHY 提供,也可以由 MCU 的 PA1 输出,CubeMX 里ETH_RMII_REF_CLK的引脚模式如果选成输出而实际是 PHY 供时钟,MAC 就收不到时钟。解决:确认原理图上 25MHz 晶振接在 PHY 还是 MCU,PHY 供时钟时 PA1 要配成输入浮空。
4.2 ping 通但丢包严重
现象:能 ping 通,但丢包率 20% 以上,或者 ping 几十包后彻底断。原因多半是 D-Cache 没关或 MPU 没配。H743 的以太网 DMA 描述符在 SRAM 里,如果这段内存被 Cache 缓存,CPU 读到的描述符状态和 DMA 写的不一致。解决:把 DMA 描述符所在的内存区域通过 MPU 配成 Write-Through 或 Non-Cacheable,或者干脆在MX_ETH_Init()前关掉 D-Cache 测试。我一般用 MPU 配 0x30000000 开始的 32KB 为 Non-Cacheable。
4.3 Modbus 连接建立后立刻断开
现象:Modbus Poll 工具显示 Connected,但读寄存器时立刻 Disconnected。原因通常是TCP_SND_BUF或PBUF_POOL_SIZE太小,Modbus 响应帧发不出去。FreeModbus 一次读 10 个保持寄存器,响应帧大约 25 字节,但如果同时有多个连接,pbuf 池很快耗尽。解决:PBUF_POOL_SIZE从默认 16 加到 32,TCP_SND_BUF给到 8KB,MEM_SIZE给到 24KB。
4.4 eMBPoll 任务导致系统卡死
现象:Modbus 任务运行后,其他任务不再调度,串口无输出。原因是eMBPoll()内部的 socket recv 是阻塞的,且没有设超时,一旦客户端连上又不发数据,任务永久阻塞。如果这个任务优先级最高,低优先级任务全饿死。解决:在xMBTCPPortInit里把 socket 设成非阻塞或设 recv 超时,或者把 Modbus 任务优先级调到最低,并在eMBPoll外层加osDelay。
4.5 从机地址和寄存器映射对不上
现象:Modbus Poll 能连上,但读出来的寄存器全是 0 或报异常码 02。原因是eMBRegHoldingCB回调没实现或寄存器地址偏移算错。FreeModbus 的保持寄存器回调里,usAddress是从 0 开始的偏移,不是 Modbus 协议里的 40001。解决:在回调里把usAddress直接当数组下标用,并确认usRegHoldingNum和实际数组长度一致。
5. 装完怎么验证:三层递进确认法
5.1 第一层:网络层验证
板子下载后,先不接 Modbus 工具,直接用电脑 ping。连续 ping 500 包,丢包率应为 0,延迟稳定在 1ms 以内。如果这一步不过,不要往下走,回去查第 4 章的前两条。我一般会同时开一个串口终端,在MX_LWIP_Init()后打印一句LWIP init done,确认代码确实跑到了网络初始化。
5.2 第二层:TCP 层验证
用电脑的 telnet 或 nc 命令连板子的 502 端口,确认能建立 TCP 连接。
# 电脑端测试 502 端口是否监听 nc -zv 192.168.1.10 502 # 预期输出:Connection to 192.168.1.10 502 port [tcp/*] succeeded!如果这一步失败,说明xMBTCPPortInit里的 socket 没绑定成功,检查端口是否被占用、eMBEnable()是否在eMBInit()之后调用。注意eMBInit和eMBEnable的调用顺序不能反,反了会返回MB_ENOERR但实际没使能。
5.3 第三层:Modbus 协议层验证
用 Modbus Poll 或 QModMaster 工具,连接 192.168.1.10:502,从机地址 1,功能码 03,起始地址 0,读 10 个寄存器。如果前面在eMBRegHoldingCB里填了测试数据,这里应该能读到。我习惯在回调里放一个自增的计数器,每次读都变,这样能确认轮询确实在跑。
| 验证层 | 工具 | 通过标准 |
|---|---|---|
| 网络层 | ping | 500 包 0 丢包 |
| TCP 层 | nc / telnet | 502 端口可连接 |
| 协议层 | Modbus Poll | 功能码 03 返回正确数据 |
三层都过了之后,我还会做一件事:把板子断电重启,等 10 秒再连一次。因为有些配置在冷启动时才会暴露问题,比如 PHY 复位时间不够导致自协商失败。从那以后我每次移植网络协议栈,都强制走一遍这三层验证,不再凭感觉“应该通了”。希望帮到你。
本文还有配套的精品资源,点击获取