单片机学习!
目录
前言
一、数据包
二、HEX数据包
三、文本数据包
四、HEX数据包和文本数据包优缺点
4.1 HEX数据包
4.2 文本数据包
五、HEX数据包接收
六、文本数据包接收
总结
前言
本文介绍了串口数据包收发的思路和流程。
一、数据包
数据包的作用是把一个个单独的数据给打包起来,方便进行多字节的数据通信。之前串口的代码就是发送一个字节,接收一个字节,都可以实现。但在实际应用中可能需要把多个字节打包为一个整体进行发送。
例如用陀螺仪传感器,需要串口发送数据到STM32.陀螺仪的数据为X轴一个字节,Y轴一个字节,Z轴一个字节,总共3个数据需要连续不断地发送。如果像这样XYZXYZXYZ连续发送的时候,就会出现一个问题,就是接收方不知道这些数据哪个对应X、哪个对应Y、哪个对应Z,因为接收方可能会从任意位置开始接收,所以会出现数据错位的现象。
这时候就需要研究一种方式,把数据进行分割,就是把XYZXYZXYZ这一串数据分割开,分成一个个数据为XYZ数据包。这样再接收的时候就知道。
- 数据包的第一个数据就是X,
- 数据包的第二个数据就是Y,
- 数据包的第三个数据就是Z。
以上是数据包的任务,就是把属于同一批的数据进行打包和分割,方便接收方进行识别。一串数据如何进行分割打包,需看实际情况来决定,只要逻辑行的通,可以发挥想象力来设计。
比如可以给XYZXYA数据流中设计数据包的第一个数据,也就是X的数据包,把它的最高位,置为1。其余数据包的最高位,都置为0。当接收到数据之后,判断一下最高位,如果是1,那就是X数据,然后紧跟着的两个数据就分别是Y和Z。这就是一种可行的分割方法,这种方法就是把每个数据位的最高位当做标志位来进行分割的。实际也有应用的例子,比如UTF8的编码方法和这种类似,当然UTF8要更高级一些。
二、HEX数据包
本节主要讲的数据包分割方法并不是在数据的高位添加标志位这种方式,因为这种方式破坏了原有数据,使用起来比较复杂。串口数据包通常使用的是额外添加包头包尾这种方式。比如图中列举的两种数据包格式:
第一种是固定包长,含包头包尾。也就是每个数据包的长度都固定不变。数据包前面是包头,后面是包尾。
第二种是可变包长,含包头包尾。也就是每个数据包的长度可以是不一样的,数据包前面是包头,后面是包尾。
这两种的数据包格式可以是用户根据需求来规定,也可以是买一个模块,别的开发者规定。比如在这里规定一批数据有4个字节。
- 在这4个字节之前,加一个包头,定义0xFF为包头;
- 在这4个字节之后,加一个包尾,定义0xFE为包尾。
当接收到0xFF之后就知道一个数据包来了,接着再接收到的4个字节就当作数据包的第1、2、3、4个数据,存在一个数组里。
最后跟一个包尾,当接收到0xFE之后就可以置一个标志位,告诉程序收到了一个数据包。然后新的数据包过来,再重复之前的过程。这样就可以在一个连续不断的数据流中分割出需要的数据包了。这就是通过添加包头包尾实现数据分割打包的思路。
以下来研究几个问题:
第一个问题是,包头、包尾和数据载荷重复的问题。
假设定义FF为包头,FE为包尾,如果传输的数据本身就是FF和FE怎么办呢?也就是发生包头、包尾和数据载荷重复的问题,会引起误判。
对应这个问题有如下几种解决方法:
- 第一种,限制载荷数据的范围,如果可以的话,在数据发送的时候对数据进行限幅。比如XYZ这3个数据,变化范围都可以是0~100.那就可以在载荷中只发送0~100的数据,数据中就不会有FF和FE这样的值存在,这样就不会和包头、包尾重复了。
- 第二种,如果无法避免载荷数据和包头、包尾重复,那就尽量使用固定长度的数据包。由于载荷数据是固定的,只要通过包头、包尾对齐了数据,就可以严格知道哪个数据应该是包头,哪个数据应该是包尾,哪个数据应该是载荷数据。在接收载荷数据的时候,并不会判断它是否是包头或者包尾;而在接收包头包尾的时候,会判断它是不是确实是包头包尾,用于数据对齐。这样在经过几个数据包的对其之后,剩下的数据包应该就不会出现问题了。
- 第三种,就是增加包头包尾的数量,并且让它尽可能呈现出载荷数据出现不了的状态。比如使用FF、FE作为包头,FD、FC作为包尾。这样也可以避免载荷数据和包头包尾重复的情况发生。
第二个问题是,包头、包尾是不是全部都需要呢?
包头、包尾并不是全部都需要。
比如可以只要一个包头,把包尾删掉,这样数据包的格式就是,1个包头FF加4个数据。当检测到FF就开始接收,收够4个字节后置标志位,一个数据包接收完成。
不过这种方式会有弊端,载荷和包头重复的问题会更严重一些。比如最严重的情况下,载荷全是FF,包头也是FF。那肯定不知道哪个是包头了。而加上FE作为包尾,无论数据怎么变化,都是可以分辨出包头和包尾的。
第三个问题是,固定包长和可变包长的选择问题。
对应HEX数据包来说:
如果载荷会出现和包头、包尾重复的情况,那就最好选择固定包长,这样可以避免接收错误。如果载荷会出现和包头、包尾数据重复的情况下,又选择了变包长,那一个数据包的包头包尾就有了不确定性,相应的数据包的长度也会有影响,这就会导致数据乱套。
如果载荷不会和包头、包尾重复,那可以选择可变包长。数据长度如4位、3位、1位、10位等等,来回任意变肯定都没问题。因为包头、包尾是唯一的。只要出现包头,就开始数据包;只要出现包尾,就结束数据包。这样就非常灵活。
第四个问题是,各种数据转换为字节流的问题。
这里的数据包都是一个字节一个字节组成的。如果想发送16位的整型数据、32位的整型数据、float、double甚至是结构体其实都没问题。因为它们内部其实都是由一个字节一个字节组成的,只需要用一个uint8_t的指针指向它,把它们当作一个字节数组发送就可以了。
三、文本数据包
文本数据包和HEX数据包就分别对应了文本模式和HEX模式这两种模式。
- 在HEX数据包里面,数据都是以原始的字节数据本身呈现的。
- 在文本数据包里面,每个字节就经过了一层编码和译码,最终表现出来的就是文本格式。
但实际上,每个文本字符的背后其实都还是一个字节的HEX数据。
文本数据包同样举例固定包长和可变包长这两种模式。由于数据译码成了字符形式,这就会存在大量的字符可以作为包头和包尾。可以有效避免载荷和包头包尾重复的问题。
比如图中规定的就是以@这个字符作为包头;以\r和\n,也就是换行,这两个字符作为包尾。在载荷数据中间可以出现除了包头包尾的任意字符。所以文本数据包基本不用担心载荷数据和包头、包尾重复的问题,使用非常灵活。可变包长、各种字母、符号、数字都可以随意使用。
当接收到载荷数据之后得到的就是一个字符串,在软件中再对字符串进行操作和判断,就可以实现各种指令控制的功能了。而且字符串数据包表达的意义很明显,可以把字符串数据包直接打印到串口助手上,什么指令、什么数据一眼就能看明白。所以这个文本数据包通常会以换行作为包尾。这样在打印的时候就可以一行一行地显示了,非常方便。
四、HEX数据包和文本数据包优缺点
HEX数据包和文本数据包这两种对比下来,其实也是各有优缺点。需要根据实际场景来选择和设计数据包格式。
4.1 HEX数据包
- 优点是传输最直接,解析数据非常简单,比较适合一些模块发送原始的数据。比如一些使用串口通信的陀螺仪、温湿度传感器。
- 缺点是灵活性不足,载荷数据容易和包头、包尾重复。
4.2 文本数据包
- 优点是数据直观易理解,非常灵活,比较适合一些输入指令进行人机交互的场合,比如蓝牙模块常用的AT指令、CNC和3D打印机常用的G代码,都是文本数据包的格式。
- 缺点是解析效率低,比如发送一个数100,HEX数据包就是一个字节100,完事;文本数据包就得是3个字节的字符:1、0、0,收到之后还要把字符转换成数据,才能得到100.
五、HEX数据包接收
本节来看一下数据包收发流程。
首先是数据包的发送,数据包的发送比较简单。
- 在HEX数据包这里,如果想发送一个数据包,就定义一个数组,填充数据,然后用SendArray函数一发就完事了。
- 在文本数据包这里,写一个字符串,然后调用SendString函数一发送也就完事了。
所以说发送数据包简单,是因为发送过程完全是自主可控的,想发啥就发啥,写代码的时候也能感受到,串口发送比串口接收简单多了。
接收一个数据包就比较复杂了。这里演示固定包长HEX数据包的接收方法和可变包长文本数据包的接受方法。其他的数据包也都可以套用这个形式。写数据包接收的程序就会根据这里面的流程来。
先看一下如何接收固定包长HEX数据包根据上一章博文的代码可以知道,每收到一个字节程序都会进一遍中断。在中断函数里,可以拿到这一个字节。但拿到字节之后就得退出中断了,所以每拿到一个数据都是一个独立的过程。
STM32 USART串口发送+接收_配置stm32的usart串口参数,编写程序实现数据的发送和接收-CSDN博客https://blog.csdn.net/Echo_cy_/article/details/143817933
而对于数据包来说它具有前后关联性,包头之后是数据,数据之后是包尾。对于包头、数据和包尾这3种状态,需要有不同的处理逻辑。
所以在程序中需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移。这种程序设计思维就叫做状态机。在这里就使用状态机的方法来接收一个数据包。
要想设计一个好的状态机程序,画一个状态转移图是必要的。如图所示,对于图中这样一个固定包长HEX数据包来说,可以定义3个状态。
- 第一个状态是等待包头、
- 第二个状态是接收数据、
- 第三个状态是等待包尾。
每个状态需要用一个变量来标志一下,比如这里用变量S来标志,三个状态依次为S=0、S=1、S=2这一点类似于置标志位。只不过标志位只有0和1,而状态机是多标志位状态的一种方式。
执行流程:
第一个状态:最开始S=0,收到一个数据,进中断,根据S=0,进入第一个状态的程序,判断数据是不是包头0xFF。如果是0xFF则代表收到包头。之后置S=1,退出中断,结束。这样下次再进中断,根据S=1,就可以进行接收数据的程序了。那在第一个状态,如果收到的不是0xFF,就证明数据包没有对齐,应该等待数据包包头的出现。这时状态仍然是0。下次进中断,就还是判断包头的逻辑。直到出现0xFF才能转到下一个状态。
第二个状态:那之后出现了0xFF,就可以转移到接收数据的状态了。这时再收到数据,就直接把它存在数组中。另外再用一个变量,记录收了多少个数据。如果没收到4个数据,就一直是接收状态。如果收够了,就置S=2,下次进中断时就可以进入下一个状态了。
第三个状态:最后一个状态就是等待包尾了,判断数据是不是0xFE,正常情况,应该是0xFE,这样就可以置S=0,回到最初的状态,开始下一个轮回。当然也有可能这个数据不是0xFE,比如数据和包头重复,导致包头位置判断错了。那这个包尾位置就有可能不是0xFE,这时就可以进入重复等待包尾的状态,直到接收到真正的包尾。这样加入包尾的判断,更能预防因数据和包头重复造成的错误。
以上就是使用状态机接收数据包的思路。
状态机其实是一种很广泛的编程思路,在很多地方都可以用到。使用的基本步骤是,先根据项目要求定义状态,画几个圈。然后考虑好各个状态在什么情况下会进行转移,如何转移,画好线和转移条件。最后根据这个图来进行编程。这样思维就会非常清晰了。
六、文本数据包接收
可变包长文本数据包的接收流程同样也是利用状态机,定义3个状态。
第一个状态,等待包头,判断收到的是不是规定的@符号。如果收到@就进入到第二个状态。
第二个状态,接收状态。在接收状态下依次接收数据。同时,第二个状态还应该要兼具等待包尾的功能。因为这是可变包长,接受数据的时候也要时刻监视,是不是收到包尾了。一旦收到包尾就结束。这个状态的逻辑就应该是收到一个数据,判断是不是\r。
- 如果不是\r,则正常接收;
- 如果是\r,则不接收,同时跳到下一个状态,等待包尾\n。
第三个状态,因为这里数据包有两个包尾\r和\n所以需要第三个状态。如果只有一个包尾,那在出现包尾之后就可以直接回到初始状态了,只需要两个状态就行。因为接收数据和等待包尾需要在一个状态里同时进行。由于串口的包头、包尾不会出现在数据中,所以基本不会出现数据错位的现象。
以上就是使用状态机接收文本数据包的方法。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了接收数据包的思路。