CRC校验——以SHT4xA温湿度传感器为例
一、简介
循环冗余校验码(CRC)的基本原理是:在K位信息码后再拼接R位的校验码,整个编码长度为N位,因此,这种编码又叫(N,K)码。对于一个给定的(N,K)码,可以证明存在一个最高次幂为N-K=R的多项式G(x)。根据G(x)可以生成K位信息的校验码,而G(x)叫做这个CRC码的生成多项式。
二、计算方法
具体参考CRC校验——以SHT30温湿度传感器为例(内附SHT30的驱动代码)
(一)步骤
(1) 预置一个值为0xFFFF的16位寄存器,此寄存器为CRC寄存器
(2) 把第一个8位二进制数与16位的CRC寄存器相异或,异或的结果存在CRC寄存器中
(3) CRC寄存器的内容左移一位,用0填补最低位,并检测移出位是0还是1
(4) 如果移出位是0,则重复步骤3,如果移出位是1,则与多项式进行异或
(5) 重复步骤3、4,直到左移8位,这样整个8位数据都进行了处理
(6) 重复步骤2~5,进行下一个字节的处理
(7)最后得到的CRC寄存器的内容即为CRC校验码
(二)参考代码(C语言)
#include <stdio.h>
typedef unsigned char uint8_t;
/*
* crc8校验函数,data为要校验的数据,len为要校验的数据的字节数
*/
uint8_t crc8(const uint8_t *data, int len)
{
const uint8_t POLYNOMIAL = 0x31;
uint8_t crc = 0xFF;
int i, j;
for (i=0; i<len; ++i)
{
crc ^= *data++;
for (j=0; j<8; ++j)
{
crc = ( crc & 0x80 )? (crc << 1) ^ POLYNOMIAL: (crc << 1);
}
}
return crc;
}
int main(int argc, const char *argv[])
{
unsigned char data1[2] = {0x61, 0x04};
unsigned char data2[2] = {0xBE, 0xEF};
printf("0x%02X\n", crc8(data1, 2));
printf("0x%02X\n", crc8(data2, 2));
return 0;
}
/* 输出结果
linux@linux-VirtualBox:~$ ./a.out
0xE4
0x92
*/
(三)检验:CRC(0xBEEF)= 0x92
多项式:0x31 (
x
8
x^8
x8+
x
5
x^5
x5+
x
4
x^4
x4+1)换成二进制为 9‘b1_0011_0001
检验:CRC(0xBEEF)= 0x92
1.第一个8位数据0xBE
:CRC寄存器值为8'hFF
,两者异或
1 0 1 1 1 1 1 0
XOR 1 1 1 1 1 1 1 1
————————————————————
0 1 0 0 0 0 0 1
1)左移一位,移出0,CRC值变成
1 0 0 0 0 0 1 0
2)左移一位,移出1,与多项式0x31
异或
0 0 0 0 0 1 0 0
XOR 0 0 1 1 0 0 0 1
————————————————————
0 0 1 1 0 1 0 1
3)左移一位,移出0,CRC值变成
0 1 1 0 1 0 1 0
4)左移一位,移出0,CRC值变成
1 1 0 1 0 1 0 0
5)左移一位,移出1,与多项式0x31
异或
1 0 1 0 1 0 0 0
XOR 0 0 1 1 0 0 0 1
————————————————————
1 0 0 1 1 0 0 1
6)左移一位,移出1,与多项式0x31
异或
0 0 1 1 0 0 1 0
XOR 0 0 1 1 0 0 0 1
————————————————————
0 0 0 0 0 0 1 1
7)左移一位,移出0,CRC值变成
0 0 0 0 0 1 1 0
8)左移一位,移出0,CRC值变成
0 0 0 0 1 1 0 0
2.第二个8位数据0xEF
,CRC寄存器值0x0C
,两者异或:
1 1 1 0 1 1 1 1
XOR 0 0 0 0 1 1 0 0
————————————————————
1 1 1 0 0 0 1 1
1)左移一位,移出1,与多项式0x31
异或
1 1 0 0 0 1 1 0
XOR 0 0 1 1 0 0 0 1
————————————————————
1 1 1 1 0 1 1 1
2)左移一位,移出1,与多项式0x31
异或
1 1 1 0 1 1 1 0
XOR 0 0 1 1 0 0 0 1
————————————————————
1 1 0 1 1 1 1 1
3)左移一位,移出1,与多项式0x31
异或
1 0 1 1 1 1 1 0
XOR 0 0 1 1 0 0 0 1
————————————————————
1 0 0 0 1 1 1 1
4)左移一位,移出1,与多项式0x31
异或
0 0 0 1 1 1 1 0
XOR 0 0 1 1 0 0 0 1
————————————————————
0 0 1 0 1 1 1 1
5)左移一位,移出0,CRC值变成
0 1 0 1 1 1 1 0
6)左移一位,移出0,CRC值变成
1 0 1 1 1 1 0 0
7)左移一位,移出1,与多项式0x31
异或
0 1 1 1 1 0 0 0
XOR 0 0 1 1 0 0 0 1
————————————————————
0 1 0 0 1 0 0 1
8)左移一位,移出0,CRC值变成
1 0 0 1 0 0 1 0
(3)可以看出CRC值最终变成了0x92.
三、Verilog检验CRC
(一)实现
module crc(
input clk ,//时钟信号
input rst_n ,//复位信号
input [23:0] din ,
input din_vld ,
output reg [15:0] dout ,
output reg dout_vld,
output reg right //判断正确错误
);
//参数定义
localparam CNT_SHIFT = 8 ,
DATA_NUM = 2 ;
localparam CRC_INIT = 8'hFF ,//初始值
POLY = 8'h31 ;//多项式
localparam IDLE = 5'b00001,
START = 5'b00010,
SHIFT = 5'b00100,
CHANGE = 5'b01000,
DONE = 5'b10000;
//信号定义
reg [4:0] state_c ;
reg [4:0] state_n ;
reg [23:0] din_latch;
reg [7:0] data ;
// reg msb ;//高有效位
reg [7:0] crc ;//crc寄存器
reg [2:0] shift_cnt;
reg data_cnt ;
reg data_done;
//din_latch
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
din_latch<= 0;
else if(din_vld)
din_latch<= din;
end
//状态机设计
always @(posedge clk or negedge rst_n) begin
if (rst_n==0)
state_c <= IDLE ;
else
state_c <= state_n;
end
//状态跳转
always @(*) begin
case(state_c)
IDLE :begin
if(din_vld)
state_n = START ;
else
state_n = state_c;
end
START :state_n = SHIFT ;
SHIFT :begin
if(shift_cnt== CNT_SHIFT-1)
state_n = CHANGE;
else
state_n = state_c;
end
CHANGE:begin
if(!data_done)
state_n = START ;
else if(data_done)
state_n = DONE ;
else
state_n = state_c;
end
DONE :state_n = IDLE ;
default : state_n = IDLE ;
endcase
end
//shift_cnt
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
shift_cnt <= 0;
else if(shift_cnt== CNT_SHIFT-1)
shift_cnt <= 0;
else if(state_c == SHIFT)
shift_cnt <= shift_cnt + 1'b1;
end
//data_cnt
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_cnt <= 0;
else if((data_cnt== DATA_NUM-1)&(shift_cnt== CNT_SHIFT-1))
data_cnt <= 0;
else if(shift_cnt== CNT_SHIFT-1)
data_cnt <= data_cnt + 1'b1;
end
//data
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data<= 0;
else if(din_vld)
data<= din[23:16];
else if((shift_cnt== CNT_SHIFT-2)&(data_cnt== DATA_NUM-2))
data<= din_latch[15:8];
end
//crc
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
crc<= CRC_INIT;
else if(din_vld)
crc<= CRC_INIT;
else if(state_c == START)
crc= crc^data;
else if(crc[7]&(state_c == SHIFT))
crc<= (crc<<1)^POLY;
else if(!crc[7]&((state_c == SHIFT)))
crc<= (crc<<1);
end
//data_done
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data_done<= 0;
else if((data_cnt== DATA_NUM-1)&(shift_cnt== CNT_SHIFT-1))
data_done<= 1'b1;
else
data_done<= 1'b0;
end
//right
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
right <= 0;
else if(din_vld)
right <= 1'b0;
else if((crc==din_latch[7:0])&data_done)
right <= 1'b1;
else if((crc!=din_latch[7:0])&data_done)
right <= 1'b0;
end
//dout
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
dout<= 0;
else if(data_done)
dout<= din_latch[23:8];
end
//dout_vld
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
dout_vld<= 0;
else if(state_n ==DONE)
dout_vld<= 1'b1;
else
dout_vld<= 1'b0;
end
endmodule
(二)仿真
`timescale 1 ns / 1 ps
module tb;
reg clk ;
reg rst_n ;
reg [23:0] din ;
reg din_vld ;
wire [15:0] dout ;
wire dout_vld;
wire right ;
always #20 clk =~clk;
//初始化
initial begin
clk = 1'b1;
rst_n =1'b1;
#40 ;
rst_n = 1'b0;
#80
rst_n = 1'b1;
#100000;
$stop;
end
integer i = 0,j = 0;
//初始化
initial begin
din = 0;
din_vld = 0;
#200;
for(i=0;i<30;i=i+1)begin
if((i==1)|(i==10)|(i==20))
din = 24'hBEEF92;
else
din = {$random};
@(posedge clk) ;
for(j=0;j<50;j=j+1)begin
if(j==1)
din_vld = 1'b1;
else
din_vld =1'b0;
@(posedge clk) ;
end
end
end
//模块例化
crc tb_crc(
/* input */.clk (clk ),//时钟信号
/* input */.rst_n (rst_n ),//复位信号
/* input [23:0] */.din (din ),
/* input */.din_vld (din_vld ),
/* output [15:0] */.dout (dout ),
/* output */.dout_vld (dout_vld),
/* output */.right (right ) //判断正确错误
);
endmodule
(三)波形
1.检验CRC(0xBEEF)= 0x92?
可以看到输入数据din=24’hBEEF92(数据16'hBEEF
+ CRC8'h92
),输出结果为right(right==1
),此时CRC校验结果是正确的。
2.检验CRC(0x895E)= 0x81?
可以看到输入数据din=24’895E81(数据16'h895E
+ CRC8'h81
),输出结果为wrong(right==0
),此时CRC校验结果是错误的。正确的CRC应该是8'h00
,非8’h81