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

野火STM32Modbus主机读取寄存器/线圈失败(一)-解决接收中断不触发的问题

接收中断不触发

前情提要

在自己的开发板上移植了野火的modbus主机程序。
野火主机程序移植
野火主机代码理解与使用

问题背景

我使用STM32显示板作为Modbus主机连接电脑,并在电脑上运行Modbus Slave软件。测试中发现,读取保持寄存器和输入寄存器均失败,但写入操作正常。例如,当我的板子作为主机发送读取从机1的40、41地址(其中预先写入了4000和6500)的请求时,Modbus Slave可以正确接收到请求帧:

Rx: 001205-01 03 00 28 00 02 44 03
Tx: 001206-01 03 04 0F A0 19 64 B1 97

这说明主机发出的命令没有问题。然而,在我的代码中,用于存储保持寄存器的数组 usMRegHoldBuf[][] 始终为0,未能更新。进一步排查发现,程序并未进入回调函数 eMBMasterRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)

后续测试表明,Modbus主机的接收中断从未被触发,因此初步判断问题出在Modbus主机的接收中断部分,可能存在配置或实现上的错误。


现象

在portserial_m.c中添加调试代码,变量tx_int_count递增,但是rx_int_count始终为0,说明没触发接收中断。

/* * Create an interrupt handler for the transmit buffer empty interrupt* (or an equivalent) for your target processor. This function should then* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that* a new character can be sent. The protocol stack will then call * xMBPortSerialPutByte( ) to send the character.*/
void prvvUARTTxReadyISR(void)
{/* 发送状态机 */extern volatile uint32_t tx_int_count;tx_int_count++;pxMBMasterFrameCBTransmitterEmpty();
}/* * Create an interrupt handler for the receive interrupt for your target* processor. This function should then call pxMBFrameCBByteReceived( ). The* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the* character.*/
void prvvUARTRxISR(void)
{/* 接收状态机 */extern volatile uint32_t rx_int_count;rx_int_count++;pxMBMasterFrameCBByteReceived();
}

解决

将portserial_m.c中的void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)进行修改。

void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*/if(xRxEnable){/* 串口2接收中断使能 */__HAL_USART_ENABLE_IT(&huart2,USART_IT_RXNE); #if defined(MODBUS_MASTER_USE_CONTROL_PIN)	/* 485低电平接收 */HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_LOW);#endif}else{/* 串口2接收中断关闭 */__HAL_USART_DISABLE_IT(&huart2,USART_IT_RXNE); #if defined(MODBUS_MASTER_USE_CONTROL_PIN)/* 485高电平发送 */HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_HIGH);#endif}if(xTxEnable){/* 串口2发送中断使能 */__HAL_USART_ENABLE_IT(&huart2,USART_IT_TXE); #if defined(MODBUS_MASTER_USE_CONTROL_PIN)/* 485高电平发送*/HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_HIGH);#endif}else{/* 串口2发送中断关闭 */__HAL_USART_DISABLE_IT(&huart2,USART_IT_TXE); #if defined(MODBUS_MASTER_USE_CONTROL_PIN)	/* 485低电平接收*/HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_LOW);#endif}
}

改成

void vMBMasterPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*/if(xRxEnable){#if defined(MODBUS_MASTER_USE_CONTROL_PIN)	/* 485低电平接收 */HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_LOW);/* 添加小延时确保485切换完成 */for(volatile int i = 0; i < 100; i++);#endif/* 串口2接收中断使能 */__HAL_USART_ENABLE_IT(&huart2,USART_IT_RXNE); }else{/* 串口2接收中断关闭 */__HAL_USART_DISABLE_IT(&huart2,USART_IT_RXNE); #if defined(MODBUS_MASTER_USE_CONTROL_PIN)/* 485高电平发送 */HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_HIGH);#endif}if(xTxEnable){#if defined(MODBUS_MASTER_USE_CONTROL_PIN)/* 485高电平发送*/HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_HIGH);/* 添加小延时确保485切换完成 */for(volatile int i = 0; i < 100; i++);#endif/* 串口2发送中断使能 */__HAL_USART_ENABLE_IT(&huart2,USART_IT_TXE); }else{/* 串口2发送中断关闭 */__HAL_USART_DISABLE_IT(&huart2,USART_IT_TXE); #if defined(MODBUS_MASTER_USE_CONTROL_PIN)	/* 485低电平接收*/HAL_GPIO_WritePin(MODBUS_MASTER_GPIO_PORT,MODBUS_MASTER_GPIO_PIN,MODBUS_MASTER_GPIO_PIN_LOW);#endif}
}

就可以触发了,rx_int_count递增。

原因分析

您遇到的问题非常典型,涉及到RS-485半双工通信中一个关键且容易忽视的细节:方向控制引脚(DE/RE)切换与UART中断使能的时序问题

问题根本原因分析

(暂不清楚,以下为ai的解释)
中断使能(__HAL_USART_ENABLE_IT)的操作在GPIO方向切换(HAL_GPIO_WritePin)之前。这会引发一个竞态条件(Race Condition):

  1. 使能接收中断的时刻:当调用 __HAL_USART_ENABLE_IT(&huart2, USART_IT_RXNE) 后,USART2的接收中断立即被打开。此时,USART硬件已经开始监听总线上的数据。
  2. 切换485方向的时刻:紧接着,代码才去设置GPIO引脚为接收模式(低电平)。从CPU执行完第一条指令到GPIO电平实际稳定、收发器切换到接收状态,存在一个极短但不可忽略的延迟。

在这个短暂的延迟窗口内,总线状态是不确定的:

  • 如果总线上恰好有数据(例如,上一帧数据的残留、噪声、或者其他设备的发送),USART的接收寄存器(RXNE)会立即被置位。
  • 由于接收中断已经使能,CPU会立刻响应并进入接收中断服务程序(ISR)。
  • 然而,此时485收发器可能还未完全切换到接收模式,导致它无法正确地将总线差分信号传递给USART,或者传递的是错误的电平。
  • 最终结果是,ISR读取到的可能是无效数据帧错误(FE)、或噪音。更常见的是,这个过早触发的中断会清空RXNE标志,但并没有读到有效数据。而当Modbus协议栈真正开始等待响应时,由于标志位已被清除,无法再次触发中断,导致接收超时。

修改后的代码为什么能工作?

修改后的代码调整了时序

  1. 先切换485方向:首先通过GPIO将收发器稳稳地设置为目标模式(接收或发送)。
  2. 添加短暂延时:使用 for 循环提供一个短暂的阻塞延时。这个延时确保了GPIO电平有足够的时间稳定下来,并且485收发器内部的电路已经完成了切换(通常需要几百纳秒到几微秒)。虽然用循环实现延时不是最优雅的方式(更好的做法是使用硬件定时器或精确延时函数),但它简单有效,解决了硬件切换的时序问题。
  3. 最后使能中断:在确保硬件已经完全准备好之后,再打开USART中断。这样,当中断使能时,总线状态和收发器状态都是稳定和正确的,只有在使能之后到来的新数据才会触发中断,从而避免了“旧”数据或噪音的误触发。

简单比喻:

  • 修改前:就像先打开麦克风的开关(中断使能),然后再告诉别人“现在你可以对我说话了”(485切接收)。在你告诉他之前,任何环境噪音(总线数据)都会被麦克风录进去。
  • 修改后:先告诉别人“现在你可以对我说话了”(485切接收),确保自己准备好了,然后再打开麦克风开关(中断使能)。这样录到的才是对方真正要对你说的话。

总结与建议

总结: 接收中断不触发的根本原因不是中断没打开,而是中断被过早地触发并在错误的时刻被处理掉了。修改后的代码通过调整GPIO操作和中断使能的顺序,并加入延时确保硬件稳定,解决了这个问题。

改进建议:

  1. 替换循环延时for循环延时受编译器优化和CPU频率影响,不精确。建议使用HAL_Delay(1)(延时1ms,对于485切换绰绰有余)或者查询硬件定时器来实现更可靠的微秒级延时。
  2. 检查中断优先级:确保USART中断的优先级设置合理,不会被其他高优先级中断长时间阻塞。
  3. 检查中断服务程序(ISR):确保在USART2的IRQHandler中正确处理了USART_IT_RXNE事件,并且清除了相应的标志位。
http://www.xdnf.cn/news/18842.html

相关文章:

  • 嵌入式-定时器的时基单元,自制延迟函数-Day21
  • AI驱动的前端性能优化:从监控到自动化修复
  • C# 字符和字符串
  • 《信息检索与论文写作》实验报告三 中文期刊文献检索
  • 【算法速成课1 | 题解】洛谷P3366 【模板】最小生成树 MST(Prim Kruskal)
  • GitHub 宕机自救指南:保障开发工作连续性
  • Android中点击链接跳转到对应App页面的底层原理
  • 信号线串扰仿真
  • 【C++】类和对象 --- 类中的6个默认成员函数
  • 达梦数据库-控制文件 (二)
  • 如何在开发工具中使用钉钉MCP
  • 数据结构:在堆中插入元素(Inserting In a Heap)
  • 深度学习-----详解MNIST手写数字数据集的神经网络实现过程
  • Magicodes.IE.Pdf 生成导出PDF文件 bytes Stream FileStreamResult 下载
  • MYSQL---存储过程
  • 能源行业数据库远程运维安全合规实践:Web化平台的落地经验
  • AI 暗战: 回声室攻击 —— 解锁大模型安全防御的隐秘战场
  • [Sync_ai_vid] 唇形同步评判器 | 图像与视频处理器 | GPU测试
  • 【RabbitWQ】基于 Java 实现轻量级消息队列(二)
  • 使用 ROS2 构建客户端-服务器通信:一个简单的计算器示例
  • 储能变流器学习之MPPT
  • 汽车盲点检测系统的网络安全分析和设计
  • k8s-容器化部署论坛和商城服务
  • 开源 | 推荐一套企业级开源AI人工智能训练推理平台(数算岛):完整代码包含多租户、分布式训练、模型市场、多框架支持、边缘端适配、云边协同协议:
  • PMP项目管理知识点-⑮预测型项目概念辨析
  • Web 自动化测试常用函数实战(一)
  • Unity自定义Inspector面板之使用多选框模拟单选框
  • 测试分类(超详解)
  • vue拖动排序,vue使用 HTML5 的draggable拖放 API实现内容拖并排序,并更新数组数据
  • 基于SpringBoot的社区儿童疫苗接种预约系统设计与实现(代码+数据库+LW)