64位程序怎么判断指针是否有效_串口接收程序遇到的那些问题

上两篇文章介绍了设计串口协议的基础知识,接下来的几篇将介绍在编写调试串口代码时可能遇到的一些问题。


以下内容不会用太多的笔墨描述如何写发送、接收函数,而是重点关注串口接收过程中可能遇到的一些问题,如果说描述到了发送、接收函数,别会错意,顺带的。

以下大部分问题都是因为采用 RXNE(接收不为空)中断方式导致的问题,只有一个问题是鱼鹰从前没有考虑到,也是 IDLE + DMA 方式不可忽略的问题。

这就是为什么鱼鹰建议采用 IDLE + DMA 的原因,不仅是因为效率问题,更因为它能避免很多问题,当然水平足够高的话,采用 RXNE 也是完全("完全"就未必,里面有一个问题是 RXNE 方式难以避免的问题)没有问题。

事实上,即使鱼鹰采用 RXNE 方式接收数据,也能避免以下大部分的问题,因为鱼鹰的基础足够扎实,会在一开始编写代码的时候自然而然避免一些问题的出现。

但是看完以下内容后,相信各位道友写出一个高效且健壮的串口接收程序根本不是问题,因为这就是所谓的经验啊。

1、 传入参数指针

前面鱼鹰已经提到了需要一个指针作为函数的参数,这里说说这个指针问题。

我们知道,为了维护方便,也是为了节省空间,一般都会将类似的功能整合成一个函数,比如串口经常要用的发送、接收功能,但是所发送的数据内存空间可能就处于五湖四海了,他们通过指针来指向将要发送的数据。

1d27e77c208801be739398173c6fcd56.png

为了节省 RAM 空间或者其它不为人知的原因,传递给发送函数的指针就是实际发送数据的地址,并且在计算校验值的时候也是直接使用这个地址进行校验计算,然后采用循环查询的方式发送数据,这样一来,就不必拷贝一个数据的副本进行发送,而是直接从数据源的地址进行发送,节省了部分 RAM 空间。

但是这样真的好吗?

你是否考虑过在计算数据帧校验值的时候,数据源改变了的问题呢?

比如说你采用和校验,数据一开始是 0x55,计算数据帧的校验和值为 tx_sum,然后被中断程序或者 DMA 修改了这个数据源,变成了 0xAA,此时你再使用这个数据地址进行发送,接收端接收到了 0xAA,接收端计算校验和的时候是 rx_sum,那么 rx_sum 必然不等于 tx_sum,然后接收端就认为该数据帧是错误的,然后丢失这帧数据,而这种情况是比较少见的,但确实是会偶尔出现接收错误的情况(当时发现这个问题时始终不得其解,明明我发送的是这个校验值,为什么你计算的校验值是另一个?开始怀疑是校验函数的问题,但其他数据帧计算时没有问题,只有一种数据帧会出现问题,然后鱼鹰怀疑是数据源的问题,是的,鱼鹰很快就怀疑数据源的问题,但当时验证时,只改了校验那部分地址,发送时的地址还是使用源地址,导致问题还是没解决,过了好久之后才发现这个发送地址没改,囧。所以说,即使你的思路是对的,但如果你解决时错了,问题也很严重)。

如果说接收端(从机)具有重发机制,那么问题不是很大,丢失一帧数据而已,再重发就是,但事实是,一般串口设计成主从模式,主机会在没有接收到从机的应答数据时会进行重发,但是从机一般不会主动重发数据的,它无法判断主机是否成功接收,而从机一般会在成功发送完数据后开始清除一些标志位(比如键盘按键数据清空,不然主机下次获取按键信息时还是同一个按键数据),事实上这个动作必须在对方成功接收才能进行(否则这次按键信息就丢失了),从这个角度来看,我们必须设计一个机制用于判断主机是否成功接收。

I2C、CAN 总线都有应答信号,但这是这些是总线自带的特性,我们不可能在接收到一个字节后发送一个应答信号给主机,那么是否有其他办法呢。

人们很容易想到的一个办法就是在主机收到正确数据后,主动发送一帧专用数据帧用于清除这个标志(这个帧和普通帧一样,所以可以确保主机数据能准确送达从机,因为如果超时没有送达,会触发重发机制)。这样只要在获取完这帧数据后,再额外的发送一帧数据用于对方确认即可,从机接收到后,即可开始着手清除一些标志位。

但这样会有一个问题,因为这种特殊的需要从机确认的数据包(其他类型数据不需要确认是因为如果主机没有正确收到数据还可以继续获取,获取的数据是一样并没有关系,但这种需要从机确认,一旦从机认为发送成功了,数据就被清除了这种情况就需要确认,典型的就是按键信息了),我们需要额外处理并占用发送带宽。这是鱼鹰不愿忍受的。

那么是否有更好的办法?

或许我们可以从 USB 协议中获得启发(这是在写这篇笔记的时候想到的,当时写按键板代码的时候没有想到过,但因为当时测试时传输成功率 100%,所以就放弃处理这种情况了)。

USB 协议是典型的主从机制,主机不主动获取数据,从机是无法主动发送数据的。那么从机是如何确定对方成功接收数据了呢?

一个 bit 的翻转位。

每当主机成功发送一帧数据后再发送下一帧数据时,就会翻转这个位,从机就可以根据这个位判断主机是属于重发数据(重发数据表示主机接收失败了)还是新数据了,这样从机就能从下一帧数据确定上一帧数据是否成功发送了。

而主机发送的数据是由从机发送应答包确定的,和上面的串口协议类似,所以这个方向的数据是没有问题的。

那么我们该如何重新设计这个协议呢?可以尽可能的不改变原来协议的情况下实现吗?

或许可以从功能字出发。

为了保证对功能字的原有定义保持基本不变,使用最高 bit 作为这个特殊的位,这个 bit 开始是 0,之后主机每接收一个从机应答数据就进行翻转,如果因为没有接收到从机的应答数据,就会使用相同的翻转位重复发送;而从机也根据这个 bit 来确定自己的上一帧数据对方是否接收(对比上一帧数据的翻转位),如果主机没有成功接收,就不清除标志位(之后主机会重发数据再次获取),否则清除标志位,。

因为是鱼鹰刚想到的,就不多说了,仅提供一个思路。

现在回到指针那块的问题。

现在已经知道,如果你在计算校验和与发送的过程中出现源数据改变的情况,就会导致数据帧校验失败,那么有什么解决办法?

如果说你坚持使用查询方式发送来节省部分空间,那么只要在计算校验值之前拷贝一份源数据,然后用这份数据计算并发送即可。

另一种方法就是,直接把整帧数据拷贝到一个数据缓存中,使用 DMA 发送。

现在还有一个问题,那就是如果我想发送一个数据域为空的数据该怎么发送?

一般来说,在使用指针的时候,不会使用 NULL 空指针,但是在数据为空的情况下,就需要使用 NULL 指针了,并长度设置为 0,这个时候在检查指针的时候,不能看到空指针就退出函数,还要判断长度信息,当长度为 0 时在打包时就不拷贝源数据,但最终还是要发送数据帧的(当时别人写的代码将指针和长度判断同时放到了 for 循环的条件里面,鱼鹰觉得效率太低,导致修改代码是没考虑指针为空的情况,所以导致了一个小 bug)。

下集精彩, 串口缓存互斥锁释放顺序,喜欢的话记得关注鱼鹰哦!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值