c语言串口 writefile返回997_如何写一个健壮且高效的串口接收程序?(五)

上篇文章介绍了串口接收遇到的互斥锁释放问题,今天说说串口数据帧检查问题。


你是否会对接收的数据进行检查?如果不进行检查会发生什么?

我们知道一帧数据中,每个部分都有各自的含义,甚至有些部分可能在某些数据帧中不存在,比如数据域部分,我们需要根据长度信息来判断数据域部分是否存在。

但是你能保证你所接收的数据都是准确的吗?你能确保在工作环境下不会因为各种干扰导致数据长度信息由 0x05 变成 0x85(最高位翻转)吗?,如果出现了会导致什么后果?

假设你采用 RXNE 中断方式来一个、一个字节的接收数据,分析如下:

因为是单字节接收数据,所以你需要把所有接收的数据当成数据流,根据帧头信息来确定帧的开始,一旦确定帧头信息之后,你就可以根据接下来的一系列数据保证一帧数据的结束,同时开始新帧的接收……

233e58ee2aaeb1cfa849bdc18e16a5fc.png

初看这个接收流程没有问题,但是真的如此吗?

但是就像前面所说,你能保证你的数据没有问题吗?如果说你接收到一个长度信息,本来是 0x05,但是最终接收的数据是 0x85,这就意味着你接下来的数据域的长度是 0x85,根据你的接收流程,你需要再接收 0x85 个字节之后,才能判断校验字节是否正确。

可能你会说,虽然你的长度信息由 0x05 变成了 0x85,之后接收校验过程肯定是失败的,那么这帧数据就会被接收程序丢弃,从而导致接收程序进入重新寻找帧头的流程,这个过程不是挺正常的吗?按理说上述异常情况是能被接收流程处理掉的。

那么首先确认一点,上述异常能被接收流程处理吗?

答案是能!

既然上述异常是能被接收状态机处理的,那么还会有什么问题?

问题就出在这个错误数据本身!

因为你是根据错误数据来决定接下来需要接收多少数据,而一般来说,接收缓存大小设置为最大帧的长度,那么就出现一个问题,你的缓存够你接收 0x85 个字节吗?

如果说你开辟的接收缓存空间很大,足够接收这么多数据,那么就算遇上以上情况,也是没有任何问题,但是万一你比较节省资源,缓存不够大会出现什么情况?

这就涉及到内存分配问题:

714dd6ad9aea0b1102946b07d4ae74c3.png

你的串口缓存一般在 Data 区域,一旦你接收的数据超出了你开辟的空间,那么必然导致缓存空间溢出!

那么缓存空间溢出会导致什么危害?

我们通过上图可以知道,一旦缓存溢出,必然导致该缓存周围的数据出现异常(数据被篡改),如果你的其它代码刚好需要这个数据作为重要参考,而你在使用的时候又没有对这个数据的有效性进行检查,那么可能导致另一个灾难性后果,而这个后果又导致了其他后果,从而导致雪崩效应。

而你修复这个 bug 时,你以为修复了,但你只修复了表面,真正内在 bug 还存在!

所以,千万别太相信内存中的数据,每一个数据的输入都要进行严格检查,这个数据可以错误,但是不能导致程序崩溃!

所以千万别写能篡改别人数据的代码,这是很危险的事情,也是很难解决的 bug,因为你不知道它会在什么时候篡改哪里的数据!

再假如你的接收缓存放在栈中了呢(稍微有 C 语言常识的程序员都不会把串口接收缓存放在栈中,但鱼鹰偏偏遇到过这种代码,而为了解决这个 bug 整整花了一星期,这还是在 bug 复现率高的情况下)?

根据前面的图可知,栈一般存放在高地址,并且一般栈生长方向为向低地址生长。如果出现上述情况(接收的数据大于开辟的栈缓存空间)会发生什么?

栈帧被破坏!

50131ec57617007c5cbe67ec95cf9dea.png

灰色部分因为接收的数据太多,导致原本存在的栈数据被串口的接收的数据修改了(注意篡改的数据可能不是连续的,因为每一次进入时,开辟的那部分栈空间可能都不在同一个地址),假如这个数据是保存返回寄存器 LR 的,那么必然导致返回错误,极可能触发 HardFault 中断!

那么有什么办法解决栈被破坏的问题?

最有效的方式鱼鹰觉得是使用 ITM,如果无法在线调试,可以尝试 DMA 循环传输 PC 指针值(但是如何得到这个值?毕竟这个寄存器本身是没有地址概念的)到一块内存中,这样就可以得到最后正常执行的代码地址,从而定位错误代码的位置。

如果单片机不支持这些功能呢?鱼鹰现有的知识体系好像无法解决,只能佛系调 bug 了(看和 bug 之间的缘分),囧。

前面说了由于外部工作环境导致数据长度信息错误从而出现数组溢出这种情况,如果说你保证工作环境非常好,不可能出现这种干扰,是否还会出现问题?

当然会!

前面分析了外在原因,现在分析内在原因,你的接收程序能保证及时接收发送端发送过来的数据吗?如果不及时接收数据会出现什么问题?

我们知道,一个系统一般都有很多中断需要处理,如果说你的接收程序的中断优先级不是最高的,那么很可能出现接收程序无法及时接收的情况,即 RXNE 中断来临时,因更高优先级中断需要处理,而且处理时间较长,那么就会出现当前接收的字节因为没有接收完成而被后续的数据冲掉,即出现 ORE(溢出错误)。

这样会导致什么问题?

8cf03a928c615d5b8140c4b6de534faf.png

数据域信息(也可能是校验值等数据)当成了长度信息(为什么只讨论长度,而不讨论功能字之类的数据,难道他们不会出现 ORE 的情况吗?),这样一来,如果这个数据很大,接收程序就会以为接下来还需要接收很多数据才能完成一帧的接收,导致后果和前面分析的数据干扰一样严重。

那么采用 RXNE 接收方式时该怎么解决这种问题?

检查长度信息的合理性,只要长度信息不会导致缓存溢出即可。

a340ff1844aee9df82dc413a966d2659.png

但是上面的解决方案会导致什么问题?

因为你的程序设计问题(采用 RXNE 接收导致不能及时处理),使得原本能接收的数据无法及时处理(DMA 可以及时处理),最终使得当前这一帧数据无法正常接收(如果错误的长度信息够大的话,还有可能接下来很多帧数据都无法接收),这你能接受吗?

但是采用 DMA 为什么就不会有上述问题,除了 DMA 能自动接收数据提高效率之外,还有一点就是它不根据接收的数据来判断接下来还需要接收多少数据,而是根据设定的接收数据长度来接收的(如果加入 IDLE 中断,可以提前结束接收工作),这就避免了上述的缓存溢出接收不及时问题。

最后再分析上述接收的另一个问题,那就是一帧数据中可能出现没有数据域的情况,这种情况该怎么处理?

只要根据长度信息分开处理即可。如果不对没有数据域的情况分开处理,那么你接收的下一个数据直接就是校验值,而你的接收流程却认为这是数据域的数据,必然导致校验失败。

现在总结使用 RXNE 方式接收的几个问题:

1. 缓存溢出。

缓存溢出有两种可能,第一种就是环境干扰导致长度信息出错,从而出现缓存溢出情况;第二种情况就是因为接收不及时,导致数据错位,如果刚好是长度信息错误,并且这个长度信息太大,而你的代码未对长度进行检查,那么也会出现缓存溢出 bug,而这种 bug 一旦出现,很难发现。所以在代码中对数据的合理性检查是非常有必要的一件事

2. 中断及时处理。

如果中断不及时处理,会导致数据错位,轻则丢失至少一帧数据,重则缓存溢出!

3. 状态机是否需要接收数据部分。

由于数据帧有可能没有数据域的情况,所以必须区别处理,保证代码接收的准确性,否则有可能把校验值当成数据了,这样必然无法通过校验,这一帧数据必然会丢失!


下集精彩,串口空闲与通信量,喜欢的话,就来关注鱼鹰吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值