当前位置: 首页 > ops >正文

STM32 中实现 Modbus RTU

在 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 库处理,你只需要关注数据采集和寄存器回调函数的实现。

http://www.xdnf.cn/news/15076.html

相关文章:

  • OneCode AI注解框架:让传统软件15分钟升级为AI原生应用
  • 从零开始搭建深度学习大厦系列-3.卷积神经网络基础(5-9)
  • 【Note】Linux Kernel 实时技术深入:详解 PREEMPT_RT 与 Xenomai
  • python+django/flask基于微信小程序的农产品管理与销售APP系统
  • 数据仓库:企业数据管理的核心枢纽
  • 20250710解决KickPi的K7的SDK编译异常:rk3576-android14.0-25250704.tar.gz【降低BUILD_JOBS】
  • OrCAD 24.1补丁005中文界面切换指南
  • RT-Thread 的 SCons 构建系统的语法、常用用法,并举例说明如何编写典型的 `Kconfig` 和 `SConscript` 文件
  • 解析几何几百年重大错误:将无穷多各异圆盘(球)误为同一点集
  • PyTorch Tensor 的创建与操作入门
  • TCP-与-UDP-协议详解:原理、区别与应用场景全解析
  • 使用SpringAOP自定义权限控制注解
  • UE5 Rotate 3 Axis In One Material
  • Android Studio 打 release 包 Algorithm HmacPBESHA256 not available 问题解决
  • Vue 中监测路由变化时,通常不需要开启深度监听(deep: true)
  • Linux中rw-rw-r--相关的访问权限讲解
  • android TabLayout 标题栏切换 事件拦截
  • 达梦数据库不兼容 SQL_NO_CACHE 报错解决方案
  • 三、神经网络——网络优化方法
  • Ansible:强大的自动部署工具
  • 线上事故处理记录
  • STM32单片机_3
  • Linux驱动开发(platform 设备驱动)
  • RV1126平台(Buildroot Linux)+ SunplusIT SPCA2688 USB摄像头 RTSP推流全流程复盘与问题解决记录
  • 对象序列化与反序列化
  • 快速将照片从三星手机传输到电脑
  • 节点小宝:手机图片备份至电脑功能实测体验
  • linux系统---ISCSI存储服务
  • GitHub信息收集
  • Flutter跨平台开发全解析