串口成帧协议
串口接收中的问题
在电子系统中,最简单、最广泛的通信方式无疑是串口了,几乎所有与模块相关的产品,差不多都有串口的操作方式,如串口蓝牙模块、串口WIFI模块、串口ZigBee模块、串口语音模块等。不仅是这样,在嵌入式开发中,很多时侯调试离不开串口,固件升级离不开串口,可见搞电子的人如果不能很好的操作串口,那就真是说不过去了。
串口的应用场合非常多,但是有一个残酷的现实摆在我们面前,那就是通信问题。我们知道串口发送数据时是面向字节的,接收方接收数据时也是面向字节的,而在实际应用中,收发数据往往都是面向帧的,这样往往会对接收方接收数据造成一定的困难。举个例子吧,一个单片机系统通过串口往PC机发送一帧100字节数据,但是PC机在调用系统API来接收数据时,往往不能一次性接收完100字节,可能第一次接收5字节,第二接收10字节……,虽然最后都能收到100字节。如果单片机发送两帧数据,并且这两帧数据之间的时间间隔非常短,那PC就无法区别出两帧数据的边界了。初入行的人或许会想到一些诸如增加标志位、判断时间等方法来解决这个问题,但不够严谨的。所以我们有必要设计一个相对可靠一点的串口成帧的方法来解决接收难题。
也许搞技术的人,或多或少都听说过网络通信的OSI模型,标准的OSI模型有7层之多,像串口这种简单的通信,并不需要这么多层的实现,但是至少要实现链路层,才能解决数据组帧问题。很多人之所以在接收串口数据时出现困难就是因为从物理层接收数据之后,直接提交给应用层,这样应用层必然是面向字节的,通信方式是如下形式:
物理层 –>> 应用层
在这里,我们要做的是在物理层与应用层之间插入一个链路层,也即OSI模型的第二层,本文要描述的就是这个链路层的协议,通信方式会变成如下形式:
物理层 –>> 链路层 –>> 应用层
由于大部分人都习惯于直接操作串口,所以链路层只需要实现最基本的组帧功能即可,没有必要搞的那么复杂。这里链路层的功能负责将物理层的字节数据,按照一定规则封装成帧,然后提交给应用层,这样应用层接收的便是完整的一帧数据,而不是零零散散的字节数据。
那么链路层使用什么样的方法来进行封装会比较好呢,这里可以效仿网络通信协议中一些知名协议的做法,比如SLIP、PPP协议等,这些协议的链路层都是通过转义的方法来组帧。因此,我们的串口成帧协议也是使用转义的方法,但是要说明一点,这里只提供一种组帧的方法,任何可靠性的问题如检测、纠错等必须由上层来实现。
串口成帧链路层组帧规则如下:
1、首先我们要定义三个特殊字符:帧起始标志SOF,用0x7d表示;帧结束标志EOF,用0x7e表示、转义标志ESC表示0x7f。数据帧封装格式如下表所示:
串口链路层帧格式
域名称 描述
SOF 帧起始标志,固定为0x7d,SOF是Start of Frame的英文编写
DATA 要传输的数据,串口数据建议在512字节以内为宜。
EOF 帧结束标志,固定为0x7e,EOF是End of Frame的英文缩写
2、由数据帧的封装格式中可以看出,0x7d和0x7e是帧的起始和末尾标志,那要是DATA中要传输0x7d和0x7e怎么办呢,这就需要进行转义了,我们定义了一个转义字符ESC,用0x7f 表示。当发送数据时,需要将数据域中的0x7d用0x7f 、0x00两字符替代、这就是所谓的转义,同样的,发送方将数据中的0x7e转义为0x7f、0x01,将0x7f转义成0x7f、0x02。对于接收方可以按照这样的规则对数据进行还原。转义字符表总结如下:
0x7d –>> 0x7f、0x00
0x7e –>> 0x7f、0x01
0x7f –>> 0x7f、0x02
以上两点就是链路层的规则,规则虽然简单,但是对于组帧却非常有用,而且不会产生歧义。
样例一:
应用层发送数据:0x55 0x66 0x88 0x99
数据经过链路层之后,实际发送数据:0x7d 0x55 0x66 0x88 0x99 0x7e
从这里可以看出,接收方根据起始标志0x7d和末尾标志0x7e很容易提取出实际数据0x55 0x66 0x88 0x99
样例二
应用层发送数据:0x55 0x7d 0x88 0x99
数据链路层发送数据:0x7d 0x55 0x7f 0x00 0x88 0x99 0x7e
这里可以看出,应用层发送数据中是包含了起始标志0x7d,但是在链路层就被转义成了0x7f、0x00,接收方连续收到0x7f、0x00就能还原为0x7d。
样例三
应用层发送数据:0x55 0x7f 0x00 0x99
数据链路层发送数据:0x7d 0x55 0x7f 0x02 0x00 0x99 0x7e
从上面三个例子中可以看出,发送的数据帧的数据域中是不可能出现0x7d和0x7e这两个字符的,因为链路层已经对它进行了转义,所以接收方一旦接收到0x7d就可以断定这是一帧的开始,接收到0x7e就表示一帧的结束,而0x7d与0x7e之间的数据就是发送方的数据内容,接收方需要在链路层将被转义的字符还原,然后再提交给应用层。
这么一来,就解决了数据成帧的问题。串口之间的通讯由面向字节通讯变成了面向帧的通讯,也就是说应用层数据不再是以字节为单位,而是以帧为单位。好比单片机发送一帧100字节的数据给PC,PC应用程序接收的便是一帧100字节的数据了。