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博客https://blog.csdn.net/ybhuangfugui/article/details/126357314
任务间通信就是要用RTOS提供的数据结构。