前言
上一篇文章简单叙述了串口通信的参数,这一篇讲叙串口通信的校验方式。
初识Android串口通信(一)
为什么要校验传输数据?
串口通信中的数据传输过程中,可能会受到多种干扰和误差,如电磁干扰、信号衰减、信号失真等。这些干扰和误差可能会导致数据的丢失、损坏、重复或错位等问题,从而导致数据传输错误。 因此,在串口通信中引入校验机制是必要的,它可以检测数据传输过程中出现的错误或损坏,从而保证数据的正确性和完整性。
串口通信校验码发送与接收方式
串口通信中的校验码通常是通过在数据包的末尾附加一个固定长度的校验码来实现的,发送方在发送数据时计算校验码并将其附加在数据包的末尾,接收方在接收到数据后同样计算校验码,并与发送方发送的校验码进行比对,以判断数据传输是否正确。
校验流程
- 发送方在发送数据之前,将要发送的数据进行校验计算,得到校验码。然后将数据和校验码一起组成一个数据包,发送给接收方。
- 接收方在接收到数据之后,首先进行校验和解析。解析方式是:先计算收到的数据包中除校验码之外的数据的校验和,然后将校验和与接收到的校验码进行比较,如果两者相等,则说明数据传输正确,否则说明数据传输出现错误。
- 如果校验码不正确,接收方可以向发送方发送一个重传请求,要求发送方重新发送数据。
注意点
校验码的长度应该根据具体的应用场景和数据传输速率来确定,长度过短可能会导致误判,长度过长则会增加通信开销。同时,不同的校验方法具有不同的校验效率和可靠性,可以根据具体的需求选择合适的校验方法。
常见校验方法
常用的校验方法包括奇偶校验、校验和和循环冗余校验(CRC)。
奇偶校验(Parity Check)
在每个数据字节的最高位或最低位添加一个校验位,使得每个数据字节的1的个数为奇数或偶数,从而检测数据传输过程中是否发生了单个位错误。
示例
假设我们要传输一个8位的二进制数据10110010
- 统计1的个数:数据中有4个1,因此1的个数为偶数。
- 设置校验位:因为1的个数是偶数,所以校验位设置为0。
- 发送数据:发送的数据为101100100,其中最后一位是校验位0。
- 接收数据:接收到的数据为101100101,其中最后一位是错误的,因为它应该是0,但是实际上变成了1。
- 校验数据:根据奇偶校验的规则,我们统计接收到的8位二进制数据中的1的个数,发现有5个1,因此1的个数是奇数,说明接收到的数据出现了错误。
- 纠错:由于校验位错误,我们可以认为接收到的数据有误,需要进行纠错,可以重新发送数据或者进行其他处理。
代码
代码语言:javascript复制public class ParityCheck {
public static boolean checkParity(byte b) {
int ones = 0; // 记录字节中二进制位为1的个数
for (int i = 0; i < 8; i ) {
if (((b >> i) & 1) == 1) {
ones ;
}
}
return ones % 2 == 0; // 如果二进制位为1的个数是偶数,返回true,否则返回false
}
}
校验和(Checksum)
将要发送的所有数据字节相加,然后将和的低8位作为校验码发送。接收端也将收到的所有数据字节相加,然后与接收到的校验码相加,如果和的低8位为0,则认为数据传输正确。
示例
假设要对以下4个十六进制数进行校验和计算:0x12, 0x34, 0x56, 0x78。
- 数据总和为0x12 0x34 0x56 0x78 = 0x174。因为0x174的二进制表示为0001 0111 0100,所以校验和为0x74。
- 当发送端发送这组数据时,它将附加这个校验和,并将数据和校验和一起发送到接收端。
- 接收端在接收到数据后,将对数据进行校验和计算,如果计算得到的校验和与接收到的校验和不一致,则说明数据可能被篡改或传输错误。
代码
代码语言:javascript复制public class Checksum {
public static short calculateChecksum(byte[] bytes) {
int sum = 0; // 记录所有字节的累加和
for (byte b : bytes) {
sum = b;
}
// 将累加和的高16位和低16位相加,直到高16位为0
while ((sum >> 16) > 0) {
sum = (sum & 0xFFFF) (sum >> 16);
}
return (short) (~sum & 0xFFFF); // 对累加和取反并截取低16位作为校验和
}
}
循环冗余校验
CRC校验(Cyclic Redundancy Check)
是一种基于二进制除法的循环冗余校验方法,使用异或和移位运算,能够检测多达32位的传输错误。
计算方式
- 选择一个固定的多项式作为生成多项式,如CRC-16多项式0x8005;
- 将数据的最高位添加一个0,得到一个比数据位数多1的新数据序列;
- 将生成多项式左移n位,其中n等于数据序列的长度;
- 用新数据序列除以生成多项式,得到余数;
- 将余数添加到数据序列的末尾,作为校验码。
示例
假设需要对数据0x12, 0x34, 0x56, 0x78进行CRC校验,生成多项式为CRC-16多项式0x8005:
- 数据序列为0x12345678;
- 在数据序列的末尾添加一个0,得到0x123456780;
- 生成多项式0x8005左移33位,得到0x18005;
- 将数据序列0x123456780除以生成多项式0x18005,得到余数0x40C6;
- 将余数0x40C6添加到数据序列的末尾,作为校验码,得到最终的数据序列0x1234567840C6。
代码
代码语言:javascript复制/**
* 计算crc校验
*
* @param sendData 发送的数据
* @param length 发送的数据长度
* @return crc校验值
*/
public static int calculateCrc(int[] sendData, int length) {
int crc = 0xffff;
for (int i = 0; i < length; i ) {
int value = sendData[i];
int realValue = value;
if (value < 0) {
realValue = 0x100;
}
crc ^= realValue;
for (int j = 0; j < 8; j ) {
if ((crc & 0x01) > 0) {
crc = (crc >> 1) ^ 0xa001;
} else {
crc = crc >> 1;
}
}
}
return crc;
}
CBC校验(Cyclic Redundancy Check)
CBC校验的基本原理是将要传输的数据看成一个二进制数,然后进行多项式除法运算。具体来说,CBC校验将数据和一个固定的多项式进行异或运算,然后将异或运算的结果除以另一个固定的多项式,得到余数作为校验码。发送方将数据和校验码一起发送给接收方,接收方同样对收到的数据进行计算,得到校验码,如果两者一致,则说明数据传输正确。
计算方法
- 将校验码初始化为0;
- 将每个字节与校验码进行异或操作,得到一个新的校验码;
- 重复步骤2,直到所有的字节都被处理完毕;
- 对最终的校验码取反,得到最终的校验值。
示例
假设需要对数据0x12, 0x34, 0x56, 0x78进行校验,校验码的初始值为0x00:
- 校验码的初始值为0x00;
- 对第一个字节0x12进行处理,0x00 ^ 0x12 = 0x12,得到一个新的校验码0x12;
- 对第二个字节0x34进行处理,0x12 ^ 0x34 = 0x26,得到一个新的校验码0x26;
- 对第三个字节0x56进行处理,0x26 ^ 0x56 = 0x70,得到一个新的校验码0x70;
- 对第四个字节0x78进行处理,0x70 ^ 0x78 = 0x08,得到一个新的校验码0x08;
- 对最终的校验码0x08取反,得到最终的校验值0xF7。
代码
代码语言:javascript复制private static byte calculateCbc(byte[] data) {
byte checksum = 0;
for (int i = 0; i < data.length; i ) {
checksum ^= data[i];
}
return checksum;
}
拓展校验方法
- LRC校验(Longitudinal Redundancy Check):是一种纵向冗余校验方法,对数据的每个字节进行异或运算,生成一个校验和,可以检测简单的传输错误。
- VRC校验(Vertical Redundancy Check):是一种垂直冗余校验方法,对数据的每一位进行异或运算,生成一个校验和,能够检测单比特的传输错误。
- BCC校验(Block Check Character):是一种块校验字符,将数据划分为若干个块,对每个块使用CRC或LRC等校验方法生成校验和,最后将所有校验和进行异或运算,生成一个校验和。
- Adler-32校验:是一种比CRC校验速度更快的校验方法,使用两个16位整数进行运算,能够检测多达32位的传输错误。
串口传输补位
在串口通信中,数据通常以字节为单位进行传输。有时候,为了保证数据的完整性和正确性,需要在数据中添加一些特殊的字节来进行补位。补位的方式有很多种,下面介绍两种常用的补位方式。
填充字节补位
填充字节补位的方式是在数据中插入一个特殊的字节,例如0x00,来进行补位。如果在数据中出现了0x00字节,那么在这个字节前面再插入一个0x01字节。这样,在数据中就不会出现连续的两个0x00字节,避免了数据的混淆。
包长度补位
包长度补位的方式是在数据包头中添加一个指示数据包长度的字段。这个字段通常是一个字节或者两个字节,用来表示数据包的长度。这样,在接收端就可以通过读取数据包头中的长度字段来确定数据包的长度,从而避免了数据的丢失或混淆。
注意
在进行补位时,需要保证发送端和接收端的补位方式相同,否则会导致数据的错误或者丢失。同时,在进行补位时还需要注意数据的效率和实时性,避免过多的数据传输延迟。