【例子】通过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的值。
上一篇:单片机定时器之改良版:时间轮定时器
下一篇:单片机校验和
推荐阅读最新更新时间:2024-03-16 15:28