自定义通讯协议基本知识

协议组成: 

协议一般由: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变量的位置

并且在接收的时候 先与上一个标志位

如果想用字符串进行控制

3.可变包长,含数据字节长度及帧头帧尾

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值