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 落地的全流程实践!

相关推荐
广州灵眸科技有限公司7 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) 开发(编译)方式说明
linux·服务器·单片机·嵌入式硬件·电脑
努力学习_小白7 小时前
ResNeXt-50——学习记录
pytorch·深度学习·学习
IT_阿水7 小时前
STM32 HAL库输入捕获配置
stm32·单片机·嵌入式硬件
三品吉他手会点灯8 小时前
C语言学习笔记 - 44.运算符和表达式 - 运算符2 - 除法与取余运算符
c语言·开发语言·笔记·算法
zlinear数据采集卡8 小时前
555触摸延时开关深度解析:从电路原理到智能楼道灯应用
单片机·嵌入式硬件
2601_colin8 小时前
Codex插件全流程实战指南
开发语言·经验分享·笔记·微信开放平台
疯狂打码的少年9 小时前
输入输出控制方式:DMA(直接存储器存取)
网络·笔记
cuso4win9 小时前
Feed 流面试笔记
笔记·面试·职场和发展
毕竟是shy哥9 小时前
基于提示引导适配器的实体级对齐遥感图文检索
人工智能·学习·bert·transformer
happyness4410 小时前
向AI学习,而不是把任务扔给AI
人工智能·学习