CRC16校验示例C语言程序

本文介绍CRC16校验码的计算方法,包括CRC16-XMODEM和CRC16-CCITT两种标准的具体实现。通过示例代码详细解释了如何计算CRC校验码并进行发送和接收报文的校验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include<stdio.h>
#include<stdlib.h>
#include"crc16.h"

int main(int argc, char **argv) {
	unsigned char meg[4]={0xB5,0x12,0xA3,0x49};
	sendMessage(meg,sizeof(meg));
	receiveMessage(meg,sizeof(meg));
	return EXIT_SUCCESS;
}





#ifndef _CRC16_H_
#define _CRC16_H_

void sendMessage(unsigned char *meg,int const length);//发送报文,报文信息,报文信息总字节数 

void receiveMessage(unsigned char *meg,int const length);//接收报文 

static unsigned short crc16_xmodem(unsigned const char *meg,int const length);//采用CRC16-XMODEM标准进行校验码计算 

static unsigned short crc16_xmodem_improve(unsigned const char *meg,int const length);//采用CRC16-XMODEM标准进行校验码计算(算法改) 

static unsigned short crc16_ccitt(unsigned const char *meg,int const length);//采用CRC16-CCITT标准进行校验码计算

#endif



#include<stdio.h> 
#include"crc16.h"
#define improvement 1     //使用普通的寄存器移位算法还是改进算法 

static unsigned short crc_send;//定义一个全局变量,用于存放发送的crc校验码 

void sendMessage(unsigned char *meg,int const length){
	unsigned char *p;
	printf("正在计算CRC码...\n");
#if improvement
	crc_send=crc16_ccitt(meg,length);
	//crc_send=crc16_xmodem_improve(meg,length);//这里计算校验码需要实际发送的数据帧字节数
#else 
	crc_send=crc16_ccitt(meg,length);
	//crc_send=crc16_xmodem(meg,length);//计算校验码只需循环数据帧的字节数(移位时自动在数据码末尾补16个0)
#endif 

	printf("正在发送报文:");
	for(p=meg;p<&meg[length];printf("%0x\t",*p++));
	printf("%0x\t%0x\012",(unsigned char)(crc_send>>8),(unsigned char)crc_send);
	printf("报文发送完毕!\012\012\012");
}

void receiveMessage(unsigned char *meg,int const length){
	unsigned char *p;
	unsigned short crc;
	
	printf("正在接收报文...");
	printf("\n报文接收完毕!\n");
	
	printf("正在校验CRC码...\n");
#if improvement
	crc=crc16_ccitt(meg,length);
	//crc=crc16_xmodem_improve(meg,length);	
#else 
	crc=crc16_ccitt(meg,length);
	//crc=crc16_xmodem(meg,length);
	
#endif
	if(crc == crc_send)
		printf("OK,报文接收成功!\n");
	else 
		printf("ERROR,报文接收失败!\n");
}

/***********

程序可以如下实现:

1)将M*x^r的前r位放入一个长度为r的寄存器;

2)如果寄存器的首位为1,将寄存器左移1位(将Mx^r剩下部分的MSB移入寄存器的LSB),

再与生成多项式G的后r位异或,否则仅将寄存器左移1位(将Mx^r剩下部分的MSB移入寄存器的LSB);

3)重复第2步,直到M全部Mx^r移入寄存器;

4)寄存器中的值则为校验码。

***********/

unsigned short crc16_xmodem(unsigned const char *meg,int const length){
	int i,j;//循环变量
	unsigned short crc_reg=(meg[0]<<8) + meg[1];//定义一个16位的CRC寄存器并初始化为meg码的前两字节 
	for(i=0;i<length;++i)
		if(i<length-2){            //当i<len-i时,需下下字节向左移位往寄存器中填充 
			for(j=0;j<8;++j)
				if(crc_reg & 0x8000)          //当crc_reg的最高位为1时所执行的操作 
					crc_reg=((crc_reg<<1) + (meg[i+2]>>7-j & 1)) ^ 0x1021;	//crc_reg左移一位,并与此字节的下下字节位往crc_reg中填充,并将最终的crc_reg与0x1021异或 
				else
					crc_reg=(crc_reg<<1) + (meg[i+2]>>7-j & 1);
		}
		else{              
			for(j=0;j<8;++j)
				if(crc_reg & 0x8000)          
					crc_reg=(crc_reg<<1) ^ 0x1021;	//当i>=len-i时,自动填充0 
				else
					crc_reg=(crc_reg<<1);
		}				 
	return crc_reg;	
}

/***********

CRC16的算法原理:

1.根据CRC16的标准选择初值crc_ini的值。

2.将数据的第一个字节tmp与crc_ini高8位异或。

3.判断最高位,若该位为 0 左移一位,若为 1 左移一位再与生成多项式Gx异或。

4.重复3直至8位全部移位计算结束。

5.重复将所有输入数据操作完成以上步骤,所得16位数即16位CRC校验码。

***********/ 

unsigned short crc16_xmodem_improve(unsigned const char *meg,int const length){
	int i, j;  
    unsigned short crc_ini=0; 
    unsigned short tmp;   //定义一个临时变量存入数据的每个字节 
    for (i=0;i<length;++i){
        tmp=meg[i]<<8;
        for (j=0;j<8;++j){
            if ((crc_ini ^ tmp) & 0x8000)
                crc_ini=(crc_ini<<1) ^ 0x1021;
            else
                crc_ini<<=1;
            tmp<<=1;           
        }
    }
    return crc_ini;	
}

/*与xmode算法类似,只不过该算法针对数据位序为低位在前,高位在后*/

/*以上的讨论中,消息的每个字节都是先传输MSB,CRC16-CCITT标准却是按照先传输LSB,消息
右移进寄存器来计算的。只需将代码改成判断寄存器的LSB,将0x1021按位颠倒后(0x8408)与
寄存器异或即可,如下所示*/

static unsigned short crc16_ccitt(unsigned const char *meg,int const length){
	int i, j;  
    unsigned short crc_ini=0; 
    unsigned short tmp;   //定义一个临时变量存入数据的每个字节 
    for (i=0;i<length;++i){
        tmp=meg[i];
        for (j=0;j<8;++j){
            if ((crc_ini ^ tmp) & 0x0008)
                crc_ini=(crc_ini>>1) ^ 0x8408;
            else
                crc_ini>>=1;
            tmp>>=1;           
        }
    }
    return crc_ini;
}

参考 :1、http://blog.youkuaiyun.com/leumber/article/details/54311811

           2、http://blog.youkuaiyun.com/exceptional_czr/article/details/70767572

<think>我们被要求用C语言编写CRC16校验代码CRC16有很多变种,常见的有CRC16-CCITT, CRC16-IBM等。这里我们选择常用的CRC16-CCITT(初始值为0xFFFF,多项式为0x1021)。如果没有特别指定,我们就用这个。 注意:CRC16的计算方式还包括输入数据是否反转、输出结果是否反转、初始值等。这里我们按照常见的CRC16-CCITT的标准: - 初始值:0xFFFF - 多项式:0x1021(正常形式,不反转) - 输入数据:每个字节按位反转(即每个字节的位顺序颠倒)?实际上,有些实现是按字节反转,有些则不是。这里我们采用常见的做法:输入数据不反转,计算过程中直接使用每个字节的位(从最高位开始)。 - 输出:结果不反转(即直接输出计算得到的16位值),但通常会将结果与0xFFFF异或?在CRC16-CCITT中,通常输出结果不与0xFFFF异或,但有些标准会。这里我们采用:初始值0xFFFF,计算完后结果不与0xFFFF异或(即直接输出)。 然而,值得注意的是,不同的标准有不同的要求。为了通用性,我们可以实现一个函数,允许传入初始值、多项式等参数。但题目没有指定,我们就按照最常见的CRC16-CCITT(也称为CRC-16/CCITT-FALSE)来实现,其特点: - 初始值:0xFFFF - 多项式:0x1021(不反转) - 输入数据:不反转 - 输出:不反转,且不与任何值异或 计算过程:对于每个输入字节,与当前CRC寄存器的高8位异或,然后整个寄存器左移8位,对异或后的字节(即当前处理的高8位)进行8次移位,每次判断最高位,如果为1则与多项式异或。 但是,还有一种更高效的方法是通过查表法。这里我们先给出直接计算法(按位计算),然后再给出查表法。由于题目没有指定效率,我们可以选择查表法(更常用)。 我们选择实现查表法,因为效率更高。首先,我们需要生成一个256个元素的查找表(每个字节对应一个CRC16值)。 步骤: 1. 定义一个256大小的数组,存放每个字节的CRC16值(初始时,对于0~255每个字节,计算其CRC16值,初始CRC寄存器为0,然后左移8位,但注意我们的计算方式与初始值无关,因为查表法在计算时是异或到高8位,然后查表得到余数)。 2. 计算整个数据的CRC:初始CRC=0xFFFF,然后对每个字节,将CRC的高8位与当前字节异或,然后查表得到一个新的值,再将CRC左移8位(即低8位成为新的高8位)与查表得到的值异或。 具体算法: - crc = 0xFFFF - 对于每个字节b in data: crc = (crc << 8) ^ crc_table[((crc >> 8) ^ b) & 0xFF]; - 最后crc即为结果。 注意:这个算法是CRC16-CCITT查表法的标准实现。 生成表的方法: 对于i从0到255: crc = i << 8; // 因为多项式是16位,所以这里将i放到高8位,低8位为0 for j from 0 to 7: if (crc & 0x8000) != 0: crc = (crc << 1) ^ 0x1021; else: crc = crc << 1; crc_table[i] = crc & 0xFFFF; 但是,请注意:在生成表的时候,我们使用的多项式是0x1021,并且计算时是左移(即从高位开始)。同时,我们只计算了16位,所以最后要&0xFFFF。 我们将编写一个函数来生成表,然后编写计算CRC的函数。 然而,为了代码的简洁,我们也可以直接静态定义表(避免每次运行时生成),因为表是固定的。 这里我们选择静态定义表,但为了完整,我们可以写一个初始化表的函数,但实际使用中,表是固定的,所以我们可以直接写一个静态表。 但是,由于题目没有要求,我们可以选择在函数内部静态定义表(只初始化一次)或者直接定义成常量。 我们采用直接定义常量表的方式,这样效率高。 不过,我们可以先写一个函数生成表,然后打印出来,再复制到代码中作为常量表。这样更准确。 但为了提交的代码完整,我们决定在代码中动态生成表(只生成一次,使用静态变量)。这样代码更通用,且不需要预先计算表。 但是,考虑到效率,在嵌入式系统中可能希望使用常量表,但这里我们使用动态生成表(第一次调用时生成,后续直接使用)。 不过,题目没有要求,我们就写一个函数,在函数内部使用静态表(预先计算好)或者动态生成。这里我们采用动态生成(在第一次调用计算CRC的函数时生成表,然后缓存起来)。 但是,为了简单,我们也可以每次计算CRC时都生成表?那样效率低。所以我们用静态变量来保存表,并且只生成一次。 然而,多线程环境需要考虑,但这里我们不考虑多线程。 我们选择:在函数内部使用静态变量,第一次调用时生成表。 步骤: 1. 定义一个静态的crc_table指针,初始为NULL。 2. 如果表未生成,则分配内存并生成表。 3. 然后使用查表法计算CRC。 但是,我们也可以不用动态内存,而是用静态数组,然后通过一个标志位判断是否初始化。这样避免动态内存分配。 我们使用静态数组,并用静态变量标记是否初始化。如果未初始化,则生成表。 但是,为了更简洁,我们也可以直接使用静态数组,并在定义时用初始化函数?C语言不允许在文件作用域内执行代码。所以我们在函数内部使用静态变量,并检查是否初始化。 另一种方法是:在文件作用域定义一个静态表,然后通过一个函数初始化它(使用C的构造函数特性(GCC的__attribute__((constructor)))?但这样不标准。 我们决定在函数内部使用静态变量,并在第一次进入时初始化。 代码结构: unsigned short crc16_ccitt(const unsigned char *data, size_t length) { static unsigned short crc_table[256]; static int initialized = 0; if (!initialized) { // 生成表 for (int i = 0; i < 256; i++) { unsigned short crc = (unsigned short)(i << 8); for (int j = 0; j < 8; j++) { if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; else crc = crc << 1; } crc_table[i] = crc; } initialized = 1; } unsigned short crc = 0xFFFF; for (size_t i = 0; i < length; i++) { crc = (crc << 8) ^ crc_table[((crc >> 8) ^ data[i]) & 0xFF]; } return crc; } 注意:上面的查表法计算步骤中,我们使用: crc = (crc << 8) ^ crc_table[((crc >> 8) ^ data[i]) & 0xFF]; 但是,这个公式是常见的。我们也可以写成: unsigned char index = (unsigned char)((crc >> 8) ^ data[i]); crc = (crc << 8) ^ crc_table[index]; 这样更清晰。 但是,我们需要注意:crc16位的,所以移位操作要确保在16位内。 另外,多项式0x1021是16位的,所以计算时用16位即可。 测试:我们可以用一些标准数据来测试,比如空数据,或者"123456789"。 根据标准,CRC16-CCITT(初始0xFFFF,多项式0x1021,输入不反转,输出不反转)对"123456789"的CRC16结果应该是0x29B1。 我们可以测试一下。 但是,由于题目要求是代码,我们直接给出完整代码。 另外,我们也可以提供另一种直接计算(按位)的方法,但题目没有要求,我们就提供查表法。 但是,如果用户希望其他多项式或初始值呢?我们可以扩展参数,但题目只要求crc16,没有具体说明,我们就用这个常见的。 下面我们写出完整的C语言代码。 注意:为了避免重复初始化表,我们使用静态变量initialized。 同时,考虑到多线程安全?这里不考虑。 代码:</think>以下是用C语言实现的CRC16校验代码(采用CRC-16/CCITT标准,多项式0x1021): ```c #include <stdio.h> #include <stdint.h> // CRC16计算函数 uint16_t crc16_ccitt(const uint8_t *data, size_t length) { uint16_t crc = 0xFFFF; // 初始值 for (size_t i = 0; i < length; i++) { crc ^= (uint16_t)data[i] << 8; // 移入新字节 for (int j = 0; j < 8; j++) { if (crc & 0x8000) crc = (crc << 1) ^ 0x1021; // 多项式异或 else crc <<= 1; } } return crc & 0xFFFF; // 返回16位结果 } // 测试示例 int main() { uint8_t test_data[] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'}; size_t len = sizeof(test_data); uint16_t crc = crc16_ccitt(test_data, len); printf("CRC16-CCITT: 0x%04X\n", crc); // 应输出0x29B1 return 0; } ``` ### 代码说明: 1. **多项式**:使用标准CCITT多项式 `0x1021`(x¹⁶ + x¹² + x⁵ + 1) 2. **初始值**:`0xFFFF` 3. **计算流程**: - 逐字节处理数据,每次异或到CRC寄存器高位 - 每字节进行8次位运算(左移+多项式异或) 4. **输出**:返回16校验值(小端格式) 测试数据 "123456789" 的预期结果为 **0x29B1**(可通过在线CRC工具验证)。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值