Madgwick IMU Filter Help

Hi, I am currently working on modifying the IMU source file for a drone flight controller because I am using a teensy 3.5 instead of an Arduino and I am using a different IMU. I am having the issue that when I rotate the IMU 90 degrees the yaw only goes to 5.494.

Now I could just scale the yaw by 90/5.494, but I feel like I would be losing a lot of resolution. :frowning:

If anyone sees an issue with the program any help would be greatly appreciated.

If I missed anything that would help, let me know and I will provide it as promptly as possible.

Here is the modified version of the code.

#include "MPU9250.h"
#include "Arduino.h"
#include "i2c_t3.h"
#include "Config1.h"

MPU9250 IMU(Wire, 0x68);    // an MPU9250 object with the MPU-9250 sensor on I2C bus 0 with address 0x68

const int MPU = 0x68;             // I2C address of the MPU-6050
unsigned int i2cErrorCnt = 0;     // running count of i2c errors

float gyroOff[3] = {0, 0, 0};   // calibration results
float accOff[3] = {0, 0, 0};    // calibration resluts
float Acc[3], Gyro[3];          // raw IMU data
float Tmp;                      // raw temperature data
float IMURoll, IMUPitch, IMUHead; // cooked IMU data
float   throttleCorrection;       // boost throttle for roll/pitch angle

//--- vars from Madgwick
#define gyroMeasError 3.14159265358979f * (5.0f / 180.0f)     // gyroscope measurement error in rad/s (shown as 5 deg/s)
#define beta sqrt(3.0f / 4.0f) * gyroMeasError                // beta is the gain between gyro and accel
float a_x, a_y, a_z;                                          // accelerometer measurements
float w_x, w_y, w_z;                                          // gyroscope measurements in rad/s
float SEq_1 = 1.0f, SEq_2 = 0.0f, SEq_3 = 0.0f, SEq_4 = 0.0f; // estimated orientation quaternion elements with initial conditions

// readIMU -- read available IMU bytes and process if full message
uint8_t imuBufPos = 0;
uint8_t imuBuf[14];

//CalibrateGyroAccel
#define ACC_1G (2048)


void requestIMU()       // read gyro and accel
{
  IMU.readSensor();
}
void requestGyro()      // read gyro only
{
  IMU.readSensor();
}
void readIMU()          // read available IMU bytes and process if full message
{

  requestIMU();

  Acc[0] = IMU.getAccelX_mss();
  Acc[1] = IMU.getAccelY_mss();
  Acc[2] = IMU.getAccelZ_mss();
  Tmp = IMU.getTemperature_C(); 
  Gyro[0] = IMU.getGyroX_rads();
  Gyro[1] = IMU.getGyroY_rads(); 
  Gyro[2] = IMU.getGyroZ_rads();     

}
void calibrateGyroAcc()
{
  // --- calibrate gyro by repeatedly taking the average of blocks of readings
  //     until the XY vibration is low enough
#define CALIBBLOCKSIZE (512)
  bool calibGood = false;
  float gyroSum[3] = {0, 0, 0};
  float accSum[3] = {0, 0, 0};
  float vibSum;
  delay(5000);
  //  LEDPIN_ON;
  while (!calibGood)
  {
    // clear stuff and wait a second
    for (int i = 0; i < 3; i++)
    {
      gyroSum[i] = 0;
      accSum[i] = 0;
    }
    vibSum = 0;
    delay(1000);

    // read a block of acc/gyro data, computing horizontal vibration
    readIMU();
    for (int i = 0; i < CALIBBLOCKSIZE; i++)
    {
      delay(3);
      readIMU();
      for (int j = 0; j < 3; j++)
      {
        gyroSum[j] += Gyro[j];
        accSum[j] += Acc[j];
      }
      vibSum = abs(a_x + accOff[0]) + abs(a_y + accOff[1]);// + abs(a_z - accOff[2]);
    }
    for (int j = 0; j < 3; j++)
    {
      gyroOff[j] = -(gyroSum[j] / CALIBBLOCKSIZE);
      accOff[j]  = -(accSum[j]  / CALIBBLOCKSIZE);
    }
    calibGood = vibSum < 1.0f;
  }
  accOff[2] += ACC_1G;
  //  LEDPIN_OFF;
}
void initIMU()          // setup the IMU modules and do calibration
{

  IMU.begin();

  Wire.setClock(400000);
  //TWBR = ((F_CPU / 400000) - 16) / 2;          // set the I2C clock rate to 400kHz


  IMU.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_184HZ);
  IMU.setGyroRange(MPU9250::GYRO_RANGE_2000DPS);         //Wire.write(0x1b); Wire.write(0x18);    // FS_SEL = 3: Full scale set to 2000 deg/sec
  IMU.setAccelRange(MPU9250::ACCEL_RANGE_8G);            // Wire.write(0x1c); Wire.write(0x10);    // ACCEL_CONFIG  -- AFS_SEL=2 (Full Scale = +/-8G)  ; ACCELL_HPF=0   //note something is wrong in the spec.
  Wire.endTransmission(true);
  IMU.setSrd(19);    // setting SRD to 19 for a 50 Hz update rate

  requestIMU();

  calibrateGyroAcc();
}

#define gyro2radpersec  (2000.0f / 32768.0f)  // scale raw gyro @2000 deg/sec full scale to rad/sec (removed 3.1415926/180 becasue it is already in rad/s
float rad2deg = (180.0f / 3.1415926f);

void calcIMU()
{
    a_x = (float)Acc[0] + accOff[0];
    a_y = (float)Acc[1] + accOff[1];
    a_z = (float)Acc[2] + accOff[2];
    w_x = ((float)Gyro[0] + gyroOff[0]) * gyro2radpersec;
    w_y = ((float)Gyro[1] + gyroOff[1]) * gyro2radpersec;
    w_z = ((float)Gyro[2] + gyroOff[2]) * gyro2radpersec;
    
    
    //--- Madgwick code ---
    // Local system variables
    float norm;                                                           // vector norm denominator
    float SEqDot_omega_1, SEqDot_omega_2, SEqDot_omega_3, SEqDot_omega_4; // quaternion derrivative from gyroscopes elements
    float f_1, f_2, f_3;                                                  // objective function elements
    float J_11or24, J_12or23, J_13or22, J_14or21, J_32, J_33;             // objective function Jacobian elements
    float SEqHatDot_1, SEqHatDot_2, SEqHatDot_3, SEqHatDot_4;             // estimated direction of the gyroscope error
    // Axulirary variables to avoid reapeated calcualtions
    float halfSEq_1 = 0.5f * SEq_1;
    float halfSEq_2 = 0.5f * SEq_2;
    float halfSEq_3 = 0.5f * SEq_3;
    float halfSEq_4 = 0.5f * SEq_4;
    float twoSEq_1 = 2.0f * SEq_1;
    float twoSEq_2 = 2.0f * SEq_2;
    float twoSEq_3 = 2.0f * SEq_3;
    // Normalise the accelerometer measurement
    norm = sqrt(a_x * a_x + a_y * a_y + a_z * a_z);
    a_x /= norm;
    a_y /= norm;
    a_z /= norm;
    throttleCorrection = a_z;

    // Compute the quaternion derrivative measured by gyroscopes
    SEqDot_omega_1 = -halfSEq_2 * w_x - halfSEq_3 * w_y - halfSEq_4 * w_z;
    SEqDot_omega_2 = halfSEq_1 * w_x + halfSEq_3 * w_z - halfSEq_4 * w_y;
    SEqDot_omega_3 = halfSEq_1 * w_y - halfSEq_2 * w_z + halfSEq_4 * w_x;
    SEqDot_omega_4 = halfSEq_1 * w_z + halfSEq_2 * w_y - halfSEq_3 * w_x;
    // only apply accels if .75 < g < 1.25
    if ((0.75f < a_z) && (a_z < 1.25f))
    {
      // Compute the objective function and Jacobian
      f_1 = twoSEq_2 * SEq_4 - twoSEq_1 * SEq_3 - a_x;
      f_2 = twoSEq_1 * SEq_2 + twoSEq_3 * SEq_4 - a_y;
      f_3 = 1.0f - twoSEq_2 * SEq_2 - twoSEq_3 * SEq_3 - a_z;
      J_11or24 = twoSEq_3; // J_11 negated in matrix multiplication
      J_12or23 = 2.0f * SEq_4;
      J_13or22 = twoSEq_1; // J_12 negated in matrix multiplication
      J_14or21 = twoSEq_2;
      J_32 = 2.0f * J_14or21; // negated in matrix multiplication
      J_33 = 2.0f * J_11or24; // negated in matrix multiplication
      // Compute the gradient (matrix multiplication)
      SEqHatDot_1 = J_14or21 * f_2 - J_11or24 * f_1;
      SEqHatDot_2 = J_12or23 * f_1 + J_13or22 * f_2 - J_32 * f_3;
      SEqHatDot_3 = J_12or23 * f_2 - J_33 * f_3 - J_13or22 * f_1;
      SEqHatDot_4 = J_14or21 * f_1 + J_11or24 * f_2;
      // Normalise the gradient
      norm = sqrt(SEqHatDot_1 * SEqHatDot_1 + SEqHatDot_2 * SEqHatDot_2 + SEqHatDot_3 * SEqHatDot_3 + SEqHatDot_4 * SEqHatDot_4);
      SEqHatDot_1 /= norm;
      SEqHatDot_2 /= norm;
      SEqHatDot_3 /= norm;
      SEqHatDot_4 /= norm;

      // Compute then integrate the estimated quaternion derrivative
      SEq_1 += (SEqDot_omega_1 - (beta * SEqHatDot_1)) * deltat;
      SEq_2 += (SEqDot_omega_2 - (beta * SEqHatDot_2)) * deltat;
      SEq_3 += (SEqDot_omega_3 - (beta * SEqHatDot_3)) * deltat;
      SEq_4 += (SEqDot_omega_4 - (beta * SEqHatDot_4)) * deltat;
	}
    else
    {
      // Compute then integrate the estimated quaternion derrivative
      // with no accel

      SEq_1 += (SEqDot_omega_1) * deltat;
      SEq_2 += (SEqDot_omega_2) * deltat;
      SEq_3 += (SEqDot_omega_3) * deltat;
      SEq_4 += (SEqDot_omega_4) * deltat;
    }
	
    // Normalise quaternion
    norm = sqrt(SEq_1 * SEq_1 + SEq_2 * SEq_2 + SEq_3 * SEq_3 + SEq_4 * SEq_4);
    SEq_1 /= norm;
    SEq_2 /= norm;
    SEq_3 /= norm;
    SEq_4 /= norm;
    //--- end Madgwick code ---

    IMUPitch = -rad2deg * atan2(2.0*(SEq_3*SEq_4 + SEq_1*SEq_2), SEq_1*SEq_1 - SEq_2*SEq_2 - SEq_3*SEq_3 + SEq_4*SEq_4);
    IMURoll = rad2deg * asin(-2.0*(SEq_2*SEq_4 - SEq_1*SEq_3));
    IMUHead = -rad2deg * atan2(2.0*(SEq_2*SEq_3 + SEq_1*SEq_4), SEq_1*SEq_1 + SEq_2*SEq_2 - SEq_3*SEq_3 - SEq_4*SEq_4);


}

Have you performed full calibration of the magnetometer? That's required for any MEMS magnetometer.
If there is any magnet (other than the earth itself) interfering with the sensor then you won't be able to
get meaningful long-term yaw values.

You should be getting reasonable accurate short-term yaw values from the gyro though.

BTW 90:5.4 is suspiciously close to 16:1 ratio...

You may have made a copy/paste error, or inappropriately initialized the sensor, for example to a rate gyro range that does not match the expectation in the code. That code does not use the MPU9250 magnetometer, so even if the gyro is set correctly, there is no yaw reference and the reported value will drift with time.

Consider using state of the art code that does use the magnetometer (being sure to calibrate it first, as mentioned above). RTIMUlib should run out of the box, after selecting the MPU9250 sensor, and includes a primitive magnetometer calibration routine.

A tutorial describing a better magnetometer calibration procedure can be found here and a brief summary of my initial tests of RTIMUlib is here.

1 Like

This define and comment makes no sense. It is a scale factor of about 1/16.

#define gyro2radpersec  (2000.0f / 32768.0f)  // scale raw gyro @2000 deg/sec full scale to rad/sec (removed 3.1415926/180 becasue it is already in rad/s

Suggest you replace that constant with 1.0 and retest.

1 Like