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-循环冗余校验代码如下:

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 ** 函数名称: CRC16Check100 ** 输    入: 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             else122             {123                   uncrcReg <<= 1; 124             } 125                126             uncur <<= 1;            127         }128     }129 130     return uncrcReg;131 } 132 /*************************************************************133 * 函数名称:BufCpy134 * 输    入: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     do148     {149         *dest++ = *src++;150         151     }while(--size!=0);152     153     return TRUE;154 }155 /****************************************************156 ** 函数名称: UartInit157 ** 输    入: 无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 ** 函数名称: UARTSendByte172 ** 输    入: b 单个字节173 ** 输    出: 无174 ** 功能描述: 串口 发送单个字节175 *****************************************************/ 176 void UARTSendByte(UINT8 b)177 {178       SBUF=b;179      while(TI==0);180      TI=0;181 }182 /****************************************************183 ** 函数名称: UartSendNBytes184 ** 输    入: 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 ** 函数名称: main198 ** 输    入: 无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)  //是否点亮Led214           {215              LED(ON);  216           }217           else218           {219              LED(OFF); 220           }221           222           223           if(bBellOn)//是否响蜂鸣器224           {225              BELL(ON);226           }227           else228           {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上传数据帧头部1239              PktCrcEx.r.m_ucHead2=UCMD_CTRL_HEAD2;//MCU上传数据帧头部2240              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 ** 函数名称: UartIRQ271 ** 输    入: 无272 ** 输    出: 无273 ** 功能描述: 串口中断服务函数274 *****************************************************/275 void UartIRQ(void)interrupt 4276 {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)//是否有效的数据帧头部1289         {290            if(uccnt<CTRL_FRAME_LEN+PktCrcEx.r.m_ucDataLength+CRC16_LEN)//是否接收完所有数据291            {292               if(uccnt>=2 && PktCrcEx.r.m_ucHead2!=DCMD_CTRL_HEAD2)//是否有效的数据帧头部2293               {294                  uccnt=0;295 296                  return;297               }298                     299            }300            else301            {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                      else331                      {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                      else345                      {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         else366         {367             uccnt=0;368         }369 370      }371 }372

代码分析

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

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

通过switch(PktCrcEx.r.m_ucOptCode)获取命令码,根据命令码来设置bLedOn、bBellOn、bReqData的值。
原文链接: https://www.cnblogs.com/wenziqi/archive/2010/07/01/1769362.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/12403

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 上午11:19
下一篇 2023年2月7日 上午11:20

相关推荐