nanoMODBUS 是一款优秀的开源 MODBUS 协议库,原生支持 MODBUS-TCP 和 MODBUS-RTU 主从机模式。本教程将详细介绍如何将 nanoMODBUS 的 MODBUS-RTU 主机功能移植到基于 STM32F4 的项目中,并使用 FreeRTOS 实现任务化管理。
1. 准备工作
1.1 STM32CubeMX 配置
在 STM32CubeMX 中,需要完成以下核心配置:
启用 FreeRTOS: 在“Middleware”菜单下启用 FreeRTOS。
配置 UART:
将波特率设置为 9600(Modbus 协议常用波特率)。
启用 UART 全局中断。
配置 DMA:
为 UART 的 **TX(发送)**通道添加 DMA 请求。
重要: nanoMODBUS 的 RTU 协议默认使用阻塞发送和 DMA 空闲中断接收。本教程以串口 DMA 发送为例。
1.2 nanoMODBUS 源码结构
从 nanoMODBUS 的 GitHub 仓库下载源码,解压后目录结构如下:
nanomodbus.c/.h: 库的核心实现文件。这是整个库最主要的部分。
examples: 作者为不同平台提供的例程,可以作为移植参考。
nmbs: 硬件层接口适配文件夹。
port.c/.h: 这是硬件层适配接口文件,移植的主要工作就是修改此文件。
2. 工程移植
2.1 文件放置与编译配置
将源码文件放置到您的 STM32 项目中,并确保它们被添加到编译文件列表中。建议的目录结构如下:
2.2 nanomodbus_config.h 配置
打开 nanomodbus_config.h 文件,手动启用 MODBUS RTU 和 DMA 功能,并指定使用的串口:
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 发送和接收用到的队列和信号量。
#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
信号量句柄为全局变量
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
|
|