1.HEX数据包格式
数据包的作用:
把一个个单独的数据打包起来,方便进行多字节的数据通信。
比如:陀螺仪传感器需要用串口发送数据到STM32,陀螺仪的数据有X轴一个字节,Y轴一个字节,Z轴一个字节,3个数据,需要连续不断地发送。如果使用XYZXYZXYZ连续发送就会出现接收方识别不出X,Y,Z对应的数据,即不知道数据哪个对应X,哪个对应Y。因为接收方可能会从任意位置接收,所以会出现数据错位的现象。所以就出现了使用包头包尾把XYZ这一批数据进行分割。分割成一个个数据包,数据包的第一个数据就是X,第二个就是Y。属于把同一批的数据进行打包分割,方便接收方进行识别。
打包的方法可以自己设计,比如在XYZXYA数据流中,数据包的第一个数据即X的数据包,最高位置1,其余数据置0,接收数据后,判断最高位,如果是1就是X数据,接下来的数据就是Y和Z。
这种方法的原理是把每个数据地最高位当作标志位来进行分割。
串口数据包通常使用的收发数据的方法:
额外添加包头包尾
1)固定包长,含包头包尾,就是每个数据包的长度固定不变,数据包前面是包头,后面是包尾
2)可变包长,含包头包尾,也就是每个数据包的长度可以是不一样的,前面是包头,后面是包尾。
数据包格式可以根据用户需求自己规定,也可以是模块开发者规定。
上图固定有效数据包长为4个字节,在加上包头包尾就是6个字节
定义0XFF为包头,在四个字节之后,定义0XFE为包尾
添加包头包尾打包数据思路:
当接收到0XFF后,就知道一个数据包来了,接下来接收的4个字节数据接当作数据包的第1,2,3,4个数据存在一个数组里。当收到包尾0XFE之后,就可以置一个标志位,告诉程序,我接收到了一个数据包。然后新的数据包过来重复之前的过程。这样就可以在数据流中分割出想要的数据包。
问题1:如果传输的数据本身和包头包尾一样,例如传输的数据是0XFF,0XFE可能会引起误判,解决方法如下:
1)限制载荷数据的范围,在发送的时候对数据进行限幅,比如X,Y,Z3个数据,变化范围可以是0-100,就可以在载荷中只发送0-100的数据,这样就不会和包头包尾重复了。
2)如果无法避免数据和包头包尾重复,就尽量使用固定长度的数据包,这样,由于载荷数据固定
只要通过包头包尾对齐数据,就可以严格知道那个数据是包头包尾,哪个数据是载荷数据,在接收载荷数据的时候不会判断它是否是包头包尾,在接收包头包尾时,会判断它是否属于包头包尾,用于数据对齐,经过几个数据包的对齐之后,剩下的数据包就不会出现问题了。
3)增加包头包尾的数量,并尽量让它呈现出载荷数据呈现不了的状态,比如使用FF,FE作为包头,使用FD,FC作为包尾,这样也可以避免载荷数据和包头包尾重复的情况。
问题2:包头包尾的数据并不是全都需要的,比如可以只要一个包头,把包尾删掉,这样数据包的格式就是一个包头FF+4个数据,当检测到FF,开始接收4个字节,置标志位,一个数据包接收完成。但是这样载荷和包头重复的问题会更严重,比如最严重时,载荷全是FF,这时包头也是FF,这时就不知道哪个是包头,加上FE作为结尾后,数据怎么变化都可以分辨出包头包尾。
问题3:固定包长和可变包长的选择
对于HEX数据包,如果载荷和包头包尾会出现重复的情况,最好选择固定包长,这样可以避免接收错误。载荷不会和包头包尾重复,可以选择可变包长这时数据长度可以任意变化,因为这时包头包尾是唯一的。
问题4:各种数据转化为字节流
由于数据包是一个字节一个字节组成的,如果想发送uint16_t,uint32_t,float,double,结构体数据都是可以的,因为它们内部是由一个字节一个字节组成的。
只需用uint8_t指针指向它,把他们当作一个字节数组发送就行了。
2.文本数据包
文本数据包需要经过编码和译码,最终表现出文本格式,但实际上每个文本字符背后还是一个字节的HEX数据。
文本传输数据的模式也是固定包长和可变包长两种模式,由于数据译码成了字符的形式,就会有很多字符可以作为包头包尾,可以有效避免载荷和包头包尾重复。所以文本数据包基本不需要担心数据包和包头包尾重复的问题。可变包长中各种字母,数字,符号都可以随意使用。
当接收到载荷数据之后得到的就是一个字符串,在软件中对字符串进行操作和判断,就可以进行指令控制的功能。字符串表达的意义比较明显
3.两种数据包的优缺点
1)HEX数据包:
优点:传输直接,解析数据简单,比较适合模块发送的原始数据。
比如使用串口通信的陀螺仪,温湿度传感器。
缺点:灵活性不足,载荷容易和包头包尾重复。
2)文本数据包
优点:数据直观易理解,非常灵活,比较适合一些输入指令进行人机交互的场合。
比如蓝牙模块常用的AT指令,CNC和3D打印机常用的G代码。
缺点:解析效率低,比如发送100,HEX数据包发送一个字节100就行了,文本数据包就需要发送3个字节的字符,‘1’,‘0’,‘0’.收到之后还要把字符转换成数据,才能得到100.
可以根据实际场景选择数据包格式
4.数据包的收发流程
1)数据包的发送流程:
比如HEX数据包:定义一个数组,填充数据,用SendArray()函数就行了
文本数据包:写一个字符串,调用SendString()
2)接收数据包流程
固定包长HEX数据包的接收方法:
根据之前的代码,每收到一个字节程序都会进行一次中断,在中断程序函数拿到字节数据,拿到数据后就退出中断,所以每拿到一个数据,都是一个独立的过程。
但是对于数据包,它具有前后关联性,包头之后是数据,数据之后是包尾,对于这3种状态需要不同的处理逻辑,所以需要设计一个能记住不同状态的机制。在不同状态进行不同的操作,同时进行状态的合理转移。
这种程序设计思维叫做状态机
这里使用状态机的方法接收数据包,要想设计一个好的状态机程序,上面的状态转移图是必要的。
对于固定包长HEX数据来说,可以定义3个状态
第一个状态是等待包头,第二个状态是接收数据,第三个状态是等待包尾。
每个状态需要用一个变量来标志比如上面用变量S进行标志.3个状态依次为S=0,S=1,S=2。
类似标志位。
执行流程:S=0,收到一个数据,进中断,根据S=0进入第一个状态的程序,判断数据是不是包头FF,如果是FF则代表收到包头,之后置S=1,退出中断,结束。下次进中断,根据S=1,就可以进行接收数据的程序。如果不是FF,证明数据包没有对齐,应该等待数据包包头的出现。这时状态等然是0,下次进中断,还是判断包头的逻辑。直到出现FF才能转到下一个状态,出现FF就可以转移到接收数据的状态,接收到了数据就直接把它存在数组中,再用另一个变量记录收到了多少个数据,如果没收够4个数据,就一直是接收状态,收够了,就置S=2.下次进中断时就可以进下一个状态了。下个状态就是等到包尾,判断是不是FE,判断完之后置S=0。
当数据和包头重复,导致包头判断错误,包尾位置可能就不是FE,这时就可以进行重复等待包尾的状态,直到接收到真正的包尾。
使用的基本步骤:
根据项目要求定义状态,(画个圈)考虑好各个状态在什么情况下会进行转移,如何转移(画好线和转移条件)根据图进行编程思维更清晰。
2)可变包长文本数据包接收流程
利用状态机定义3个状态
第一个状态:等待包头,判断接收到数据的是不是规定的‘@’符号
,如果收到@就进入接收状态,在这个状态下接收数据,同时,这个状态还应该兼具等待包尾的功能。因为这时可变包长,我们在接收数据的时候,要时刻监视是不是接收到包尾了。一旦接收到包尾了就结束。
这个状态的接受逻辑是:
收到一个数据,判断是不是\r 如果不是就正常接收,如果是就不接收,同时跳到下一个状态,等待包尾\n因为这里有2个包尾。如果只有一个包尾,在出现包尾之后就可以直接回到初始状态了。
串口的包头包尾不会出现在数据中,所以不会出现数据错位的现象