STM32 中实现 Modbus RTU

发布于:2025-07-12 ⋅ 阅读:(22) ⋅ 点赞:(0)

在 STM32 中实现 Modbus RTU 通信时,数据接收和处理是由底层驱动和协议栈共同完成的。让我详细解释这个过程:

一、通信实现的核心机制

STM32 接收 RS485 数据的过程涉及三个关键组件:

  1. 硬件层:RS485 转换器将差分信号转换为 UART 的 TTL 电平
  1. UART 驱动:STM32 的 UART 外设接收数据并触发中断
  1. 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 总线上有数据传输时:

  1. UART 接收数据:RS485 转换器将差分信号转换为 TTL 电平,STM32 的 UART 接收数据
  1. 触发接收中断:每个字节接收完成后触发 UART_RX 中断
  1. 数据存入缓冲区:中断服务程序将接收到的字节存入 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);

}

}

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

  1. 接收到完整帧后,切换到接收模式
  1. 准备发送响应前,切换到发送模式
  1. 发送完成后,切换回接收模式

四、完整的通信流程示意图


上位机发送请求 STM32响应

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

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

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

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

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

│ 检测帧结束 │

│ (3.5字符空闲) │

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

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

│ 解析Modbus帧 │

│ (校验、地址、 │

│ 功能码检查) │

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

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

│ 调用回调函数 │

│ (读取寄存器数据) │

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

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

│ 构建响应帧 │

│ (CRC校验) │

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

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

│ 切换到发送模式 │

│ (DE/RE=1) │

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

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

│ 通过UART发送 │

│ 响应数据 │

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

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

│ 切换回接收模式 │

│ (DE/RE=0) │

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

五、关键总结

  1. 数据接收:通过 UART 中断自动接收,存入协议栈缓冲区
  1. 帧检测:通过空闲时间检测 Modbus 帧边界
  1. 协议解析:检查从机地址、功能码、CRC 校验
  1. 回调处理:根据请求类型调用相应回调函数
  1. 响应发送:构建响应帧,控制 RS485 方向,发送数据

在你的代码中,这些细节都由 FreeModbus 库处理,你只需要关注数据采集和寄存器回调函数的实现。


网站公告

今日签到

点亮在社区的每一天
去签到