CRC16-循环冗余校验

/***************************************************
 *作     者:温子祺
 *联系方式:wenziqi@hotmail.com
 *说    明 :CRC16-循环冗余校验  
 ***************************************************/

 

【例子】通过CRC-16循环冗余校验的方式实现数据传输与控制,例如控制LED灯、蜂鸣器、发送数据到上位机。

     由于是数据传输与控制,需要定制一个结构体、共用体方便数据识别,同时增强可读性。从数据帧格式定义中可以定义为“PKT_CRC_EX”类型。

 识别数据请求什么操作可以通过以下手段来识别:识别数据头部1、数据头部2,操作码。当完全接收数据完毕后通过校验该数据得出的校验值与该数

据的尾部的校验值是否匹配。若匹配,则根据操作码的请求进行操作;若不匹配则丢弃当前数据帧,等待下一个数据帧的到来。

 

结构体定义如下:

(1)

typedef  struct _ PKT_CRC

{

        UINT8 m_ucHead1;       //首部

        UINT8 m_ucHead2;       //首部

        UINT8 m_ucOptCode;     //操作码

        UINT8 m_ucDataLength;  //数据长度

        UINT8 m_szDataBuf[16]; //数据

 

        UINT8 m_szCrc[2];      //CRC16校验值为2个字节 

 

}PKT_CRC;

(2)

typedef union _PKT_PARITY_EX

{

    PKT_PARITY r;

    UINT8 buf[32];

} PKT_PARITY_EX;

 

PKT_PARITY_EX PktParityEx;

 

CRC16-循环冗余校验代码如下:

 

 

 

 

 

 


2 #include "stc.h"
  3
  4  /***************************************************
  5 *          类型定义,方便代码移植
  6 ***************************************************/
  7 typedef unsigned char   UINT8;
  8 typedef unsigned int    UINT16;
  9 typedef unsigned long   UINT32;
10    
11 typedef char            INT8;
12 typedef int             INT16;
13 typedef long            INT32;
14 typedef bit             BOOL;
15
16  /***************************************************
17 *          大量宏定义,便于代码移植和阅读
18 ***************************************************/
19  //--------------------------------
20                                    //----头部----
21 #define DCMD_CTRL_HEAD1      0x10  //PC下传控制包头部1
22 #define DCMD_CTRL_HEAD2      0x01  //PC下传控制包头部2
23
24                                    //----命令码----
25 #define DCMD_NULL            0x00  //命令码:空操作
26 #define DCMD_CTRL_BELL       0x01  //命令码:控制蜂鸣器
27 #define DCMD_CTRL_LED        0x02  //命令码:控制LED
28 #define DCMD_REQ_DATA        0x03  //命令码:请求数据
29
30                                    //----数据----
31 #define DCTRL_BELL_ON        0x01  //蜂鸣器响
32 #define DCTRL_BELL_OFF       0x02  //蜂鸣器禁鸣
33 #define DCTRL_LED_ON         0x03  //LED亮
34 #define DCTRL_LED_OFF        0x04  //LED灭
35
36 //--------------------------------
37                                    //----头部----
38 #define UCMD_CTRL_HEAD1      0x20  //MCU上传控制包头部1
39 #define UCMD_CTRL_HEAD2      0x01  //MCU上传控制包头部2
40
41                                    //----命令码----
42 #define UCMD_NULL            0x00  //命令码:空操作
43 #define UCMD_REQ_DATA        0x01  //命令码:请求数据
44
45
46 #define CTRL_FRAME_LEN       0x04  //帧长度(不包含数据和校验值)
47 #define CRC16_LEN             0x02  //检验值长度
48
49 #define EN_UART()             ES=1 //允许串口中断
50 #define NOT_EN_UART()        ES=0 //禁止串口中断
51
52 #define BELL(x)             {if((x))P0_6=1 ;else P0_6=0;} //蜂鸣器控制宏函数
53 #define LED(x)              {if((x))P2=0x00;else P2=0xFF;}//LED控制宏函数   
54
55 #define TRUE                1
56 #define FALSE               0
57
58 #define HIGH                1
59 #define LOW                 0  
60
61 #define ON                  1
62 #define OFF                 0
63
64 #define NULL                (void *)0
65
66 /*使用结构体对数据包进行封装
67 *方便操作数据
68 */
69 typedef  struct _PKT_CRC
70 {
71    UINT8 m_ucHead1;       //首部1
72    UINT8 m_ucHead2;       //首部2
73    UINT8 m_ucOptCode;     //操作码
74    UINT8 m_ucDataLength;  //数据长度
75    UINT8 m_szDataBuf[16]; //数据
76
77    UINT8 m_szCrc[2];      //CRC16为2个字节
78
79 }PKT_CRC;
80
81 /*使用共用体再一次对数据包进行封装
82 *操作数据更加方便
83 */
84 typedef union _PKT_CRC_EX
85 {
86     PKT_CRC r;
87     UINT8 p[32];
88 } PKT_CRC_EX;
89
90
91 PKT_CRC_EX    PktCrcEx; //定义数据包变量
92
93
94 BOOL  bLedOn=FALSE;    //定义是否点亮LED布尔变量
95 BOOL  bBellOn=FALSE;   //定义是否蜂鸣器响布尔变量
96 BOOL  bReqData=FALSE;  //定义是否请求数据布尔变量
97
98 /****************************************************
99 ** 函数名称: CRC16Check
100 ** 输    入: buf 要校验的数据;
101              len 要校验的数据的长度
102 ** 输    出: 校验值
103 ** 功能描述: CRC16循环冗余校验
104 *****************************************************/
105 UINT16 CRC16Check(UINT8 *buf, UINT8 len)
106 {
107     UINT8  i, j;
108     UINT16 uncrcReg = 0xffff;
109     UINT16 uncur;
110
111     for (i = 0; i < len; i++)
112     {
113         uncur = buf[i] << 8;
114
115         for (j = 0; j < 8; j++)
116         {
117             if ((INT16)(uncrcReg ^ uncur) < 0)
118             {
119                  uncrcReg = (uncrcReg << 1) ^ 0x1021;
120             }
121             else
122             {
123                   uncrcReg <<= 1;
124             }
125               
126             uncur <<= 1;           
127         }
128     }
129
130     return uncrcReg;
131 }
132 /*************************************************************
133 * 函数名称:BufCpy
134 * 输    入:dest目标缓冲区;
135            Src  源缓冲区
136            size 复制数据的大小
137 * 输    出:无
138 * 说    明:复制缓冲区
139 **************************************************************/
140 BOOL BufCpy(UINT8 * dest,UINT8 * src,UINT32 size)
141 {
142     if(NULL ==dest || NULL==src ||NULL==size)
143     {
144         return FALSE;
145     }
146    
147     do
148     {
149         *dest++ = *src++;
150        
151     }while(--size!=0);
152    
153     return TRUE;
154 }
155 /****************************************************
156 ** 函数名称: UartInit
157 ** 输    入: 无
158 ** 输    出: 无
159 ** 功能描述: 串口初始化
160 *****************************************************/                                                                              
161 void UartInit(void)
162 {
163     SCON=0x40;
164     T2CON=0x34;
165     RCAP2L=0xD9;
166     RCAP2H=0xFF;
167     REN=1;
168     ES=1;
169 }
170 /****************************************************
171 ** 函数名称: UARTSendByte
172 ** 输    入: b 单个字节
173 ** 输    出: 无
174 ** 功能描述: 串口 发送单个字节
175 *****************************************************/
176 void UARTSendByte(UINT8 b)
177 {
178       SBUF=b;
179      while(TI==0);
180      TI=0;
181 }
182 /****************************************************
183 ** 函数名称: UartSendNBytes
184 ** 输    入: buf 数据缓冲区;
185              len 发送数据长度
186 ** 输    出: 无
187 ** 功能描述: 串口 发送多个字节
188 *****************************************************/
189 void UartSendNBytes(UINT8 *buf,UINT8 len)
190 {
191      while(len--)
192      {
193          UARTSendByte(*buf++);
194      }
195 }
196 /****************************************************
197 ** 函数名称: main
198 ** 输    入: 无
199 ** 输    出: 无
200 ** 功能描述: 函数主体
201 *****************************************************/
202 void main(void)
203 {
204      UINT8 i=0;
205      UINT16 uscrc=0;
206
207      UartInit();//串口初始化
208
209      EA=1;      //开总中断
210     
211      while(1)
212      {
213           if(bLedOn)  //是否点亮Led
214           {
215              LED(ON); 
216           }
217           else
218           {
219              LED(OFF);
220           }
221          
222          
223           if(bBellOn)//是否响蜂鸣器
224           {
225              BELL(ON);
226           }
227           else
228           {
229              BELL(OFF);
230           }
231          
232           if(bReqData)//是否请求数据
233           {
234              bReqData=FALSE;
235
236              NOT_EN_UART(); //禁止串口中断
237             
238              PktCrcEx.r.m_ucHead1=UCMD_CTRL_HEAD1;//MCU上传数据帧头部1
239              PktCrcEx.r.m_ucHead2=UCMD_CTRL_HEAD2;//MCU上传数据帧头部2
240              PktCrcEx.r.m_ucOptCode=UCMD_REQ_DATA;//MCU上传数据帧命令码
241
242             
243              uscrc=CRC16Check(PktCrcEx.p,
244 CTRL_FRAME_LEN+
245 PktCrcEx.r.m_ucDataLength);//计算校验值
246
247                PktCrcEx.r.m_szCrc[0]=(UINT8) uscrc;     //校验值低字节
248                PktCrcEx.r.m_szCrc[1]=(UINT8)(uscrc>>8);//校验值高字节
249
250               /*
251                 这样做的原因是因为有时写数据长度不一样,
252                    导致PktCrcEx.r.m_szCrc会出现为0的情况
253                 所以使用BufCpy将校验值复制到相应的位置
254              */
255
256              BufCpy(&PktCrcEx.p[CTRL_FRAME_LEN+PktCrcEx.r.m_ucDataLength],
257                       PktCrcEx.r.m_szCrc,
258                      CRC16_LEN);
259             
260              UartSendNBytes(PktCrcEx.p,
261                                CTRL_FRAME_LEN+
262 PktCrcEx.r.m_ucDataLength+CRC16_LEN);//发送数据
263
264              EN_UART();//允许串口中断
265                     
266           }
267      }
268 }
269 /****************************************************
270 ** 函数名称: UartIRQ
271 ** 输    入: 无
272 ** 输    出: 无
273 ** 功能描述: 串口中断服务函数
274 *****************************************************/
275 void UartIRQ(void)interrupt 4
276 {
277      static UINT8  uccnt=0;
278             UINT8  uclen;
279             UINT16 uscrc;
280     
281      if(RI) //是否接收到数据
282      {
283         RI=0;
284
285         PktCrcEx.p[uccnt++]=SBUF;//获取单个字节
286
287
288         if(PktCrcEx.r.m_ucHead1 == DCMD_CTRL_HEAD1)//是否有效的数据帧头部1
289         {
290            if(uccnt<CTRL_FRAME_LEN+PktCrcEx.r.m_ucDataLength+CRC16_LEN)//是否接收完所有数据
291            {
292               if(uccnt>=2 && PktCrcEx.r.m_ucHead2!=DCMD_CTRL_HEAD2)//是否有效的数据帧头部2
293               {
294                  uccnt=0;
295
296                  return;
297               }
298                    
299            }
300            else
301            {
302              
303               uclen=CTRL_FRAME_LEN+PktCrcEx.r.m_ucDataLength;//获取数据帧有效长度(不包括校验值)
304
305               uscrc=CRC16Check(PktCrcEx.p,uclen);//计算校验值
306
307                 /*
308                 这样做的原因是因为有时写数据长度不一样,
309                   导致PktCrcEx.r.m_szCrc会出现为0的情况
310                 所以使用BufCpy将校验值复制到相应的位置
311                */
312               BufCpy(PktCrcEx.r.m_szCrc,&PktCrcEx.p[uclen],CRC16_LEN);
313
314               if((UINT8)(uscrc>>8) !=PktCrcEx.r.m_szCrc[1]\
315                ||(UINT8) uscrc      =PktCrcEx.r.m_szCrc[0])//校验值是否匹配
316               {
317                   uccnt=0;
318
319                   return
320               }
321
322               switch(PktCrcEx.r.m_ucOptCode)//从命令码中获取相对应的操作
323               {
324                 case DCMD_CTRL_BELL://控制蜂鸣器命令码
325                 {
326                      if(DCTRL_BELL_ON==PktCrcEx.r.m_szDataBuf[0])//数据部分含控制码
327                      {
328                         bBellOn=TRUE;
329                      }
330                      else
331                      {
332                         bBellOn=FALSE;
333                      }
334                 }
335                 break;
336
337                 case DCMD_CTRL_LED://控制LED命令码
338                 {
339
340                      if(DCTRL_LED_ON==PktCrcEx.r.m_szDataBuf[0])//数据部分含控制码
341                      {
342                         bLedOn=TRUE;
343                      }
344                      else
345                      {
346                         bLedOn=FALSE;
347                      }
348                 }
349                 break;
350
351                 case DCMD_REQ_DATA://请求数据命令码
352                 {
353                      bReqData=TRUE;
354                 }
355                 break;
356
357               }
358
359               uccnt=0;
360
361               return;
362            }
363
364         }
365         else
366         {
367             uccnt=0;
368         }
369
370      }
371 }
372