MWC飞控增加声纳定高的方法

2015.12.17 更新:经过2个周末的上机测试,该算法效果很好,在低空超声锁高之后离地高度非常稳定,现在已经成功应用在低空航拍上了。

现状

MWC开源飞控已经很有点年头了,现在好多新的穿越机改用CC3D、航拍机改用APM,商业化的飞控也很多了。但是MWC作为基于Arduino的开源飞控,可以说非常成熟而且代码简单易懂,便宜,效果也不错,所以我的四轴平台依然采用了MWC飞控。MWC2.4相对前代的改进,主要是加入了对串口GPS的支持,修复了声纳崩溃问题,以及改进了计算实时性。但是声纳定高依然没有得到官方支持(包括I2C声纳)。新加入的GPS支持,由于占用内存过高,实际上在Mega之外的Arduino上无法使用。一个没有GPS定点和声纳定高的飞控,就相当于一个简单的增稳飞控而已,没有一定的技术的话生存能力不是太高。个人觉得MWC有点走偏了。

所以我的思路是在MWC2.4的基础上进一步实现声纳定高和GPS定点,至于GPS RTB、WP等功能等生存能力提高之后再考虑。声纳定高和GPS定点应该都不会对内存造成过大的压力。这次首先介绍声纳定高的方法。

传统的MWC为了支持GPS和声纳,一般是使用I2C_GPS_Nav这个项目,外置一片Arduino作为I2C GPS和声纳板。另外网上有些高手自行给MWC打了补丁,可以在低空用声纳数据覆盖气压计数据,从而利用气压定高代码。

思路

我的声纳定高基本思路也是利用气压定高代码。

但是气压计的高度读数与飞机本身姿态无关,而超声波必须考虑机身倾斜程度,将测得的距离向Z轴投影,才是真实的高度。

网上对于MWC Baro模式的描述往往就是一句气压定高,具体的使用方法并没有讲得很清楚。阅读代码发现,Baro模式的具体行为是,油门不变,MWC会维持在进入Baro时的高度上;以进入Baro模式时为基准,增减油门飞机会升降,其速率为油门每增减100点,速度相应的增减30cm/s左右;油门回到基准则维持当前高度。

用Baro模式辅助降落应该是比较有用的,飞机降到声纳有效范围之后,开启Baro模式,稍微收油,飞机缓缓降落。

实现

首先是需要把I2C_GPS_Nav中对于Pingpong型声纳的代码移植到MWC2.4的Sensor.cpp中。

我的MWC上A1和A2是空出来的,所以就用这两个作为声纳的信号引脚。

// ************************************************************************************************************
// Pingpong Sonar
// ************************************************************************************************************
// 2015.11.29 by XD : ported from I2C_GPS_NAV_v2_2
#if defined(PINGPONG_SONAR)
volatile static uint32_t Sonar_starTime = ;
volatile static uint32_t Sonar_echoTime = ;
volatile static uint16_t Sonar_waiting_echo = ; void Sonar_init()
{
// Pin change interrupt control register - enables interrupt vectors
PCICR |= (<<PCIE1); // Port C // Pin change mask registers decide which pins are enabled as triggers
PCMSK1 |= (<<PCINT10); // echo, arduino pin A2, PC2 DDRC |= 0x02; // trigger, arduino pin A1, triggerpin PC1 as output
Sonar_update();
} ISR(PCINT1_vect) {
//uint8_t pin = PINC;
if (PINC & <<PCINT10) { //indicates if the bit 0 of the arduino port [B0-B7] is at a high state
Sonar_starTime = micros();
}
else {
Sonar_echoTime = micros() - Sonar_starTime; // Echo time in microseconds if (Sonar_echoTime <= *) { // valid distance
sonarAlt = Sonar_echoTime / ;
}
else
{
// No valid data
sonarAlt = -;
}
Sonar_waiting_echo = ;
}
} void Sonar_update()
{
if (Sonar_waiting_echo == )
{
// Send 2ms LOW pulse to ensure we get a nice clean pulse
// PORTC &= ~(0x08);//PC3 low
PORTC &= ~(0x02);//PC1 low
delayMicroseconds(); // send 10 microsecond pulse
// PORTC |= (0x08);//PC3 high
PORTC |= (0x02);//PC1 high
// wait 10 microseconds before turning off
delayMicroseconds();
// stop sending the pulse
// PORTC &= ~(0x08);//PC3 low
PORTC &= ~(0x02);//PC1 low
Sonar_waiting_echo = ;
}
}
#endif // #if defined(PINGPONG_SONAR)

我用的声纳就是X宝上买的10多块号称比较准的超声波模块,拔掉背后的跳线帽就是Pingpong模式(不然是串口模式),这种模块还可以输出温度值用于距离补偿,但是读取温度需要额外占用10多us,而且绝对距离在飞控上意义不大,所以暂时不考虑。这种Pingpong声纳在读取距离的时候会占用10多us,相当于100多个时钟周期,软件开销还是有点的。不过好在便宜,I2C的声纳要100多软妹币,这个只要10多块。

官方I2C声纳的代码最后,如果所有的I2C声纳都没有定义,则会把Sonar_init和Sonar_update定义为空函数。这里(2.4版本Sensor.cpp:1576)需要进行一点修改,避免重复定义Sonar_init和Sonar_update:

#if !defined(PINGPONG_SONAR) // 2015.11.29 by XD : check for pingpong
inline void Sonar_init() {}
void Sonar_update() {}
#endif

在Config.h中开启Pingpong声纳:

#define PINGPONG_SONAR 

要注意在def.h中添加对Pingpong声纳的支持:

#if defined(SRF02) || defined(SRF08) || defined(SRF10) || defined(SRC235) || defined(I2C_GPS_SONAR) || defined(PINGPONG_SONAR) // 2015.11.29 by XD : ported from I2C_GPS_NAV_v2_2
#define SONAR 1
#else
#define SONAR 0
#endif

编译通过并且正确连接声纳(Trigger接A1,Echo接A2)的话,可以看到MWC GUI上Sonar的标签变绿了。

但是现在声纳数据还没有被使用,需要进一步添加代码使之与气压计数据结合。修改IMU.cpp中getEstimatedAltitude()函数,在BaroEstAlt LPF之后进行融合:

  BaroEstAlt = (BaroEstAlt *  + BaroAlt ) >> ; // additional LPF to reduce baro noise (faster by 30 µs)

  // 2015.11.29 by XD, if sonar reads less than 4.2m (sensor limit 4.5m - safety margin 0.3m), use sonar
// this maybe unsafe...
if ((sonarAlt > && sonarAlt < ) ||
((att.angle[ROLL] > - && att.angle[ROLL] < ) && (att.angle[PITCH] > - && att.angle[PITCH] < )))
{
// actual alt = sonarAlt * cos(att.angle[ROLL]) * cos(att.angle[PITCH])
int32_t actualAlt = abs((int32_t)sonarAlt * (_cos10(att.angle[ROLL]) * _cos10(att.angle[PITCH]) >> ));
alt.EstAlt = actualAlt >> ;
}
else
alt.EstAlt = BaroEstAlt;

上面这段代码中,声纳的测量值乘以Roll和Pitch的余弦就是向Z轴的投影,最终高度结果是以cm为单位。_cos10()是用于计算余弦的函数。

其实这个算法个人感觉有可能不安全,当高度超过声纳阈值的时候会突然切换到气压计高度,中间的误差可能导致飞机猛烈晃动。可以考虑设置一个过渡区,根据飞机的高度对声纳/气压数据进行权重加和,权重根据高度自动调节。

Arduino库中的cos函数开销太大,经过测试,使用原装cos函数,MWC较难在2.8ms内跑完一个周期(计算周期可以在GUI的Cycle Time查看,2.4版本大部分时候都在2800us,略有跳动)。

所以需要自己编写快速余弦函数:

// 2015.11.30 by XD, x is in 0.1 deg, returns cos * (1 << 10)
// when x approaches zero, cos(x) = 1 - x ^ 2 / 2, x is in radians
// https://en.wikipedia.org/wiki/Small-angle_approximation
// rad = deg / 180 * PI
int32_t _cos10(int16_t x)
{
// x within [-1800, 1800]
int32_t radTemp = (int32_t)x * ; // rad = x * ((PI / 1800) << 16), rad within [-205200, 205200]
int32_t rad = radTemp >> ; // rad ^ 2 within [-657922500, 657922500] int32_t cos20 = ((uint32_t) << ) - ((rad * rad) >> );
int32_t result = cos20 >> ; return result;
}

使用的算法和MWC IMU差不多,在小角度的条件下计算三角函数的级数展开式前两项。对于cos来说:cos(x) = 1 - x ^ 2 / 2,x是弧度单位。

由于Arduino没有浮点单元和硬件除法器,需要尽量避免这两种运算,这里直接手工计算PI / 1800并扩大65536倍(1 << 16)。

在求平方的时候,为了保证计算不越界,需要预先进行移位操作。

计算完成后将结果右移,最终保持10位2进制有效数字,差不多相当于4位10进制有效数字。最终结果相当于余弦乘以1024。

在前面的数据融合代码中,我们也可以看到移位操作,就是用来避免计算越界,以及把余弦多乘的1024除回来。

最后在四轴上实际测试的效果不错,Cycle Time没有明显变化,算出来的高度还是很准的,准备周末去实地飞行了。

05-06 16:56