MPU6050 - getting absolute pitch angle

Hey guys,

I'm currently trying to program a kind-of self-balancing scooter using an MPU6050 & ESP32. When you power up the scooter, regardless of the orientation, I had expected for the MPU6050 to know what 'level' is, from the offsets applied during calibration. Seems to not be the case though. At whatever pitch angle I hold the ESP32 & IMU and apply power, it reads that pitch as 0. Is this expected?

If so, is there a way to get absolute angles from the MPU6050 rather than relative angles (change since power on)? Will DMP do this for me? Would like to not have to calibrate the IMU for angle for 0 degrees (level) each power cycle.

I don't believe I'm doing anything different to what people who design inclinometers are doing, and yet it does not work without the IMU being level at power up, each time. The meat of my code calculating pitch is below:

void setup() {
  ...
  mpu6050.initialize();  
  mpu6050.setXAccelOffset(-4114);
  mpu6050.setYAccelOffset(-24);
  mpu6050.setZAccelOffset(1667);
  mpu6050.setXGyroOffset(82);
  mpu6050.setYGyroOffset(-33);
  mpu6050.setZGyroOffset(8);
}
void loop() {
  previousTime = currentTime;
  currentTime = millis();
  elapsedTimeSecs = (currentTime - previousTime) / 1000.0;  

  yGyro = getGyRaw();
  gyroPitch = getGyPitch(yGyro);
  accPitch = getAccPitch();
  pitch = getCompPitch(gyroPitch, accPitch);
  ...
}

float getGyRaw() {
  mpu6050.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);  
  yGyro = (float) gy / gyroLimit;
  yGyro = yGyro + 0.0775; // Drift offset
  return yGyro;
}

float getGyPitch(float gyro) {
  return gyroPitch += gyro * elapsedTimeSecs;
}

float getAccPitch() {  
  return accPitch = (float) atan2((-ax), sqrt(ay * ay + az * az)) * 57.3;;
}

float getCompPitch(float gyro, float acc) {  
  pitch = 0.95 * gyro + 0.05 * acc;
  return pitch;
}

Any help appreciated,

Taylor

Ok so I now understand that using the gyro is what is affecting the readings every power cycle. The deg/s/s value is integrated to get degrees moved and therefor can never know where 'level' is. Is there a way to deduce pitch without being reliant on solely the accelerometer though? Such that I don't have to calibrate level each power cycle? Cheers.

If you want absolute tilt angles (pitch and roll), which can only be obtained if the sensor is stationary, the gyro doesn't help at all. The gyro measures rate of rotation and helps only to correct for errors made when the sensor is subject to accelerations induced by rotations.

For accurate pitch and roll calculations, first calibrate your accelerometer by following the general procedure outlined here: Programming and Calibration | ADXL345 Digital Accelerometer | Adafruit Learning System.

Then you can use this simple code (after implementing the corrections obtained).

/ minimal MPU-6050 tilt and roll (sjr)
// works perfectly with GY-521, pitch and roll signs agree with arrows on sensor module 7/2019
// tested with eBay Pro Mini, **no external pullups on SDA and SCL** (works with internal pullups!)
//
#include<Wire.h>
const int MPU_addr1 = 0x68;
float xa, ya, za, roll, pitch;

void setup() {

  Wire.begin();                                      //begin the wire communication
  Wire.beginTransmission(MPU_addr1);                 //begin, send the slave adress (in this case 68)
  Wire.write(0x6B);                                  //make the reset (place a 0 into the 6B register)
  Wire.write(0);
  Wire.endTransmission(true);                        //end the transmission
  Serial.begin(9600);
}

void loop() {

  Wire.beginTransmission(MPU_addr1);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr1, 6, true); //get six bytes accelerometer data

  xa = Wire.read() << 8 | Wire.read();
  ya = Wire.read() << 8 | Wire.read();
  za = Wire.read() << 8 | Wire.read();

  roll = atan2(ya , za) * 180.0 / PI;
  pitch = atan2(-xa , sqrt(ya * ya + za * za)) * 180.0 / PI;

  Serial.print("roll = ");
  Serial.print(roll,1);
  Serial.print(", pitch = ");
  Serial.println(pitch,1);
  delay(400);
}

An explanation of the above code is outlined here: How_to_Use_a_Three-Axis_Accelerometer_for_Tilt_Sensing-DFRobot

Thanks. What I've ended up doing is using kalman filtered accelerometer data to determine when to start 'accepting' gyro data.

If the accelerometer data indicates that the IMU is level (acceleration entirely vertical and pitch is horizontal (using atan2), then begin feeding in gyro data to the complementary filter:

  yGyro = getGyRaw();
  accPitch = getAccPitch();
  zAccG = getAccVal();
  
  // If deemed to be level, horizontally, (given by vertical g (m/s) value & accPitch) then begin accepting gyro data
  if (firstRun == true) {
    if (zAccG >= 9.75 && pitch >= -1 && pitch <= 1) {
      firstRun = false;
    } else {
      pitch = getCompPitch(0.0, accPitch * 20.0); // Ignore gyro data if not horizontally level
    }
  } else {
    gyroPitch = getGyPitch(yGyro);
    pitch = getCompPitch(gyroPitch, accPitch);
  }
  pitch = getKalPitch(pitch);

Haven't tried DMP to see if this gives me a similar result. Maybe it will help others though.

Taylor,

Might help others.

Looks you are using any existing DMP6/motion library, as I do. Or did you write your own?

I had myself troubles to get some accurate data out of my MPU6050. By the way, I tested different libraries versions. And so I noticed what you described with a very recent library:

But that might be a feature. I didn't check if that lib now includes an option for what is zero at startup.

PS: Following is what I'm currently getting out from my MPU laying still at approx 26°. Polling at approx 90 packets per second, sometime weird values, I do now skip them. Is this a dead chip, usual noise, or any code issue?

15:41:52.542 -> MPU Roll oops, skipped, was 52.59
15:41:57.422 -> MPU Roll oops, skipped, was -128.53
15:41:58.862 -> MPU Roll oops, skipped, was 36.80
15:42:03.062 -> MPU Roll oops, skipped, was 39.10
15:42:27.542 -> MPU Roll oops, skipped, was 36.10
15:42:32.462 -> MPU Roll oops, skipped, was 179.99
15:42:34.542 -> MPU Roll oops, skipped, was 101.74
15:42:35.942 -> MPU Roll oops, skipped, was -180.00
15:42:36.662 -> MPU Roll oops, skipped, was 52.63
15:42:42.942 -> MPU Roll oops, skipped, was -170.53