libmodbus 移植 STM32(基础篇)

目录

一、前言

在前几篇笔记中,我们完整解析了 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版本进行修改,核心准备步骤如下:

  1. 复制源码中的modbus-rtu.c文件,重命名为modbus-st-rtu.c(专属 STM32 的 RTU 模式实现文件);

  2. modbus-st-rtu.c添加到 Source Insight 4.0 中,方便源码编辑与阅读;

  3. 删除无关代码:

    • 适配 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)绑定,完成硬件参数的落地配置。

六、总结

  1. libmodbus 移植 STM32 的核心是保留上层 API,替换底层硬件操作函数,删除跨平台无关代码;
  2. 基础改造步骤:复制专属源码文件→删除系统适配代码→简化连接状态判断→定制上下文创建函数;
  3. 核心待修改函数为串口收发、连接、关闭、刷新、超时等待,需替换为 STM32 HAL/LL 库函数。

七、结尾

本次完成了 libmodbus 移植到 STM32 的基础准备工作,核心是梳理出需要改造的硬件操作函数,定制化适配 STM32 的串口参数配置逻辑。后续将进一步讲解如何将这些待修改函数替换为 STM32 HAL 库代码,并适配 FreeRTOS 的任务调度机制,实现真正可运行的嵌入式 Modbus 通信程序。感谢各位的阅读,持续关注本系列笔记,一起完成 libmodbus 从源码解析到 STM32 落地的全流程实践!

相关推荐
无聊的小坏坏2 小时前
实习笔记:用 /etc/crontab 实现定期数据/日志清理
笔记·实习日记
香芋Yu2 小时前
【机器学习教程】第04章 指数族分布
人工智能·笔记·机器学习
●VON2 小时前
Flutter for OpenHarmony 21天训练营 Day03 总结:从学习到输出,迈出原创第一步
学习·flutter·openharmony·布局·技术
香芋Yu3 小时前
【大模型教程——第四部分:大模型应用开发】第1章:提示工程与上下文学习 (Prompt Engineering & ICL)
学习·prompt
LYS_06183 小时前
寒假学习10(HAL库1+模数电10)
学习
runningshark3 小时前
【项目】示波器学习与制作
学习
€8113 小时前
Java入门级教程24——Vert.x的学习
java·开发语言·学习·thymeleaf·数据库操作·vert.x的路由处理机制·datadex实战
自可乐3 小时前
n8n全面学习教程:从入门到精通的自动化工作流引擎实践指南
运维·人工智能·学习·自动化
深蓝海拓4 小时前
PySide6从0开始学习的笔记(二十六) 重写Qt窗口对象的事件(QEvent)处理方法
笔记·python·qt·学习·pyqt