现在四轴炙手可热。由于之前对航模比较感兴趣,因此自然而然对四轴也比较感兴趣。我对四轴了解不多,因此这一系列博客将是个循序渐进的过程。
博客将包括:对四轴原理的理解+算法的研究+对MWC算法的解读与修改。 (MWC程序解读以MultiWii_1_8版本为主,因为版本越高,外设越多,代码越多。我们先只看核心代码。)
关于PPM信号可参考:
http://www.geek-workshop.com/thread-2408-1-1.html
http://diydrones.com/profiles/blogs/705844:BlogPost:38393
http://blog.sina.com.cn/s/blog_658fcbde010155zo.html
注:此图为发射PPM,接收信号高低电平与发射相反。
注:此图为接收PPM。可参照解码。
类似于舵机的控制,每一帧为20ms,再将20ms划分为每2ms一小帧,则一共有10个小帧,也即10个channel。但由于需要加入同步帧,则最多有9个channel。
每一channel有2ms,这2ms由固定的0.5ms再加上可调节的0.5ms~1.5ms构成。
原理清楚了,则可以使用单片机进行解码,为了方便,使用arduino。
下面是MWC的解码代码分析。
MWC接收解码在RX文件中进行,MWC支持三种接收方式:
1. (默认)一般PPM信号(也就是接收机对PPM信号帧处理后分到各个接口上去的),一般接收机都是这种。
2. 串口PPM信号,也就是没对信号帧分解的原始信号,例如上面代码解码的就是。
3. SPEKTRUM信号。
假设我们以Arduino pro mini为核心板制作四轴,并且使用SERIAL_SUM_PPM方式接收(例如蓝牙)。黑色为保留的代码,红色为可删除的多余代码。
RX文件代码:
static uint8_t pinRcChannel[8] = {ROLLPIN, PITCHPIN, YAWPIN, THROTTLEPIN, AUX1PIN,AUX2PIN,CAM1PIN,CAM2PIN};
volatile uint16_t rcPinValue[8] = {1500,1500,1500,1500,1500,1500,1500,1500}; // interval [1000;2000]
static int16_t rcData4Values[8][4]; //滤波时候使用,存储最近次channel值。
static int16_t rcDataMean[8] ;
// ***PPM SUM SIGNAL***
#ifdef SERIAL_SUM_PPM
static uint8_t rcChannel[8] = {SERIAL_SUM_PPM};//SERIAL_SUM_PPM在config文件中定义,为接收模式定义
#endif
volatile uint16_t rcValue[8] = {1500,1500,1500,1500,1500,1500,1500,1500}; // interval [1000;2000]
// Configure each rc pin for PCINT
void configureReceiver() {
#if !defined(SERIAL_SUM_PPM) && !defined(SPEKTRUM)
for (uint8_t chan = 0; chan < 8; chan++)
for (uint8_t a = 0; a < 4; a++)
rcData4Values[chan][a] = 1500; //we initiate the default value of each channel. If there is no RC receiver connected, we will see those values
#if defined(PROMINI)
// PCINT activated only for specific pin inside [D0-D7] , [D2 D4 D5 D6 D7] for this multicopter
PORTD = (1<<2) | (1<<4) | (1<<5) | (1<<6) | (1<<7); //enable internal pull ups on the PINs of PORTD (no high impedence PINs)
PCMSK2 |= (1<<2) | (1<<4) | (1<<5) | (1<<6) | (1<<7);
PCICR = 1<<2; // PCINT activated only for the port dealing with [D0-D7] PINs
#endif
#if defined(MEGA)
// PCINT activated only for specific pin inside [A8-A15]
DDRK = 0; // defined PORTK as a digital port ([A8-A15] are consired as digital PINs and not analogical)
PORTK = (1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6) | (1<<7); //enable internal pull ups on the PINs of PORTK
PCMSK2 |= (1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6) | (1<<7);
PCICR = 1<<2; // PCINT activated only for PORTK dealing with [A8-A15] PINs
#endif
#endif
#if defined(SERIAL_SUM_PPM)
PPM_PIN_INTERRUPT //在def文件中定义,为attachInterrupt(0, rxInt, RISING); //PIN 0设置0口中断,产生中断时触发rxInt()函数。
#endif
#if defined (SPEKTRUM)
#endif
}
#if !defined(SERIAL_SUM_PPM) && !defined(SPEKTRUM)
ISR(PCINT2_vect) { //this ISR is common to every receiver channel, it is call everytime a change state occurs on a digital pin [D2-D7]
uint8_t mask;
uint8_t pin;
uint16_t cTime,dTime;
static uint16_t edgeTime[8];
static uint8_t PCintLast;
#if defined(PROMINI)
pin = PIND; // PIND indicates the state of each PIN for the arduino port dealing with [D0-D7] digital pins (8 bits variable)
#endif
#if defined(MEGA)
pin = PINK; // PINK indicates the state of each PIN for the arduino port dealing with [A8-A15] digital pins (8 bits variable)
#endif
mask = pin ^ PCintLast; // doing a ^ between the current interruption and the last one indicates wich pin changed
sei(); // re enable other interrupts at this point, the rest of this interrupt is not so time critical and can be interrupted safely
PCintLast = pin; // we memorize the current state of all PINs [D0-D7]
cTime = micros(); // micros() return a uint32_t, but it is not usefull to keep the whole bits => we keep only 16 bits
// mask is pins [D0-D7] that have changed // the principle is the same on the MEGA for PORTK and [A8-A15] PINs
// chan = pin sequence of the port. chan begins at D2 and ends at D7
if (mask & 1<<2) //indicates the bit 2 of the arduino port [D0-D7], that is to say digital pin 2, if 1 => this pin has just changed
if (!(pin & 1<<2)) { //indicates if the bit 2 of the arduino port [D0-D7] is not at a high state (so that we match here only descending PPM pulse)
dTime = cTime-edgeTime[2]; if (900<dTime && dTime<2200) rcPinValue[2] = dTime; // just a verification: the value must be in the range [1000;2000] + some margin
} else edgeTime[2] = cTime; // if the bit 2 of the arduino port [D0-D7] is at a high state (ascending PPM pulse), we memorize the time
if (mask & 1<<4) //same principle for other channels // avoiding a for() is more than twice faster, and it's important to minimize execution time in ISR
if (!(pin & 1<<4)) {
dTime = cTime-edgeTime[4]; if (900<dTime && dTime<2200) rcPinValue[4] = dTime;
} else edgeTime[4] = cTime;
if (mask & 1<<5)
if (!(pin & 1<<5)) {
dTime = cTime-edgeTime[5]; if (900<dTime && dTime<2200) rcPinValue[5] = dTime;
} else edgeTime[5] = cTime;
if (mask & 1<<6)
if (!(pin & 1<<6)) {
dTime = cTime-edgeTime[6]; if (900<dTime && dTime<2200) rcPinValue[6] = dTime;
} else edgeTime[6] = cTime;
if (mask & 1<<7)
if (!(pin & 1<<7)) {
dTime = cTime-edgeTime[7]; if (900<dTime && dTime<2200) rcPinValue[7] = dTime;
} else edgeTime[7] = cTime;
#if defined(MEGA)
if (mask & 1<<0)
if (!(pin & 1<<0)) {
dTime = cTime-edgeTime[0]; if (900<dTime && dTime<2200) rcPinValue[0] = dTime;
} else edgeTime[0] = cTime;
if (mask & 1<<1)
if (!(pin & 1<<1)) {
dTime = cTime-edgeTime[1]; if (900<dTime && dTime<2200) rcPinValue[1] = dTime;
} else edgeTime[1] = cTime;
if (mask & 1<<3)
if (!(pin & 1<<3)) {
dTime = cTime-edgeTime[3]; if (900<dTime && dTime<2200) rcPinValue[3] = dTime;
} else edgeTime[3] = cTime;
#endif
#if defined(FAILSAFE)
if (mask & 1<<THROTTLEPIN) { // If pulse present on THROTTLE pin (independent from ardu version), clear FailSafe counter - added by MIS
if(failsafeCnt > 20) failsafeCnt -= 20; else failsafeCnt = 0; }
#endif
}
#endif
#if defined(SERIAL_SUM_PPM)
//接收,将每个channel数据存到对应的rcValue[channel]中。
void rxInt() {
uint16_t now,diff;
static uint16_t last = 0;
static uint8_t chan = 0;
now = micros();
diff = now - last; //获得每个channel时间
last = now;
if(diff>3000) chan = 0; //接收到同步帧
else {
if(900<diff && diff<2200 && chan<8 ) { //Only if the signal is between these values it is valid, otherwise the failsafe counter should move up 1ms~2ms
rcValue[chan] = diff;
#if defined(FAILSAFE)//掉电保护
if(failsafeCnt > 20) failsafeCnt -= 20; else failsafeCnt = 0; // clear FailSafe counter - added by MIS //incompatible to quadroppm
#endif
}
chan++;
}
}
#endif
#if defined(SPEKTRUM)
#endif
//为了准确,在关闭中断的时候读取接收的chan信道数据rcValue[chan]
uint16_t readRawRC(uint8_t chan) {
uint16_t data;
uint8_t oldSREG;
oldSREG = SREG;
cli(); // Let's disable interrupts
#ifndef SERIAL_SUM_PPM
data = rcPinValue[pinRcChannel[chan]]; // Let's copy the data Atomically
#else
data = rcValue[rcChannel[chan]];
#endif
SREG = oldSREG;
sei();// Let's enable the interrupts
#if defined(PROMINI) && !defined(SERIAL_SUM_PPM)
if (chan>4) return 1500;
#endif
return data; // We return the value correctly copied when the IRQ's where disabled
}
//对数据进行滤波计算操作
void computeRC() {
static uint8_t rc4ValuesIndex = 0;
uint8_t chan,a;
rc4ValuesIndex++;
for (chan = 0; chan < 8; chan++) {
rcData4Values[chan][rc4ValuesIndex%4] = readRawRC(chan);//rcData4Values[chan][]保存最近四次chan信道的数据
rcDataMean[chan] = 0;
for (a=0;a<4;a++) rcDataMean[chan] += rcData4Values[chan][a];
rcDataMean[chan]= (rcDataMean[chan]+2)/4;//求取最近4次chan信道值的平均值
if ( rcDataMean[chan] < rcData[chan] -3) rcData[chan] = rcDataMean[chan]+2;
if ( rcDataMean[chan] > rcData[chan] +3) rcData[chan] = rcDataMean[chan]-2;//并没有直接将rcDataMean[chan]赋值给rcData[chan],而是慢慢修正rcData[chan]的值,稳定性更高。rcData[8]在主文件里面定义。
}
}
流程图就不画了,描述下吧:
rxInt() 接收PPM帧信号,将每个信道的数据存储到rcValue[chan]里面,之后可以用readRawRC(chan)函数在关闭中断时候准确读出每个信道的值,再用computeRC()函数对每个信道取最近4次值,之后计算平均值,并依此修正 rcData[chan]。可见rcData[chan]为我们最终要修改的值,也是后面的控制算法使用的值。
可参照此图理解上面的中断解码。
如有错误请指正,我们共同进步。
对MWC重要函数的理解
目录
一、原文件的理解
二、我对main loop()主程序的分析
三、conputeIMU函数以及它调用的估计姿态函数getEstimatedAttitude()函数的分析
四、旋转矩阵求坐标解析 五、计算俯仰角度的依据文件
2
一、原文件的理解
void computeIMU () { uint8_t axis;
static int16_t gyroADCprevious[3] = {0,0,0};
int16_t gyroADCp[3];
int16_t gyroADCinter[3];
static uint32_t timeInterleave = 0;
//we separate the 2 situations because reading gyro values with a gyro only setup can be acchieved at a higher rate
//gyro+nunchuk: we must wait for a quite high delay betwwen 2 reads to get both WM+ and Nunchuk data. It works with 3ms
//gyro only: the delay to read 2 consecutive values can be reduced to only 0.65ms
#if defined(NUNCHUCK)//定义NUNCHUCK(一种硬件设备)执行下面的
annexCode();
while((micros()-timeInterleave)<INTERLEAVING_DELAY) //interleaving delay between 2 consecutive reads
timeInterleave=micros();
ACC_getADC();
getEstimatedAttitude(); // computation time must last less than one interleaving delay
while((micros()-timeInterleave)<INTERLEAVING_DELAY) //interleaving delay between 2 consecutive reads
timeInterleave=micros();
f.NUNCHUKDATA = 1;
while(f.NUNCHUKDATA) ACC_getADC(); // For this interleaving reading, we must have a gyro update at this point (less delay)
for (axis = 0; axis < 3; axis++) {
// empirical, we take a weighted value of the current and the previous values
// /4 is to average 4 values, note: overflow is not possible for WMP gyro here
gyroData[axis] = (gyroADC[axis]*3+gyroADCprevious[axis])/4;
gyroADCprevious[axis] = gyroADC[axis]; }
#else
#if ACC//……………………………………………………….① ACC_getADC();//获得加计的ADC值
getEstimatedAttitude();//获取估计姿态
#endif
#if GYRO
Gyro_getADC();
#endif
for (axis = 0; axis < 3; axis++)
gyroADCp[axis] = gyroADC[axis];
timeInterleave=micros();
annexCode();//通过串口向电脑或GUI发送MWC的实时数据。
if ((micros()-timeInterleave)>650) {
annex650_overrun_count++;//运行时间超时 并进行计数
}
else {
while((micros()-timeInterleave)<650) //empirical, interleaving delay between 2 consecutive reads //当运行时间不足650ms时 依据验证 进行2次连续的读内部延时 }
#if GYRO
Gyro_getADC();
#endif
for (axis = 0; axis < 3; axis++) {//陀螺仪的值求平均
gyroADCinter[axis] = gyroADC[axis]+gyroADCp[axis];
// empirical, we take a weighted value of the current and the previous values
//通过试验获得前面的值和当前值的权重
gyroData[axis] = (gyroADCinter[axis]+gyroADCprevious[axis])/3;
gyroADCprevious[axis] = gyroADCinter[axis]/2;
if (!ACC) accADC[axis]=0; }
#endif
#if defined(GYRO_SMOOTHING)
static int16_t gyroSmooth[3] = {0,0,0};//静态变量设置 第一次运行时进行初始化,以后保留前次值再更新。
for (axis = 0; axis < 3; axis++) {
gyroData[axis] = (int16_t) ( ( (int32_t)((int32_t)gyroSmooth[axis] * (conf.Smoothing[axis]-1) )+gyroData[axis]+1 ) / conf.Smoothing[axis]);//陀螺仪数据平滑滤波 gyroSmooth[axis] = gyroData[axis]; }
#elif defined(TRI)//假如定义为三脚机
static int16_t gyroYawSmooth = 0;
gyroData[YAW] = (gyroYawSmooth*2+gyroData[YAW])/3;
gyroYawSmooth = gyroData[YAW]; #endif }
// **************************************************
// Simplified IMU based on "Complementary Filter"//基于互补滤波简化的IMU计算 // Inspired by 创新来自于http://starlino.com/imu_guide.html //
// adapted by 改编来自于ziss_dm : http://www.multiwii.com/forum/viewtopic.php?f=8&t=198 //
// The following ideas was used in this project://后面的网站知识将要用在这个工程里面 // 1) Rotation matrix旋转矩阵:http://en.wikipedia.org/wiki/Rotation_matrix // 2) Small-angle approximation小角度近似算法
: http://en.wikipedia.org/wiki/Small-angle_approximation // 3) C. Hastings approximation for atan2() //antan2的近似算法 // 4) Optimization tricks优化:http://www.hackersdelight.org/ //
// Currently Magnetometer uses separate CF which is used only // for heading approximation.//磁力计 用于近似定向 //
IMU传感器数据处理(MWC)
IMU主要分为3大部分:
1. computeIMU ():提供给外的接口函数,也是传感器处理的总函数;
2. getEstimatedAttitude():获取估算的姿态,主要处理ACC、Gyro和Mag传感器数据;
3.getEstimatedAltitude():获取估算的高度,主要处理Baro传感器数据。
黑色为保留的代码,红色为可删除的多余代码。
/***************************************************************************************/
//1.
void computeIMU () {
uint8_t axis;
static int16_t gyroADCprevious[3] = {0,0,0};
int16_t gyroADCp[3];
int16_t gyroADCinter[3];
static int16_t lastAccADC[3] = {0,0,0};
static uint32_t timeInterleave = 0;
static int16_t gyroYawSmooth = 0;
//we separate the 2 situations because reading gyro values with a gyro only setup can be acchieved at a higher rate
//gyro+nunchuk: we must wait for a quite high delay betwwen 2 reads to get both WM+ and Nunchuk data. It works with 3ms
//gyro only: the delay to read 2 consecutive values can be reduced to only 0.65ms
if (!ACC && nunchuk) {
annexCode();
while((micros()-timeInterleave)<INTERLEAVING_DELAY) ; //interleaving delay between 2 consecutive reads
timeInterleave=micros();
WMP_getRawADC();
getEstimatedAttitude(); // computation time must last less than one interleaving delay
#if BARO
getEstimatedAltitude();
#endif
while((micros()-timeInterleave)<INTERLEAVING_DELAY) ; //interleaving delay between 2 consecutive reads
timeInterleave=micros();
while(WMP_getRawADC() != 1) ; // For this interleaving reading, we must have a gyro update at this point (less delay)
for (axis = 0; axis < 3; axis++) {
// empirical, we take a weighted value of the current and the previous values
// /4 is to average 4 values, note: overflow is not possible for WMP gyro here
gyroData[axis] = (gyroADC[axis]*3+gyroADCprevious[axis]+2)/4;
gyroADCprevious[axis] = gyroADC[axis];
}
} else {
if (ACC) {
ACC_getADC(); //有加速度传感器
getEstimatedAttitude(); //估算姿态
if (BARO) getEstimatedAltitude(); //有气压传感器,估算高度
}
if (GYRO) Gyro_getADC(); else WMP_getRawADC();
for (axis = 0; axis < 3; axis++)
gyroADCp[axis] = gyroADC[axis]; //将从Sensors传来的数据保存
timeInterleave=micros();
annexCode(); //此函数代码后续再做探讨
while((micros()-timeInterleave)<650) ; //empirical, interleaving delay between 2 consecutive reads //给annexCode(); 运行时间
if (GYRO) Gyro_getADC(); else WMP_getRawADC();
for (axis = 0; axis < 3; axis++) {
gyroADCinter[axis] = gyroADC[axis]+gyroADCp[axis];
// empirical, we take a weighted value of the current and the previous values
gyroData[axis] = (gyroADCinter[axis]+gyroADCprevious[axis]+1)/3; //对最近三次的Gyro数据取平均值
gyroADCprevious[axis] = gyroADCinter[axis]/2;
if (!ACC) accADC[axis]=0;
}
}
#if defined(TRI)
gyroData[YAW] = (gyroYawSmooth*2+gyroData[YAW]+1)/3;
gyroYawSmooth = gyroData[YAW];
#endif
}
/***************************************************************************************/
//2
// **************************************************
// Simplified IMU based on "Complementary Filter"
// Inspired by http://starlino.com/imu_guide.html
//
// adapted by ziss_dm : http://wbb.multiwii.com/viewtopic.php?f=8&t=198
//
// The following ideas was used in this project:
// 1) Rotation matrix: http://en.wikipedia.org/wiki/Rotation_matrix
// 2) Small-angle approximation: http://en.wikipedia.org/wiki/Small-angle_approximation
// 3) C. Hastings approximation for atan2()
// 4) Optimization tricks: http://www.hackersdelight.org/
//
// Currently Magnetometer uses separate CF which is used only
// for heading approximation.
//
// Last Modified: 19/04/2011
// Version: V1.1
// **************************************************
//****** advanced users settings *******************
/* Set the Low Pass Filter factor for ACC */
/* Increasing this value would reduce ACC noise (visible in GUI), but would increase ACC lag time*/
/* Comment this if you do not want filter at all.*/
/* Default WMC value: 8*/
#define ACC_LPF_FACTOR 8
/* Set the Low Pass Filter factor for Magnetometer */
/* Increasing this value would reduce Magnetometer noise (not visible in GUI), but would increase Magnetometer lag time*/
/* Comment this if you do not want filter at all.*/
/* Default WMC value: n/a*/
//#define MG_LPF_FACTOR 4
/* Set the Gyro Weight for Gyro/Acc complementary filter */
/* Increasing this value would reduce and delay Acc influence on the output of the filter*/
/* Default WMC value: 300*/
#define GYR_CMPF_FACTOR 310.0f
/* Set the Gyro Weight for Gyro/Magnetometer complementary filter */
/* Increasing this value would reduce and delay Magnetometer influence on the output of the filter*/
/* Default WMC value: n/a*/
#define GYR_CMPFM_FACTOR 200.0f
//****** end of advanced users settings *************
#define INV_GYR_CMPF_FACTOR (1.0f / (GYR_CMPF_FACTOR + 1.0f))
#define INV_GYR_CMPFM_FACTOR (1.0f / (GYR_CMPFM_FACTOR + 1.0f))
#if GYRO
#define GYRO_SCALE ((2000.0f * PI)/((32767.0f / 4.0f ) * 180.0f * 1000000.0f) * 1.155f)
// +-2000/sec deg scale
//#define GYRO_SCALE ((200.0f * PI)/((32768.0f / 5.0f / 4.0f ) * 180.0f * 1000000.0f) * 1.5f)
// +- 200/sec deg scale
// 1.5 is emperical, not sure what it means
// should be in rad/sec
#else
#define GYRO_SCALE (1.0f/200e6f)
// empirical, depends on WMP on IDG datasheet, tied of deg/ms sensibility
// !!!!should be adjusted to the rad/sec
#endif
// Small angle approximation
#define ssin(val) (val)
#define scos(val) 1.0f
typedef struct {
float X;
float Y;
float Z;
} t_fp_vector_def;
typedef union {
float A[3];
t_fp_vector_def V;
} t_fp_vector;
int16_t _atan2(float y, float x){ //坐标(x,y)与x轴形成的角度
#define fp_is_neg(val) ((((byte*)&val)[3] & 0x80) != 0)
float z = y / x;
int16_t zi = abs(int16_t(z * 100));
int8_t y_neg = fp_is_neg(y);
if ( zi < 100 ){
if (zi > 10)
z = z / (1.0f + 0.28f * z * z);
if (fp_is_neg(x)) {
if (y_neg) z -= PI;
else z += PI;
}
} else {
z = (PI / 2.0f) - z / (z * z + 0.28f);
if (y_neg) z -= PI;
}
z *= (180.0f / PI * 10);
return z;
}
void getEstimatedAttitude(){ //估计姿态
uint8_t axis;
uint16_t accLim, accMag = 0;
static t_fp_vector EstG = {0,0,300};
static t_fp_vector EstM = {0,0,300};
float scale, deltaGyroAngle;
static int16_t mgSmooth[3]; //projection of smoothed and normalized magnetic vector on x/y/z axis, as measured by magnetometer
static uint16_t previousT;
uint16_t currentT;
currentT = micros();
scale = (currentT - previousT) * GYRO_SCALE;
previousT = currentT;
// Initialization
for (axis = 0; axis < 3; axis++) {
#if defined(ACC_LPF_FACTOR)
// accSmooth[axis] = (accSmooth[axis] * (ACC_LPF_FACTOR - 1) + accADC[axis]) / ACC_LPF_FACTOR; // LPF for ACC values
accSmooth[axis] =(accSmooth[axis]*7+accADC[axis]+4)>>3;
#define ACC_VALUE accSmooth[axis]
#else
accSmooth[axis] = accADC[axis]; //对ACC数据进行滤波
#define ACC_VALUE accADC[axis]
#endif
accMag += (ACC_VALUE * 10 / acc_1G) * (ACC_VALUE * 10 / acc_1G); //788 // 加速度的数量级 ax*ax+ay*ay+az*az
#if MAG //对MAG进行滤波处理
#if defined(MG_LPF_FACTOR)
mgSmooth[axis] = (mgSmooth[axis] * (MG_LPF_FACTOR - 1) + magADC[axis]) / MG_LPF_FACTOR; // LPF for Magnetometer values
#define MAG_VALUE mgSmooth[axis]
#else
#define MAG_VALUE magADC[axis]
#endif
#endif
}
//根据Gyro数据算出angle[Roll] 和 angle[Pitch]
// Rotate Estimated vector(s), ROLL
deltaGyroAngle = gyroADC[ROLL] * scale;
EstG.V.Z = scos(deltaGyroAngle) * EstG.V.Z - ssin(deltaGyroAngle) * EstG.V.X;
EstG.V.X = ssin(deltaGyroAngle) * EstG.V.Z + scos(deltaGyroAngle) * EstG.V.X;
#if MAG
EstM.V.Z = scos(deltaGyroAngle) * EstM.V.Z - ssin(deltaGyroAngle) * EstM.V.X;
EstM.V.X = ssin(deltaGyroAngle) * EstM.V.Z + scos(deltaGyroAngle) * EstM.V.X;
#endif
// Rotate Estimated vector(s), PITCH
deltaGyroAngle = gyroADC[PITCH] * scale;
EstG.V.Y = scos(deltaGyroAngle) * EstG.V.Y + ssin(deltaGyroAngle) * EstG.V.Z;
EstG.V.Z = -ssin(deltaGyroAngle) * EstG.V.Y + scos(deltaGyroAngle) * EstG.V.Z;
#if MAG
EstM.V.Y = scos(deltaGyroAngle) * EstM.V.Y + ssin(deltaGyroAngle) * EstM.V.Z;
EstM.V.Z = -ssin(deltaGyroAngle) * EstM.V.Y + scos(deltaGyroAngle) * EstM.V.Z;
#endif
// Rotate Estimated vector(s), YAW
deltaGyroAngle = gyroADC[YAW] * scale;
EstG.V.X = scos(deltaGyroAngle) * EstG.V.X - ssin(deltaGyroAngle) * EstG.V.Y;
EstG.V.Y = ssin(deltaGyroAngle) * EstG.V.X + scos(deltaGyroAngle) * EstG.V.Y;
#if MAG
EstM.V.X = scos(deltaGyroAngle) * EstM.V.X - ssin(deltaGyroAngle) * EstM.V.Y;
EstM.V.Y = ssin(deltaGyroAngle) * EstM.V.X + scos(deltaGyroAngle) * EstM.V.Y;
#endif
//滤波,当加速度数量级不满足要求时候就舍弃数据
// Apply complimentary filter (Gyro drift correction)
// If accel magnitude >1.4G or <0.6G and ACC vector outside of the limit range => we neutralize the effect of accelerometers in the angle estimation.
// To do that, we just skip filter, as EstV already rotated by Gyro
accLim = acc_1G*4/10;
if ( ( 36 < accMag && accMag < 196 ) || ( abs(accSmooth[ROLL])<accLim && abs(accSmooth[PITCH])<accLim ) ) {
for (axis = 0; axis < 3; axis++)
EstG.A[axis] = (EstG.A[axis] * GYR_CMPF_FACTOR + ACC_VALUE) * INV_GYR_CMPF_FACTOR;
}
// Attitude of the estimated vector
angle[ROLL] = _atan2(EstG.V.X, EstG.V.Z) ;
angle[PITCH] = _atan2(EstG.V.Y, EstG.V.Z) ;
#if MAG //对磁场数据进行处理,定向功能
// Apply complimentary filter (Gyro drift correction)
for (axis = 0; axis < 3; axis++)
EstM.A[axis] = (EstM.A[axis] * GYR_CMPFM_FACTOR - MAG_VALUE) * INV_GYR_CMPFM_FACTOR;
// Attitude of the cross product vector GxM
heading = _atan2(EstG.V.Z * EstM.V.X - EstG.V.X * EstM.V.Z, EstG.V.Y * EstM.V.Z - EstG.V.Z * EstM.V.Y) / 10;
#endif
}
/***************************************************************************************/
//3.
float InvSqrt (float x){
union{
int32_t i;
float f;
} conv;
conv.f = x;
conv.i = 0x5f3759df - (conv.i >> 1);
return 0.5f * conv.f * (3.0f - x * conv.f * conv.f);
}
int32_t isq(int32_t x){return x * x;}
#define UPDATE_INTERVAL 25000 // 40hz update rate (20hz LPF on acc)
#define INIT_DELAY 4000000 // 4 sec initialization delay
#define Kp1 0.55f // PI observer velocity gain
#define Kp2 1.0f // PI observer position gain
#define Ki 0.001f // PI observer integral gain (bias cancellation)
#define dt (UPDATE_INTERVAL / 1000000.0f)
//对气压计数据进行处理,高度估算。
void getEstimatedAltitude(){
static uint8_t inited = 0;
static float AltErrorI = 0.0f;
static float AccScale = 0.0f;
static uint32_t DeadLine = INIT_DELAY;
float AltError;
float InstAcc = 0.0f;
float Delta;
if (currentTime < DeadLine) return;
DeadLine = currentTime + UPDATE_INTERVAL;
// Soft start
if (!inited) {
inited = 1;
EstAlt = BaroAlt;
EstVelocity = 0.0f;
AltErrorI = 0.0f;
AccScale = (9.80665f / acc_1G);
}
// Estimation Error
AltError = BaroAlt - EstAlt;
AltErrorI += AltError;
// Gravity vector correction and projection to the local Z
InstAcc = (accADC[YAW] * (1 - acc_1G * InvSqrt(isq(accADC[ROLL]) + isq(accADC[PITCH]) + isq(accADC[YAW])))) * AccScale + (Ki) * AltErrorI;
// Integrators
Delta = InstAcc * dt + (Kp1 * dt) * AltError;
EstAlt += ((EstVelocity + Delta * 0.5f) * dt + (Kp2 * dt) * AltError);
EstVelocity += Delta;
}
附图:
其实IMU程序写的还是有些不足之处的。
1. EstG.A[i]和EstM.A[i]用于ACC和MAG的数据融合和滤波,但其实根本没有起作用;
2.computeIMU其实应该在确定定义了GYRO传感器之后再进行姿态估计。
3. 。。。
总之,我分析的是MultiWii_1_8版本,高版本的代码更加完善与健壮,但大体思路应该相差不大。
Sensors 传感器操作(MWC)
MWC的传感器函数在Sensors文件中定义,该文件中主要实现对各种传感器的操作。
1. 主要支持:ACC(加速度计)、Gyro(陀螺仪)、Mag(电磁传感器)、Baro(气压传感器)
2. gyroADC[3],
accADC[3],
magADC[3],
BaroAlt //分别存放传感器读取的数据。
MWC的Sensors文件对传感器的操作可以简化为以下描述:
//ACC相关操作
void Acc_getADC () { //获取accADC[3]
}
void ACC_init() {
acc_1G = 256; //这是 加速度的精度,1g时候对应的数值。不同传感器数据不同。
}
//Gyro相关操作
void Gyro_getADC () {
}
void Gyro_init() {
}
//Mag相关操作
void Mag_getADC() {
}
void Mag_init() {
}
//Baro相关操作
void Baro_update() {
}
void Baro_init() {
}
void initSensors() {
delay(100);
if (ACC) ACC_init();
if (GYRO) Gyro_init();
if (MAG) Mag_init();
if (BARO) Baro_init();
}
附图形说明:
四轴——油门曲线(MWC)
接收的PPM信号为1000~2000ms,为了便于PID控制,需要将信号进行处理。
将ROLL/PITCH/YAW 变为 -500~500范围,将THROTTLE变为1000~2000范围。
MWC具体实现代码为:
1. 先定义lookup函数,用于映射,在EEPROM中;
2. 根据接收的PPM信号进行映射,处理到相应的数据范围。
具体代码如下:
为了更加直观的看出具体的曲线,使用Matlab对其绘制:
(1)lookup函数曲线:
红色为ROLL和PITCH的lookup曲线;
蓝色为THROTTLE的lookup曲线。
matlab代码为:
(2)使用lookup函数进行数据映射处理:
红色为Yaw的rcCommand曲线;蓝色为Roll和Pitch的rcCommand曲线;绿色的为THROTTLE曲线。
somehow... why the blue beyond it's ... 或许roll和pitch需要constrain一下。不管了,呵呵~
代码如下: