一般用串口进行数据通讯时,我们会在发送端(master)把传输的数据流按照一定的规则划分成数据帧进行传输,接收端(client)根据约定好的规则将接收的数据流分帧解析执行。传统的方式主要有两种:
1、首先将传输的数据转化成ASCII码形式(这样实际传输的数值范围被锁定在0x30~0x7a之间),然后在数据的前后加上特定的字节数据(比如0x02'STX',0x03'ETX'),形成一帧数据。这样可以保证帧头帧尾的标志字节不会和实际数据内容重合,分帧解析变得非常容易,只要找到相应的帧头、帧尾即可。这种方式有个弊端,把数据转化成ASCII时,转化后的数据长度是转化前的2倍,效率非常低下,这对于实时性要求高(比如485总线通讯中有多个从机的时候)的应用,有时无法满足要求。
2、通过控制发送字节的时间间隔进行分帧,modbus中也叫RTU模式。发送端精准控制每个字节的发送时间,一般两个字节之间的发送间隔为一个传送字节时间(具体跟波特率有关), 帧与帧之间的时间间隔要>=5个字符时间,接收端不断的判断下一个字节数据的到达时间,如果时间超过间隔时间则判定一帧数据结束。该方式实时性较高,但是需要精确的计算发送时间,有可能出现无法识别的情况。
COBSforN(Consistent Overhead Byte Stuffing,恒定开销的字节填充)是一种固定开销的字节填充算法。字节填充用于处理那些包含了特殊或保留字符的字符序列编码,通过一定的算法在原有的字符序列长度+1 生成编码后的字符序列,新的序列将不会包含之前序列中出现的特殊或者保留的字符。其具体算法思想以及研究见 ,以下举两个例子:
Example | Unencoded data (hex) | Encoded with COBS (hex) |
---|---|---|
1 | 00 | 01 01 00 |
2 | 00 00 | 01 01 01 00 |
3 | 11 22 00 33 | 03 11 22 02 33 00 |
4 | 11 22 33 44 | 05 11 22 33 44 00 |
5 | 11 00 00 00 | 02 11 01 01 01 00 |
6 | 01 02 ... FE | FF 01 02 ... FE 00 |
算法源码如下:
1、编码
/* * StuffData byte stuffs “length” bytes of * data at the location pointed to by “ptr”, * writing the output to the location pointed * to by “dst”. */#define FinishBlock(X) (*code_ptr = (X), \ code_ptr = dst++, \ code = 0x01 )void StuffData(const unsigned char *ptr, unsigned long length, unsigned char *dst){ const unsigned char *end = ptr + length; unsigned char *code_ptr = dst++; unsigned char code = 0x01; while (ptr < end) { if (*ptr == 0) FinishBlock(code); else { *dst++ = *ptr; code++; if (code == 0xFF) FinishBlock(code); } ptr++; } FinishBlock(code);}
2、解码
/* * UnStuffData decodes “length” bytes of * data at the location pointed to by “ptr”, * writing the output to the location pointed * to by “dst”. */void UnStuffData(const unsigned char *ptr, unsigned long length, unsigned char *dst){ const unsigned char *end = ptr + length; while (ptr < end) { int i, code = *ptr++; for (i = 1; i < code; i++) *dst++ = *ptr++; if (code < 0xFF) *dst++ = 0; }}