凌霄飞控添加灵活格式帧数据传输到上位机

      一、介绍  

        官方的凌霄飞控程序里面将匿名通信协议封装成通用型了,初学者想要根据飞控的通信协议框架编写自己的数据传输比较困难,即通过使用匿名上位机的灵活格式帧,功能码(0XFx)进行数据传输。

 接下来,我将给大家介绍如何添加代码,完成自己的数据上传到上位机的功能。

二、具体操作

        首先,我们需要对凌霄飞控程序有个基本了解,这里我不多介绍,只介绍关于通信协议的部分。

        我们查找源码,可以发现飞控数据与上位机通信是在1ms定时器里面通过使用ANO_LX_Data_Exchange_Task()函数进行的

         我们进一步查找可以发现它里面都是调用Check_To_Send()函数,有很多这个函数,函数的参数是匿名通信的功能码,所以这里使用不同的功能码就可以传输功能码对应的“数据类型”,光流数据、GPS数据、CMD指令等等。

我们找到了第一个需要增加语句的部分,我们要在这个函数里面添加一个功能码为0xF1的语句

Check_To_Send(0xF1);    //灵活格式帧(Open_MV数据)

 

        进一步寻找Check_To_Send()函数的作用,里面有很多结构体参数判断,这里我们先不管这部分,后面会说明这部分的功能,直接找到最重要的函数Frame_Send()

         注意Frame_Send()函数的参数frame_num,这个是我们在Check_To_Send()函数填写的功能码,注意这一点,非常重要,后面都是根据功能码来填写参数的

       

        我们进一步寻找Frame_Send()函数的具体功能,查看之后我们应该知道这个函数是匿名通信协议框架,具体情况观看B站UP主“匿名_茶不思”大佬的匿名通信协议介绍

匿名上位机V7版--0基础教程3--从0开始写匿名协议发送源码_哔哩哔哩_bilibili

static void Frame_Send(u8 frame_num, _dt_frame_st *dt_frame)
{
	u8 _cnt = 0;	//地址偏移:	每写完一次自动++	  实现地址偏移

	send_buffer[_cnt++] = 0xAA;					//帧头		[HEAD]	(固定值)
	send_buffer[_cnt++] = dt_frame->D_Addr;		//目标地址	[D_ADDR](将发送框架里面的地址存入缓存数组)
	send_buffer[_cnt++] = frame_num;			//功能码		[ID]	(0X01、0X30等等)
	send_buffer[_cnt++] = 0;					//数据长度	[LEN]
	
	//==填充需要发送的数据
	Add_Send_Data(frame_num,&_cnt, send_buffer);//发送数据	[DATA]	(根据功能码对应填充格式)

	//数据长度更新
	send_buffer[3] = _cnt - 4;

	//==进行数据校验	和校验 + 附加校验
	u8 check_sum1 = 0, check_sum2 = 0;
	for (u8 i = 0; i < _cnt; i++)
	{
		check_sum1 += send_buffer[i];			//和校验
		check_sum2 += check_sum1;				//附加校验
	}
	send_buffer[_cnt++] = check_sum1;			//将校验结果存入数组
	send_buffer[_cnt++] = check_sum2;
	
	
	//==校验结果数据传递
	if (dt.wait_ck != 0 && frame_num == 0xe0)
	{
		dt.ck_back.ID = frame_num;				//ID
		dt.ck_back.SC = check_sum1;				//和检验
		dt.ck_back.AC = check_sum2;				//附加校验
	}
	
	//==发送数组函数(需要发送的数组,数组长度)
	ANO_DT_LX_Send_Data(send_buffer, _cnt);		
	
}

在这个函数里面我们最需要注意的就中间的Add_Send_Data()函数,这个函数是填写具体数据的,我们需要根据自己的需要填写数据

        我们进入Add_Send_Data函数

static void Add_Send_Data(u8 frame_num, u8 *_cnt, u8 send_buffer[])
{
	s16 temp_data;
	s32 temp_data_32;
	
	//根据需要发送的帧ID,也就是frame_num,来填充数据,填充到send_buffer数组内
	switch (frame_num)
	{
		case 0x00:	//CHECK返回	数据校验帧
		{
			send_buffer[(*_cnt)++] = dt.ck_send.ID;
			send_buffer[(*_cnt)++] = dt.ck_send.SC;
			send_buffer[(*_cnt)++] = dt.ck_send.AC;
		}
		break;
		case 0x0d:	//电池数据
		{
			for (u8 i = 0; i < 4; i++)
			{
				send_buffer[(*_cnt)++] = fc_bat.byte_data[i];
			}
		}
		break;
		case 0x30:	//GPS数据
		{
			//
			for (u8 i = 0; i < 23; i++)
			{
				send_buffer[(*_cnt)++] = ext_sens.fc_gps.byte[i];
			}
		}
		break;
		case 0x33:	//通用速度测量数据
		{
			//
			for (u8 i = 0; i < 6; i++)
			{
				send_buffer[(*_cnt)++] = ext_sens.gen_vel.byte[i];
			}
		}
		break;
		case 0x34:	//通用距离测量数据
		{
			//
			for (u8 i = 0; i < 7; i++)
			{
				send_buffer[(*_cnt)++] = ext_sens.gen_dis.byte[i];
			}
		}
		break;
		case 0x40:	//遥控数据帧
		{
			for (u8 i = 0; i < 20; i++)
			{
				send_buffer[(*_cnt)++] = rc_in.rc_ch.byte_data[i];
			}
		}
		break;
		case 0x41:	//实时控制数据帧
		{
			for (u8 i = 0; i < 14; i++)
			{
				send_buffer[(*_cnt)++] = rt_tar.byte_data[i];
			}
		}
		break;
		case 0xe0:	//CMD命令帧
		{
			send_buffer[(*_cnt)++] = dt.cmd_send.CID;
			for (u8 i = 0; i < 10; i++)
			{
				send_buffer[(*_cnt)++] = dt.cmd_send.CMD[i];
			}
		}
		break;
		case 0xe2:	//PARA返回
		{
			temp_data = dt.par_data.par_id;
			send_buffer[(*_cnt)++] = BYTE0(temp_data);
			send_buffer[(*_cnt)++] = BYTE1(temp_data);
			temp_data_32 = dt.par_data.par_val;
			send_buffer[(*_cnt)++] = BYTE0(temp_data_32);
			send_buffer[(*_cnt)++] = BYTE1(temp_data_32);
			send_buffer[(*_cnt)++] = BYTE2(temp_data_32);
			send_buffer[(*_cnt)++] = BYTE3(temp_data_32);
		}
		break;
		case 0xF1:	//灵活格式数据
		{
			//==将int型数据拆成高8位和低8位进行发送
			send_buffer[(*_cnt)++] = BYTE0(openmv.red_receive_distance );
			send_buffer[(*_cnt)++] = BYTE1(openmv.red_receive_distance);
		}
		break;
		default:	break;
	}
}

我们发现里面使用Switch-case语句进行选择,选择条件就是frame_num——功能码。我们再看case里面的语句,发现有的使用for语句,有的直接使用 BYTE0(temp_data_32),看到BYTE1()是不是比较熟悉了?没错,这个就是例程里面的数据拆分函数,将数据拆分完后存到数组里面

        虽然前面有使用for循环进行数据填充的,但是我们的灵活格式帧因为数据长度不确定,所以直接用最简单的方式即可——像例程一般,直接根据自己数据的类型进行数据拆分

		case 0xF1:	//灵活格式数据
		{
			//==将int型数据拆成高8位和低8位进行发送
			send_buffer[(*_cnt)++] = BYTE0(openmv.red_receive_distance );
			send_buffer[(*_cnt)++] = BYTE1(openmv.red_receive_distance);
		}
		break;

我这里是传输一个s16型的数据,所以需要拆分两次。这部分代码根据自己的实际需要进行修改,我相信看过视屏的同学应该能自己编写这一部分代码了。

        这部分语句就是需要我们自己添加的第二部分代码。

        注意因为这个函数里面没有数据这个参数选项,我们需要使用结构体进行数据传递:openmv.red_receive_distance,使用结构体进行参数传递是最方便的。还需要注意的一点是,我们通信函数是放在1ms定时器里面的,结构体数据的更新也尽量要同步,所以我们可以将结构体参数更新放到1ms调度器里面,这样数据就不会不同步了

        接下来回到Frame_Send()函数,在例程中我们需要自己计算数据的长度,并事先赋值好,这样比较容易出错,所以在凌霄飞控的通信协议框架中,我们是再填写完数据之后再确定数据长度的,在缓存数组的第四位send_buffer[3]存放的是数据长度,我们之前是直接给0,填写完数据之后我们就可以更新数据长度了

	//数据长度更新
	send_buffer[3] = _cnt - 4;

下面的和校验和附加校验,然后是将校验结果存放到相关结构体里面,这部分应该没问题。

        根据例程,我们应该使用数组发送函数进行数据发送,通信就完成了

	//==发送数组函数(需要发送的数组,数组长度)
	ANO_DT_LX_Send_Data(send_buffer, _cnt);	

这里使用的是一个嵌套重映射:ANO_DT_LX_Send_Data()函数 -> UartSendLXIMU()函数 -> DrvUart5SendBuf()函数,这3个函数是等效的,为什么要这么麻烦的重重嵌套,我也不知道,也许是方便修改成自己的函数。

        到这里其实一个通信函数已经写好了,在例程中,我们是直接将框架函数放到调度器里面的,但是因为凌霄程序里面使用了层层调用,我们不是直接放在调度器里面的,而是放在1ms定时器里面,所以我们需要添加一部分代码。

        我们回到Check_To_Send()函数,这里面一堆结构体参数

static void Check_To_Send(u8 frame_num)
{
	//==定时触发发送
	if (dt.fun[frame_num].fre_ms)
	{
		//==初始相位小于定时发送周期,等待时间
		if (dt.fun[frame_num].time_cnt_ms < dt.fun[frame_num].fre_ms)
		{
			dt.fun[frame_num].time_cnt_ms++;
		}
		else
		{
			dt.fun[frame_num].time_cnt_ms = 1;
			dt.fun[frame_num].WTS = 1;			//标记等待发送
		}
	}
	else
	{
		//等待外部触发
	}
	//==等待发送
	if (dt.fun[frame_num].WTS)
	{
		dt.fun[frame_num].WTS = 0;
		
		//==进行数据填充、实际发送
		Frame_Send(frame_num, &dt.fun[frame_num]);	
	}
}

我们对结构体参数进行分析,发现它判断的值其实是在ANO_DT_Init()函数里面设置的,

void ANO_DT_Init(void)
{
	//========定时触发
	//电压电流数据
	dt.fun[0x0d].D_Addr = 0xff;
	dt.fun[0x0d].fre_ms = 100;	  //触发发送的周期100ms
	dt.fun[0x0d].time_cnt_ms = 1; //设置初始相位,单位1ms
	//遥控器数据
	dt.fun[0x40].D_Addr = 0xff;
	dt.fun[0x40].fre_ms = 20;	  //触发发送的周期100ms
	dt.fun[0x40].time_cnt_ms = 0; //设置初始相位,单位1ms
	//灵活格式帧
	dt.fun[0xF1].D_Addr = 0xff;
	dt.fun[0xF1].fre_ms = 500;	  //触发发送的周期500*1ms
	dt.fun[0xF1].time_cnt_ms = 0; //设置初始相位,单位1ms
	
	
	//========外部触发
	//GPS 传感器信息
	dt.fun[0x30].D_Addr = 0xff;
	dt.fun[0x30].fre_ms = 0;	  //0 由外部触发
	dt.fun[0x30].time_cnt_ms = 0; //设置初始相位,单位1ms
	//通用速度型传感器数据
	dt.fun[0x33].D_Addr = 0xff;
	dt.fun[0x33].fre_ms = 0;	  //0 由外部触发
	dt.fun[0x33].time_cnt_ms = 0; //设置初始相位,单位1ms
	//通用测距传感器数据
	dt.fun[0x34].D_Addr = 0xff;
	dt.fun[0x34].fre_ms = 0;	  //0 由外部触发
	dt.fun[0x34].time_cnt_ms = 0; //设置初始相位,单位1ms
	//实时控制帧
	dt.fun[0x41].D_Addr = 0xff;
	dt.fun[0x41].fre_ms = 0;	  //0 由外部触发
	dt.fun[0x41].time_cnt_ms = 0; //设置初始相位,单位1ms
	//CMD 命令帧
	dt.fun[0xe0].D_Addr = 0xff;
	dt.fun[0xe0].fre_ms = 0;	  //0 由外部触发
	dt.fun[0xe0].time_cnt_ms = 0; //设置初始相位,单位1ms
	//参数写入、参数读取返回
	dt.fun[0xe2].D_Addr = 0xff;
	dt.fun[0xe2].fre_ms = 0;	  //0 由外部触发
	dt.fun[0xe2].time_cnt_ms = 0; //设置初始相位,单位1ms
}

这里设置了定时触发和外部触发,像电池电压、遥控数据,它们是变化的,我们是需要它经常返回上位机的。像光流、GPS、CMD命令等这些数据是外部传感器发送给飞控的,不受飞控控制,飞控只是数据传输到上位机的中介,所以设置为外部触发。

        我们自己发送的灵活格式帧,是需要它定时返回上位机的(跟放在调度器里面一样),所以我们需要配置为定时触发,根据需要设置dt.fun[0xF1].fre_ms的值。

	//灵活格式帧
	dt.fun[0xF1].D_Addr = 0xff;
	dt.fun[0xF1].fre_ms = 500;	  //触发发送的周期500*1ms
	dt.fun[0xF1].time_cnt_ms = 0; //设置初始相位,单位1ms

        这里是需要添加的第三部分代码

        在Check_To_Send()函数里面使用if语句判断dt.fun[0xF1].fre_ms,如果大于0,就进入定时触发,标记dt.fun[frame_num].WTS标志位,否则等待外部触发。

	if (dt.fun[frame_num].fre_ms)
	{
		//==初始相位小于定时发送周期,等待时间
		if (dt.fun[frame_num].time_cnt_ms < dt.fun[frame_num].fre_ms)
		{
			dt.fun[frame_num].time_cnt_ms++;
		}
		else
		{
			dt.fun[frame_num].time_cnt_ms = 1;
			dt.fun[frame_num].WTS = 1;			//标记等待发送
		}
	}

定时时间到了,dt.fun[frame_num].WTS标志位为1,下面即进入发送框架函数

	if (dt.fun[frame_num].WTS)
	{
		dt.fun[frame_num].WTS = 0;
		
		//==进行数据填充、实际发送
		Frame_Send(frame_num, &dt.fun[frame_num]);	
	}

这部分功能实际跟直接将发送框架函数放到调度器里面是相同的,但是由于这个是一个通用型的发送框架,我们需要做适当的调整。

        凌霄飞控的通用通信协议函数介绍到这里。我们只需要添加上面3部分代码即可实现发送自己的灵活格式帧数据。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值