Spikes when using Mahony filter

I want to get roll pitch and yaw data from BMI270 and BMM150 sensors. I have a Nano 33 BLE Rev2 and these sensors are onboard it. I used adafruit ahrs library's mahony filter to remove noise. The data is accrate and noise free when the board is stationary. But when I tilt it a bit and make it stationary in some other position, there are huge spikes in values and only after about 2-3 seconds I get proper data again. I have already done calibration too.

Screenshot 2023-03-26 162344

Screenshot 2023-03-26 162254

#include "Adafruit_AHRS_Mahony.h"
#include "Adafruit_AHRS_Madgwick.h"
#include <Adafruit_AHRS_NXPFusion.h>
#include <Arduino_BMI270_BMM150.h>
#include <Wire.h>

// Mag calibration values are calculated via ahrs_calibration.
// These values must be determined for each baord/environment.
// See the image in this sketch folder for the values used
// below.

// Offsets applied to raw x/y/z mag values
float mag_offsets[3] = { -20.80F, 10.19F, -18.41 };

// Soft iron error compensation matrix
float mag_softiron_matrix[3][3] = { { 1.004, -0.006, 0.003 },
                                    { -0.006, 0.984, -0.004 },
                                    { 0.003, -0.004, 1.013 } };

float mag_field_strength = 44.75F;

// Offsets applied to compensate for gyro zero-drift error for x/y/z
// Raw values converted to rad/s based on 250dps sensitiviy (1 lsb = 0.00875 rad/s)
float rawToDPS = 0.00875F;
float dpsToRad = 0.017453293F;
/*float gyro_zero_offsets[3]      = { 175.0F * rawToDPS * dpsToRad,
                                   -729.0F * rawToDPS * dpsToRad,
                                    101.0F * rawToDPS * dpsToRad };
*/

// Mahony is lighter weight as a filter and should be used
// on slower systems
Adafruit_Mahony filter;
//Adafruit_Madgwick filter;
//Adafruit_NXPSensorFusion filter;

float accelX, accelY, accelZ,          // units m/s/s i.e. accelZ if often 9.8 (gravity)
  gyroX, gyroY, gyroZ,                 // units dps (degrees per second)
  gyroDriftX, gyroDriftY, gyroDriftZ,  // units dps
  magX, magY, magZ, mag_x, mag_y;

long lastInterval, lastTime, a;


void setup() {
  Serial.begin(9600);

  // Wait for the Serial Monitor to open (comment out to run without Serial Monitor)
  while (!Serial)
    ;
  Serial.println(F("Adafruit AHRS BLE Fusion Example"));

  IMU.begin();
  calibrateIMU(250, 250);
  lastTime = micros();
  // Filter expects 25 samples per second
  // The filter only seems to be responsive with a much smaller value, though!
  // ToDo: Figure out the disparity between actual refresh rate and the value
  // provided here!
  filter.begin(25);
}

void loop() {
  readIMU();
  unsigned long currentTime = micros();
  lastInterval = currentTime - lastTime;  // expecting this to be ~104Hz +- 4%
  lastTime = currentTime;
  // Update the filter
  filter.update(gyroX, gyroY, gyroZ,
                accelX, accelY, accelZ,
                magX, magY, magZ);

  // Print the orientation filter output
  if (millis() - a >= 250) {
    a += 250;
    float roll = filter.getRoll();
    float pitch = filter.getPitch();
    float heading = filter.getYaw();
    Serial.print(millis());
    Serial.print(" - Orientation: ");
    Serial.print(roll);
    Serial.print(" ");
    Serial.print(pitch);
    Serial.print(" ");
    Serial.println(heading);
    /*
    // Print the orientation filter output in quaternions.
    // This avoids the gimbal lock problem with Euler angles when you get
    // close to 180 degrees (causing the model to rotate or flip, etc.)
   float qw, qx, qy, qz;
    filter.getQuaternion(&qw, &qx, &qy, &qz);
    Serial.print(millis());
    Serial.print(" - Quat: ");
    Serial.print(qw);
    Serial.print(" ");
    Serial.print(qx);
    Serial.print(" ");
    Serial.print(qy);
    Serial.print(" ");
    Serial.println(qz);
 */
  }
}

void readIMU() {
  IMU.readAcceleration(accelX, accelY, accelZ);
  IMU.readGyroscope(gyroX, gyroY, gyroZ);
  IMU.readMagneticField(magX, magY, magZ);
}

void calibrateIMU(int delayMillis, int calibrationMillis) {

  int calibrationCount = 0;

  delay(delayMillis);  // to avoid shakes after pressing reset button

  float sumX, sumY, sumZ;
  int startTime = millis();
  while (millis() < startTime + calibrationMillis) {
      readIMU();
      // in an ideal world gyroX/Y/Z == 0, anything higher or lower represents drift
      sumX += gyroX;
      sumY += gyroY;
      sumZ += gyroZ;
      calibrationCount++;
  }

  if (calibrationCount == 0) {
    Serial.println("Failed to calibrate");
  }

  gyroDriftX = sumX / calibrationCount;
  gyroDriftY = sumY / calibrationCount;
  gyroDriftZ = sumZ / calibrationCount;
}
1 Like

Have you examined the input data?

I will check that again tomorrow and post the results as I don't have access to the board right now.

I suspect that the proportional gain constant is not correct (too high). Since that is Adafruit code, you should raise the issue on their Github site.

But first make certain that the gyro unit conversion factors are correct. The gyro contribution must be in radians/second and seems far too large. (The steady state filter output values are determined entirely by the magnetometer and the accelerometer).

Edit: Also make sure that the axis identification of the sensors agree and the axial system is right handed. The default axis assignments don't agree on many modern IMU modules, and that must be corrected or the filter output will be nonsense.

1 Like

Thanks for your help. The issue was that the library gyroscope expected data in rad/s but I was providing data in dps. I still have one confusion though, the examples in library state that data needs to be provided in dps as can be seen below:

Here is the final code:

#include "Adafruit_AHRS_Mahony.h"
#include "Adafruit_AHRS_Madgwick.h"
#include <Adafruit_AHRS_NXPFusion.h>
#include <Arduino_BMI270_BMM150.h>
#include <Wire.h>

class MyBoschSensor : public BoschSensorClass {

public:
  MyBoschSensor(TwoWire& wire = Wire)
    : BoschSensorClass(wire){};

protected:
  virtual int8_t configure_sensor(struct bmi2_dev* dev) {
    int8_t rslt;
    uint8_t sens_list[2] = { BMI2_ACCEL, BMI2_GYRO };

    struct bmi2_int_pin_config int_pin_cfg;
    int_pin_cfg.pin_type = BMI2_INT1;
    int_pin_cfg.int_latch = BMI2_INT_NON_LATCH;
    int_pin_cfg.pin_cfg[0].lvl = BMI2_INT_ACTIVE_HIGH;
    int_pin_cfg.pin_cfg[0].od = BMI2_INT_PUSH_PULL;
    int_pin_cfg.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE;
    int_pin_cfg.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE;

    struct bmi2_sens_config sens_cfg[2];
    sens_cfg[0].type = BMI2_ACCEL;
    sens_cfg[0].cfg.acc.bwp = BMI2_ACC_OSR2_AVG2;
    sens_cfg[0].cfg.acc.odr = BMI2_ACC_ODR_100HZ;
    sens_cfg[0].cfg.acc.filter_perf = BMI2_PERF_OPT_MODE;
    sens_cfg[0].cfg.acc.range = BMI2_ACC_RANGE_4G;
    sens_cfg[1].type = BMI2_GYRO;
    sens_cfg[1].cfg.gyr.filter_perf = BMI2_PERF_OPT_MODE;
    sens_cfg[1].cfg.gyr.bwp = BMI2_GYR_OSR2_MODE;
    sens_cfg[1].cfg.gyr.odr = BMI2_GYR_ODR_100HZ;
    sens_cfg[1].cfg.gyr.range = BMI2_GYR_RANGE_2000;
    sens_cfg[1].cfg.gyr.ois_range = BMI2_GYR_OIS_2000;

    rslt = bmi2_set_int_pin_config(&int_pin_cfg, dev);
    if (rslt != BMI2_OK)
      return rslt;

    rslt = bmi2_map_data_int(BMI2_DRDY_INT, BMI2_INT1, dev);
    if (rslt != BMI2_OK)
      return rslt;

    rslt = bmi2_set_sensor_config(sens_cfg, 2, dev);
    if (rslt != BMI2_OK)
      return rslt;

    rslt = bmi2_sensor_enable(sens_list, 2, dev);
    if (rslt != BMI2_OK)
      return rslt;

    return rslt;
  }
};

MyBoschSensor myIMU(Wire1);

// Mag calibration values are calculated via ahrs_calibration.
// These values must be determined for each baord/environment.
// See the image in this sketch folder for the values used
// below.

// Offsets applied to raw x/y/z mag values
float mag_offsets[3] = { -20.80F, 10.19F, -18.41 };

// Soft iron error compensation matrix
float mag_softiron_matrix[3][3] = { { 1.004, -0.006, 0.003 },
                                    { -0.006, 0.984, -0.004 },
                                    { 0.003, -0.004, 1.013 } };

float mag_field_strength = 44.75F;

// Offsets applied to compensate for gyro zero-drift error for x/y/z
// Raw values converted to rad/s based on 250dps sensitiviy (1 lsb = 0.00875 rad/s)
float rawToDPS = 0.00875F;
float dpsToRad = 0.017453293F;
float gyro_zero_offsets[3] = { 0.0F, 0.0F, 0.0F };


// Mahony is lighter weight as a filter and should be used
// on slower systems
Adafruit_Mahony filter;
//Adafruit_Madgwick filter;
//Adafruit_NXPSensorFusion filter;

float accelX, accelY, accelZ,          // units m/s/s i.e. accelZ if often 9.8 (gravity)
  gyroX, gyroY, gyroZ,                 // units dps (degrees per second)
  gyroDriftX, gyroDriftY, gyroDriftZ,  // units dps
  magX, magY, magZ, mag_x, mag_y;

long lastInterval, lastTime, a;


void setup() {
  Serial.begin(115200);

  // Wait for the Serial Monitor to open (comment out to run without Serial Monitor)
  while (!Serial)
    ;
  Serial.println(F("Adafruit AHRS BLE Fusion Example"));

  myIMU.begin();

  calibrateIMU(250, 250);
  lastTime = micros();
  // Filter expects 25 samples per second
  // The filter only seems to be responsive with a much smaller value, though!
  // ToDo: Figure out the disparity between actual refresh rate and the value
  // provided here!
  filter.begin(25);
}

void loop() {
  readIMU();
  unsigned long currentTime = micros();
  lastInterval = currentTime - lastTime;  // expecting this to be ~104Hz +- 4%
  lastTime = currentTime;

  // Apply mag offset compensation (base values in uTesla)
  float x = magX - mag_offsets[0];
  float y = magY - mag_offsets[1];
  float z = magZ - mag_offsets[2];

  // Apply mag soft iron error compensation
  float mx = x * mag_softiron_matrix[0][0] + y * mag_softiron_matrix[0][1] + z * mag_softiron_matrix[0][2];
  float my = x * mag_softiron_matrix[1][0] + y * mag_softiron_matrix[1][1] + z * mag_softiron_matrix[1][2];
  float mz = x * mag_softiron_matrix[2][0] + y * mag_softiron_matrix[2][1] + z * mag_softiron_matrix[2][2];

  // Apply gyro zero-rate error compensation
  float gx = gyroX - gyro_zero_offsets[0];
  float gy = gyroY - gyro_zero_offsets[1];
  float gz = gyroZ - gyro_zero_offsets[2];

  // Update the filter
  filter.update(gx * dpsToRad, gy * dpsToRad, gz * dpsToRad,
                accelX, accelY, accelZ,
                mx, my, mz);

  // Print the orientation filter output
  if (millis() - a >= 250) {
    a += 250;
    float roll = filter.getRoll();
    float pitch = filter.getPitch();
    float heading = filter.getYaw();
    Serial.print(millis());
    Serial.print(" - Orientation: ");
    Serial.print(roll);
    Serial.print(" ");
    Serial.print(pitch);
    Serial.print(" ");
    Serial.println(heading);
    /*
    // Print the orientation filter output in quaternions.
    // This avoids the gimbal lock problem with Euler angles when you get
    // close to 180 degrees (causing the model to rotate or flip, etc.)
   float qw, qx, qy, qz;
    filter.getQuaternion(&qw, &qx, &qy, &qz);
    Serial.print(millis());
    Serial.print(" - Quat: ");
    Serial.print(qw);
    Serial.print(" ");
    Serial.print(qx);
    Serial.print(" ");
    Serial.print(qy);
    Serial.print(" ");
    Serial.println(qz);
 */
  }
}

void readIMU() {
  myIMU.readAcceleration(accelX, accelY, accelZ);
  myIMU.readGyroscope(gyroX, gyroY, gyroZ);
  myIMU.readMagneticField(magX, magY, magZ);
}

void calibrateIMU(int delayMillis, int calibrationMillis) {

  int calibrationCount = 0;

  delay(delayMillis);  // to avoid shakes after pressing reset button

  float sumX, sumY, sumZ;
  int startTime = millis();
  while (millis() < startTime + calibrationMillis) {
    readIMU();
    // in an ideal world gyroX/Y/Z == 0, anything higher or lower represents drift
    sumX += gyroX;
    sumY += gyroY;
    sumZ += gyroZ;
    calibrationCount++;
  }

  if (calibrationCount == 0) {
    Serial.println("Failed to calibrate");
  }

  gyroDriftX = sumX / calibrationCount;
  gyroDriftY = sumY / calibrationCount;
  gyroDriftZ = sumZ / calibrationCount;
}
2 Likes

I dunno. Perhaps the comments are wrong and the examples were never properly tested.

The Mahony algorithm requires the gyro data in radians/sec. The library can require other units, but it must do the conversion somewhere, before presentation to the basic Mahony filter code. Which, as far as I know, has been written in C only once, by Sebastion Madgwick.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.