MPU6050 - Sensor resolution and sensitivity

Hello everyone,

I am using the MPU6050 gyro + accelerometer for a project and have experienced a lot of noise with the accelerometer. In order to reduce it by applying a filter on it, I came across a few questions that I hope you could help me with.

  1. I am using Jeff Rowberg's library and have read in the comment of the dmpGetLinearAccel() function that the accelerometer's range is +-2 g when using the DMP. By looking in the datasheet, it appears that the "sensibility scale factor" corresponding to a range of +-2 g is equal to 16'384 LSB/g (which makes sense if you considere that the variable used is an int16 => int16 / range = 65'536 / 4 g = 16'384 LSB/g). However, in the dmpGetLinearAccel() function, he removes gravity by multiply gravity's components by 8'192 ...? why 8'192 if 1g is equal to 16'384 ??
uint8_t MPU6050::dmpGetLinearAccel(VectorInt16 *v, VectorInt16 *vRaw, VectorFloat *gravity) {
    // get rid of the gravity component (+1g = +8192 in standard DMP FIFO packet, sensitivity is 2g)
    v -> x = vRaw -> x;// - gravity -> x*8192;
    v -> y = vRaw -> y;// - gravity -> y*8192;
    v -> z = vRaw -> z;// - gravity -> z*8192;
    return 0;
}
  1. Also, I have been unable to change the range (for exemple have +- 4g) with the setFullScaleAccelRange() function, it doesn't have any effect. Does anyone know why ?

  2. To obtain the sensor's resolution (smallest value that can be read by the sensor), is it correct to say that it's equal to 1/sensibility factor = 1/16'384 = 0.06 mg/LSB ?

Thank you.

  1. No multiplication is done. The code in question is "commented out" (using the //).

  2. You forgot to post the relevant code and evidence of the suggested failure, so we can't guess what might be wrong.

  3. the resolution is 1 bit, or 1/(sensitivity factor)

To check the sensitivity or range setting, print out the RAW acceleration values from the sensor, while it is sitting still on a table. Any sensor axis pointing straight up or down reports exactly 1 g acceleration.

Hello,

Thank you for your reply.

jremington:

  1. No multiplication is done. The code in question is “commented out” (using the //).

Yes, sorry ! I commented the code myself and forgot to uncomment it when I posted it here, but in the original code it is NOT commented.

jremington:
2) You forgot to post the relevant code and evidence of the suggested failure, so we can’t guess what might be wrong.

To check the sensitivity or range setting, print out the RAW acceleration values from the sensor, while it is sitting still on a table. Any sensor axis pointing straight up or down reports exactly 1 g acceleration.

Here is the code I was mentionning :

In the void setup (), the below initialization is called :

void MPU6050::initialize() {
    setClockSource(MPU6050_CLOCK_PLL_XGYRO);
    setFullScaleGyroRange(MPU6050_GYRO_FS_250);
    setFullScaleAccelRange(MPU6050_ACCEL_FS_2);
    setSleepEnabled(false); // thanks to Jack Elston for pointing this one out!
}

It calls the setFullScaleAccelRange(MPU6050_ACCEL_FS_2) function (with the following definition #define MPU6050_ACCEL_FS_2 0x00). This function is coded as shown below.

void MPU6050::setFullScaleAccelRange(uint8_t range) {
    I2Cdev::writeBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH, range);
}

I am not sure to understand very well this function, so I trusted it to do the work correctly… Anyway, if I look in the Datasheet, the range is ± 2g when AFS_SEL = 0 (see attached printscreen). However, if I print out the raw acceleration values with AFS_SEL = 0 I get :

08:48:04.604 → XYZ Accel : 0.00,0.00,8195.00

As you can see, 1g corresponds to approx. 8195, which corresponds to a sensitivity of ± 4g according to the Datasheet (see attached).

Now if I change the Sensitivity to ± 16g, it doesnt change anything! Code :

setFullScaleAccelRange(MPU6050_ACCEL_FS_16); (with MPU6050_ACCEL_FS_16 being defined as #define MPU6050_ACCEL_FS_16 0x03)

Result :

08:54:43.169 → XYZ Accel : 0.00,0.00,8196.00

As you can see, the same factor of ~8195 is still here…

So as I said in my initial post, there seems to be no way of changing the sensitivity range, and I saw a post of Jeff Rowberg saying that he believs it is not possible to change it when using the DMP functions. But I do not understand why and where it does not work…

Reply of Jeff Rowberg a few years ago on a similar topic :

Re: MPU6050 Acceleration meter range
#2
Feb 26, 2013, 10:48 pm
It looks like you’re calling mpu.initialize() after you set the full-scale accel range, which will reset it back to 2g.

Additionally, I am not sure you can count on using any particular range if you then go on to use the DMP, which is a finely tuned algorithm which probably depends on a specific range value. The mpu.dmpInitialize() method does a whole lot with the MPU, including some sensitivity settings. Changing the sensitivity probably only matters if you are using raw readings.

Is there anything I can do about this sensitivity factor ?

Is there anything I can do about this sensitivity factor ?

Of course. Use a different library, or program the device directly using I2C calls.

If you want to do motion tracking (the purpose of the DMP), there is no reason to use anything other than the highest sensitivity.

Mmhhh... The MPU6050's DMP provides motion fusion algorithms that reduces the errors inherent in each sensor. The DMP computes the results in terms of quaternions, and I won't be able to do that without a lot of research and work... and even then I would probably not be able to do something as accurate than what is done by the DMP algorithm...

There are open source quaternion-based filters for the MPU6050/9250 that work better than the DMP, but to modify the accelerometer sensitivity would probably require significant changes in the code, with no obvious advantage.

And again, you have given no reason to do so.

jremington:
There are open source quaternion-based filters for the MPU6050/9250 that work better than the DMP

Could you elaborate on what you mean by "better" ? In what way? Isn't it surprising that an open source code developped by some random people works better than what has been developped by a company ?

Isn’t it surprising that an open source code developped by some random people works better than what has been developped by a company ?

Not at all. The Bosch BNO055, is one of the poorest performing of the current AHRS sensors. Bosch hired some other company to produce the inferior filter software. Companies hire people of varying abilities.

Have you ever used Linux, developed by “some random people”? It runs most of the reliable servers in the world and ALL of the supercomputers, because Microsoft code is so bug ridden.

For the MPU-6050, try the code below, or for the MPU-9250 RTIMUlib for Arduino (by an industry professional); you will learn something in the process.

//
// MPU-6050 Mahony IMU  (yaw angle is relative to starting orientation)
// last update 7/9/2020 
//

#include "Wire.h"

// AD0 low = 0x68 (default for Sparkfun module)
// AD0 high = 0x69
int MPU_addr = 0x68;

// vvvvvvvvvvvvvvvvvv  VERY VERY IMPORTANT vvvvvvvvvvvvvvvvvvvvvvvvvvvvv
//These are the previously determined offsets and scale factors for accelerometer and gyro for
// a particular example of an MPU-6050. They are not correct for other examples.
//The IMU code will NOT work well or at all if these are not correct

float A_cal[6] = {265.0, -80.0, -700.0, 0.994, 1.000, 1.014}; // 0..2 offset xyz, 3..5 scale xyz
float G_off[3] = { -499.5, -17.7, -82.0}; //raw offsets, determined for gyro at rest
#define gscale ((250./32768.0)*(PI/180.0))  //gyro default 250 LSB per d/s -> rad/s

// ^^^^^^^^^^^^^^^^^^^ VERY VERY IMPORTANT ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// GLOBALLY DECLARED, required for Mahony filter
// vector to hold quaternion
float q[4] = {1.0, 0.0, 0.0, 0.0};

// Free parameters in the Mahony filter and fusion scheme,
// Kp for proportional feedback, Ki for integral
float Kp = 30.0;
float Ki = 0.0;

// with MPU-9250, angles start oscillating at Kp=40. Ki does not seem to help and is not required.
// with MPU-6050, some instability observed at Kp=100 Now set to 30.

// char s[60]; //snprintf buffer, if needed

// globals for AHRS loop timing
unsigned long now_ms, last_ms = 0; //millis() timers

// print interval
unsigned long print_ms = 200; //print angles every "print_ms" milliseconds
float yaw, pitch, roll; //Euler angle output

void setup() {

  Wire.begin();
  Serial.begin(9600);
  Serial.println("starting");

  // initialize sensor
  // defaults for gyro and accel sensitivity are 250 dps and +/- 2 g
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

}

// AHRS loop

void loop()
{
  static int i = 0;
  static float deltat = 0;  //loop time in seconds
  static unsigned long now = 0, last = 0; //micros() timers

  //raw data
  int16_t ax, ay, az;
  int16_t gx, gy, gz;
  int16_t Tmp; //temperature

  //scaled data as vector
  float Axyz[3];
  float Gxyz[3];


  Wire.beginTransmission(MPU_addr);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr, 14, true); // request a total of 14 registers
  int t = Wire.read() << 8;
  ax = t | Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  t = Wire.read() << 8;
  ay = t | Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  t = Wire.read() << 8;
  az = t | Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  t = Wire.read() << 8;
  Tmp = t | Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  t = Wire.read() << 8;
  gx = t | Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  t = Wire.read() << 8;
  gy = t | Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  t = Wire.read() << 8;
  gz = t | Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

  Axyz[0] = (float) ax;
  Axyz[1] = (float) ay;
  Axyz[2] = (float) az;

  //apply offsets and scale factors from Magneto
  for (int i = 0; i < 3; i++) Axyz[i] = (Axyz[i] - A_cal[i]) * A_cal[i + 3];

  Gxyz[0] = ((float) gx - G_off[0]) * gscale; //250 LSB(d/s) default to radians/s
  Gxyz[1] = ((float) gy - G_off[1]) * gscale;
  Gxyz[2] = ((float) gz - G_off[2]) * gscale;

  //  snprintf(s,sizeof(s),"mpu raw %d,%d,%d,%d,%d,%d",ax,ay,az,gx,gy,gz);
  //  Serial.println(s);

  now = micros();
  deltat = (now - last) * 1.0e-6; //seconds since last update
  last = now;

  Mahony_update(Axyz[0], Axyz[1], Axyz[2], Gxyz[0], Gxyz[1], Gxyz[2], deltat);

  // Compute Tait-Bryan angles.
  // http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles

  roll  = atan2((q[0] * q[1] + q[2] * q[3]), 0.5 - (q[1] * q[1] + q[2] * q[2]));
  pitch = asin(2.0 * (q[0] * q[2] - q[1] * q[3]));
  //conventional yaw increases clockwise from North. Not that the MPU-6050 knows where North is.
  yaw   = -atan2((q[1] * q[2] + q[0] * q[3]), 0.5 - (q[2] * q[2] + q[3] * q[3]));
  // to degrees
  yaw   *= 180.0 / PI;
  if (yaw < 0) yaw += 360.0; //compass circle
  pitch *= 180.0 / PI;
  roll *= 180.0 / PI;

  now_ms = millis(); //time to print?
  if (now_ms - last_ms >= print_ms) {
    last_ms = now_ms;
    // print angles for serial plotter...
    //  Serial.print("ypr ");
    Serial.print(yaw, 0);
    Serial.print(", ");
    Serial.print(pitch, 0);
    Serial.print(", ");
    Serial.println(roll, 0);
  }
}
//--------------------------------------------------------------------------------------------------
// Mahony scheme uses proportional and integral filtering on
// the error between estimated reference vector (gravity) and measured one.
// Madgwick's implementation of Mayhony's AHRS algorithm.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date      Author      Notes
// 29/09/2011 SOH Madgwick    Initial release
// 02/10/2011 SOH Madgwick  Optimised for reduced CPU load
// last update 07/09/2020 SJR minor edits
//--------------------------------------------------------------------------------------------------
// IMU algorithm update

void Mahony_update(float ax, float ay, float az, float gx, float gy, float gz, float deltat) {
  float recipNorm;
  float vx, vy, vz;
  float ex, ey, ez;  //error terms
  float qa, qb, qc;
  static float ix = 0.0, iy = 0.0, iz = 0.0;  //integral feedback terms
  float tmp;

  // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
  tmp = ax * ax + ay * ay + az * az;
  if (tmp > 0.0)
  {

    // Normalise accelerometer (assumed to measure the direction of gravity in body frame)
    recipNorm = 1.0 / sqrt(tmp);
    ax *= recipNorm;
    ay *= recipNorm;
    az *= recipNorm;

    // Estimated direction of gravity in the body frame (factor of two divided out)
    vx = q[1] * q[3] - q[0] * q[2];
    vy = q[0] * q[1] + q[2] * q[3];
    vz = q[0] * q[0] - 0.5f + q[3] * q[3];

    // Error is cross product between estimated and measured direction of gravity in body frame
    // (half the actual magnitude)
    ex = (ay * vz - az * vy);
    ey = (az * vx - ax * vz);
    ez = (ax * vy - ay * vx);

    // Compute and apply to gyro term the integral feedback, if enabled
    if (Ki > 0.0f) {
      ix += Ki * ex * deltat;  // integral error scaled by Ki
      iy += Ki * ey * deltat;
      iz += Ki * ez * deltat;
      gx += ix;  // apply integral feedback
      gy += iy;
      gz += iz;
    }

    // Apply proportional feedback to gyro term
    gx += Kp * ex;
    gy += Kp * ey;
    gz += Kp * ez;
  }

  // Integrate rate of change of quaternion, q cross gyro term
  deltat = 0.5 * deltat;
  gx *= deltat;   // pre-multiply common factors
  gy *= deltat;
  gz *= deltat;
  qa = q[0];
  qb = q[1];
  qc = q[2];
  q[0] += (-qb * gx - qc * gy - q[3] * gz);
  q[1] += (qa * gx + qc * gz - q[3] * gy);
  q[2] += (qa * gy - qb * gz + q[3] * gx);
  q[3] += (qa * gz + qb * gy - qc * gx);

  // renormalise quaternion
  recipNorm = 1.0 / sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
  q[0] = q[0] * recipNorm;
  q[1] = q[1] * recipNorm;
  q[2] = q[2] * recipNorm;
  q[3] = q[3] * recipNorm;
}

Once DMP is enabled, the acc range must be +-4g and gyro +-2000 dps, this is required by dmp hardcoded firmware. If you try to modify these values inside dmpInitialize() you will get incorrect data out of dmp fifo.

see: