一.概述
为了在串口通信或者固件升级时防止数据传输错误导致出现问题,一般要引入校验算法,嵌入式常见简易校验算法(c语言)主要有:
奇偶校验发,校验和,异或校验,CRC校验,md5,每种校验方法都有自己的特点,在不同场景可以选择合适的算法进行应用。
二.各种校验算法详细介绍
1.奇偶校验法
奇偶校验算法(Parity Check Algorithm)是一种简单的错误检测方法,用于验证数据传输中是否发生了位错误。
1.1 原理示例:
采用奇偶校验数据是四个byte:0xf1,0xf3,0xf7,0xff, 那加入奇偶校验后发送数据为多少?
对于给定的四个byte数据:0xf1, 0xf3, 0xf7, 0xff,我们首先需要将这些byte转换为二进制形式,并统计其中“1”的总数。然后,根据选择的奇偶校验类型(奇校验或偶校验),我们会在数据末尾添加一个额外的bit作为校验位。
首先,将给定的byte转换为二进制:
0xf1: 1111 0001
0xf3: 1111 0011
0xf7: 1111 0111
0xff: 1111 1111
接下来,统计这四个byte中“1”的总数:
0xf1中有4个“1”
0xf3中有4个“1”
0xf7中有4个“1”
0xff中有8个“1”
总共是 4 + 4 + 4 + 8 = 20 个“1”。
现在,我们需要决定是添加“0”还是“1”作为校验位:
如果选择奇校验,因为当前“1”的总数是偶数(20),我们需要添加一个“1”来使总数变为奇数。
如果选择偶校验,因为当前“1”的总数是偶数(20),我们实际上不需要改变它(即添加一个“0”),但在这里,为了明确表示我们添加了一个校验位,并且这个校验位是为了满足偶校验的要求(尽管在这个特定情况下它看起来是多余的),我们仍然会添加一个“0”。但请注意,在大多数情况下,如果原始数据中“1”的数量已经是偶数,并且我们正在进行偶校验,那么我们不会添加额外的bit,或者可以说校验位是隐式的“0”。
然而,为了回答这个问题,并且假设我们确实需要显式地添加一个校验位,那么:
对于奇校验,发送的数据(包括校验位)将是:1111 0001 1111 0011 1111 0111 1111 1111 1(校验位)
对于偶校验,虽然在这个特定情况下添加“0”是多余的,但按照问题的要求,发送的数据(包括校验位)将是:1111 0001 1111 0011 1111 0111 1111 1111 0(校验位)
但请注意,在实际通信中,校验位通常是作为单独的一部分来处理的,而不是直接附加在数据字节的末尾形成一个更长的字节序列。此外,校验位的处理也取决于通信协议和硬件/软件的实现方式。
1.2 代码实现
1.2.1 发送方函数
void sender_send_data_with_parity(unsigned char* data, int length) {
// 统计数据字节中1的个数
int count = 0;
for (int i = 0; i < length; i++) {
unsigned char byte = data[i];
for (int j = 0; j < 8; j++) {
if ((byte >> j) & 1) {
count++;
}
}
}
// 计算奇偶校验位,如果1的个数是偶数,则校验位为0,否则为1
unsigned char parity_bit = (count % 2 == 0) ? 0 : 1;
// 发送数据字节和奇偶校验位
for (int i = 0; i < length; i++) {
send_byte(data[i]);
}
send_byte(parity_bit);
}
1.2.2接收方函数
void receiver_receive_data_with_parity() {
// 接收数据
unsigned char received_data[MAX_LENGTH];
int length = receive_data(received_data);
// 统计接收到的数据字节中1的个数
int count = 0;
for (int i = 0; i < length - 1; i++) {
unsigned char byte = received_data[i];
for (int j = 0; j < 8; j++) {
if ((byte >> j) & 1) {
count++;
}
}
}
// 比较接收到的奇偶校验位与数据字节中1的个数是否一致
unsigned char expected_parity_bit = (count % 2 == 0) ? 0 : 1;
unsigned char received_parity_bit = received_data[length - 1];
if (expected_parity_bit != received_parity_bit) {
// 发生了位错误
handle_error();
} else {
// 数据传输正常
process_data(received_data, length - 1);
}
}
2.校验和
2.1原理介绍
校验和是最基本,也是嵌入式软件工程师最常用的一种校验算法,其实现方法很简单。
实现原理:按每个字节,计算累加和。
实现的方式方法很多,不同的编程语言,不同的应用有所不同,有Checksum-8/16/32等,下面以C语言8位校验和为例:
2.2代码示例
uint8_t CheckSum(uint8_t *Buf, uint8_t Len)
{
uint8_t i = 0;
uint8_t sum = 0;
uint8_t checksum = 0;
for(i=0; i<Len; i++)
{
sum += *Buf++;
}
checksum = sum & 0xff;
return checksum;
}
3.异或校验
3.1原理介绍
【异或校验】与【校验和】类似,对数据进行“异或”,最终得到一个“异或值”。
实现原理:按每个字节异或,求结果。
校验和、异或校验的方式有很多种,比如有的还会传入一个参数作为异或校验的值。实现的方式方法很多,不同的编程语言,不同的应用有所不同,下面以C语言为例:
3.2代码示例
uint8_t CheckXOR(uint8_t *Buf, uint8_t Len)
{
uint8_t i = 0;
uint8_t x = 0;
for(i=0; i<Len; i++)
{
x = x^(*(Buf+i));
}
return x;
}
4.CRC校验
4.1基本原理
CRC:Cyclic Redundancy Check,即循环冗余校验。
CRC是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
实现原理:设置crc值和多项式码;依次遍历每个字节,与crc值进行异或;crc值取出最低位的值,并右移一位;如果最低位值位1,则于多项式码进行异或;循环直到8位结束。
CRC校验属于冗余校验中的一种,大学学计算机相关专业的同学都应该学过CRC校验。
CRC有多种变体,比如:CRC-1、 CRC-5-USB、 CRC-8、 CRC-16、 CRC-32、 CRC-64等。其中,在嵌入式领域,CRC-16用的比较多。
4.2 RC算法的具体实现过程如下:
将待发送的数据视为一个二进制多项式D(x),其中每一位代表一个系数。
选取一个生成多项式G(x),该多项式的长度决定了CRC校验码的长度。
对D(x)乘以x^n(n为生成多项式的长度减1),形成一个与G(x)同阶的多项式。
使用生成多项式G(x)对该扩展后的多项式进行模2除法,得到的余数即为CRC校验码。
将CRC校验码附加到原始数据的末尾,形成完整的数据包。
在接收端,对数据包再次进行相同的模2除法运算,若余数为零,则认为数据包未发生错误。
4.3 下面是一个CRC-16算法的C语言实现示例
uint16_t Crc16(uint8_t *data,uint16_t len)
{
uint16_t crc16 = 0xFFFF;
uint32_t uIndex ; //CRC查询表索引
while (len --)
{
uIndex = (crc16&0xff) ^ ((*data) & 0xff) ; //计算CRC
data = data + 1;
crc16 = ((crc16>>8) & 0xff) ^ crc16_tab[uIndex];
}
return crc16 ;//返回CRC校验值
}
4.4 下面是一个CRC-32算法的C语言实现示例
include <stdint.h>
uint32_t crc32(const unsigned char *buf, size_t len) {
uint32_t crc = 0xFFFFFFFF;
const unsigned char *end = buf + len;
uint32_t table[256];
// Pre-compute the CRC table
for (int i = 0; i < 256; i++) {
uint32_t c = i;
for (int j = 0; j < 8; j++) {
if (c & 1) {
c = 0xEDB88320 ^ (c >> 1);
} else {
c = c >> 1;
}
}
table[i] = c;
}
// Process each byte of the data
while (buf < end) {
crc = table[(crc ^ *buf++) & 0xFF] ^ (crc >> 8);
}
return crc ^ 0xFFFFFFFF;
}
4.4 查表法
在CRC(Cyclic Redundancy Check)算法的实现中,经常使用一个预计算的查找表(lookup table),这个查找表就是一个数组,用来加速CRC的计算过程。这个数组通常被称为“CRC表”或“CRC查找表”。
CRC算法的核心是基于二进制的多项式除法,其中使用的除数是一个固定的多项式(即生成多项式)。在软件实现中,特别是当需要快速计算CRC校验值时,查找表可以显著减少计算量。
查找表的工作原理:
预计算: 在CRC算法的初始化阶段,程序会预先计算出所有可能的8位(或其他位数,取决于查找表的设计)输入与生成多项式进行模2除法的结果,并将这些结果存储在一个数组中。这个数组的大小通常是256个元素,每个元素对应一个8位输入的CRC校验值。
快速计算: 当实际计算数据的CRC校验值时,算法会对数据进行逐字节处理。对于每个字节,算法会在查找表中查找对应的CRC值,然后与之前计算得到的部分CRC值进行异或操作。这个过程会重复直到所有的数据字节都被处理完毕。
最终CRC值: 在处理完所有数据后,累积的CRC值会经过可能的反转(reflect)和初始值(seed)调整,得到最终的CRC校验值。
查找表优点:
使用查找表的主要优点是减少了每次迭代中的复杂计算,尤其是避免了多项式除法,而代之以简单的数组查找和异或操作,这在大多数现代计算机架构上是非常快速的。
查找表的生成:
查找表的生成涉及对每一个可能的8位输入(从0到255)执行CRC算法的完整计算过程,并存储最终结果。这个过程只在程序启动时执行一次,之后就可以复用这个查找表来快速计算任何数据的CRC校验值。
查找表的使用使得CRC计算在软件中变得既快速又高效,尤其在实时系统和大量数据处理中,这一点尤为重要。
5.MD5算法
MD5:Message-Digest Algorithm 5,即“信息-摘要算法。消息摘要算法又称为哈希算法、散列算法,输出的消息摘要又称为哈希值、散列值。
从名字来看就知道它是从MD3、MD4发展而来的一种加密算法,其主要通过采集文件的信息摘要,以此进行计算并加密。
压缩性:MD5可以将任意长度的输入转化为128位长度的输出;
不可逆性:MD5是不可逆的,我们无法通过常规方式从MD5值倒推出它的原文;
抗修改性:对原文做一丁点儿改动,MD5值就会有巨大的变动,也就是说就算两个MD5值非常相似,你也不能想当然地认为它们俩对应的原文也非常相似。
实现原理:MD5是输入不定长度信息,输出固定长度128-bits的算法。经过程序流程,生成四个32位数据,最后联合起来成为一个128-bits散列。基本方式为,求余、取余、调整长度、与链接变量进行循环运算。得出结果。
因为MD5可以被暴力破解,所以MD5不再是安全的了,对安全性要求较高的场合,不建议直接使用MD5。
MD5的源码在网上都能找到现成的,而且有不同编程语言(C、 C++、 JAVA)版本。
应用:可以对升级包进行MD5校验,在嵌入式linux下可以直接通过命令进行MD5计算,应用非常方便。