[应用相关] STM32 中实现 Modbus RTU

[复制链接]
 楼主| 观海 发表于 2025-7-17 08:21 | 显示全部楼层 |阅读模式
在 STM32 中实现 Modbus RTU 通信时,数据接收和处理是由底层驱动和协议栈共同完成的。让我详细解释这个过程:

一、通信实现的核心机制
STM32 接收 RS485 数据的过程涉及三个关键组件:

硬件层:RS485 转换器将差分信号转换为 UART 的 TTL 电平
UART 驱动:STM32 的 UART 外设接收数据并触发中断
Modbus 协议栈:解析接收到的数据并执行相应操作
二、数据接收的具体流程
1. 初始化阶段

// 主函数中的初始化代码

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

MX_USART1_UART_Init(); // 配置UART1为9600-8N1

MX_ADC1_Init();

/* 初始化Modbus RTU从机 */

eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE);

eMBEnable();

MX_USART1_UART_Init() 配置 UART1 的波特率、数据位、停止位等参数
eMBInit() 初始化 Modbus 协议栈,设置从机地址、通信参数
2. 数据接收机制
当 RS485 总线上有数据传输时:

UART 接收数据:RS485 转换器将差分信号转换为 TTL 电平,STM32 的 UART 接收数据
触发接收中断:每个字节接收完成后触发 UART_RX 中断
数据存入缓冲区:中断服务程序将接收到的字节存入 Modbus 协议栈的接收缓冲区
在 FreeModbus 库中,这通常由portserial.c文件中的中断服务程序实现:


// 典型的UART接收中断服务程序

void USARTx_IRQHandler(void)

{

if(USART_GetITStatus(USARTx, USART_IT_RXNE) != RESET)

{

/* 读取接收到的字节 */

ucByte = (unsigned char)USART_ReceiveData(USARTx);

/* 将字节传递给Modbus协议栈 */

if(xMBPortSerialPutByte((CHAR)ucByte) != TRUE)

{

/* 处理缓冲区溢出 */

}

/* 清除中断标志 */

USART_ClearITPendingBit(USARTx, USART_IT_RXNE);

}

}

3. Modbus 帧检测
Modbus RTU 协议规定:

两个连续字节之间的空闲时间超过 3.5 个字符时间,表示一帧结束
例如,在 9600 波特率下,3.5 个字符时间约为 3.65ms
协议栈通过定时器检测这个空闲时间:


// 伪代码:检测帧结束

if (idle_time > 3.5 * char_time) {

frame_complete = true;

process_modbus_frame();

}

4. 协议解析与处理
在主循环中,eMBPoll()函数不断检查是否有完整的 Modbus 帧:


while (1) {

/* 采集和更新数据 */

HAL_ADC_Start(&hadc1);

uint16_t adc_value = HAL_ADC_GetValue(&hadc1);

// ... 更新寄存器 ...

/* 处理Modbus请求 */

eMBPoll();

HAL_Delay(10);

}

eMBPoll()的核心逻辑:


eMBErrorCode eMBPoll( void )

{

eMBErrorCode eStatus = MB_ENOERR;

eMBEventType eEvent;

/* 检查是否接收到完整帧 */

if( xMBPortEventGet( &eEvent ) == TRUE )

{

switch ( eEvent )

{

case EV_FRAME_RECEIVED:

/* 处理接收到的请求 */

eStatus = eMBProcessRxFrame();

break;

case EV_EXECUTE_COMPLETE:

/* 发送响应 */

eStatus = eMBSendTxFrame();

break;

default:

break;

}

}

return eStatus;

}

5. 回调函数处理请求
当协议栈解析出有效的 Modbus 请求后,会调用相应的回调函数:


/* Modbus回调函数 - 处理读输入寄存器请求 */

eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) {

// 检查地址范围

if ((usAddress >= REG_INPUT_START) && (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS)) {

// 将寄存器数据复制到响应缓冲区

iRegIndex = (int)(usAddress - REG_INPUT_START);

while (usNRegs > 0) {

*pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] >> 8);

*pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] & 0xFF);

iRegIndex++;

usNRegs--;

}

return MB_ENOERR;

} else {

return MB_ENOREG; // 无效地址

}

}

三、RS485 方向控制的实现
在你提供的代码中,RS485 方向控制是隐式实现的。在 FreeModbus 库中,通常通过以下方式控制:


// 在portserial.c中实现

static void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )

{

if( xRxEnable )

{

/* 使能接收,关闭发送 */

HAL_GPIO_WritePin(DE_RE_GPIO_Port, DE_RE_Pin, GPIO_PIN_RESET);

}

else if( xTxEnable )

{

/* 使能发送,关闭接收 */

HAL_GPIO_WritePin(DE_RE_GPIO_Port, DE_RE_Pin, GPIO_PIN_SET);

}

}

这个函数会在以下时机被调用:

接收到完整帧后,切换到接收模式
准备发送响应前,切换到发送模式
发送完成后,切换回接收模式
四、完整的通信流程示意图

上位机发送请求 STM32响应

┌─────────────────┐ ┌─────────────────┐

│ 发送Modbus请求 │───RS485───► │ UART接收数据 │

│ (功能码04) │ │ 存入接收缓冲区 │

└─────────────────┘ └─────────┬───────┘





┌─────────────────┐

│ 检测帧结束 │

│ (3.5字符空闲) │

└─────────────────┘





┌─────────────────┐

│ 解析Modbus帧 │

│ (校验、地址、 │

│ 功能码检查) │

└─────────────────┘





┌─────────────────┐

│ 调用回调函数 │

│ (读取寄存器数据) │

└─────────────────┘





┌─────────────────┐

│ 构建响应帧 │

│ (CRC校验) │

└─────────────────┘





┌─────────────────┐

│ 切换到发送模式 │

│ (DE/RE=1) │

└─────────────────┘





┌─────────────────┐

│ 通过UART发送 │

│ 响应数据 │

└─────────────────┘





┌─────────────────┐

│ 切换回接收模式 │

│ (DE/RE=0) │

└─────────────────┘

五、关键总结
数据接收:通过 UART 中断自动接收,存入协议栈缓冲区
帧检测:通过空闲时间检测 Modbus 帧边界
协议解析:检查从机地址、功能码、CRC 校验
回调处理:根据请求类型调用相应回调函数
响应发送:构建响应帧,控制 RS485 方向,发送数据
在你的代码中,这些细节都由 FreeModbus 库处理,你只需要关注数据采集和寄存器回调函数的实现。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_62383467/article/details/149253625

LOVEEVER 发表于 2025-7-29 23:33 | 显示全部楼层
Modbus RTU标准协议
updownq 发表于 2025-8-20 08:08 | 显示全部楼层
使用 Modbus Poll              
pl202 发表于 2025-8-20 15:07 | 显示全部楼层
处理接收到的数据帧,合理管理收发缓冲区,防止数据溢出
lihuami 发表于 2025-8-20 15:25 | 显示全部楼层
实现Modbus RTU协议栈:包括地址识别、功能码处理、数据封装与解封装、CRC校验等功能
mnynt121 发表于 2025-8-20 15:58 | 显示全部楼层
合理安排任务优先级和中断处理              
timfordlare 发表于 2025-8-20 16:19 | 显示全部楼层
Modbus RTU 是一种基于串口(RS485)的主从通信协议
sheflynn 发表于 2025-8-20 17:37 | 显示全部楼层
提取寄存器起始地址和数量,读取对应数据。
claretttt 发表于 2025-8-20 19:51 | 显示全部楼层
执行读写寄存器等操作,构建响应数据帧
cashrwood 发表于 2025-8-20 20:24 | 显示全部楼层
收发方向控制引脚(如RE/DE)设置正确,以实现半双工通信
beacherblack 发表于 2025-8-20 22:55 | 显示全部楼层
对非**能码、地址越界等返回异常响应
mollylawrence 发表于 2025-8-21 15:10 | 显示全部楼层
帧与帧之间需大于3.5个字符时间作为分割
updownq 发表于 2025-8-21 15:38 | 显示全部楼层
适配 STM32 的轻量级 Modbus 协议栈,支持 RTU/ASCII 模式。
lihuami 发表于 2025-8-21 16:49 | 显示全部楼层
时钟精度(误差≤±1%),避免因漂移导致CRC校验失败。
louliana 发表于 2025-8-21 17:32 | 显示全部楼层
校验帧地址是否匹配本机地址。              
benjaminka 发表于 2025-8-21 18:38 | 显示全部楼层
长距离通信时两端加120Ω电阻              
sanfuzi 发表于 2025-8-21 18:59 | 显示全部楼层
将 Modbus 寄存器映射到实际变量
primojones 发表于 2025-8-21 19:59 | 显示全部楼层
定时器需精确检测帧间隔,避免误判
linfelix 发表于 2025-8-21 20:21 | 显示全部楼层
测试Modbus RTU通讯。              
i1mcu 发表于 2025-8-21 20:47 | 显示全部楼层
校验CRC,提取有效数据              
您需要登录后才可以回帖 登录 | 注册

本版积分规则

131

主题

4323

帖子

1

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