准备电赛的飞控题,买来了匿名的飞控学习一下,这里整理了一下匿名飞控中比较关键的几部分,学习了一下原理,然后代码解读都写注释里了,篇幅较长。
目录
一、遥控器信号接收
匿名的飞控提供了ppm和sbus两种遥控器信号的接收,在这里我只使用了ppm,从程序里看应该是最多可以接收7个通道。
1.代码解读
(1)ppm/sbus初始化
首先是在 Drv_BspInit() 中调用了 Remote_Control_Init() ,
void Remote_Control_Init()
{
//
RC_IN_MODE = Ano_Parame.set.pwmInMode;///在参数初始化设置中默认设置为ppm模式
//
if(RC_IN_MODE == SBUS)
{
Drv_SbusInit();
}
else
{
Drv_PpmInit();
// PWM_IN_Init(RC_IN_MODE);
}
}
在 Drv_PpmInit() 中初始化输入捕获,开启中断,这里是配置成向上计时模式,即捕获到上升沿后,进入中断,并以80M的频率计数,计数区间为0~0xffffff。
void Drv_PpmInit(void)
{
ROM_SysCtlPeripheralEnable(PPM_SYSCTL);
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_WTIMER1);
/*GPIOC配置为定时器捕获模式*/
ROM_GPIOPinTypeTimer(PPM_PORTS, PPM_PIN);
ROM_GPIOPinConfigure(PPM_FUNCTION);
/*配置定时器5B为捕获上升沿*/
ROM_TimerConfigure( WTIMER1_BASE ,TIMER_CFG_SPLIT_PAIR | TIMER_CFG_B_CAP_TIME_UP );
ROM_TimerControlEvent(WTIMER1_BASE,TIMER_B,TIMER_EVENT_POS_EDGE);
ROM_TimerLoadSet( WTIMER1_BASE , TIMER_B , 0xffff );
ROM_TimerPrescaleSet( WTIMER1_BASE , TIMER_B , 0xff );
///这里是配置成向上计时模式,即捕获到上升沿后,进入中断,并以80M的频率计数,计数区间为0~0xffffff
/*开启定时器中断*/
TimerIntRegister(WTIMER1_BASE, TIMER_B , PPM_Decode);
ROM_IntPrioritySet( INT_WTIMER1B , USER_INT6);
ROM_TimerIntEnable( WTIMER1_BASE , TIMER_CAPB_EVENT);
ROM_TimerEnable( WTIMER1_BASE, TIMER_B );
ROM_IntEnable( INT_WTIMER1B );
}
(2)中断中进行数据解析
当PPM输入引脚捕获到高电平脉冲上升沿时触发中断, PPM_Decode(void) 为中断服务函数,调用PPM_Cal(PulseHigh) 计算出当前脉冲宽度。脉冲宽度在1000us-2000us之间,一帧数据结束标志位脉冲宽度大于5000us,即留出了5ms作为空闲判断的标志。
static void PPM_Cal(uint32_t PulseHigh)
{
static uint8_t Chan = 0;
/*脉宽高于一定值说明一帧数据已经结束*/
if(PulseHigh > 5000)
{
/*一帧数据解析完成*/
Chan = 0;
}
else
{
/*脉冲高度正常*/
if (PulseHigh > PULSE_MIN && PulseHigh < PULSE_MAX)
{
if(Chan < 16)
{
ch_watch_dog_feed(Chan);
RC_PPM.Captures[Chan++] = PulseHigh;
}
}
}
}
static void PPM_Decode(void)
{
static uint32_t PeriodVal1,PeriodVal2 = 0;
static uint32_t PulseHigh;
/*清除中断标志*/
ROM_TimerIntClear( WTIMER1_BASE , TIMER_CAPB_EVENT );
/*获取捕获值*/
PeriodVal1 = ROM_TimerValueGet( WTIMER1_BASE , TIMER_B );
if( PeriodVal1 > PeriodVal2 )
PulseHigh = (PeriodVal1 - PeriodVal2) /80;
else
PulseHigh = (PeriodVal1 - PeriodVal2 + 0xffffff)/80;
PeriodVal2 = PeriodVal1;
PPM_Cal(PulseHigh);
}
(3)遥控器任务
读出脉冲宽度后,调用下面的函数,先把脉冲宽度信息处理成合适的杆量信息。然后检测遥感状态,是否触发解锁、传感器校准等特殊动作。
void RC_duty_task(u8 dT_ms) //建议2ms调用一次
{
if(flag.start_ok)
{
/获得通道数据
// if(RC_IN_MODE == PWM)
// {
// for(u8 i=0;i<CH_NUM;i++)
// {
// if(chn_en_bit & (1<<i))//(Rc_Pwm_In[i]!=0)//该通道有值,==0说明该通道未插线(PWM)
// {
// CH_N[i] = 1.25f *((s16)Rc_Pwm_In[i] - 1500); //1100 -- 1900us,处理成大约+-500摇杆量
// }
// else
// {
// CH_N[i] = 0;
// }
// CH_N[i] = LIMIT(CH_N[i],-500,500);//限制到+—500
// }
// }
// else if(RC_IN_MODE == PPM)
if(RC_IN_MODE == PPM || RC_IN_MODE == PWM)
{
for(u8 i=0;i<CH_NUM;i++)
{
if(chn_en_bit & (1<<i))//(Rc_Ppm_In[i]!=0)//该通道有值
{
CH_N[i] = ((s16)RC_PPM.Captures[i] - 1500); //1000 -- 2000us,处理成大约+-500摇杆量
}
else
{
CH_N[i] = 0;
}
CH_N[i] = LIMIT(CH_N[i],-500,500);//限制到+—500
}
}
else//sbus
{
for(u8 i=0;i<CH_NUM;i++)
{
if(chn_en_bit & (1<<i))//该通道有值
{
CH_N[i] = 0.65f *((s16)Rc_Sbus_In[i] - 1024); //248 --1024 --1800,处理成大约+-500摇杆量
}
else
{
CH_N[i] = 0;
}
CH_N[i] = LIMIT(CH_N[i],-500,500);//限制到+—500
}
}
///
//解锁监测
unlock(dT_ms);
//摇杆触发功能监测
stick_function(dT_ms);
//通道看门狗
ch_watch_dog(dT_ms);
//失控保护检查
fail_safe_check(dT_ms);//3ms
}
}
2.ppm原理
ppm的原理其实比较简单,下面图基本可以说清楚,
其实就是是把多路PWM波压缩成一路ppm信号,通常20ms为一个周期,用一系列高电平脉冲之间的间隔时间表示每一路PWM波的脉宽,各通道的高电平信号是一个挨着一个的,最大的时间是2ms,而不是每个通道分配2ms的时间,但实际可能出现不是紧挨着的,但是对接码没影响。
这部分相关内容也可以参考:
https://blog.youkuaiyun.com/wxc971231/article/details/95983705
https://blog.youkuaiyun.com/nicekwell/article/details/53866204
二、传感器数据读取
匿名使用的icm20602、ak8975、spl06都是SPI通信,通过cs脚进行片选,占用资源少且速度快。另外匿名飞控上还有一点spi flash,不过看了一下代码感觉是没有用上。对于用户自己加传感器,匿名预留了5个串口,其中1是gps,2是数传,3空的,4是光流,5是空的。
1.SPI代码和原理
(1)以icm20602读取为例
我们平时使用的spi在TM4C123GH6PM中即使ssi外设,该芯片中包括四个同步串行接口(SSI)模块,是ti公司定义的一种高速、全双工、同步串行接口协议,兼容spi。首先是spi初始化,初始化clk、mosi、miso三个脚,读写函数。
void Drv_Spi0Init(void)
{
ROM_SysCtlPeripheralEnable( SYSCTL_PERIPH_SSI0 );
ROM_SysCt