单片机
返回首页

CRC16-循环冗余校验

2016-12-31 来源:eefocus

【例子】通过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 < 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=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的值。


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

最新器件
精选电路图
  • 离子检测器电路分析

  • 非常简单的150W功放电路图

  • 如何使用LED驱动器LM3915制作振动计

  • 分享一个电网倾角计电路

  • 使用NE555和磁簧开关的橱柜照明电路

  • 电谐波图形均衡器示意图

    相关电子头条文章