打印
[应用相关]

STM32串口:字节中断与帧中断不同导致的BUG

[复制链接]
741|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
gwsan|  楼主 | 2021-9-6 13:08 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、问题背景
1.1 硬件连接框图
  如图1所示是简化的硬件连接框图,本来有18个SLAVE设备,问题的复现只需要两个所以只画了两个SLAVE设备。MASTER和SLAVE1、SLAVE2使用485总线进行通信,其中MONITOR是一个监听设备,用于监听所有在总线上的数据,监听数据并分析是在调试各类总线型通信时常用的手段,两个120欧电阻是首尾匹配电阻所以485总线的阻抗是60欧。

  其中SLAVE1、SLAVE2、MONITOR都是使用STM32F207+ADM2682扩展出来485,接收方式是使用帧中断;MASTER是对方设计的所以不清楚使用的主控芯片、中断方式是帧中断接收还是字节中断接收,通过图6可以分析出来应该是字节中断,因为MASTER反应时间t2是远小于SLAVE的反应时间t1。

  双方约定命令主要有两种:①、MASTER会定时20ms向SLAVE发送取数指令,SLAVE向MASTER返回传感器测试值Y;②、如果MASTER解析出来传感器测试值Y在[-X,X]范围内,MASTER会向SLAVE发送清零指令,SLAVE需要将Y值清零,SLAVE不需要返回任何数据。



图1 硬件连接框图

使用特权

评论回复
沙发
gwsan|  楼主 | 2021-9-6 13:08 | 只看该作者
1.2 玄学的BUG
  前期自测:因为没有MASTER设备,所以使用USB-485模块模拟MASTER设备,使用XCOM串口软件作为上位机发送取数据指令以及清零指令。单独测试读数据命令时,定时20ms,SLAVE每次都回返回数据无丢帧。

  因为MSATER和SLAVE之间的数据交互都是一包数据10个字节的,所以使用了串口的USART_IT_IDLE中断就是帧中断,它是当串口收到一帧数据或者说一包数据后产生的中断。以MASTER发送给SLAVE2的取数据指令包含10个字节:AA 01 52 00 00 00 00 00 52 85,当接收完0xAA时,会触发一次USART_IT_RXNE字节中断,而不会触发USART_IT_IDLE帧中断,当接收完0x85后,会触发USART_IT_RXNE字节中断和USART_IT_IDLE帧中断,所以这个命令会触发10次USART_IT_RXNE字节中断和1次USART_IT_IDLE帧中断。

  中期联调:当SLAVE传感器值Y不在[-X,X]范围时(蓝色部分),无异常现象;当SLAVE传感器值Y在[-X,X]范围时(蓝色部分),预期现象时会出现清零,即Y值会显示为0。但是实测出现了如图3所示的不清零情况,-0.7因为在[-X,X]范围内,所以-0.7会瞬间变成0,但是一直在0.7停留,并且状态灯一直是绿色的,所以通信是没有断掉的【有一次没有返回数据就会绿灯变成红色,并且通过MONITOR记录下的数据发现是每次都会返回数据的,所以MONITOR是很有必要的,用以BUG调试时的数据记录回看】,即MASTER的取数据命令都得到了SLAVE的反应。



图2 传感器值与阈值范围


图3 错误现象
  因为出现了玄学现象,为了复现BUG所以通过改变传感器的状态使传感器值反复经过[-X,X]红色线部分看看能不能复现故障。复现的一次情况是SLAVE1设备的角度在1.3度停止不动,并且不会清零。通过在线Debug查看MONITOR接收到的数据,发现一个帧中断接受到了30个数据,前十个字节数据是SLAVE1回的数据(其中解析出来的数据是1.3,属于红色线范围,会被MASTER清零),中间十个字节是SLAVE向SLAVE1发出的清零指令,后面十个字节是MASTER向SLAVE2发出的读角度指令。

  这里有个问题为什么不Debug SLAVE1设备呢?其实是Debug了的,只不过没有有效的收获,因为导致这个BUG的原因是SLAVE1+MASTER+SLAVE2一起导致的,如果Debug这三个中的任何一个单个设备,均不能复现这个BUG。

BB 00 52 01 66 66 A6 3F 68 EF 是SLAVE1回MASTER设备的数据指令,10个字节

AA 09 53 00 00 00 00 00 22 05 是MASTER发送给SLAVE1的清零指令,10个字节

AA 01 52 00 00 00 00 00 52 85 是MASTER发送给SLAVE2的取数据指令,10个字节

  使用Debug发现了一次帧中断出现了“三包数据”(这里的“三包数据”指的是本应该是三包数据的,但是却被识别成了一包数据,所以这里带引号的三包数据实际上按照一包数据接收的,同理“两包数据”),本来是一包数据就会触发一次帧中断的,也就是说本应该触发三次帧中断的只触发了一次帧中断如图4所示。也有出现一次帧中断接收“两包数据”的,当时只保存了示波器抓到一次帧中断接收“两包数据”的波形,没有Debug时的RS485_RX_BUF截图。



图4 一个帧中断接收“三帧数据”


使用特权

评论回复
板凳
gwsan|  楼主 | 2021-9-6 13:09 | 只看该作者
1.3 帧中断触发条件
  中断产生条件:STM32的帧中断是IDLE中断,当接收到一帧数据,就会产生IDLE中断。帧中断常被用于接收不定长度字节数据。
  优点:因为IDLE中断是一帧数据/一包数据产生一次中断,然后读取寄存器里面存取的数据可以获得本次帧中断所接收的数据长度与数据内容,即使某一帧由于未知原因增加一个字节或者减小一个字节,可以根据接收到的数据长度和约定的不一致所以不采用这一帧数据,但是下一帧数据是不受前面错误帧数据的影响,这个是下面使用字节中断可能会出现的问题。
  缺点:当使用帧中断时,因为判断帧结束的标志需要默认电平持续的时间>发送一个字节所需要的时间,所以对于一帧数据的响应时间一定是大于一个字节数据信号的时间,即下文的t Byre t_{\text {Byre}}t
Byre
​       


1.4 字节中断触发条件
  中断产生条件:STM32的字节中断是RXNE中断,当接收到1个字节,就会产生RXNE中断。字节中断可以用来接收定长字节数据,比如针对上面的AA 09 53 00 00 00 00 00 22 05命令,每接收到一个字节数据,触发一次RXNE中断,RxCount++、保存接收到的这一字节数据到RXBuffer并指针加1,当接收到的数据计数到10(10时约定好的命令)时,判断RXBuffer是否是约定的命令,如果是则进行Response。
  缺点:使用RXNE中断的缺点显而易见,因为没有按照一帧数据一帧数据接收,一旦某一帧因为未知原因增加一个字节或者减小一个字节就会导致后续接收到的数据包是错位的,所以相应地需要增加相应措施解决这个问题。
  优点:这个优点是相对于帧中断来讲的,因为按照字节来接收到的,而单片机对于一个字节数据产生中断所需要的时间与t Byte t_{\text {Byte}}t
Byte
​       
暂时没有发现有关系(不知道与发送一位数据所需要的时间是否有关,t bit t_{\text {bit}}t
bit
​       
),所以其响应时间相比着帧中断会快很多。本文为了将“两包数据”中包含的命令解析出来,最后就是将帧中断改为了字节中断去接收,然后循环判断是否是约定的命令。
后面需要查找资料:485总线上数据格式,如何判定一位,一位数据与中间电平是否有关系


使用特权

评论回复
地板
gwsan|  楼主 | 2021-9-6 13:33 | 只看该作者
二、解决问题
2.1 复现BUG:一个帧中断“2包数据”?
  发现了是帧中断导致的一个帧中断接收了多帧数据,我们从事故的一开始分析一个帧中断是如何导致接收到“两包数据”的,首先是MASTER发送给SLAVE1的读数据命令为事件的起始点,从MASTRE、SLAVE1、SLAVE2、MONITOR四个角度看485总线上都发生了,因为MONITOR是不参与命令交互的,所以以MONITOR为视角看到的485总线上的信号时序图是出现在485总线上的所有信号的真实时序,如图5所示,其中

t1大小的影响因素:SLAVE1对M→S1读数据命令的反应时间
t2大小的影响因素:MASTER对S1→M回数据命令的反应时间,判定在阈值范围的红色曲线上,所以需要清零,如果是蓝色区域,则没有这一步。
t3大小的影响因素:因为t3位于M→S1清零和与M→S2读数据这两个命令之间,这两个命令都是由MASTER发送的,中间应该是没有时间差的。
t4大小的影响因素:SLAVE2对M→S2读数据命令的反应时间
过渡电平是485总线上没有数据时总线的默认电平,等于(Vh+Vl)/2,485的空闲是差分的所以应该是中间值同CAN与CANFD,TTL的空闲电平是逻辑高电平。
图6中t1前面的信号为一帧数据,记为第1帧;t1后面的信号为一帧数据,记为第2帧。对于图5,t1后面t3前面,“2+t2+3”是第2帧;t1前面即“1”是第1帧。


图5 两包数据合为一包时序图
  使用示波器捕捉的“两包数据”合为一帧的波形如图6所示,此时示波器的时间刻度是2.5ms(没拍好所以看不清楚了)。串口的波特率是9600,单片机经过芯片ADM2682将TTL电平转化为485电平,因为只是电平的转换,所以转换后的485信号的波特率依旧是9600,所以发送一位需要的时间是1s/9600。对于串口除了设置波特率外,我们还要设置停止位(1)、数据位(8)、奇偶校验位(无),所以发送一个字节需要t Byte = ( 1 s / 9600 ) ⋅ 9 = 0.9375 m s t_{\text {Byte}}=(1 s / 9600) \cdot 9=0.9375 \mathrm{ms}t
Byte
​       
=(1s/9600)⋅9=0.9375ms,所以要区分开来两帧数据如第1帧和第2帧,那么两帧数据之间过渡电平的持续时间t1应该大于t Byte t_{\text {Byte}}t
Byte
​       
,这样才能识别为两帧数据;按照这个来发送一位数据需要时间t bit = ( 1 s / 9600 ) = 0.1042 m s t_{\text {bit}}=(1 s / 9600)=0.1042 \mathrm{ms}t
bit
​       
=(1s/9600)=0.1042ms,怎么区分是两个字节数据(查资料后补充)。
  使用下面的代码测试串口在一包数据出现多久可以以触发IDLE中断,测试的结果如图7所示。代码示例如下,可以发现一包数据需要等待1ms的时间才会触发IDLE中断,后面可以通过改变波特率验证,波特率越高,等待时间越小。
  在MODBUS通信协议帧数据之间的停顿间隔**“3.5字符”定义:MODBUS通讯规定主机发送完一组命令必须间隔3.5个字符再发送下一组新命令,这个3.5字符主要用来告诉其他设备这次命令已经结束,3.5个字符的定义在波特率为9600的情况下,只要大于4.01ms即可,计算方法同上,只不过发送一个字节按照最大时间来计算所以发送一个字节需要t Byte = ( 1 s / 9600 ) ⋅ 11 = 1.1458333 m s t_{\text {Byte}}=(1 s / 9600) \cdot 11=1.1458333 \mathrm{ms}t
Byte
​       
=(1s/9600)⋅11=1.1458333ms,即停止位2、数据位8、有奇/偶检验一共11位**。

void USART1_IRQHandler(void)
{
        if(USART_GetFlagStatus(USART1,USART_FLAG_IDLE)!=Bit_RESET)//如果接收到1帧数据
        {
               PA10_High();//拉高GPIO PA10
        clear=USART1->SR;//读SR寄存器
        clear=USART1->DR;//读DR寄存器(先读SR寄存器再读DR,就是为了清除IDLE中断)                                               
                PA10_Low();//拉低GPIO PA10
    }       
}



图6 示波器捕捉的两帧数据合为一帧

图7 发送一包数据多久可以触发IDLE中断
  根据两包数据合为一包数据的原因,推测三包数据合为一包数据的485总线上的时序图如图8所示,因为没有捕捉到波形,所以这里只是猜测,没有得到验证,后续想办法模拟验证吧。推测是t2和t3太短,导致S1→M回数据与M→S1清零与M→S2读数据这三帧数据合并为了一帧数据。因为SLAVE设备是使用帧中断进行接收的,所以t1=t4>t Byte t{\text {Byte}}tByte即反应比较慢,他们回数据信号不会和前面的读数据信号混到一起成为一帧数据,即1和2是不会混为一帧数据,4和5不会混为一帧数据,只有使用字节中断的会导致反应比较快如t2<t Byte t{\text {Byte}}tByte,即2+3+4合为了一帧数据。



图8 假设的三包数据合为一包时序图


使用特权

评论回复
5
gwsan|  楼主 | 2021-9-6 13:33 | 只看该作者
2.2 项目总结思考
  因为不知道对方MASTER设备的接收中断方式使用的是帧中断还是字节中断,所以提前确认好对方设备或者说项目中的测试设备及对接设备的通信具体形式等细节是十分有必要的,否则单独测试时双方都是OK的,等到联调的时候就会出现一些玄学的BUG。
  我们要做到首先遇到BUG不要慌,BUG不会凭空产生,而是许多关键事件的连锁反应。其次判断BUG是人为失误还是设计有瑕疵,就需要我们去剖析那些BUG的细节,寻找线索。



使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

69

主题

3434

帖子

1

粉丝