【例子】通过校验和的方式实现数据传输与控制,例如控制LED灯、蜂鸣器、发送数据到上位机。
由于是数据传输与控制,需要定制一个结构体、共用体方便数据识别,同时增强可读性。从数据帧格式定义中可以定义为“PKT_SUM_EX”类型。
识别数据请求什么操作可以通过以下手段来识别:识别数据头部1、数据头部2,操作码。当完全接收数据完毕后通过校验该数据得出的校验值与该数据的尾部的校验值是否匹配。
若匹配,则根据操作码的请求进行操作;若不匹配则丢弃当前数据帧,等待下一个数据帧的到来。
结构体定义:
/*使用结构体对数据包进行封装
*方便操作数据
*/
typedef struct _PKT_SUM
{
UINT8 m_ucHead1; //首部1
UINT8 m_ucHead2; //首部2
UINT8 m_ucOptCode; //操作码
UINT8 m_ucDataLength; //数据长度
UINT8 m_szDataBuf[16]; //数据
UINT8 m_ucCheckSum; //CRC16为2个字节
}PKT_SUM;
/*使用共用体再一次对数据包进行封装
*操作数据更加方便
*/
typedef union _PKT_SUM_EX
{
PKT_SUM r;
UINT8 p[32];
} PKT_SUM_EX;
校验和代码如下:
#include "stc.h" /*************************************************** * 类型定义,方便代码移植 ***************************************************/ typedef unsigned char UINT8; typedef unsigned int UINT16; typedef unsigned long UINT32; typedef char INT8; typedef int INT16; typedef long INT32; typedef bit BOOL; /*************************************************** * 大量宏定义,便于代码移植和阅读 ***************************************************/ //-------------------------------- //----头部---- #define DCMD_CTRL_HEAD1 0x10 //PC下传控制包头部1 #define DCMD_CTRL_HEAD2 0x01 //PC下传控制包头部2 //----命令码---- #define DCMD_NULL 0x00 //命令码:空操作 #define DCMD_CTRL_BELL 0x01 //命令码:控制蜂鸣器 #define DCMD_CTRL_LED 0x02 //命令码:控制LED #define DCMD_REQ_DATA 0x03 //命令码:请求数据 //----数据---- #define DCTRL_BELL_ON 0x01 //蜂鸣器响 #define DCTRL_BELL_OFF 0x02 //蜂鸣器禁鸣 #define DCTRL_LED_ON 0x03 //LED亮 #define DCTRL_LED_OFF 0x04 //LED灭 //-------------------------------- //----头部---- #define UCMD_CTRL_HEAD1 0x20 //MCU上传控制包头部1 #define UCMD_CTRL_HEAD2 0x01 //MCU上传控制包头部2 //----命令码---- #define UCMD_NULL 0x00 //命令码:空操作 #define UCMD_REQ_DATA 0x01 //命令码:请求数据 #define CTRL_FRAME_LEN 0x04 //帧长度(不包含数据和校验值) #define CHECKSUM_LEN 0x01 //检验值长度 #define EN_UART() ES=1 //允许串口中断 #define NOT_EN_UART() ES=0 //禁止串口中断 #define BELL(x) {if((x))P0_6=1 ;else P0_6=0;} //蜂鸣器控制宏函数 #define LED(x) {if((x))P2=0x00;else P2=0xFF;}//LED控制宏函数 #define TRUE 1 #define FALSE 0 #define HIGH 1 #define LOW 0 #define ON 1 #define OFF 0 #define NULL (void *)0 /*使用结构体对数据包进行封装 *方便操作数据 */ typedef struct _PKT_SUM { UINT8 m_ucHead1; //首部1 UINT8 m_ucHead2; //首部2 UINT8 m_ucOptCode; //操作码 UINT8 m_ucDataLength; //数据长度 UINT8 m_szDataBuf[16]; //数据 UINT8 m_ucCheckSum; //CRC16为2个字节 }PKT_SUM; /*使用共用体再一次对数据包进行封装 *操作数据更加方便 */ typedef union _PKT_SUM_EX { PKT_SUM r; UINT8 p[32]; } PKT_SUM_EX; PKT_SUM_EX PktSumEx; //定义数据包变量 BOOL bLedOn=FALSE; //定义是否点亮LED布尔变量 BOOL bBellOn=FALSE; //定义是否蜂鸣器响布尔变量 BOOL bReqData=FALSE; //定义是否请求数据布尔变量 /**************************************************** ** 函数名称: CheckSum ** 输 入: buf 要校验的数据; len 要校验的数据的长度 ** 输 出: 校验值 ** 功能描述: 计算校验和 *****************************************************/ UINT16 CheckSum(UINT8 *buf, UINT8 len) { UINT8 i=0,Sum=0; for (i=0;i=2 && PktSumEx.r.m_ucHead2!=DCMD_CTRL_HEAD2)//是否有效的数据帧头部2 { uccnt=0; return; } } else { uclen=CTRL_FRAME_LEN+PktSumEx.r.m_ucDataLength;//获取数据帧有效长度(不包括校验值) ucCheckSum=CheckSum(PktSumEx.p,uclen);//计算校验值 /* 这样做的原因是因为有时写数据长度不一样, 导致PktSumEx.r.m_ucCheckSum会出现为0的情况 所以使用BufCpy将校验值复制到相应的位置 */ BufCpy(&PktSumEx.r.m_ucCheckSum, &PktSumEx.p[uclen], CHECKSUM_LEN); if(ucCheckSum!=PktSumEx.r.m_ucCheckSum)//校验值是否匹配 { uccnt=0; return; } switch(PktSumEx.r.m_ucOptCode)//从命令码中获取相对应的操作 { case DCMD_CTRL_BELL://控制蜂鸣器命令码 { if(DCTRL_BELL_ON==PktSumEx.r.m_szDataBuf[0])//数据部分含控制码 { bBellOn=TRUE; } else { bBellOn=FALSE; } } break; case DCMD_CTRL_LED://控制LED命令码 { if(DCTRL_LED_ON==PktSumEx.r.m_szDataBuf[0])//数据部分含控制码 { bLedOn=TRUE; } else { bLedOn=FALSE; } } break; case DCMD_REQ_DATA://请求数据命令码 { bReqData=TRUE; } break; } uccnt=0; return; } } else { uccnt=0; } } }
代码分析:
(1)在main函数主体中,主要检测bLedOn、bBellOn、bReqData这三个标志位的变化,根据每个标志位的当前值然后进行相对应的操作。
(2)在UartIRQ中断服务函数当中,主要处理数据接收和数据校验,当数据校验成功后,
通过switch(PktSumEx.r.m_ucOptCode)获取命令码,根据命令码来设置bLedOn、bBellOn、bReqData的值。
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』