11530|15

7815

帖子

57

TA的资源

裸片初长成(中级)

楼主
 

通信程序基本问题:我们怎么处理完整数据帧的接收问题 [复制链接]

 
本帖最后由 辛昕 于 2014-8-11 08:27 编辑

我本来已经睡下了,虽然脑子里乱七八糟想着事情,但还是打算忍着不爬起来,以免又拖到很晚才睡着。
然而某刻我突然想起什么,拿着手机简单登陆了一下论坛。

无意看到曹世鹏的一篇博客,发现对于 串口通信(其实是任何通信),如何 判断 接收一个数据帧已经完成 这个 很基本的通信问题,可能很多新手还不清楚它的一个处理套路。
而我们在回答的时候,可能回答得过于简单,或者掺杂着其他更复杂情形的回答。
导致这个主要是两个原因,对于前者,我们总是习惯性错误以为他们和我们一样,对这种惯常的解决方式习以为常,其二,我们脑子里总是在想着我们自己面对的更加复杂的情况——
包括通信接收在内,任何事情都是如此,它有很一般的处理方法,甚至可能可以解决绝大多数情况遇到的要求,但如果想取得更好的性能和效果,哪怕只是看起来一点点改进,它所采用的方法都可能会复杂许多。
这类似于 纯度从 99%提高到 99.9% 和 从95%提高到99%,前者所需付出的努力远不是后者所能比的。

不管哪一种原因,我都决定,用一个实际的例子,包括 什么地方调用,什么地方开始接收数据,从零说起,只求能真正传达这种,其实在我们这些有一点经验的所谓老手里,惯常使用的处理手段和方法。

最新回复

在写串口通信这类问题的时候,总是想着各种情况,结果就是越想越觉得写出来的代码都有缺陷,但是实际使用过程中,并不会经常出现在写代码之前的那些”特殊情况“,代码也运行得很好  详情 回复 发表于 2018-5-25 23:36
点赞 关注(3)
个人签名

强者为尊,弱者,死无葬身之地

 

回复
举报

7815

帖子

57

TA的资源

裸片初长成(中级)

推荐
 
本帖最后由 辛昕 于 2014-8-11 01:47 编辑 我就以 串口为例子,也是我手机DIY里面的代码(因为一些小意外,我原来的GTM900模块的SIM卡座坏了,导致没法继续,新买的模块,淘宝卖家发货晚了估计最快也得明天下午才能到,所以这周末我基本上在干别的事,也就耽搁了)。 现在就发代码。 首先,是所有串口接收的最起源的地方。 那就是 串口中断接收,它接收回来的是一个 字节。当然,你也可以用查询,这是不要紧的。 反正不管如何,当你判断到(或者中断了),你,就收到了一个字节。 这个时候,你应该找一个 数组,把它存起来。 我的原始代码里 采用 了队列 来处理,但我不想再次把问题复杂化,所以我会稍微修改成一般的简单的用数组缓冲的方式。
  1. //这是我增加的简化的数组处理代码部分

    static U8 UartBuff[100]; 串口接收缓冲;

    static U8 RecvLength = 0; // 串口接收长度;

  2. // 串口接收中断服务程序 ---------- 这只是一个stm32 中断函数的格式写法,没有什么太特别的地方,总之,找到你能接收到串口数据(一个字//节)的地方就是了。
  3. void USART1_IRQHandler(void)
  4. {
  5. static U16 dat;
  6. if( USART_GetITStatus(USART1,USART_IT_RXNE) )
  7. {
  8. USART_ClearITPendingBit(USART1,USART_IT_RXNE); // 这只是清除串口接收标志位
  9. dat = USART_ReceiveData(USART1); // 看好了,这里就是我们接收到一个字节的地方
  10. if(RecvLength < 100) // 这是个非常基础的防止写越界的判读方式

    UartBuff[RecvLength++] = dat; // 这样,接收到的数据就被一个一个存入这个数组里缓冲起来;

  11. }
  12. }
复制代码
个人签名

强者为尊,弱者,死无葬身之地

 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

板凳
 
接下来是判断;
要记住一个很关键的地方,前面我们只管接收并把数据缓存到数组里,但不意味着我们要马上处理它,这就是 分离 数据的接收 和 处理的思维模式——也就不存在你所说的,我怎么处理和接收数据,是不是一直原地等待?

  1. U8 isCmdCompleted(U8 *buffer,U8 length)
  2. {
  3.       int i;
  4.       int flag = 0;
  5.       for(i = 0;i < length;i++)
  6.       {
  7.            if(buffer[i] == 0x0d)  
  8.               flag = 1;
  9.            if( (buffer[i] == 0x0a) && (flag == 1))
  10.             ;//找到了,这个地方你可以有很多如何取出查到的当前一个完整字符串的方式,比如说通过另一个数组把这个字符串的内容传递出去,然后把字符串的长度 作为返回值返回(在查不到的情况下,就返回0)
  11.       }

  12. }
复制代码
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

4
 
因为是临时写的,写的有点随意。
但我觉得,内部实现的一些小疏忽并不是最重要的。

关键的是下面这部分,我告诉你怎么调用。

  1. void main(void)
  2. {
  3.      uart_init(); //为了使用串口必须有串口初始化
  4.      others_init(); //当然还会有其他初始化

  5.      while(1)  // 然后是轮询
  6.      {
  7.               RecvLen = isCmdCompleted(buffer,BuffLen);
  8.                //各种对 RecvLen, buff的判断和处理;
  9.               // 其他各种你需要的代码;
  10.      }
  11. }

  12. 然后你当然还需要编写 串口中断的代码,那个是独立于main()函数存在的,如2L所写,它只处理吧接收的字节一个一个存进数组里。
复制代码
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

5
 
我发现 代码 里 意外增加了很多奇怪的符号,真是很让人讨厌。
你先将就看着,暂时不管这个了,回头我再弄弄,或者等我完全写好了再扔上来。

这里稍微总结一下,希望对你看代码有帮助

首先,我们利用一个全局数组(你就先用全局,细节的地方暂时不多说,以免扰乱思路影响理解)

先用全局数组 存储接收的数据——这个动作是发生在 串口接收中断函数里的。

然后,你在你要 取命令的地方——比如 主函数里 的 轮询调用的地方。

对 数组里的数据进行判断,并取出你需要的部分。

哦,这里还忘了一个 善后的工作

那就是,当你取出了你要用的数据以后,要把数组里相应的位置清除掉。
这里同样简单把操作变成是

当你发现了全局数组里有一个完整的数据串(以 0x0d 0x0a结束后)
就把数组全部清零,并把 接收长度 重新清零 即可。

——在简单的情况下,比如手机DIY的应用。这些处理已经足够了。
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

603

帖子

1

TA的资源

纯净的硅(中级)

6
 
当你发现了全局数组里有一个完整的数据串(以 0x0d 0x0a结束后)
就把数组全部清零,并把 接收长度 重新清零 即可。


在这里直接改接收长度,是十分危险的行为
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

7
 
sjtitr 发表于 2014-8-11 06:25
在这里直接改接收长度,是十分危险的行为

是的。
就我目前的做法而言,我觉得 队列是最简单也是最安全最可靠 最少假设的 适用方案。
只是在这里,我只是想说明 分离 通信数据接收 和 处理 的思路 和 大致使用方法 而已。

当然,这只是对于还不了解这种惯常思路的人,对于老手么,呵呵,那就是多余了~
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

1944

帖子

32

TA的资源

纯净的硅(高级)

8
 
如何判断完整接收数据帧,如果考虑到高效的话,会是一个很复杂的问题。
对于变长数据帧,
常用的方法利用通讯协议,在某字段规定当前帧长度,收满即可。——当然如果通讯出错,比如热插拔了,总之总有出错的可能,还没有收到当前帧长度,你需要一个容错机制去处理它。
当然你还可以利用“超时”的机制来判断数据是否接收完毕,不论软件超时还是硬件超时,如果软件超时,需要精心设计定时器的周期才能保证效率。这种方法对于那种有“热插拔”需求的通讯比较有效。
通过查询的方式效率相对较低,而且必须保证数据帧中不会出现报文头和报文结束的标示字符,当然这个实现相对容易,不过,有的时候外界干扰也还是需要考虑的,容错机制必不可少。
 
 
 

回复

260

帖子

0

TA的资源

一粒金砂(高级)

9
 
淘宝卖家发货晚了估计最快也得明天下午才能到,所以这周末我基本上在干别的事
个人签名中空板|防静电中空板www.cheng-sen.com
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

10
 
azhiking 发表于 2014-8-11 09:10
如何判断完整接收数据帧,如果考虑到高效的话,会是一个很复杂的问题。
对于变长数据帧,
常用的方法利用 ...

超时,是一种方法,但并不算特别好

对于这个话题。
这个帖子里展示的 这种 最简单的 方法
是我早期第一个解决方案;(当然具体起来还有一些其他处理,这里只是简化版本)。

超时,是我中间的一个方案,是我在上家公司老板那里学来的;

现在我采用的是 队列 加 数据帧 的帧头 帧尾 判断。

超时的时候可以不依赖 帧头帧尾,但我后来想了想,觉得这是一种很薄弱的方法,不好。

至于你说的 热拔插 什么的,我觉得这个,基本上来说,不管哪种方法,因为本身 字符流已经被破坏,只能选择判断后,以(中间某几个)数据帧的完整性已经被破坏,放弃它来处理。
硬件上的是很难用软件办法复原的——因为数据已经实实在在的被破坏了。
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

11
 
azhiking 发表于 2014-8-11 09:10
如何判断完整接收数据帧,如果考虑到高效的话,会是一个很复杂的问题。
对于变长数据帧,
常用的方法利用 ...

重新了老兄的回复
发现老兄的回复很细致。

是的,我现在用的 队列 加 帧头帧尾 的方式
在 帧头帧尾的设计上,确实最怕的就是 数据帧因为某种方式被截断 的情形(而不仅是 硬件上热插拔)

但相对而言,我觉得这个方式还是不错的。
超时 我之所以觉得不够 队列好,一则它过于依赖 时间,但往往这不是合理的——总是会有各种情况导致 程序在接受和判断的时候,时间是不均匀的。
而单纯使用 帧头帧尾 这种方式 相对简单,而且依赖更少的因素。

但是帧头帧尾 遇到 被截断的方式是很头疼的。
所以如果是我自己设计 帧头帧尾,我就会考虑让帧头帧尾至少是 两个甚至四个特殊字节,这样会使 分辨 被截断 还是 普通的数据无意出现重复 的情形的几率大大减少。

——当然,这的确是个无法根治的很头疼的问题。
比如最近我在弄一个 别的厂家的 基于通信的 串口屏 就有这类问题

最后我的解决方案 还是选择简单的基于 帧头帧尾,这个方案基于两点考虑:
1.被截断的可能性其实极小;
2.即使发生了,那我最多损失的只是 前后一个 数据帧(而我的程序本身就有其他方式保证 重新获取这个数据帧,当然这个手段不仅是为了应对这个问题,它也可以增加 模块功能本身更加牢固);
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

12
 
您好?请问版主在吗,想请教您关于NRF24L01的问题,它的帧的头头部是怎么加的?虽然知道它是自动加帧头,但是它是怎么加上去的?
NRF24L01集成的通信协议中,我使用是Shockburst模式,不是增强型shockburst模式
发送端怎么处理有效数据中也含有和帧头同样的bit,是否使用了字节填充还是位填充的成帧方法?
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

13
 
箫声剑影 发表于 2015-5-10 10:16
您好?请问版主在吗,想请教您关于NRF24L01的问题,它的帧的头头部是怎么加的?虽然知道它是自动加帧头,但是它是怎么加上去的?
NRF24L01集成的通信协议中,我使用是Shockburst模式,不是增强型shockburst模式
发送端怎么处理有效数据中也含有和帧头同样的bit,是否使用了字节填充还是位填充的成帧方法?

这种硬件内部完成的方式,说实话我也不知道怎么回事。



但问题是我也不太会关心这类问题——难道我还能干涉或者定制这个增加的帧头?



而且我记得,如果都是用nrf24l01成对的话,接收方收到以后也会自动去掉帧头。

所以就更加不必在意了。
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

578

帖子

0

TA的资源

纯净的硅(初级)

14
 
在写串口通信这类问题的时候,总是想着各种情况,结果就是越想越觉得写出来的代码都有缺陷,但是实际使用过程中,并不会经常出现在写代码之前的那些”特殊情况“,代码也运行得很好
个人签名刻苦学习,共同进步
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

15
 
迈尔风随 发表于 2018-5-25 23:36
在写串口通信这类问题的时候,总是想着各种情况,结果就是越想越觉得写出来的代码都有缺陷,但是实际使用过 ...

越防范越出问题,这个是很有道理的。
不过,并不是靠着觉得 与其这样还是不管了就好。
而是想出更有效更简洁的防范方式。

以及保证一些最基本的原则
不管外面发生了什么事,至少我这个函数这个语句不会引发问题;
个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

16
 

其实这就是一个 对 io模型的封装问题。

FreeRTOS+IO足矣。

当然如果非要再纠结一下,看一下linux中的IO模型,即可理解其思路。

 

外设虽多,但涉及数据交换的,不过就是 字节型 和 字符流 两大类而已。

至于网络,嗯,那是另一个,虽然我不明白为什么会单独分出来,只不过我也就根本没做过相关的东西,自然也就不关心。

个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表