/***************************************************
*作 者:温子祺
*联系方式: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 ** 函数名称: 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的值。