目录
一、前言
在上一篇笔记中,我们完成了 libmodbus 适配 STM32 板载串口后端的核心代码改造,实现了板载串口与 libmodbus 协议层的解耦与统一管理。本次实验基于该改造成果,搭建双串口主从通信场景:将 UART2 作为 Modbus 主机(Client)发送请求,UART4 作为 Modbus 从机(Server)接收并响应请求,通过 LED 闪烁、LCD 显示寄存器累加数值的直观现象,验证板载串口后端的实际运行效果。
二、实验核心逻辑:双串口主从任务设计
实验通过 FreeRTOS 创建两个独立任务,分别实现 Modbus 主机(UART2)与从机(UART4)的核心逻辑,同时在 FreeRTOS 初始化函数中完成任务创建,以下是完整代码(关键逻辑已添加注释,未修改原有代码):
1. 从机任务:CH2_UART4_ServerTask(UART4 接收请求并响应)
c
static void CH2_UART4_ServerTask( void *pvParameters )
{
uint8_t *query; // 请求报文缓冲区
modbus_t *ctx; // Modbus从机上下文
int rc; // 函数返回值
modbus_mapping_t *mb_mapping; // 寄存器映射结构体
char buf[100]; // 临时缓冲区(未使用)
int cnt = 0; // 计数变量(未使用)
// 创建UART4的RTU上下文:波特率115200、无校验、8数据位、1停止位
ctx = modbus_new_st_rtu("uart4", 115200, 'N', 8, 1);
modbus_set_slave(ctx, 1); // 设置从机地址为1
query = pvPortMalloc(MODBUS_RTU_MAX_ADU_LENGTH); // 分配请求缓冲区
// 初始化寄存器映射:四类寄存器起始地址0,数量各10
mb_mapping = modbus_mapping_new_start_address(0,
10,
0,
10,
0,
10,
0,
10);
// 初始化线圈寄存器为0(LED初始熄灭)
memset(mb_mapping->tab_bits, 0, mb_mapping->nb_bits);
// 初始化保持寄存器为0x55(测试用初始值)
memset(mb_mapping->tab_registers, 0x55, mb_mapping->nb_registers*2);
// 建立UART4串口连接
rc = modbus_connect(ctx);
if (rc == -1) {
//fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
modbus_free(ctx);
vTaskDelete(NULL);; // 连接失败则删除任务
}
// 无限循环接收并响应主机请求
for (;;) {
do {
// 接收主机请求报文,过滤无效请求(返回0的查询)
rc = modbus_receive(ctx, query);
/* Filtered queries return 0 */
} while (rc == 0);
/* The connection is not closed on errors which require on reply such as
bad CRC in RTU. */
// 非CRC错误则继续循环
if (rc == -1 && errno != EMBBADCRC) {
/* Quit */
continue;
}
// 解析请求并回复主机
rc = modbus_reply(ctx, query, rc, mb_mapping);
if (rc == -1) {
//break;
}
// 线圈寄存器第0位控制PC12引脚LED:1点亮,0熄灭
if (mb_mapping->tab_bits[0])
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_RESET);
else
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);
// 延时1秒后,保持寄存器1的值自增1
vTaskDelay(1000);
mb_mapping->tab_registers[1]++;
}
// 释放资源(实际循环不会执行到此处)
modbus_mapping_free(mb_mapping);
vPortFree(query);
modbus_close(ctx);
modbus_free(ctx);
vTaskDelete(NULL);
}
2. 主机任务:CH1_UART2_ClientTask(UART2 发送请求)
c
static void CH1_UART2_ClientTask( void *pvParameters )
{
modbus_t *ctx; // Modbus主机上下文
int rc; // 函数返回值
uint16_t val; // 存储读取的寄存器值
int nb = 1; // 单次读取的寄存器数量
int level = 1; // LED控制电平(初始为1)
char buf[100]; // 临时缓冲区(未使用)
int cnt = 0; // 计数变量(未使用)
// 创建UART2的RTU上下文:波特率115200、无校验、8数据位、1停止位
ctx = modbus_new_st_rtu("uart2", 115200, 'N', 8, 1);
modbus_set_slave(ctx, 1); // 设置目标从机地址为1
// 建立UART2串口连接
rc = modbus_connect(ctx);
if (rc == -1) {
//fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
modbus_free(ctx);
vTaskDelete(NULL);; // 连接失败则删除任务
}
// 无限循环执行主机操作
for (;;) {
/* read hoding register 1 */
// 读取从机保持寄存器1的值
rc = modbus_read_registers(ctx, 1, nb, &val);
if (rc != nb)
{
continue; // 读取失败则跳过本次循环
}
/* display on lcd */
// 将读取到的寄存器值显示在LCD屏幕上
Draw_Number(0, 0, val, 0xff0000);
/* delay 2s */
// 延时2秒后,翻转LED控制电平并写入从机线圈寄存器0
vTaskDelay(2000);
modbus_write_bit(ctx, 0, level);
level = !level;
}
// 释放资源(实际循环不会执行到此处)
modbus_close(ctx);
modbus_free(ctx);
vTaskDelete(NULL);
}
3. FreeRTOS 任务初始化
c
void MX_FREERTOS_Init(void) {
// 创建主机任务:UART2客户端任务
xTaskCreate(
CH1_UART2_ClientTask,
"CH1_UART2_ClientTask",
200, // 任务栈大小
NULL,
osPriorityNormal, // 任务优先级
NULL);
// 创建从机任务:UART4服务端任务
xTaskCreate(
CH2_UART4_ServerTask,
"CH2_UART4_ServerTask",
200, // 任务栈大小
NULL,
osPriorityNormal, // 任务优先级
NULL);
}
补充:双任务均采用
osPriorityNormal普通优先级,栈大小配置为 200,适配本实验的轻量通信需求;若需扩展功能(如更多寄存器操作、复杂数据处理),可适当增大栈大小。
三、实验现象与功能验证
烧录程序后,开发板呈现以下核心现象,验证双串口主从通信逻辑正常:
- LED 闪烁:主机每 2 秒翻转一次从机线圈寄存器 0 的电平,从机根据该值控制 LED 亮灭,实现周期性闪烁;
- LCD 数值累加:从机每 1 秒将保持寄存器 1 的值自增 1,主机每 2 秒读取该值并显示在 LCD 上,可见数值持续累加。
实验效果如下图所示:

四、总结
- 实验基于双串口(UART2 主机、UART4 从机)搭建主从通信场景,验证了板载串口作为 libmodbus 后端的有效性;
- 主机任务实现寄存器读取、LCD 显示、LED 电平控制,从机任务实现请求响应、寄存器累加、LED 硬件联动,功能闭环完整;
- FreeRTOS 双任务调度稳定,核心现象(LED 闪烁、数值累加)符合预期,证明代码改造与任务设计的正确性。
五、结尾
本次实验完成了 libmodbus 板载串口后端的最终功能验证,通过双串口主从通信实现了硬件联动与数据可视化,这套双串口主从设计方案可直接复用至工业现场的多设备通信场景。至此,libmodbus 在 STM32 平台的 USB 串口、板载串口后端适配与验证已全部完成,形成了完整的移植与应用闭环。感谢各位的阅读,持续关注本系列笔记,一起探索更多嵌入式通信与开源库落地的实战方案!