[应用相关] stm32Hal库移植freemodbus,modbusRTU功能实现

[复制链接]
1485|23
观海 发表于 2025-9-9 18:44 | 显示全部楼层 |阅读模式
nanoMODBUS 是一款优秀的开源 MODBUS 协议库,原生支持 MODBUS-TCP 和 MODBUS-RTU 主从机模式。本教程将详细介绍如何将 nanoMODBUS 的 MODBUS-RTU 主机功能移植到基于 STM32F4 的项目中,并使用 FreeRTOS 实现任务化管理。

1. 准备工作
1.1 STM32CubeMX 配置
在 STM32CubeMX 中,需要完成以下核心配置:

启用 FreeRTOS: 在“Middleware”菜单下启用 FreeRTOS。

4141068bfa55a673ad.png

配置 UART:

将波特率设置为 9600(Modbus 协议常用波特率)。

5404268bfa5557f4e0.png

启用 UART 全局中断。

配置 DMA:

为 UART 的 **TX(发送)**通道添加 DMA 请求。

8550668bfa54fb6fef.png

重要: nanoMODBUS 的 RTU 协议默认使用阻塞发送和 DMA 空闲中断接收。本教程以串口 DMA 发送为例。

1.2 nanoMODBUS 源码结构
从 nanoMODBUS 的 GitHub 仓库下载源码,解压后目录结构如下:

8161968bfa54aee1f7.png

nanomodbus.c/.h: 库的核心实现文件。这是整个库最主要的部分。

examples: 作者为不同平台提供的例程,可以作为移植参考。

3165968bfa544aad39.png

nmbs: 硬件层接口适配文件夹。

761268bfa53eb4be5.png

port.c/.h: 这是硬件层适配接口文件,移植的主要工作就是修改此文件。

8428268bfa530b43e3.png

2. 工程移植
2.1 文件放置与编译配置
将源码文件放置到您的 STM32 项目中,并确保它们被添加到编译文件列表中。建议的目录结构如下:

2967668bfa527d4866.png

2.2 nanomodbus_config.h 配置
打开 nanomodbus_config.h 文件,手动启用 MODBUS RTU 和 DMA 功能,并指定使用的串口:

3709968bfa521a3732.png

9840868bfa51d9d8f5.png

2.3 nanomodbus_config.c 接口实现
打开 nanomodbus_config.c 文件,根据 STM32 的 HAL 库和 FreeRTOS 实现硬件接口。

全局变量:

需要一个全局的二进制信号量用于发送同步。

发送接口 write_serial:

static int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg)


使用二进制信号量来确定 DMA 发送是否完成。在发送前获取信号量,DMA 发送完成后在中断中释放信号量。

static int32_t write_serial(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg) {
#if MB_UART_DMA
    if (rtu_tx_sem==NULL)
        return 0;

    xSemaphoreTake(rtu_tx_sem, 0);

    if (HAL_UART_Transmit_DMA(&MB_UART, buf, count)!=HAL_OK)
        return 0;

    if (xSemaphoreTake(rtu_tx_sem,pdMS_TO_TICKS(byte_timeout_ms))==pdTRUE)
    {
        return count;
    }

    HAL_UART_AbortTransmit(&MB_UART);

    return 0;
#else
    HAL_StatusTypeDef status = HAL_UART_Transmit(&MB_UART, buf, count, byte_timeout_ms);
    if (status == HAL_OK) {
        return count;
    }
    else {
        return 0;
    }
#endif
}



DMA 发送完成回调函数:

在 stm32f4xx_it.c 文件中找到 HAL_UART_TxCpltCallback 回调函数,并在其中释放信号量,唤醒发送任务。

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance==MB_UART.Instance)
    {
        BaseType_t xHigherPriorityTaskWoken=pdFALSE;
        xSemaphoreGiveFromISR(rtu_tx_sem, &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}


初始化函数 nmbs_client_init:

在 nmbs_client_init 函数中初始化 DMA 发送和接收用到的队列和信号量。

7138468bfa508eb4be.png

#if MB_UART_DMA
    if (rtu_tx_sem == NULL) {
        rtu_tx_sem = xSemaphoreCreateBinary();
    }
    xSemaphoreGive(rtu_tx_sem);
    rtu_rx_q = xQueueCreate(MB_RX_BUF_SIZE, sizeof(uint8_t));
    HAL_UARTEx_ReceiveToIdle_DMA(&MB_UART, rtu_rx_b, MB_RX_BUF_SIZE);
#endif


信号量句柄为全局变量

5326768bfa50080d21.png

3. 编写 Modbus 任务
最后,创建一个 Modbus 任务来处理协议通信。此文件可以参考源码中的 examples/stm32/modbus_rtu.c。

任务代码:

#include "modbusrtu_task.h"
#include "FreeRTOS.h"
#include "task.h"
#include "printf_rtos.h"
#include "nanomodbus_config.h"
nmbs_t g_nmbs;
void modbus_master_task(void* args) {
    UNUSED(args);
    uint16_t regs_test[32]; // 用于存储读取结果的缓冲区
    // 1. 初始化 Modbus 客户端
    nmbs_client_init(&g_nmbs);
    // nmbs_set_read_timeout(&g_nmbs, 3000); // 设置1秒的响应超时

    for (;;) {
        // 2. 设置目标从站地址 (例如: 地址为 1 的从站)
        nmbs_set_destination_rtu_address(&g_nmbs, 0x01);

        // 3. 发送读保持寄存器请求 (FC03)
        // 从地址 0 开始,读取 10 个寄存器,存入 regs_test 数组
        nmbs_error status = nmbs_read_holding_registers(&g_nmbs, 0, 1, regs_test);

        // 4. 检查结果
        if (status == NMBS_ERROR_NONE) {
            // 读取成功,可以在这里处理 regs_test 数组中的数据
            printf_rtos("Read success: regs[0]=%d\n", regs_test[0]);
        }
        // else {
        //     // 发生错误,可以根据 status 的值进行处理
        //     printf_rtos("Modbus error: %s\n", nmbs_strerror(status));
        // }

        // 延时一段时间再进行下一次轮询
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}



————————————————
版权声明:本文为CSDN博主「19y_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2301_80049663/article/details/151158375

kkzz 发表于 2025-9-21 13:11 | 显示全部楼层
portserial.c:修改此文件以适配STM32的串口中断处理。需要实现串口接收和发送的回调函数。
porttimer.c:修改此文件以适配STM32的定时器中断处理。需要实现定时器的启动、停止和回调函数。
10299823 发表于 2025-9-21 13:26 | 显示全部楼层
在main.c中初始化Modbus协议栈,并配置Modbus从站或主站的相关参数。
mikewalpole 发表于 2025-9-21 13:48 | 显示全部楼层
使用 DMA 提升大数据量吞吐量
hilahope 发表于 2025-9-21 14:07 | 显示全部楼层
功能码分级处理              
wangdezhi 发表于 2025-9-21 14:56 | 显示全部楼层
在主函数中调用eMBInit函数初始化Modbus协议栈,设置通信模式(RTU或TCP)、从机地址、波特率等参数。
rosemoore 发表于 2025-9-21 16:59 | 显示全部楼层
接收数据时,通过串口中断将字节存入Modbus协议栈的接收缓冲区,并触发帧结束检测。
发送数据时,通过串口中断将字节从发送缓冲区逐个发送。
wengh2016 发表于 2025-9-21 17:24 | 显示全部楼层
调用eMBEnable函数启用Modbus协议栈,开始监听通信请求。
modesty3jonah 发表于 2025-9-21 19:37 | 显示全部楼层
FreeModbus是跨平台的Modbus协议栈
mikewalpole 发表于 2025-9-21 19:57 | 显示全部楼层
Modbus RTU 使用同一根总线(半双工),发送和接收需严格切换,否则会导致数据冲突
sdCAD 发表于 2025-9-21 21:42 | 显示全部楼层
在STM32CubeMX中配置USART时,需启用全局中断,并确保中断优先级高于定时器中断。
geraldbetty 发表于 2025-9-21 22:30 | 显示全部楼层
FreeModbus内部使用ucMBFrameBuffer存储接收的Modbus帧
yorkbarney 发表于 2025-9-21 22:58 | 显示全部楼层
Modbus RTU协议对时序有严格要求
51xlf 发表于 2025-9-22 13:16 | 显示全部楼层
Modbus RTU协议通过3.5个字符时间的空闲间隔判断一帧结束。
lzmm 发表于 2025-9-22 13:57 | 显示全部楼层
STM32 HAL 库移植 FreeModbus 的核心是精准的时序控制
lihuami 发表于 2025-9-22 14:51 | 显示全部楼层
地址唯一,避免同时发送导致总线冲突
adolphcocker 发表于 2025-9-22 15:33 | 显示全部楼层
在主循环中调用eMBPoll,用于处理Modbus请求和响应。
mnynt121 发表于 2025-9-22 16:05 | 显示全部楼层
Modbus RTU通过​​3.5个字符时间​​(T3.5)检测帧超时
elsaflower 发表于 2025-9-22 17:57 | 显示全部楼层
在中断服务程序中尽量减少耗时操作,如避免在中断中调用耗时的函数或进行复杂的计算。
mickit 发表于 2025-9-22 19:40 | 显示全部楼层
Modbus RTU依赖串口通信,需将FreeModbus的底层串口I/O函数替换为STM32 HAL库的实现。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

147

主题

4359

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部