/*******
作 者:温子祺
联系方式:wenziqi@hotmail.com
说 明 :CRC16-循环冗余校验
********/
【例子】通过CRC-16循环冗余校验的方式实现数据传输与控制,例如控制LED灯、蜂鸣器、发送数据到上位机。
由于是数据传输与控制,需要定制一个结构体、共用体方便数据识别,同时增强可读性。从数据帧格式定义中可以定义为“PKT_CRC_EX”类型。
识别数据请求什么操作可以通过以下手段来识别:识别数据头部1、数据头部2,操作码。当完全接收数据完毕后通过校验该数据得出的校验值与该数
据的尾部的校验值是否匹配。若匹配,则根据操作码的请求进行操作;若不匹配则丢弃当前数据帧,等待下一个数据帧的到来。
结构体定义如下:
(1)
typedef struct _ PKT_CRC
{
UINT
UINT
UINT
UINT
UINT
UINT
}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
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!