TPS929120的CRC校验的三种实现方法


最近在帮客户编写基于S32K144编写TPS929120的软件驱动时,需要在发送数据的末端增加8bit的CRC校验码。因为之前使用E522.49时,其数据手册直接提供了自身CRC校验算法的代码,而TPS929120的手册只告知了算法的CRC多项式为 X 8 + X 5 + X 4 + 1 X^8+X^5+X^4+1 X8+X5+X4+1,初始值为0xFF,所以代码部分需要自己实现。

我查阅了TLD7002,BD18331,MPQ7225等LED Driver的数据手册,都是采用的CRC校验算法,其中MPQ7225的CRC校验算法和TPS929120是一样的。因为之前接触CRC校验不多,特定将相关的学习过程记录下来,方便后面有需要时快速完成其他厂家的LED Driver软件驱动。

CRC基础知识

这一章节主要介绍CRC概念和参数模型(来源于网络),如果想要更深入的了解,推荐阅读如下两篇文章:

CRC概念

CRC(Cyclic Redundancy Checksum)是一种纠错技术,代表循环冗余校验和。

数据通信领域中最常用的一种差错校验码,其信息字段和校验字段长度可以任意指定,但要求通信双方定义的CRC标准一致。主要用来检测或校验数据传输或者保存后可能出现的错误。它的使用方式可以说明如下图所示:

在数据传输过程中,无论传输系统的设计再怎么完美,差错总会存在,这种差错可能会导致在链路上传输的一个或者多个帧被破坏(出现比特差错,0变为1,或者1变为0),从而接受方接收到错误的数据。

为尽量提高接受方收到数据的正确率,在接收方接收数据之前需要对数据进行差错检测,当且仅当检测的结果为正确时接收方才真正收下数据。检测的方式有多种,常见的有奇偶校验因特网校验循环冗余校验等。

CRC参数模型

一个完整的CRC参数模型应该包含以下信息:WIDTH,POLY,INIT,REFIN,REFOUT,XOROUT

  • NAME:参数模型名称。
  • WIDTH:宽度,即生成的CRC数据位宽,如CRC-8,生成的CRC为8位
  • POLY:十六进制多项式,省略最高位1,如 X 8 + X 2 + X + 1 X^8 + X^2 + X + 1 X8+X2+X+1,二进制为1 0000 0111,省略最高位1,转换为十六进制为0x07。
  • INIT:CRC初始值,和WIDTH位宽一致。
  • REFIN:true或false,在进行计算之前,原始数据是否翻转,如原始数据:0x34 = 0011 0100,如果REFIN为true,进行翻转之后为0010 1100 = 0x2c
  • REFOUT:true或false,运算完成之后,得到的CRC值是否进行翻转,如计算得到的CRC值:0x97 = 1001 0111,如果REFOUT为true,进行翻转之后为11101001 = 0xE9。
  • XOROUT:计算结果与此参数进行异或运算后得到最终的CRC值,和WIDTH位宽一致。

通常如果只给了一个多项式,其他的没有说明则:INIT=0x00,REFIN=false,REFOUT=false,XOROUT=0x00

常用的21个标准CRC参数模型:

CRC算法名称多项式公式WIDTHPOLYINITXOROUTREFINREFOUT
CRC-4/ITUx4 + x + 14030000TRUETRUE
CRC-5/EPCx5 + x3 + 15090900FALSEFALSE
CRC-5/ITUx5 + x4 + x2 + 15150000TRUETRUE
CRC-5/USBx5 + x2 + 15051F1FTRUETRUE
CRC-6/ITUx6 + x + 16030000TRUETRUE
CRC-7/MMCx7 + x3 + 17090000FALSEFALSE
CRC-8x8 + x2 + x + 18070000FALSEFALSE
CRC-8/ITUx8 + x2 + x + 18070055FALSEFALSE
CRC-8/ROHCx8 + x2 + x + 1807FF00TRUETRUE
CRC-8/MAXIMx8 + x5 + x4 + 18310000TRUETRUE
CRC-16/IBMx16 + x15 + x2 + 116800500000000TRUETRUE
CRC-16/MAXIMx16 + x15 + x2 + 11680050000FFFFTRUETRUE
CRC-16/USBx16 + x15 + x2 + 1168005FFFFFFFFTRUETRUE
CRC-16/MODBUSx16 + x15 + x2 + 1168005FFFF0000TRUETRUE
CRC-16/CCITTx16 + x12 + x5 + 116102100000000TRUETRUE
CRC-16/CCITT-FALSEx16 + x12 + x5 + 1161021FFFF0000FALSEFALSE
CRC-16/X25x16 + x12 + x5 + 1161021FFFFFFFFTRUETRUE
CRC-16/XMODEMx16 + x12 + x5 + 116102100000000FALSEFALSE
CRC-16/DNPx16 + x13 + x12 + x11 + x10 + x8 + x6 + x5 + x2 + 1163D650000FFFFTRUETRUE
CRC-32x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 13204C11DB7FFFFFFFFFFFFFFFFTRUETRUE
CRC-32/MPEG-2x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 13204C11DB7FFFFFFFF0FALSEFALSE

TPS929120的CRC值计算方式

CRC参数:

根据数据手册,TPS929120选择的是CRC多项式为 X 8 + X 5 + X 4 + 1 X^8+X^5+X^4+1 X8+X5+X4+1,初始值为0xFF;所以POLY为0x31,INIT为FF。由于UART的数据传输是LSB(先传输低位),所以REFIN为True。综上所述,TPS929120的CRC参数表为:

NAME = CRC_TPS929120
WIDTH = 8
POLY = 0x31 = 0011 0001(最高位1已经省略)
INIT = 0xFF
XOROUT = 0x00
REFIN = TRUE
REFOUT = FALSE

算法图:

TPS929120数据手册提供了自身CRC校验的算法图,如下所示:

因为TPS929120要求第一个发送的数据0x55(用于同步)不参与计算,所以下面介绍的几种计算方式都不会将0x55纳入CRC计算范围。

计算方法一:

方法一按照TPS929120提供的算法图进行实现。

计算步骤:

结合TPS929120数据手册提供的算法图,方法一的计算步骤如下所示:


1. 将0xFF作为CRC初始值。

2. 当第一个输入字节的Bit0到来时,

   2.1 将初始值的Bit7和输入值的Bit0进行异或运算,得到的值保留,记作Temp_Bit;

   2.2 将初始值的Bit6代替原本的Bit7;

   2.3 将初始值的Bit5代替原本的Bit6;

   2.4 将初始值的Bit4和Temp_Bit进行异或运算,使用得到的值代替原本的Bit5;

   2.5 将初始值的Bit3和Temp_Bit进行异或运算,使用得到的值代替原本的Bit4;

   2.6 将初始值的Bit2代替原本的Bit3;

   2.7 将初始值的Bit1代替原本的Bit2;

   2.8 将初始值的Bit0代替原本的Bit1;

   2.9 使用Temp_Bit代替原本的Bit0;

3. 第一个输入字节的剩余7bit也是同样的操作,然后得到第一个输入字节的CRC值。

4. 第二个输入字节到来,使用上一次计算得到的CRC值作为CRC初始值,重复2,3步骤。

5. 当最后一个输入字节计算完毕之后,就得到了最终的CRC校验值。
实现代码为:
  • 单个字节的CRC值计算
#define BIT0 (0x01)
#define BIT1 (0x02)
#define BIT2 (0x04)
#define BIT3 (0x08)
#define BIT4 (0x10)
#define BIT5 (0x20)
#define BIT6 (0x40)
#define BIT7 (0x80)

unsigned int CRC_Calculation(unsigned int CRC_Initial, unsigned int Input_Data)
{
	unsigned int Temp_Bit, Input_Bit;
    /* store every bit value of Input_Data */
	unsigned int bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7;   
	/* store the Input_Data (byte) 's CRC */
    unsigned int CRC=0; 
    /* get every bit of CRC initial value */
	unsigned int i=0; bit0 = CRC_Initial & BIT0; 
    
	bit1 = (CRC_Initial & BIT1)>>1;
	bit2 = (CRC_Initial & BIT2)>>2;
	bit3 = (CRC_Initial & BIT3)>>3;
	bit4 = (CRC_Initial & BIT4)>>4;
	bit5 = (CRC_Initial & BIT5)>>5;
	bit6 = (CRC_Initial & BIT6)>>6;
	bit7 = (CRC_Initial & BIT7)>>7;
    
	for(i=0; i<8; i++)
	{
        /* extract one bit of Input Data (from LSB to MSB) */
		Input_Bit = (Input_Data >> i) & 0x01;
        /* Do Input_Bit XOR bit7 */
		Temp_Bit = Input_Bit ^ bit7; 
		bit7 = bit6;
		bit6 = bit5;
        /* Do bit4 XOR Temp_Bit */
		bit4 = bit4 ^ Temp_Bit; 
		bit5 = bit4;
        /* Do bit3 XOR Tmep_Bit */
		bit3 = bit3 ^ Temp_Bit; 
		bit4 = bit3;
		bit3 = bit2;
		bit2 = bit1;
		bit1 = bit0;
		bit0 = Temp_Bit;
	}
	
	CRC = (bit7<<7)|(bit6<<6)|(bit5<<5)|(bit4<<4)|(bit3<<3)|(bit2<<2)|(bit1<<1)|bit0;
	return CRC;
}
  • 命令帧的CRC值计算
unsigned int CRC(unsigned int commandFrame_withoutCRC[], unsigned int byteLength)
{
	unsigned int j;
  	unsigned int CRCtemp;
  	for(j=0; j<byteLength-1; j++)
  	{
  		if(j==0)
  		{
      		/* CRC initial value is 0xFF, and SYNC byte is not engaged in CRC calculation
             */
  			CRCtemp = CRC_Calculation(0xFF, commandFrame_withoutCRC[j+1]); 
  		}
  		else
  		{
            /* assign the last calculated CRC to CRC_initial and calculate the second, 
             * third,..., byte's CRC value until all bytes have been calculated 
             */
  			CRCtemp = CRC_Calculation(CRCtemp, commandFrame_withoutCRC[j+1]);
  		}
  	}
    /* after calculating the final byte's CRC value we get this frame's CRC */
  	return CRCtemp; 
}

计算方法二:

Github上面有一套成熟的开源CRC算法,如下,

GitHub - whik/crc-lib-c: 基于C语言的CRC校验库,包括常用的21个CRC参数模型实现

参考其中的CRC-8/MAXIM,并根据TPS929120的CRC参数进行微调,得到的算法如下:

/* as UART transmit from LSB to MSB, 
 * invert the polynomial 0x31 (0011 0001) to 0x8C (1000 1100)
 */
#define polynomialINV 0x8C 
#define LSB 0x01

/* calculate CRC of command frame */
unsigned int CRC_LUT(unsigned int commandFrame_withoutCRC[], unsigned int byteLength) 
{
	unsigned int remainder, k, j;
    /* assign the initial value 0xFF */
	remainder = 0xFF; 
    /* the first SYNC byte not engage CRC calculation */
	for(k=1;k<byteLength;k++) 
	{
        /* input data byte XOR remainder */
		remainder ^= commandFrame_withoutCRC[k]; 
		for(j=0; j<8; j++)
		{
			if(remainder & LSB)
			{
                /* right shift 1 bit and do the XOR operation with 0x8C */
				remainder = (remainder>>1) ^ polynomialINV; 
			}
			else
			{
				remainder = remainder>>1;
			}
		}
	}
	/* we have to reverse the final remainder to get the CRC value, for example	
     * for example, if final remainder = 0010 1100, we need to reverse it to 0011 0100
     */
	remainder = ((remainder & 0x80)>>7) + ((remainder & 0x40) >>5) + ((remainder & 0x20) >>3) + ((remainder & 0x10)>>1) + ((remainder & 0x08)<<1) + ((remainder & 0x04) <<3) + ((remainder & 0x02) <<5) + ((remainder & 0x01)<<7);
	return remainder;
}

计算方法三:

方法三在方法二的基础上进行优化,将CRC的计算部分(如下图)提前做成数组,涵盖0-255的CRC计算结果,然后用到的时候直接查表。

for(i=0; i<8; i++)
	{
		if(remainder & LSB)
		{
            /* right shift 1 bit and do the XOR operation with 0x8C */
			remainder = (remainder>>1) ^ polynomialINV; 
		}
		else
		{
			remainder = remainder>>1;
		}
	}

最终的实现形式如下:

/* as UART transmit from LSB to MSB, 
 * invert the polynomial 0x31 (0011 0001) to 0x8C (1000 1100)
 */
#define polynomialINV 0x8C 
#define LSB 0x01
unsigned int crcArray[256];

/* calculate and store the CRC of data from 0x00 to 0xFF */
void crcInitial() 
{
	unsigned int k, j, remainder;
	for(k=0;k<256;k++)
	{
		remainder=k;
		for(j=8;j>0;j--)
		{
			if(remainder & LSB)
			{ 
                /* right shift 1 bit and do the XOR operation with 0x8C */
				remainder = (remainder>>1) ^ polynomialINV; 
			}
			else
			{
				remainder = remainder>>1;
			}
		}
		crcArray[k]=remainder;
	}
}

/* calculate CRC of command frame */
unsigned int CRC_LUT(unsigned int commandFrame_withoutCRC[], unsigned int byteLength) 
{
	unsigned int remainder, tempData, k;
    /* assign the initial value 0xFF */
	remainder = 0xFF; 
    /* the first SYNC byte not engage CRC calculation */
	for(k=1;k<byteLength;k++) 
	{
        /* input data byte XOR remainder */
		tempData = remainder ^ commandFrame_withoutCRC[k];
        /* use tempData as the index to retrieve its CRC from crcArray */
		remainder = crcArray[tempData]; 
	}
	/* we have to reverse the final remainder to get the CRC value, for example	
     * for example, if final remainder = 0010 1100, we need to reverse it to 0011 0100
     */
	remainder = ((remainder & 0x80)>>7) + ((remainder & 0x40) >>5) + ((remainder & 0x20) >>3) + ((remainder & 0x10)>>1) + ((remainder & 0x08)<<1) + ((remainder & 0x04) <<3) + ((remainder & 0x02) <<5) + ((remainder & 0x01)<<7);
	return remainder;
}

验证:

使用一个数组{0x55,0x80,0x61,0x00}进行验证,其中0x55不参与CRC计算,以计算方法一为例,添加打印的mian函数如下:

#include <stdio.h>
int main()
{
	unsigned int data[4] = {0x55,0x80,0x61,0x00};
	unsigned int crc;

	for(int i = 0; i < 4; i++)
	{
		printf("%02x ", data[i]);
	}
	printf("\n");
	crc = CRC(data,4);
	printf("Method 1:CRC result of TPS929120 is %02x\n", crc);
	return 0;
}

使用在线C语言编译器(菜鸟教程在线编辑器 (runoob.com))进行验证,计算结果如下图,为0x74:

使用在线CRC计算工具进行验证,结果也是0x74:

使用TPS929120自带的Excel形式的CRC计算工具进行验证,结果也是0x74:

其他两种方式经过验证也是一样的结果,有兴趣的读者可以使用在线C语言编辑器试一下。

TPS929120相关资料

以下是基于STM32串口向TPS929120烧录代码的代码示例: ```c // 定义TPS929120的I2C地址 #define TPS929120_ADDR 0x22 // 定义TPS929120的Flash地址 #define TPS929120_FLASH_ADDR 0x0000 // 定义需要烧录的代码数据 const uint8_t code_data[128] = {0x00, 0x01, ...}; // 向TPS929120写入代码数据 void tps929120_write_code_data() { // 1. 发送起始信号和设备地址 HAL_I2C_Master_Transmit(&hi2c1, TPS929120_ADDR << 1, NULL, 0, 100); // 2. 发送Flash地址 uint8_t addr_data[2]; addr_data[0] = TPS929120_FLASH_ADDR >> 8; addr_data[1] = TPS929120_FLASH_ADDR & 0xFF; HAL_I2C_Master_Transmit(&hi2c1, TPS929120_ADDR << 1, addr_data, 2, 100); // 3. 发送代码数据 HAL_I2C_Master_Transmit(&hi2c1, TPS929120_ADDR << 1, (uint8_t *)code_data, 128, 100); // 4. 发送停止信号 HAL_I2C_Stop(&hi2c1); } // 初始化TPS929120 void tps929120_init() { // 1. 发送起始信号和设备地址 HAL_I2C_Master_Transmit(&hi2c1, TPS929120_ADDR << 1, NULL, 0, 100); // 2. 发送控制字节和配置数据 uint8_t cfg_data[2] = {0xB0, 0x06}; // 这里的配置数据可以根据实际需求进行修改 HAL_I2C_Master_Transmit(&hi2c1, TPS929120_ADDR << 1, cfg_data, 2, 100); // 3. 发送停止信号 HAL_I2C_Stop(&hi2c1); } // 主函数 int main(void) { // 1. 初始化I2C通信 HAL_I2C_Init(&hi2c1); // 2. 初始化TPS929120 tps929120_init(); // 3. 烧录代码数据 tps929120_write_code_data(); // 4. 程序结束 while(1); } ``` 以上代码主要包含以下几个步骤: 1. 定义TPS929120的I2C地址和Flash地址,以及需要烧录的代码数据。 2. 初始化TPS929120,将配置数据写入寄存器中。 3. 调用函数向TPS929120写入代码数据。 4. 主函数结束。 需要注意的是,具体的通信协议和寄存器配置可能会因不同的TPS929120型号而有所不同,您需要参考相关的产品手册进行编写。同时,以上代码中的代码数据是示例数据,您需要将其替换为您需要烧录的代码数据。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Auto FAE进阶之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值