正电原子MiniFly Remoter程序学习笔记--发送飞控命令任务

adc.c文件中主要就是ADC的初始化,如下:

//初始化ADC1,使用DMA传输
//通道PA0\PA1\PA2\PB0\PB1
/*对应ADC_Channel_0
      ADC_Channel_1
      ADC_Channel_2
      ADC_Channel_8
      ADC_Channel_9
*/
void Adc_Init(void)
{ 	
	GPIO_InitTypeDef GPIO_InitStructure;
	ADC_InitTypeDef ADC_InitStructure; 
	DMA_InitTypeDef DMA_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);//使能GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//使ADC1时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA时钟
	
	//PA0\1\2 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//PB0\1 作为模拟通道输入引脚 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//DMA 配置
    //DMA的DMA1_Channel1对应ADC1
	DMA_DeInit(DMA1_Channel1);
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;	 //ADC1->DR地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&adc_value;//内存地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	DMA_InitStructure.DMA_BufferSize = 5*ADC_SAMPLE_NUM;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址固定
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址增加
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//半字
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;		//循环传输
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	DMA_Cmd(DMA1_Channel1, ENABLE);
	
	ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;				//扫描模式,用于多通道采集
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	//连续转换模式
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 5;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
	ADC_DMACmd(ADC1, ENABLE);//使能ADC1 DMA
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
	//配置连续转换通道,55.5个采样周期
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);	//1个通道转换一次耗时21us 4个通道
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5);	//采样个数ADC_SAMPLE_NUM
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5);	//总共耗时4*21*ADC_SAMPLE_NUM(64)=5.4ms<10ms
	ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 4, ADC_SampleTime_239Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 5, ADC_SampleTime_239Cycles5);
	
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
 
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能

}	

 还有一个均值滤波,就是采样十次然后取平均,这个是5个通道都获取,获取的值存到参数中给的数组中去。

//ADC均值滤波
void ADC_Filter(u16* adc_val)
{
	u16 i=0;
	u32 sum[5]={0,0,0,0};
	
	for(;i<ADC_SAMPLE_NUM;i++)
	{
		sum[0]+=adc_value[5*i+0];
		sum[1]+=adc_value[5*i+1];
		sum[2]+=adc_value[5*i+2];
		sum[3]+=adc_value[5*i+3];
		sum[4]+=adc_value[5*i+4];
	}
	adc_val[0]=sum[0]/ADC_SAMPLE_NUM;
	adc_val[1]=sum[1]/ADC_SAMPLE_NUM;
	adc_val[2]=sum[2]/ADC_SAMPLE_NUM;
	adc_val[3]=sum[3]/ADC_SAMPLE_NUM;
	adc_val[4]=sum[4]/ADC_SAMPLE_NUM;
}

 另一个是针对某一个通道的采样值均值滤波,只返回一个通道的值:

//axis八成是某一通道,应该是0~4
u16 getAdcValue(u8 axis)
{
	u32 sum=0;
	for(u8 i=0;i<ADC_SAMPLE_NUM;i++)
	{
		sum += adc_value[5*i+axis];
	}
	return sum/ADC_SAMPLE_NUM;
}

这个的参数axis是固定可选的,在adc.h中定义,对应5个通道:

#define  ADC_BAT		0
#define  ADC_ROLL		1
#define  ADC_PITCH	 	2
#define  ADC_YAW		3
#define  ADC_THRUST		4

接下就看看这些东西在哪里调用了,主要在joystick.c文件中:

/*摇杆初始化*/
void joystickInit(void)
{
	if(isInit) return;
	Adc_Init();
	jsParam = &configParam.jsParam;
	isInit = true;
}

这个摇杆初始化很直接,没有封装很多层,这个函数是直接在主函数中调用的。

这个jsParam是干嘛的?而configPara又是多少?目前只知道主函数main()中有个:

configParamInit();	/*配置参数初始化*/

很可能此参数跟这个有联系,这个也是很大的一个模块,稍后再看。

这里说一下joystick.c中IsInit的定义:

static bool isInit;

其实,每个初始化函数中都有Isinit的使用,每个初始化函数又在不同的文件中,为了互不影响,使用了静态变量的方法,那么这个Isinit只能被本文件中函数所改变,即使其他文件中有关于这个变量的外部声明也不行,会找不到这个变量的定义的错误,本文件的函数只能访问本文件中的变量(包括成功外部声明的变量),所以说即使名字但是不会互相干扰的。

那么这个值就保证了一个程序开始运行后,一个功能模块只初始化一次。

接下来看通过看joystick.h文件来知道joystick.c中还有什么文件,joystick.h中有:

两个函数形式的宏定义;

#define MAX(a,b) a>=b? a:b
#define MIN(a,b) a<=b? a:b

方向枚举;

enum dir_e
{
	CENTER,
	FORWARD,
	BACK,
	LEFT,
	RIGHT,
	BACK_LEFT,
	BACK_RIGHT,
};

单个摇杆活动轴量程结构体变量定义;

//摇杆单方向量程参数
struct range_s
{
	s16 range_pos;	//摇杆正量程
	u16 mid;
	s16 range_neg;	//摇杆负量程
};

所有活动轴摇杆量程参数类型定义,4条轴,4个结构体,就是4个上述类型变量的组合;

//摇杆量程参数
typedef struct
{
	struct range_s thrust;
	struct range_s roll;
	struct range_s pitch;
	struct range_s yaw;
}joystickParam_t;

飞控数据结构,浮点型;

//飞控数据结构
typedef struct 
{
	float roll;
	float pitch;
	float yaw;
	float thrust;
}joystickFlyf_t;

飞控数据结构,整型;

//飞控数据结构
typedef struct 
{
	u16 roll;
	u16 pitch;
	u16 yaw;
	u16 thrust;
}joystickFlyui16_t;

这里有一个c语言学习的点:

typedef是类型定义的意思。typedef struct 是为了使用这个结构体方便。

具体区别在于:
若是

struct range_s {

}

这样来定义结构体的话。在申请range_s的变量时,需要这样写:

struct range_s n;

若用typedef,可以这样写:

typedef struct range_s{

}RANGE_S;

在申请变量时就可以这样写:

RANGE_S n;

区别就在于使用时,是否可以省去struct这个关键字。

注意:在c++中可以不需要typedef就可以Student stu2是因为在c++中struct也是一种类,

           所以可以直接使用Student stu2来定义一个Student的对象,但c中去不可以。

剩下就是5个函数的声明:

void joystickInit(void);

void getFlyDataADCValue(joystickFlyui16_t *adcValue);

void ADCtoFlyDataPercent(joystickFlyf_t *flyDataPercent);

void joystcikParamSet(joystickParam_t set);

enum dir_e getJoystick1Dir(u8 mode);

enum dir_e getJoystick2Dir(u8 mode);

第一个,获取摇杆的各个轴的值,这函数一下子就把摇杆的4个方向的当前值就都读完了,参数中需要给出一个整型的飞控数据结构类型(joystickFlyui16_t)变量。函数中调用的是adc.c中的针对某一个通道的采样值均值滤波函数getAdcValue();

/*获取摇杆ADC值*/
void getFlyDataADCValue(joystickFlyui16_t *adcValue)
{
	adcValue->thrust = getAdcValue(ADC_THRUST);
	adcValue->roll = getAdcValue(ADC_ROLL);
	adcValue->pitch = getAdcValue(ADC_PITCH);
	adcValue->yaw = getAdcValue(ADC_YAW);
}

第二个是把飞控数据转化成百分比的函数,这个函数需要传入一个浮点型飞控数据结构型(joystickFlyf_t)变量

/*ADC值转换成飞控数据百分比*/
void ADCtoFlyDataPercent(joystickFlyf_t *percent)
{
	s16 adcValue;
	
	//THRUST
	adcValue = getAdcValue(ADC_THRUST) - jsParam->thrust.mid;
	adcValue = deadband(adcValue,MID_DB_THRUST);
	if(adcValue>=0)
		percent->thrust = (float)adcValue/(jsParam->thrust.range_pos-MID_DB_THRUST-DB_RANGE);
	else
		percent->thrust = (float)adcValue/(jsParam->thrust.range_neg-MID_DB_THRUST-DB_RANGE);
	
	//ROLL
	adcValue = getAdcValue(ADC_ROLL) - jsParam->roll.mid;
	adcValue = deadband(adcValue, MID_DB_ROLL);
	if(adcValue >= 0)
		percent->roll = (float)adcValue/(jsParam->roll.range_pos-MID_DB_ROLL-DB_RANGE);
	else
		percent->roll = (float)adcValue/(jsParam->roll.range_neg-MID_DB_ROLL-DB_RANGE);
	
	//PITCH
	adcValue = getAdcValue(ADC_PITCH) - jsParam->pitch.mid;
	adcValue = deadband(adcValue, MID_DB_PITCH);
	if(adcValue >= 0)
		percent->pitch = (float)adcValue/(jsParam->pitch.range_pos-MID_DB_PITCH-DB_RANGE);
	else
		percent->pitch = (float)adcValue/(jsParam->pitch.range_neg-MID_DB_PITCH-DB_RANGE);
	
	//YAW
	adcValue = getAdcValue(ADC_YAW) - jsParam->yaw.mid;
	adcValue = deadband(adcValue, MID_DB_YAW);
	if(adcValue >= 0)
		percent->yaw = (float)adcValue/(jsParam->yaw.range_pos-MID_DB_YAW-DB_RANGE);
	else
		percent->yaw = (float)adcValue/(jsParam->yaw.range_neg-MID_DB_YAW-DB_RANGE);
}

先来看第一段,给THRUST量转百分比,用当前获取到的ADC_THRUST量减去地址为jsParam的joystickParam_t类型变量的thrust成员的mid值。mid值很可能就是摇杆回中时的值。采集量减去mid值就是ADC值变化量。这里的地址为jsParam的值来自于摇杆初始化函数joystickInit()中:

jsParam = &configParam.jsParam;

跟上边一样,configParam是个大功能模块,稍后另说。当然到现在也知道了configParam里边有摇杆4个轴正负量程及中值的预定义。

然后接着thrust转百分比中第二句使用了deadband()函数,这个函数也是在joystick.c中定义的,一探究竟:

/*去除死区函数*/
int deadband(int value, const int threshold)
{
	if (abs(value) < threshold)
	{
		value = 0;
	}
	else if (value > 0)
	{
		value -= threshold;
	}
	else if (value < 0)
	{
		value += threshold;
	}
	return value;
}

叫去除死区函数, 很明显,当ADC变化量小于threshold时,使得变化量为0,这就印证了“死区”这个名字。不管ADC变化量是增大还是减小,最终的变化量是减去死区值threshold后的值。

从调用这个函数时的参数就可以看出MID_DB_THRUST为threshhold值,MID_DB_THRUST值定义也在joystick.c中:

//摇杆中间软件死区值(ADC值)
#define MID_DB_THRUST		150	
#define MID_DB_YAW			300	
#define MID_DB_PITCH		150
#define MID_DB_ROLL			150

包括其他三个轴的死区量。

回到给THRUST量转百分比,去除死区值后,则使用公式:

thrust百分比=thrustADC值/(jsParam参数中规定的量程-中点处死区值-最大量程死区值)

来计算百分比,因为正负量程可能不同,所以正负区分。

其他三个量转换百分比,与thrust的过程一模一样。

从定义中可以看出,getFlyDataADCValue()和ADCtoFlyDataPercent()没有什么必要的联系,都是自己从ADC中读取值的,两个不分先后函数互不影响。

奇了怪了,第三个joystcikParamSet(joystickParam_t set);函数没有定义,没有发现其定义的位置。

剩下两个函数为获取两个摇杆方向的函数。从定义可以看出两个函数返回的都是方向枚举类型。方向枚举类型定义为:

enum dir_e
{
	CENTER,
	FORWARD,
	BACK,
	LEFT,
	RIGHT,
	BACK_LEFT,
	BACK_RIGHT,
};

这个方向应该指的是遥控器上摇杆的实际方向。可是不明白为什么没有左前和右前两个方向呢?

*  后来又打开遥控器看了一下,终于明白了,这个是把摇杆当按键使用了,前后左右是菜单中切换时用到的,后左后右是飞行控制解锁上锁时用的。 

先不管,看第一个函数:

/*获取摇杆1方向*/
/*mode:0,不支持连续按;1,支持连续按*/
enum dir_e getJoystick1Dir(u8 mode)
{
	enum dir_e ret=CENTER;
	joystickFlyui16_t adcValue;
	static bool havebackToCenter = true;
	
	getFlyDataADCValue(&adcValue);
	if(mode) havebackToCenter = true;
	if(havebackToCenter == true)//摇杆回到过中间位置
	{
		if(adcValue.thrust > (jsParam->thrust.mid+DIR_MID_THRUST))
			ret = FORWARD;
		else if(adcValue.thrust < (jsParam->thrust.mid-DIR_MID_THRUST))
			ret = BACK;
		
		if(ret==BACK && adcValue.yaw>(jsParam->yaw.mid+DIR_MID_YAW))
			ret = BACK_RIGHT;
		else if(ret==BACK && adcValue.yaw<(jsParam->yaw.mid-DIR_MID_YAW))
			ret = BACK_LEFT;
		else if(adcValue.yaw > (jsParam->yaw.mid+DIR_MID_YAW))
			ret = RIGHT;
		else if(adcValue.yaw < (jsParam->yaw.mid-DIR_MID_YAW))
			ret = LEFT;
		
		havebackToCenter = false;//摇杆离开了中间位置
		if(ret == CENTER)//摇杆依然在中间位置
			havebackToCenter = true;
	}
	else if( adcValue.thrust >= (jsParam->thrust.mid-DIR_MID_THRUST) &&
			 adcValue.thrust <= (jsParam->thrust.mid+DIR_MID_THRUST) &&
			 adcValue.yaw >= (jsParam->yaw.mid-DIR_MID_YAW) &&
			 adcValue.yaw <= (jsParam->yaw.mid+DIR_MID_YAW) 
		   )//摇杆离开了中间位置,现在查询摇杆是否回中
	{
		havebackToCenter = true;
		ret = CENTER;
	}
	
	return ret;
}

判断ADC的值是否大于ADC中值附加一个方向中值,这里的方向中值为:

//获取摇杆方向时定义在中间的范围值(ADC值)
#define DIR_MID_THRUST		800
#define DIR_MID_YAW			800
#define DIR_MID_PITCH		800
#define DIR_MID_ROLL		800

就是说只有ADC的变化量大于这个值才能判定摇杆的方向不为中。静态布尔变量havebackToCenter在函数执行完成后会消失。

此处产生了一个疑问,贴出自己的疑问:

if(havebackToCenter == true)//摇杆回到过中间位置

  是不是在每次调用这个函数的时候这个条件的判断结果都为真,那么其对应的else if里边的内容在任何情况下都执行不了?mode的值对这个函数的执行结果会无任何结果?

稍后调试一下看看。

最后一个函数也是这样的过程,是判断第二个摇杆的方向:

/*获取摇杆2方向*/
/*mode:0,不支持连续按;1,支持连续按*/
enum dir_e getJoystick2Dir(u8 mode)
{
	enum dir_e ret = CENTER;
	joystickFlyui16_t adcValue;
	static bool havebackToCenter = true;
	
	getFlyDataADCValue(&adcValue);
	if(mode) havebackToCenter = true;
	if(havebackToCenter == true)//摇杆回到过中间位置
	{	
		if(adcValue.pitch > (jsParam->pitch.mid+DIR_MID_PITCH))
			ret = FORWARD;
		else if(adcValue.pitch < (jsParam->pitch.mid-DIR_MID_PITCH))
			ret = BACK;
		
		if(ret==BACK && adcValue.roll>(jsParam->roll.mid+DIR_MID_ROLL))
			ret = BACK_RIGHT;
		else if(ret==BACK && adcValue.roll<(jsParam->roll.mid-DIR_MID_ROLL))
			ret = BACK_LEFT;
		else if(adcValue.roll>(jsParam->roll.mid+DIR_MID_ROLL))
			ret = RIGHT;
		else if(adcValue.roll<(jsParam->roll.mid-DIR_MID_ROLL))
			ret = LEFT;

		havebackToCenter = false;//摇杆离开了中间位置
		if(ret == CENTER)//摇杆依然在中间位置
			havebackToCenter = true;
	}
	else if( adcValue.pitch >= (jsParam->pitch.mid-DIR_MID_PITCH) &&
			 adcValue.pitch <= (jsParam->pitch.mid+DIR_MID_PITCH) &&
			 adcValue.roll >= (jsParam->roll.mid-DIR_MID_ROLL) &&
			 adcValue.roll <= (jsParam->roll.mid+DIR_MID_ROLL) 
		   )//摇杆离开了中间位置,现在查询摇杆是否回中
	{
		havebackToCenter = true;
		ret = CENTER;
	}
	
	return ret;
}

 

到此处,明白了摇杆的动作量是ADC采集成数据的,文件adc.c配置了ADC,joystick.c文件中定义了一些ADC数据处理的函数,但是还是没有看出数据怎么发送出去的,如何处理与四轴之间往来的数据的。

下边我们来看看数据是怎么处理的,来到remoter_ctrl.c和remoter_ctrl.h文件中,先不要看这些数据定义,很烦。到用得到的时候再看。

在remoter_ctrl.h中最主要的就是定义了commanderTask任务函数:

void commanderTask(void* param);
joystickFlyf_t getFlyControlData(void);
float limit(float value,float min, float max);
void sendRmotorCmd(u8 cmd, u8 data);
void sendRmotorData(u8 *data, u8 len);

commanderTask任务函数的主要功能应该就是把ADC采集到的数据值打包添加到发送队列中。这个函数超长的:

/*发送飞控命令任务*/
void commanderTask(void* param)
{
	float max_thrust = LOW_SPEED_THRUST;
	float max_pitch = LOW_SPEED_PITCH;
	float max_roll = LOW_SPEED_ROLL;
	joystickFlyf_t  percent;
	
	while(1)
	{
		vTaskDelay(10);
		switch(configParam.flight.speed)
		{
			case LOW_SPEED:
				max_thrust = LOW_SPEED_THRUST;
				max_pitch = LOW_SPEED_PITCH;
				max_roll = LOW_SPEED_ROLL;
				break;
			case MID_SPEED:
				max_thrust = MID_SPEED_THRUST;
				max_pitch = MID_SPEED_PITCH;
				max_roll = MID_SPEED_ROLL;
				break;
			case HIGH_SPEED:
				max_thrust = HIGH_SPEED_THRUST;
				max_pitch = HIGH_SPEED_PITCH;
				max_roll = HIGH_SPEED_ROLL;
				break;
		}
		
		ADCtoFlyDataPercent(&percent);
		
		//THRUST
		if(configParam.flight.ctrl == ALTHOLD_MODE || configParam.flight.ctrl == THREEHOLD_MODE)/*定高模式 和定点模式*/
		{
			flydata.thrust = percent.thrust * ALT_THRUST;
			flydata.thrust += ALT_THRUST;
			flydata.thrust = limit(flydata.thrust, 0, 100);
		}
		else
		{
			flydata.thrust = percent.thrust * (max_thrust - MIN_THRUST);
			flydata.thrust += MIN_THRUST;
			flydata.thrust = limit(flydata.thrust, MIN_THRUST, max_thrust);
		}
		//ROLL
		flydata.roll = percent.roll * max_roll;
		flydata.roll = limit(flydata.roll, -max_roll, max_roll);
		//PITCH
		flydata.pitch = percent.pitch * max_pitch;
		flydata.pitch = limit(flydata.pitch, -max_pitch, max_pitch);
		//YAW
		flydata.yaw = percent.yaw * MAX_YAW;
		flydata.yaw = limit(flydata.yaw, -MAX_YAW, MAX_YAW);
		
		/*发送飞控数据*/
		if(getRCLock()==false && radioinkConnectStatus()==true && getIsMFCanFly()==true)
		{	
			remoterData_t send;
			switch(configParam.flight.mode)
			{
				case HEAD_LESS:
					send.flightMode = 1;
					break;
				case X_MODE:
					send.flightMode = 0;
					break;
			}
			
			switch(configParam.flight.ctrl)
			{
				case ALTHOLD_MODE:
					send.ctrlMode = 1;
					break;
				case MANUAL_MODE:
					send.ctrlMode = 0;
					break;
				case THREEHOLD_MODE:
					send.ctrlMode = 3;
					break;
			}
			
			if(flydata.thrust<=MIN_THRUST && send.ctrlMode==0)
			{
				send.thrust = 0;
			}
			else
			{
				send.thrust = flydata.thrust;
			}
			
			if(getTrimFlag() == true)
			{
				send.pitch = 0;
				send.roll = 0;
			}
			else
			{
				send.pitch = flydata.pitch ;
				send.roll = flydata.roll;
			}
			send.yaw = flydata.yaw;
			send.trimPitch = configParam.trim.pitch;
			send.trimRoll = configParam.trim.roll;
			
			/*发送飞控数据*/
			sendRmotorData((u8*)&send, sizeof(send));
		}
		
		/*发送遥感数据至匿名上位机*/
		if(radioinkConnectStatus()==true)
		{
			atkp_t p;
			joystickFlyui16_t rcdata;
			
			rcdata.thrust = flydata.thrust*10 + 1000;
			rcdata.pitch = percent.pitch*500 + 1500;
			rcdata.roll = percent.roll*500 + 1500;
			rcdata.yaw = percent.yaw*500 + 1500;
			
			p.msgID = DOWN_RCDATA;
			p.dataLen = sizeof(rcdata);
			memcpy(p.data, &rcdata, p.dataLen);
			radiolinkSendPacket(&p);
		}
	}
}

首先是定义了三个最大值分别对应于油门量、俯仰量、滚转量,分别代表上升下降快慢,前进后退快慢,左右平移快慢。这个三个值是可以改的,不同的速度下有不同的值。定义一个 浮点型飞控数据值来存储摇杆数据。然后就开始进入循环了。

循环中,首先判断configParam参数中飞行速度设定,然后根据速度设置最大值。

这里不明白这个累加是何用意,百分之50的油门量直接赋值不就行了吗?后又仔细推敲后发现,这里的flydata可能就是最终发送的飞控控制数据。

而他是怎么求得的呢?

首先,必定要采集一下当前的摇杆量:

ADCtoFlyDataPercent(&percent);

循环中这句就是了。采集完之不能直接就把数据上传上去,还需要根据控制模式对摇杆量做出调整。以定高和顶点模式来说,飞行过程中,即使没有油门量,也会稳定的悬停,说明这里需要有个基础的油门量,而 ALT_THRUST就是这个基础的油门量。

所以判断为定高和定点模式后的执行语句中,首先使飞控数据等于以基础油门为量程的变化量,此时油门量只是个变化量,需要加上基础量,下边的第二局就是这个意思,只要把基础油门量加上去就行了。这个语句不是一直累加的,采集一次摇杆变化,就执行一次而已。

flydata.thrust = percent.thrust * ALT_THRUST;
			flydata.thrust += ALT_THRUST;
			flydata.thrust = limit(flydata.thrust, 0, 100);

如果条件判断结果为手动模式,则基础量油门量为MIN_THRUST,而摇杆采集的变化量以 (max_thrust - MIN_THRUST)为量程。过程与其他两个模式相似。

第三句是对计算后的油门量做了限制,使用的是remoter_ctrl.h中定义的第三个函数。不明白到底是什么意外能使得上述过程得到的油门量不在0到100内。不过限制调整一下也好。

下边就是对俯仰、滚转、偏航三个量的采集了。上述油门量中需要考虑无油门摇杆变化量时悬停的问题,所以需要加上基础量。而这个三个量中不涉及基础量,当没有变化量时,那采集到是0就传输0。

至此,数据应该与摇杆和ADC就无关了,只剩怎么传到飞控上了。

下边是数据发送的语句。

首先需要判断一个能不能飞,能飞才发送,有三个条件都需要满足:飞行控制解锁、无线连接正常、飞控能飞。

发送数据的过程中用到了一个新的数据类型:

/*遥控数据结构*/
typedef __packed struct
{
	float roll;      
	float pitch;  
	float yaw;      
	float thrust;
	float trimPitch;
	float trimRoll;
	u8 ctrlMode;
	bool flightMode;
	bool RCLock;
} remoterData_t;

这个飞行模式flightMode是有头和无头模式,有头为0,无头为1。飞行模式ctrlMode是定高、手动、定点。

判断飞行模式设定,给Send数据的飞行模式值赋值;

判断控制模式设定,给Send数据的控制模式值赋值;

判断手动模式下,油门量若小于最小值,则油门量为0,突然想到这个判断的设定或者这个最小值的设定很精妙,可以防止该模式下桨叶在无操作时不由自主的转动。

getTrimFlag()获取这个标志后,使得俯仰和滚转为0,这个是微调标志。

trimPitch和trimRoll是微调量。

然后将Send数据通过无线发送到飞控上去,调用了下边这个函数:

/*发送遥控控制数据*/
void sendRmotorData(u8 *data, u8 len)
{
	if(radioinkConnectStatus() == false)
		return;
	atkp_t p;
	p.msgID = DOWN_REMOTOR;
	p.dataLen = len + 1; 
	p.data[0] = REMOTOR_DATA;
	memcpy(p.data+1, data, len);
	radiolinkSendPacket(&p);
}

 这个函数是remoter_ctrl.h中倒数第二个函数,这个函数的功能是将send的数据传入其定义的atk_p数据,发送数据时的指令ID为DOWN_REMOTOR,调用radiolinkSendPacket(&p)函数来将atk_p数据发送到txQueue队列中,这个函数定义在radiolink.c文件中。

这里很自然的用到了任务之间的通信,由这个过程可以看出,为不同的任务所创建的文件和函数可以由不同的任务调用,只要包含头文件就行,没有什么其他的局限。但是两个任务所共享的数据要用队列。至于为什么不用全局变量,我还专门搜索了一下:(175条消息) RTOS任务间通信为什么不用全局变量?_strongerHuang的博客-CSDN博客icon-default.png?t=N4P3https://blog.csdn.net/ybhuangfugui/article/details/126357314

任务间通信就是要用RTOS提供的数据结构。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值