| 
 
| 在 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
 
 
 | 
 |