• 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 ** 函数名称: 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

     

     代码分析

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

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

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

  • 相关阅读:
    4、springboot之全局异常捕获
    3、springboot之热部署
    可重入锁
    2、springboot返回json
    1、springboot之HelloWorld
    [转]查询 SQL Server 系统目录常见问题
    设计模式原则详解
    [转]第二章 控制反转和依赖注入
    [转]Spring.Net介绍
    [转]Oracle High Water Level高水位分析
  • 原文地址:https://www.cnblogs.com/wenziqi/p/1769362.html
Copyright © 2020-2023  润新知