这个校验方式的根本思想是在发送数据的后面拼接上一些额外的数据位,使得这个拼接之后的数据可以被一个固定的数模2整除(这里的模2整除是一种区别于普通的除法的运算方法,也不难理解),之后将数据发送……接收端同样将整个拼接后的数据与之前确定的固定的数据做模2除法,若能整除,则说明传输过程没有出错,若不能整除,则可以根据余数判断是哪一位发生了错误。
上面说到的要发送的数据被称为信息码;额外的数据位被称为校验码;在模2除法中做除数的那个固定的数被称为生成多项式;拼接完成之后的整个数据被称为CRC 码;
一些关于生成多项式、模2除法的知识在这里就不细说了,可以参考这一篇博客,我也是参考了这篇博客里的好多内容理解的这个CRC:
http://www.cnblogs.com/BitArt/archive/2012/12/26/2833100.html
信息码数据:使用7位拨码开关来作为数据的输入。
生成多项式是: 。即 二进制的“111010001”。
7 位信息码与生成多项式相除就能得到 8 位校验码,将校验码拼接在信息码后就得到 15 位的CRC 码。
/*****************获得CRC码部分**************/
reg fd;
reg [7:0] crc_tmp=8'd0;
reg [7:0] crc=8'd0;
integer i;
/*使用1Hz的时钟来控制CRC校验码的生成*/
always @(posedge clk or negedge rst)
begin
if(!rst)
crc <= 8'b0;
else if(clk_1Hz_posedge==1'b0)
crc <= crc_tmp;
end
always@( data_in or crc) begin
crc_tmp = 8'd0;
for(i=6; i>=0; i=i-1) begin
fd = crc_tmp[7] ^ data_in[i];
crc_tmp[7] = crc_tmp[6] ^ fd;
crc_tmp[6] = crc_tmp[5] ^ fd;
crc_tmp[5] = crc_tmp[4] ;
crc_tmp[4] = crc_tmp[3] ^ fd;
crc_tmp[3] = crc_tmp[2] ;
crc_tmp[2] = crc_tmp[1] ;
crc_tmp[1] = crc_tmp[0] ;
crc_tmp[0] = fd ;
end
end
//-----------------分频-----1Hz----------------
reg [26:0] Count_1Hz;
reg clk_1Hz;
always @ (posedge clk) begin
if(Count_1Hz >= 27'd24_999_999) begin
Count_1Hz <= 27'd0;
clk_1Hz <= ~clk_1Hz;
end
else
Count_1Hz <= Count_1Hz +27'd1;
end
//--------------采样clk_1Hz的上升沿-------------
reg clk_1Hz_d0;
reg clk_1Hz_d1;
wire clk_1Hz_posedge;
assign clk_1Hz_posedge = clk_1Hz_d0 && (~clk_1Hz_d1);
always @ (posedge clk ) begin
clk_1Hz_d0 <= clk_1Hz;
clk_1Hz_d1 <= clk_1Hz_d0;
end
/*-------------故意制造错误部分-------------*/
reg [7:0] Num_Disp1_temp;
always @ (posedge clk) begin
Num_Disp1_temp <= Num_Disp1;
case(key)
7'b1111110: Num_Disp1_temp[0] <= ~Num_Disp1_temp[0];
7'b1111101: Num_Disp1_temp[1] <= ~Num_Disp1_temp[1];
7'b1111011: Num_Disp1_temp[2] <= ~Num_Disp1_temp[2];
7'b1110111: Num_Disp1_temp[3] <= ~Num_Disp1_temp[3];
7'b1101111: Num_Disp1_temp[4] <= ~Num_Disp1_temp[4];
7'b1011111: Num_Disp1_temp[5] <= ~Num_Disp1_temp[5];
7'b0111111: Num_Disp1_temp[6] <= ~Num_Disp1_temp[6];
endcase
end
/*---------------串口发送模块--------------*/
reg bit_flag = 1'd0;
always @ (posedge clk) begin
if(ready) begin
if(bit_flag) begin
bit_flag <= 1'd0;
txd_data <= Num_Disp1_temp;
tx_data_en <= 1'd1;
end
else begin
bit_flag <= 1'd1;
txd_data <= Num_Disp2;
tx_data_en <= 1'd1;
end
end
else
tx_data_en <= 1'd0;
end
reg tx_data_en = 1'd1;
reg [7:0] txd_data;
Uart_Txd Uart_U1
(
//input
.clk_in(clk),
.rst_n(rst),
.tx_data_en(tx_data_en),
.txd_data(txd_data),
//output
.ready(ready), //空闲状态下ready为1,发送数据时为0
.txd(txd)
);
串口接收数据:
/*----------------------串口接收------------------------*/
wire [7:0] DATA;
wire Uart_Get;
reg bit_flag = 1'd1;
Data_Rece Data_Rece_U1
(
//input
.clk_in(clk),
.rst_n(rst_n),
.rxd(rxd),
//output
.Data_Out(DATA),
.Rxd_Data_En(Uart_Get)
);
reg [7:0] UART_DATA1;
reg [7:0] UART_DATA2;
always @ ( posedge clk ) begin
if(Uart_Get_posedge) begin
if(bit_flag) begin
bit_flag <= 1'd0;
UART_DATA1 <= DATA;
end
else begin
bit_flag <= 1'd1;
UART_DATA2 <= DATA;
end
end
end
//采样Uart_Get的上升沿
reg Uart_Get_d0;
reg Uart_Get_d1;
wire Uart_Get_posedge;
assign Uart_Get_posedge = Uart_Get_d0 && (~Uart_Get_d1);
always @ (posedge clk ) begin
Uart_Get_d0 <= Uart_Get;
Uart_Get_d1 <= Uart_Get_d0;
end
得到发送端发来的数据包,此处的数据包使用两个寄存器保存,分别为UART_DATA1和UART_DATA2,DATA1的低7位中存的是信息码,最高位为0,DATA2中是CRC校验码,将两个数据拼接成一个15位的数据传入CRC校验模块,得到余数CRC_DATA。
/*-----------------------------------CRC校验部分----------------------------------------*/
wire [7:0] CRC_DATA;
crc CRC_U1
(
.rst(rst_n),
.clk(clk),
.data_in( { UART_DATA1[6:0] , UART_DATA2 } ),
.crc( CRC_DATA )
);
CRC校验模块与发送端的CRC模块不同,此处传入的数据为15个bit,得到的余数位仍然为8位。模块代码如下:
module crc(
rst,
clk,
data_in,
crc
);
/*************************************/
input rst;
input clk;
input [14:0] data_in;
output reg[7:0] crc;
reg feedback;
reg [7:0] crc_tmp=8'd0;
wire crc_start;
assign crc_start = clk_1Hz_posedge;
always @(posedge clk or negedge rst)
begin
if(!rst)
crc <= 8'b0;
else if(crc_start==1'b0)
crc <= crc_tmp;
end
integer i;
always@( data_in or crc)
begin
crc_tmp = 8'd0;
for(i=14; i>=0; i=i-1)
begin
feedback = crc_tmp[7] ^ data_in[i];
crc_tmp[7] = crc_tmp[6] ^ feedback;
crc_tmp[6] = crc_tmp[5] ^ feedback;
crc_tmp[5] = crc_tmp[4] ;
crc_tmp[4] = crc_tmp[3] ^ feedback;
crc_tmp[3] = crc_tmp[2] ;
crc_tmp[2] = crc_tmp[1] ;
crc_tmp[1] = crc_tmp[0] ;
crc_tmp[0] = feedback ;
end
end
//----------------------------------------分频-----1Hz-----------------------
reg [26:0] Count_1Hz;
reg clk_1Hz;
always @ (posedge clk) begin
if(Count_1Hz >= 27'd24_999_999) begin
Count_1Hz <= 27'd0;
clk_1Hz <= ~clk_1Hz;
end
else
Count_1Hz <= Count_1Hz +27'd1;
end
//采样clk_1Hz的上升沿
reg clk_1Hz_d0;
reg clk_1Hz_d1;
wire clk_1Hz_posedge;
assign clk_1Hz_posedge = clk_1Hz_d0 && (~clk_1Hz_d1);
always @ (posedge clk ) begin
clk_1Hz_d0 <= clk_1Hz;
clk_1Hz_d1 <= clk_1Hz_d0;
end
endmodule
仿真波形如图:CRC码为0x2A1A,校验完成后得到的crc余数为0,表明数据传输没有错误。
实际的板子上的显示情况与之一致:后两位显示0x00;
手动制作一个错误,按下第5个按键,对应的第五位数取反,发送的数据成了0x0A,而此时校验码不变,还是为0x1A,仿真波形如图:发现CRC余数不为0,为0x40
此时电路板上的显示为0x0A40(0A表示手动制造误差后的数据,40表示CRC余数):
查阅余数与出错位置表可以验证正好是按键按下的第五位发生了错误
至此,CRC的产生与验证便完成了。
来张合照留个念:
2018//11/30