×
嵌入式 > 技术百科 > 详情

CRC16-循环冗余校验

发布时间:2020-06-16 发布时间:
|

【例子】通过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-循环冗余校验代码如下:

   

  1 
  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 >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=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


 

 代码分析

(1)在main函数主体中,主要检测bLedOn、bBellOn、bReqData这三个标志位的变化,根据每个标志位的当前值然后进行相对应的操作。

(2)在UartIRQ中断服务函数当中,主要处理数据接收和数据校验,当数据校验成功后,

通过switch(PktCrcEx.r.m_ucOptCode)获取命令码,根据命令码来设置bLedOn、bBellOn、bReqData的值。



『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ADI 高精度低功耗精密放大器