协议组成:
协议一般由:1. 帧头 2.地址信息3. 数据类型 4.数据长度 5.数据块 6.校验码 7.帧尾 组成。
如果想简单点可以这样分:
帧头和帧尾:
帧头和帧尾的设置要注意 要与你的数据载荷匹配率要尽量的小选择一些特殊点的字节。
一般有两种情况:
1.如果数据载荷是人为定的 可预测的 这时候就不重复就行。
2.数据不可预测 很容易出现重复的 所以可以增加帧头 帧尾数量变成2个以上的字节。
地址信息:
主要是用于多机通讯。
数据类型:
当前数据是一个命令包还是数据包 可以用来区分命令和数据 例如 0x00是表示命令 0x01表示数据。
数据长度:
一帧数据的字节数
数据块:
这一帧数据 所要表达的 真实 数据
校验码:
校验码分很多 有CRC校验 异或校验和校验
1.CRC校验
uint16_t calculate_crc16(uint8_t* data, uint16_t length)
{
uint16_t crc = 0xFFFF;
for (int i = 0; i < length; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if ((crc & 0x0001) != 0) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
2.异或校验和(北斗校验就用的这个)
/*
异或校验和算法
*/
static unsigned char xor_checksum (unsigned char *buf, unsigned int len)
{
unsigned int i;
unsigned char checksum = 0;
for (i = 0; i < len; ++i)
{
checksum ^= *(buf++);
}
return checksum;
}
数据包
数据包的接收和解析是两回事!!!!
数据简单时:
可以边接收边解析 用状态机的方式接收
数据复杂时:
可以用 串口空闲中断+DMA等方式 先接收到 缓存区BUF里面 再在主函数中进行解析!
1.HEX数据包
HEX数据包解析:
固定长度的 数据帧
方法一:在中断函数中直接分析 只存储数据 没有用空闲中断 数据已经在RX_Buf里了 可以直接解析
#define RX_Size 100
uint8_t RX_Buf[RX_Size]={0};//接收固定包长 长度为RX_Size的 数据包 缓存区
uint8_t rx_index = 0;//接收字节索引
uint8_t rx_data_Flag = 0;//接收完成标志
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp = 0;
uint8_t stat = 0;
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
ucTemp = USART_ReceiveData(DEBUG_USARTx);//接收到数据
switch(stat)
{
case 0:
if(ucTemp == '$')//判断帧头
{
stat =1;
}
else
{
stat =0;
}
break;
case 1://判断数据
RX_Buf[rx_index++] = ucTemp;
if(rx_index >= 4)//固定包长
{
stat =2;
}
break;
case 2:
if(ucTemp == 0xFE)//判断帧尾
{
rx_data_Flag =1;
rx_index=0;
stat=0;
}
break;
}
}
解析数据包时,可以把它与结构体相结合例如下面这种方式,将结构体各个成员赋值。
//内部结构体 主要嵌入下面 icxx_struct结构体中
struct icxx_info_struct
{
unsigned char frame_id;//帧号
unsigned char broadcast_id[3];//通播号
unsigned char user_feature;//用户特征
unsigned int service_frequency;//服务频度
unsigned char comm_level;//通信等级
unsigned char encryption_flag;//加密标志
unsigned int user_num;//下属用户总数
};
//解析的结构体 主要用于解析 接收到的 ICXX命令
struct icxx_struct
{
unsigned char instruction[5];//$icxx
unsigned int packet_len;//长度22字节
unsigned char user_addr[3];//用户地址
struct icxx_info_struct icxx_info;//内部结构
unsigned char checksum;//校验码
};
//解析函数
void parse_icxx(struct icxx_struct* icxx,unsigned char *icxx_buf)// 24 49 43 58 58 00 16 8B 36 80 00 0F 12 06 06 00 3C 03 00 00 00 27
{
unsigned int i;
for (i = 0; i < 5; ++i)
{
(*icxx).instruction[i] = icxx_buf[i];// $ICXX 24 49 43 58 58
}
(*icxx).packet_len = icxx_buf[5] * 256 + icxx_buf[6];// 长度 00 16 =22个字节
for (i = 0; i < 3; ++i)
{
(*icxx).user_addr[i] = icxx_buf[i + 7];//用户地址8B 36 80
}
(*icxx).icxx_info.frame_id = icxx_buf[10];// 00
for (i = 0; i < 3; ++i)//0F 12 06
{
(*icxx).icxx_info.broadcast_id[i] = icxx_buf[i + 11];
}
(*icxx).icxx_info.user_feature = icxx_buf[14];//
(*icxx).icxx_info.service_frequency = icxx_buf[15] * 256 + icxx_buf[16];//
(*icxx).icxx_info.comm_level = icxx_buf[17];//
(*icxx).icxx_info.encryption_flag = icxx_buf[18];//
(*icxx).icxx_info.user_num = icxx_buf[19] * 256 + icxx_buf[20];//
(*icxx).checksum = icxx_buf[21];//27
}
2.文本数据包
接收文本数据的时候要考虑一下 需不需要加一个 字符串结束符 '\0'
文本数据包解析:
void wenbenjiexi()//$ABCD\r\n
{
if(strcmp(RX_BUF,"$bf") == 0)//字符串比较函数 ==0意思是 BUF 与 字符串 相同
{
printf("布放命令已下达\r\n");
}
if(strcmp(RX_BUF,"$ts") == 0)
{
printf("进入调试阶段\r\n");
}
if(strcmp(RX_BUF,"$??") == 0)
{
printf("进入介绍阶段\r\n");
}
}
数据包一般包括下面三个类型
1.固定包长含帧头帧尾
2.可变包长含帧头帧尾
可变包长 没有帧长的情况下 判断完帧头后 判断帧尾
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucTemp = 0;
uint8_t stat = 0;
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
ucTemp = USART_ReceiveData(DEBUG_USARTx);//接收到数据
switch(stat)
{
case 0:
if(ucTemp == '$')//判断帧头
{
stat =1;
}
else
{
stat =0;
}
break;
case 1://判断数据
if(ucTemp == '\r')//判断是不是帧尾
{
stat =2;
}
else
{
RX_Buf[rx_index++] = ucTemp;
}
break;
case 2:
if(ucTemp == '\n')//判断帧尾
{
rx_data_Flag =1;
rx_index=0;
stat=0;
}
break;
}
如果数据比较频繁 则先可以 先等处理函数处理完成之后再通知去接收 主要是flag变量的位置
并且在接收的时候 先与上一个标志位
如果想用字符串进行控制