首先描述问题,是我一同事而非我本人遇到的,公司让我协助他查找问题。
客户是卫通地面站,主控电脑使用的是麒麟系统,通过UDP和我们的设备进行网络通信,客户每间隔一秒钟发送一次状态查询指令,一天的时间内会出现3到5次设备不响应指令的状态,客户判定为丢帧。通过查看客户的通讯日志,发现不响应的时间竟然差不多都是40秒钟以上,40秒后恢复了响应。
情况诡异。一开始同事怀疑是网口模块的问题,毕竟客户那里发的比较频繁,有可能说是模块收发的数据太多,导致了硬件异常,模块接收发送失败。还有某研究所的一哥们提醒说他觉得moxa的网口模块和麒麟系统不兼容,容易出问题。。。对这样的提议我表示怀疑,首先moxa的网口芯片肯定都是经过大量的测试的,不会说有问题就生产销售了。对麒麟的操作系统我虽然不了解但是我知道你网络通信出了你的网关就和你本身没关系了,跟你接什么样子的网口模块还有关系,真是让人不禁莞尔。
对未知故障的猜测人们往往会向对自己有利,与自己无关的一方面想,这是人的弱点也是本性。但是抱着胡乱猜疑的态度去撇清自己的关系,实在不是科研人员的素养。
继续进行问题分析,我找了一块板子进行测试用,用网口助手给板子发数据,时间是1s发一次,短时间内并没有看到什么异常现象,于是加快发送的速度,改为500ms发一次,现象暴露了,出现了丢帧问题,一分钟的时间内都丢了几帧,这个丢帧率也是真的太高了吧。但是并没有出现客户所说的连续四十秒无响应的问题。
那么首先查找这个丢帧的问题吧。其实问题很隐蔽,所以在我调快发送速度的时候也就很容易出现了。
我同事他在中断里接收数据,收到数据后计数变量index增加,当接收数据为帧尾标志的时候(0x7E),便会产生一个标志位,一帧的长度最短在24个字节。他在主程序里判断到这个标志位后呢,便把index变量清零。
这样的操作方式在低频率通信时一点问题也没有,但是当通信数据增多,这样带来的问题就是,当接收完一帧数据后,主程序里还没来得及判断,又来了数据,index在之前的基础上自增,这样这一帧数据他看第【0】字节不是帧头,第【23】字节不是帧尾,帧头不对帧尾也不对就不做反应,直到index大于缓存区长度64后,再清零。
为了解决这个问题,我让他开辟了一个FIFO,数据来了,先存进FIFO了,这样不管来多少帧数据,主程序里都能一一判断到,无非是实时性不好,但是不会丢帧。
其实在找第一个bug的过程中,我就发现了一些端倪,就是每次我给板子重启,网口模块一般在30s左右的时候才能正常接收到数据并能够发送,于是我不得不猜测,是什么问题导致了网口模块发生了硬件故障从而重启了么?是第一个BUG引起了网口模块硬件异常从而重启了吗?未必不可能,但有些牵强,因为在上面的测试过程中我确实没有遇见过客户说的现象,也即网口模块重启的现象。而且时间似乎也并不能够对的上。
阴谋论的迷雾再次笼罩了我的大脑。会不会真的是客户那里有问题呢?这时候我们和客户进行了一次通话,客户说他们是两个线程在发数据,一个线程发查询状态,一个线程发控制命令。我问,如果两个发送命令撞帧了呢,会产生什么后果吗?
客户思考了一会说,有可能,但我们站上其他厂商的设备没有出现问题,我还很忙,先挂了。
大写的尴尬。冷静下来思考一下,从TCP/IP协议的多层结构开看,从一个电脑出来的数据,即便是两个线程发,协议栈也不会把两个数据揉在一起,因为网络协议栈只有一个,一个线程在发的时候另一个一定会阻塞。不可能两个同时调用,因此从技术层面来说撞帧实现不了。
此时怀疑是stm32产生了硬件异常,进入了中断,从而导致了看门狗超时让设备重启,这样的话,时间就能够对的上了。
那就在所有可能出现硬件异常中断的地方打上断点。然而让程序运行了一段时间后就是不见进硬件异常中断。
再次压力测试,加快发送数据的频率,我提高到了每100ms发送一帧查询指令。一个不经意间一看,竟然出现了只发不回的情况!我激动的看程序的调试界面,赶紧在数据接收这里打了一个断点,咦,这数据也正在接收啊,那就很匪夷所思了:
1. 程序没有进入异常中断,证明不是看门狗引起的硬件异常从而导致重启;
2. 在出现问题后我赶紧用cmd去ping板子,发现一开始能ping通,但是过一会就故障了,然后又能ping通;
2. 程序能够接收到,但是好像没有发出来,还没等我去查为啥没发的原因,网口已经恢复正常了。又开始发了(40s时间过去了)
好不容易复现的现象稍纵即逝。怀疑是不是网口模块出现故障,只能收不能发,然后自己重启了呢?
我又陷入了不断的自我怀疑和相互怀疑之中。
“再加点压力!”我想。我直接把发送时间改成了5ms一次,结果现象非常容易复现了,这时候打断点,发现了根本问题:
网口模块根本没有重启,是单片机的重启带动了网口模块的复位,而且单片机也根本没有进入异常中断,而是说不断地进入了一个中断!
不断进入中断的原因被我发现了,第一个判断条件根本就没有通过,原因是定时器竟然是DISABLE的?
同事在接收到帧头数据的时候关闭了定时器2,(可能是想接收数据不被打断...)即调用了STOP_TIME这个宏;
但是关定时器的操作有点迷:
#define START_TIME RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);TIM_Cmd(TIM2, ENABLE)
#define STOP_TIME TIM_Cmd(TIM2, DISABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , DISABLE)
使能/失能定时器的时候,干嘛还给RCC时钟树使能/失能呢?这样导致的问题就是,我在执行START_TIME时,被STOP_TIME中断,导致时钟树是关闭的,但定时是开启的,开启定时产生了计时,溢出后产生中断,但是被判断定时器是失能 的,这就导致中断清除不掉 ,但是串口又能接收数据(串口优先级比定时器高),同时看门狗一直喂不了狗,导致重启。。。。
问题找到后,同事去给客户重新烧录了程序,经过了一天的观察,问题再也没有复现,悬着的心终于落下了...