4. 协议栈源码获取与工程搭建:开源协议栈选型与MCU工程模板创建
好,咱们进入正题。这一章,说白了就是“找工具、搭台子”。你想想看,写Modbus从站协议栈,总不能从零开始造轮子吧?那太累了,而且容易出bug。我个人的习惯是,先选一个成熟的开源协议栈,然后把它移植到自己的MCU工程里。
嗯,这里要注意,选型不是随便挑一个就完事。你得考虑你的MCU资源、项目复杂度、还有你团队的技术栈。我见过不少项目,因为选型不当,后期移植改得面目全非,甚至推倒重来。所以,咱们先聊聊市面上最主流的两个选择。
4.1 开源协议栈选型:FreeModbus vs libmodbus
目前嵌入式领域,最常用的Modbus开源协议栈就两个:FreeModbus 和 libmodbus。它们俩的定位不太一样,我分别说说我的看法。
| 特性 | FreeModbus | libmodbus |
|---|---|---|
| 开发语言 | C语言,专为嵌入式MCU设计 | C语言,最初为Linux/Windows设计 |
| 资源占用 | 极小,ROM几KB,RAM几百字节 | 较大,依赖操作系统(如线程、socket) |
| 平台适配 | 裸机或RTOS,移植工作量小 | 主要面向Linux/Windows,移植到MCU较复杂 |
| 协议支持 | RTU、ASCII、TCP(从站) | RTU、ASCII、TCP(主站/从站) |
| 维护状态 | 社区维护,更新较慢 | 活跃维护,功能丰富 |
| 典型场景 | STM32、PIC、AVR等资源受限MCU | 树莓派、工控机、网关设备 |
我个人习惯,做MCU从站开发,首选FreeModbus。为什么?因为它就是为这个场景生的。你想想看,一个Cortex-M0的芯片,RAM才8KB,你让它跑libmodbus?那光线程栈就撑爆了。FreeModbus的设计哲学就是“轻量、可裁剪”,它把硬件抽象层(HAL)留给你自己实现,核心协议栈非常干净。
4.2 源码目录结构解析:以FreeModbus为例
好,选定了FreeModbus,咱们来看看它的源码长什么样。我习惯把源码下载下来后,先不急着编译,而是把目录结构捋一遍。这就像你拿到一本新书,先看目录,心里有个谱。
FreeModbus的官方源码可以从SourceForge或GitHub上获取。解压后,核心目录结构大致如下:
freemodbus/
├── demo/ # 各种MCU平台的示例工程
│ ├── ARMCM3_STM32/ # STM32的示例
│ ├── PIC24/ # Microchip PIC24示例
│ └── ... # 其他平台
├── doc/ # 文档(很少,主要看代码)
├── modbus/ # 核心协议栈源码(重点!)
│ ├── ascii/ # ASCII模式处理
│ ├── functions/ # Modbus功能码实现(01~06, 15, 16等)
│ ├── include/ # 头文件
│ ├── port/ # 移植接口(需要你修改的部分)
│ └── rtu/ # RTU模式处理
├── tools/ # 一些辅助工具
└── license.txt # 许可证(BSD,很友好)
嗯,这里要注意,modbus/port/ 这个目录是你移植工作的核心。它里面包含了几个关键文件:
- port.h:定义了一些平台相关的宏,比如临界区保护、数据类型重定义。
- portserial.c:串口底层驱动接口,你需要实现发送、接收、中断处理。
- porttimer.c:定时器驱动接口,用于RTU模式的3.5字符时间间隔判断。
- portevent.c:事件处理接口,用于协议栈与应用程序之间的通信。
说白了,你移植FreeModbus,80%的工作量都在修改这4个文件。剩下的20%是配置功能码和回调函数。
port.h中的临界区保护宏。结果在RTOS环境下,多个任务同时访问Modbus寄存器,导致数据错乱。排查了两天才找到问题。所以,如果你用RTOS,一定要把临界区宏映射到RTOS的关中断或互斥锁上。
4.3 MCU工程模板创建:从零搭建一个可移植的框架
源码看完了,接下来就是动手搭工程。我建议你不要直接修改官方demo,而是创建一个全新的工程模板。这样以后做其他项目,可以直接复用这个模板,省时省力。
我的做法是,在MCU的IDE(比如Keil、IAR、STM32CubeIDE)里,新建一个空工程,然后按以下步骤操作:
- 创建目录结构:在工程根目录下,新建
modbus/文件夹,把FreeModbus的modbus/目录整个复制进来。 - 添加源文件:把
modbus/下所有.c文件添加到IDE的工程组里。注意,demo/目录下的文件不要加,那是示例,不是协议栈本身。 - 配置头文件路径:在IDE的编译选项中,添加
modbus/include/和modbus/port/这两个路径。 - 编写移植文件:在
modbus/port/目录下,创建你自己的port.h、portserial.c、porttimer.c、portevent.c。你可以参考官方demo里的实现,但一定要根据你的MCU外设来写。 - 配置协议栈参数:在
modbus/include/mbconfig.h中,定义你需要的功能码。比如,你只需要读保持寄存器(03功能码)和写单个寄存器(06功能码),那就把其他功能码的宏注释掉,这样可以节省ROM。
下面是一个典型的 mbconfig.h 配置片段:
// 启用Modbus从站功能
#define MB_SLAVE_ENABLED 1
// 启用RTU模式
#define MB_RTU_ENABLED 1
// 禁用ASCII模式(省资源)
#define MB_ASCII_ENABLED 0
// 启用TCP模式(如果你需要)
#define MB_TCP_ENABLED 0
// 功能码配置:只启用常用的几个
#define MB_FUNC_READ_INPUT_ENABLED 0
#define MB_FUNC_READ_HOLDING_ENABLED 1 // 03功能码
#define MB_FUNC_WRITE_HOLDING_ENABLED 1 // 06功能码
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED 1 // 16功能码
工程模板搭好后,你还需要写一个简单的 main.c 来初始化协议栈。核心流程就三步:
- 调用
eMBInit()初始化协议栈,指定模式(RTU/ASCII/TCP)、从站地址、串口参数等。 - 调用
eMBEnable()使能协议栈,让它开始监听总线。 - 在主循环中,周期调用
eMBPoll()处理接收到的请求。
嗯,这里要注意,eMBPoll() 不能放在中断里,它需要在主循环中被频繁调用,才能及时响应主站的请求。我一般把它放在主循环的 while(1) 里,每隔几毫秒调用一次。
好了,工程模板创建完毕。你有了一个干净的、可移植的Modbus从站协议栈框架。下一章,咱们会深入讲解如何实现串口和定时器的底层驱动,让协议栈真正跑起来。
这张图把整个移植流程串起来了。从选型到最终跑起来,一共7步。你按照这个顺序来,基本不会出错。我个人习惯,每完成一步,就在图上打个勾,这样进度一目了然。