CRC16-循环冗余校验

/***************************************************
 *作     者:温子祺
 *联系方式:[email protected]
 *说    明 :CRC16-循环冗余校验  
 ***************************************************/

 

【例子】通过CRC-16循环冗余校验的方式实现数据传输与控制,例如控制LED灯、蜂鸣器、发送数据到上位机。

     由于是数据传输与控制,需要定制一个结构体、共用体方便数据识别,同时增强可读性。从数据帧格式定义中可以定义为“PKT_CRC_EX”类型。

 识别数据请求什么操作可以通过以下手段来识别:识别数据头部1、数据头部2,操作码。当完全接收数据完毕后通过校验该数据得出的校验值与该数

据的尾部的校验值是否匹配。若匹配,则根据操作码的请求进行操作;若不匹配则丢弃当前数据帧,等待下一个数据帧的到来。

 

结构体定义如下:

(1)

typedef  struct _ PKT_CRC

{

        UINT 8 m _ucHead1;       //首部

        UINT 8 m _ucHead2;       //首部

        UINT 8 m _ucOptCode;     //操作码

        UINT 8 m _ucDataLength;  //数据长度

        UINT 8 m _szDataBuf[16]; //数据

 

        UINT 8 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 B OOL 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的值。

你可能感兴趣的:(CRC)