在使用STM32F207这一款单片机调试串口的时候,使用两种不同的中断接收方式:帧中断IDLE和字节中断RXNE,会出现一些神奇的事情,只要我们认真地分析是可以揭开BUG后面隐藏的真相。其中IDLE中断也叫作串口空闲中断。 * n( N U8 ]- i) U; r% z/ P 如图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 硬件连接框图 + G- t2 |: t/ \- C& L! }; i 1.2 玄学的BUG 前期自测:因为没有MASTER设备,所以使用USB-485模块模拟MASTER设备,使用XCOM串口软件作为上位机发送取数据指令以及清零指令。单独测试读数据命令时,定时20ms,SLAVE每次都回返回数据无丢帧。 , L# p& N& C- k, a3 L: }) N) @; y8 f- X 因为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帧中断。 " v; U9 \+ y4 k# w; F% j9 w 中期联调:当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 错误现象 % z5 W7 D4 O7 s 因为出现了玄学现象,为了复现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。
使用Debug发现了一次帧中断出现了“三包数据”(这里的“三包数据”指的是本应该是三包数据的,但是却被识别成了一包数据,所以这里带引号的三包数据实际上按照一包数据接收的,同理“两包数据”),本来是一包数据就会触发一次帧中断的,也就是说本应该触发三次帧中断的只触发了一次帧中断如图4所示。也有出现一次帧中断接收“两包数据”的,当时只保存了示波器抓到一次帧中断接收“两包数据”的波形,没有Debug时的RS485_RX_BUF截图。
图4 一个帧中断接收“三帧数据” ' M+ F7 i( s! D. d0 z5 r2 ~3 B7 ~' Y5 M3 N1 b4 Q 3 W1 B4 D* z; }. ?* M5 H& _ 1.3 帧中断触发条件/ H$ V* {! K, M2 C! K y 中断产生条件:STM32的帧中断是IDLE中断,当接收到一帧数据,就会产生IDLE中断。帧中断常被用于接收不定长度字节数据。
中断产生条件:STM32的字节中断是RXNE中断,当接收到1个字节,就会产生RXNE中断。字节中断可以用来接收定长字节数据,比如针对上面的AA 09 53 00 00 00 00 00 22 05命令,每接收到一个字节数据,触发一次RXNE中断,RxCount++、保存接收到的这一字节数据到RXBuffer并指针加1,当接收到的数据计数到10(10时约定好的命令)时,判断RXBuffer是否是约定的命令,如果是则进行Response。 5 i' w# P+ A7 D/ Q ; r( h2 Q( D- `" b7 Y 2.1 复现BUG:一个帧中断“2包数据”?4 ?" _# c3 h, ^) N; j & c0 f; I4 G* i$ {# Y& ^7 N 发现了是帧中断导致的一个帧中断接收了多帧数据,我们从事故的一开始分析一个帧中断是如何导致接收到“两包数据”的,首先是MASTER发送给SLAVE1的读数据命令为事件的起始点,从MASTRE、SLAVE1、SLAVE2、MONITOR四个角度看485总线上都发生了,因为MONITOR是不参与命令交互的,所以以MONITOR为视角看到的485总线上的信号时序图是出现在485总线上的所有信号的真实时序,如图5所示,其中
图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}tByte=(1s/9600)⋅9=0.9375ms,所以要区分开来两帧数据如第1帧和第2帧,那么两帧数据之间过渡电平的持续时间t1应该大于t Byte t_{\text {Byte}}tByte,这样才能识别为两帧数据;按照这个来发送一位数据需要时间t bit = ( 1 s / 9600 ) = 0.1042 m s t_{\text {bit}}=(1 s / 9600)=0.1042 \mathrm{ms}tbit=(1s/9600)=0.1042ms,怎么区分是两个字节数据(查资料后补充)。
8 |" u7 F. i8 y
2 I4 }4 _7 D( {. O 图6 示波器捕捉的两帧数据合为一帧 $ t. a6 E( T, S7 s' ?* t8 e+ V9 R4 x6 p" _* k0 Y 图7 发送一包数据多久可以触发IDLE中断 & r8 f, P, G2 p7 J" E4 P- {3 N2 P5 N9 n 根据两包数据合为一包数据的原因,推测三包数据合为一包数据的485总线上的时序图如图8所示,因为没有捕捉到波形,所以这里只是猜测,没有得到验证,后续想办法模拟验证吧。推测是t2和t3太短,导致S1→M回数据与M→S1清零与M→S2读数据这三帧数据合并为了一帧数据。因为SLAVE设备是使用帧中断进行接收的,所以t1=t4>t Byre t{\text {Byre}}tByre即反应比较慢,他们回数据信号不会和前面的读数据信号混到一起成为一帧数据,即1和2是不会混为一帧数据,4和5不会混为一帧数据,只有使用字节中断的会导致反应比较快如t2<t Byre t{\text {Byre}}tByre,即2+3+4合为了一帧数据。
图8 假设的三包数据合为一包时序图 * l5 [5 {# Q6 P5 K, m$ ~: Ru9 y4 d9 Z3 u* a* r8 l- J 2.2 项目总结思考- K2 V! e3 X1 j) e 因为不知道对方MASTER设备的接收中断方式使用的是帧中断还是字节中断,所以提前确认好对方设备或者说项目中的测试设备及对接设备的通信具体形式等细节是十分有必要的,否则单独测试时双方都是OK的,等到联调的时候就会出现一些玄学的BUG。6 A3 `8 B0 C+ M' o$ Q |