一、串口数据包简介:
数据包的作用:把一个个单独的数据打包起来,方便进行多字节的数据通信
//使用【额外添加包头包尾】实现数据分割打包
【固定包长】:每个数据包的长度固定不变
【可变包长】:每个数据包的长度可以变化
-
在【HEX数据包】里面数据都是以原始的字节数据本身呈现的
-
在【文本数据包】里面每个字节都经过了一层编码和译码,最终呈现的是文本格式
-
但实际上每个文本字符背后其实还是一个字节的HEX数据
-
-
问题一:包头包尾和数据载荷重复
-
解决方法一:限制载荷数据范围
-
//在发送的时候对数据进行限幅,比如将数据范围限制在0~100,这样载荷就不会和包头包尾重复了
-
解决方法二:尽量使用固定长度的数据包
//由于载荷数据是固定的,只要通过包头包尾对齐了数据,就可以严格知道数据到底是包头包尾还是载荷数据
-
解决方法三:增加包头包尾的数量,尽量使其呈现出载荷数据出现不了的状态
//比如使用【FF、FE】作为包头,【FD、FC】作为包尾
-
问题二:包头包尾并不是全都需要
-
比如可以只要一个包头,把包尾删掉, 这样数据包的格式就是一个包头【FF】加4个数据
-
//当检测到【FF】开始接收,收够4个字节后置标志位,一个数据包接收完成
-
这样的话载荷和包头重复的问题会更严重
-
问题三:固定包长和可变包长的选择
-
对应HEX数据包来说,如果载荷会出现和包头包尾重复的情况——选择【固定包长】
-
如果载荷不会出现和包头包尾重复的情况——可以选择【可变包长】
-
因为包头包尾是唯一的,只要出现包头就开始数据包,只要出现包尾就结束数据包
-
-
二、HEX数据包
-
固定包长,含包头包尾
![](https://i-blog.csdnimg.cn/blog_migrate/2475fe829d914373597119a9d6af7adb.png)
此处规定:
一批数据规定有4个字节,在这4个字节之前加一个自定义包头【0xFF】,在这4个字节之后加一个自定义包尾【0xFE】
//当接收到【0xFF】就知道一个数据包来了,接着再接收到的4个字节当作数据包的第1、2、3、4个数据存在一个数组里
//最后跟一个包尾,当收到【0xFE】之后就可以置一个标志位告诉程序收到了一个数据包
-
可变包长,含包头包尾
![](https://i-blog.csdnimg.cn/blog_migrate/21c39ae2ec2b019214fc96aad98f3763.png)
三、文本数据包
由于数据译码成了字符形式,这就会存在大量的字符可以作为包头包尾,可以有效避免载荷和包头包尾重复的问题
文本数据包基本不用担心载荷和包头包尾重复的问题
接收到载荷数据之后得到的就是一个字符串,在软件中再对字符串进行操作和判断,就可以实现各种指令控制的功能了
-
固定包长,含包头包尾
![](https://i-blog.csdnimg.cn/blog_migrate/25d6be346d2bacc0d79071be370cdd4f.png)
此处规定:
以【@】字符作为包头,以【\r\n】两个字符作为包尾,在载荷数据中间可以出现除了包头包尾的任意字符
-
可变包长,含包头包尾
![](https://i-blog.csdnimg.cn/blog_migrate/d30845a7ddbac3e724ce61e9fbc9823e.png)
四、优缺点对比
-
HEX数据包
-
优点:传输最直接,解析数据简单,比较合适一些模块发送原始的数据(串口通信的陀螺仪,温湿度传感器)
-
缺点:灵活性不足,载荷容易和包头包尾重复
-
-
文本数据包
-
优点:数据直观易理解,非常灵活,适合一些输入指令进行人机交互的场合(蓝牙模块的AT指令)
-
缺点:解析效率低
-
五、数据包收发流程
-
数据包具有前后关联性,包头之后是数据,数据之后是包尾
-
对于包头、数据和包尾这3种状态需要有不同的处理逻辑
-
在程序中设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移
-
这种程序设计思维叫做【状态机】
-
-
-
状态机编程步骤
-
先根据项目要求定义状态,画几个圈
-
考虑好各个状态在说明情况下会进行转移,如何转移,画好线和转移条件
-
最后根据这个图来进行编程
-
1、HEX数据包接收
![](https://i-blog.csdnimg.cn/blog_migrate/18d988e1648c968e19fe34082aefb482.png)
发送HEX数据包:定义一个数组填充数据,用【SendArray】函数进行发送
此处演示【固定包长HEX数据包的接收方法】
![](https://i-blog.csdnimg.cn/blog_migrate/182d78bfddff91bfc9107f74ee73dd79.png)
-
定义3个状态
-
状态1:等待包头
-
状态2:接收数据
-
状态3:等待包尾
-
-
每个状态需要用一个变量来标志(此处使用变量S标志),3个状态依次为S=0、S=1、S=2
-
执行流程
-
最开始S=0,收到一个数据进中断,根据S=0进入第一个状态的程序,判断数据是不是包头【FF】
-
如果是【FF】则代表收到包头,之后置S=1退出中断,结束(下次进中断根据S=1进行接收数据程序)
-
如果不是【FF】证明数据包没有对齐,等待包头出现,状态仍然S=0(下次进中断根据S=0判断包头)
-
-
转移到接收数据的状态,收到数据将其存在数组中, 再用一个变量记录收了多少个数据
-
如果没收够4个数据就一直是接收状态
-
如果收够了就置【S=2】(下次进中断就可以进入下一个状态了)
-
-
等待包尾,判断数据是不是【FE】
-
如果是【FE】置【S=0】回到最初的状态,开始下一个轮回
-
如果不是【FE】进入重复等待包尾的状态,直到接收到真正的包尾
-
-
2、文本数据包接收
![](https://i-blog.csdnimg.cn/blog_migrate/9169fd5f56d13cf575070d09b69e0641.png)
发送文本数据包:写一个字符串,用【SendString】函数进行发送
此处演示【可变包长文本数据包的接收方法】
![](https://i-blog.csdnimg.cn/blog_migrate/79d60763c7b82428461363273d9f28e2.png)
-
定义3个状态
-
状态1:等待包头
-
状态2:接收数据
-
状态3:等待包尾
-
-
每个状态需要用一个变量来标志(此处使用变量S标志),3个状态依次为S=0、S=1、S=2
-
执行流程//(因为是可变包长,在接收数据的时候也要时刻监视是否收到包尾,一但收到包尾就结束)
-
最开始S=0,收到一个数据进中断,根据S=0进入第一个状态的程序,判断数据是不是包头【@】
-
如果收到【@】进入接收状态,在这个状态下依次接收数据
-
同时这个状态还应该兼具【等待包尾】的功能
-
-
收到一个数据判断是不是【\r】
-
如果不是则正常接收
-
如果是【\r】则不接收,同时跳到下一个状态等待包尾【\n】
-
-
//因为数据包有两个包尾【\r】【\n】,所以需要第三个状态
//如果只有一个包尾,在出现包尾【\r】后就可以直接回到初始状态了
-
接收数据和等待包尾需要在一个状态里同时进行