目录
- 一、前言
- [二、libmodbus 移植核心思路](#二、libmodbus 移植核心思路)
- 三、源码改造准备工作
- 四、待修改的核心硬件操作函数
- [五、定制化 RTU 上下文创建函数](#五、定制化 RTU 上下文创建函数)
- 六、总结
- 七、结尾
一、前言
在前几篇笔记中,我们完整解析了 libmodbus 的主从通信、报文收发与从机回应的核心源码,掌握了其上层 API 的封装逻辑与底层协议实现。但 libmodbus 原生仅适配 Windows、Linux 等操作系统,若要在 STM32 裸机或 FreeRTOS 环境下使用该库实现工业级 Modbus 通信,就需要完成源码的移植改造。本次笔记聚焦 libmodbus 移植到 STM32 的基础工作,讲解移植的核心思路、源码改造的准备步骤,以及待修改的核心硬件操作函数,为后续适配 STM32 串口驱动、完成完整移植打下基础。
二、libmodbus 移植核心思路
libmodbus 的移植核心原则是保留上层应用调用的各类 Modbus 函数(如 modbus_write_bits、modbus_reply 等),仅替换底层与硬件强相关的代码 ------ 具体来说,就是将_modbus_rtu_backend结构体中绑定的串口打开、数据收发、超时等待等硬件操作函数,从适配 Windows/Linux 的系统调用,替换为 STM32 的 HAL/LL 库串口操作函数,确保上层 API 的使用方式完全不变,降低应用层开发的适配成本。
补充:STM32 场景下无需保留跨平台兼容逻辑(如 Windows 的 strcpy_s、Linux 的 termios 串口配置),可直接删除无关代码,简化源码结构,适配嵌入式裸机 / FreeRTOS 的轻量需求。
三、源码改造准备工作
本次移植基于libmodbus-3.1.10.zip版本进行修改,核心准备步骤如下:
-
复制源码中的
modbus-rtu.c文件,重命名为modbus-st-rtu.c(专属 STM32 的 RTU 模式实现文件); -
将
modbus-st-rtu.c添加到 Source Insight 4.0 中,方便源码编辑与阅读; -
删除无关代码:
- 适配 Win32/Linux 系统的条件编译分支代码;
_get_termios_speed函数(STM32 串口速率可通过库函数固定配置,无需动态获取 / 解析)。
删除无关代码后,_modbus_rtu_connect函数简化为如下形式:
c
static int _modbus_rtu_connect(modbus_t *ctx)
{
ctx->s = open(ctx_rtu->device, flags);
return 0;
}
此外,以下与串口模式、流量控制相关的函数也可直接删除,因 STM32 嵌入式场景无需复杂的串口模式配置:
modbus_rtu_set_serial_mode:设置串口模式(STM32 串口模式初始化时固定配置);modbus_rtu_get_serial_mode:获取串口模式(无需动态查询);modbus_rtu_get_rts/modbus_rtu_set_rts:RTS 流量控制(工业现场极简场景下可省略);modbus_rtu_set_custom_rts/modbus_rtu_get_rts_delay/modbus_rtu_set_rts_delay:自定义 RTS 控制与延时(无需适配)。
已完成的基础修改
针对 STM32 串口初始化后默认视为 "连接状态" 的特性,修改_modbus_rtu_is_connected函数,简化连接状态判断逻辑:
c
static unsigned int _modbus_rtu_is_connected(modbus_t *ctx)
{
return 1; // STM32串口初始化完成后,默认视为已连接
}
四、待修改的核心硬件操作函数
以下函数是移植的核心改造点,原代码依赖 Windows/Linux 系统调用,需替换为 STM32 HAL/LL 库的串口操作函数,标记的待修改代码如下:
1. 串口发送函数(替换 write 为 STM32 串口发送)
c
static ssize_t _modbus_rtu_send(modbus_t *ctx, const uint8_t *req, int req_length)
{
return write(ctx->s, req, req_length); // 需替换为HAL_UART_Transmit等函数
}
2. 串口接收函数(替换 read 为 STM32 串口接收)
c
static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length)
{
return read(ctx->s, rsp, rsp_length); // 需替换为HAL_UART_Receive等函数
}
3. 串口连接函数(替换 open 为 STM32 串口初始化)
c
static int _modbus_rtu_connect(modbus_t *ctx)
{
ctx->s = open(ctx_rtu->device, flags); // 需替换为STM32串口初始化函数
return 0;
}
4. 串口关闭函数(补充 STM32 串口反初始化逻辑)
c
static void _modbus_rtu_close(modbus_t *ctx)
{
// 需添加STM32串口反初始化/禁用代码
}
5. 串口缓冲区刷新函数(替换 tcflush 为 STM32 串口清空)
c
static int _modbus_rtu_flush(modbus_t *ctx)
{
return tcflush(ctx->s, TCIOFLUSH); // 需替换为HAL_UART_Flush等函数
}
6. 超时等待函数(替换 select 为 STM32 超时判断)
c
static int
_modbus_rtu_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read)
{
int s_rc;
while ((s_rc = select(ctx->s + 1, rset, NULL, NULL, tv)) == -1) {
if (errno == EINTR) {
if (ctx->debug) {
fprintf(stderr, "A non blocked signal was caught\n");
}
/* Necessary after an error */
FD_ZERO(rset);
FD_SET(ctx->s, rset);
} else {
return -1;
}
}
if (s_rc == 0) {
/* Timeout */
errno = ETIMEDOUT;
return -1;
}
return s_rc; // 需替换为STM32基于定时器的超时等待逻辑
}
补充:
select是 Linux/Windows 的 IO 多路复用函数,STM32 无此系统调用,需基于定时器(如 SysTick、通用定时器)实现串口接收的超时等待逻辑,适配嵌入式场景的超时控制需求。
五、定制化 RTU 上下文创建函数
为适配 STM32 串口参数配置需求,定制化修改modbus_new_rtu函数,命名为modbus_new_st_rtu,核心代码如下:
c
// 定制化STM32 RTU上下文创建函数,添加串口参数配置
modbus_t *
modbus_new_st_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit)
{
modbus_t *ctx;
modbus_rtu_t *ctx_rtu;
/* Check device argument */
if (device == NULL || *device == 0) {
fprintf(stderr, "The device string is empty\n");
errno = EINVAL;
return NULL;
}
ctx = (modbus_t *) malloc(sizeof(modbus_t));
if (ctx == NULL) {
return NULL;
}
_modbus_init_common(ctx);
ctx->backend = &_modbus_rtu_backend;
ctx->backend_data = (modbus_rtu_t *) malloc(sizeof(modbus_rtu_t));
if (ctx->backend_data == NULL) {
modbus_free(ctx);
errno = ENOMEM;
return NULL;
}
ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
/* Device name and \0 */
ctx_rtu->device = (char *) malloc((strlen(device) + 1) * sizeof(char));
if (ctx_rtu->device == NULL) {
modbus_free(ctx);
errno = ENOMEM;
return NULL;
}
strcpy(ctx_rtu->device, device);
// 显式配置STM32串口参数
ctx_rtu->baud = baud;
if (parity == 'N' || parity == 'E' || parity == 'O') {
ctx_rtu->parity = parity;
} else {
modbus_free(ctx);
errno = EINVAL;
return NULL;
}
ctx_rtu->data_bit = data_bit;
ctx_rtu->stop_bit = stop_bit;
ctx_rtu->confirmation_to_ignore = FALSE;
return ctx;
}
补充:该函数删除了原
modbus_new_rtu中 Win32/Linux 的字符串拷贝分支(仅保留通用strcpy),新增波特率、奇偶校验、数据位、停止位的显式配置,后续可将这些参数与 STM32 串口初始化函数(如HAL_UART_Init)绑定,完成硬件参数的落地配置。
六、总结
- libmodbus 移植 STM32 的核心是保留上层 API,替换底层硬件操作函数,删除跨平台无关代码;
- 基础改造步骤:复制专属源码文件→删除系统适配代码→简化连接状态判断→定制上下文创建函数;
- 核心待修改函数为串口收发、连接、关闭、刷新、超时等待,需替换为 STM32 HAL/LL 库函数。
七、结尾
本次完成了 libmodbus 移植到 STM32 的基础准备工作,核心是梳理出需要改造的硬件操作函数,定制化适配 STM32 的串口参数配置逻辑。后续将进一步讲解如何将这些待修改函数替换为 STM32 HAL 库代码,并适配 FreeRTOS 的任务调度机制,实现真正可运行的嵌入式 Modbus 通信程序。感谢各位的阅读,持续关注本系列笔记,一起完成 libmodbus 从源码解析到 STM32 落地的全流程实践!