MPU6050 is very unreliable

I've been trying to build a balancing robot for a while now and after failing to tune it for ages I've realised the problem is unreliable readings from the MPU6050, no matter what library I use or what code I download.

If I rotate it very slowly then the pitch angle will be mostly accurate but any harsh movements shows at first an output angle in the opposite direction and then it will slowly catch up. You can see that here where I quickly rotate it to -20 degrees. Surely the MPU6050 should be more accurate than this? I've seen plenty projects of some impressive balancing robots that rely on it and they accelerate quickly too.

As I say I've tried lots of code trying to get this to work but here's what I'm using for that graph, I found it on another post on this forum. I've also got the same behavior using the Jeff Rowberg library and example code.

Is it possible my MPU6050 is faulty because at this point I don't know what else could be wrong.

//
// MPU-6050 Mahony AHRS  S.J. Remington 3/2020
// 7/2020 added provision to recalibrate gyro upon startup. (variable cal_gyro)

#include "Wire.h"

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

int cal_gyro = 1;  //set to zero to use gyro calibration offsets below.

// 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 AHRS 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;

// Notes: 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, Kp=30 works well

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

// 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 unsigned int i = 0; //loop counter
  static float deltat = 0;  //loop time in seconds
  static unsigned long now = 0, last = 0; //micros() timers
  static long gsum[3] = {0};
  //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)

  // calibrate gyro upon startup. SENSOR MUST BE HELD STILL (a few seconds)
  i++;
  if (cal_gyro) {

    gsum[0] += gx; gsum[1] += gy; gsum[2] += gz;
    if (i == 500) {
      cal_gyro = 0;  //turn off calibration and print results

      for (char k = 0; k < 3; k++) G_off[k] = ((float) gsum[k]) / 500.0;

      Serial.print("G_Off: ");
      Serial.print(G_off[0]);
      Serial.print(", ");
      Serial.print(G_off[1]);
      Serial.print(", ");
      Serial.print(G_off[2]);
      Serial.println();
    }
  }

  // normal AHRS calculations

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

    //apply offsets and scale factors from Magneto
    for (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.
    // In this coordinate system, the positive z-axis is down toward Earth.
    // Yaw is the angle between Sensor x-axis and Earth magnetic North
    // (or true North if corrected for local declination, looking down on the sensor
    // positive yaw is counterclockwise, which is not conventional for NED navigation.
    // Pitch is angle between sensor x-axis and Earth ground plane, toward the
    // Earth is positive, up toward the sky is negative. Roll is angle between
    // sensor y-axis and Earth ground plane, y-axis up is positive roll. These
    // arise from the definition of the homogeneous rotation matrix constructed
    // from quaternions. Tait-Bryan angles as well as Euler angles are
    // non-commutative; that is, the get the correct orientation the rotations
    // must be applied in the correct order which for this configuration is yaw,
    // pitch, and then roll.
    // http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
    // which has additional links.

    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
    //ccrrect for local magnetic declination here
    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(", ");
      //Serial.println(roll, 0);
    //}

    Serial.print("Max:");
    Serial.print(90);
    Serial.print(",");
    Serial.print("Min:");
    Serial.print(-90);
    Serial.print(",");
    Serial.print("Pitch:");
    Serial.println(pitch, 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
// 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;

  // ignore accelerometer if false (tested OK, SJR)
  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, given by gyro term
  // rate of change = current orientation quaternion (qmult) gyro rate

  deltat = 0.5 * deltat;
  gx *= deltat;   // pre-multiply common factors
  gy *= deltat;
  gz *= deltat;
  qa = q[0];
  qb = q[1];
  qc = q[2];

  //add qmult*delta_t to current orientation
  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);

  // Normalise 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;
}

1 Like

Fast reactions are critical for a balancing robot, and in your case, at every step you output a bunch of information to the serial, setting the output speed to the lowest possible baudrate. This wasted a very noticeable amount of time and your robot simply does not have time to react.

In this case yes I'm outputting values to debug it but even without outputting anything to the serial the robot doesn't respond well.

Why are you printing this at all? Your Max and Min are always the same, because you don’t output data from the module, but simply text written in the sketch
And pitch is a calculated parameter that depends on the fidelity of the program.
If you want to test a module, print raw values from the MPU6050 to the console

I'm printing the max and min so the graph is easier to read and doesn't autoscale, I don't need them when just reading the serial monitor.
I have printed raw values previously but it's no use to me, it's the calculated pitch that I'm having trouble with.

The loop timing is absolutely critical and must be fast or it won't work. By printing, you slow the loop down so much that the filter calculations fail. ESPECIALLY at the extremely slow rate of 9600 Baud.

Get rid of all the printing and use the angle result for balancing.

For balancing, you don't need all of this, either:

    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
    //ccrrect for local magnetic declination here
    pitch *= 180.0 / PI;
    roll *= 180.0 / PI;

To increase the loop speed, reduce all the above to this, which is all you need.

    pitch = asin(2.0 * (q[0] * q[2] - q[1] * q[3]));
    pitch *= 180.0 / PI;

That does seem to be working a lot better thanks. I didn't realise how dependant it was on having a fast loop time.
I'm curious does that mean then that there's no way of printing serial outputs for debugging without messing up the MPU6050?

You commented out the code that does exactly that, which is to print at infrequent intervals. Also, use a modern serial Baud rate, like 250000.

 //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 ");
1 Like

The printing is not messing the MPU6050, it just an interrupt the balancing process. The MPU itself still works fine.

Oh I see that makes a lot of sense thanks for the help!

This is a follow up to another post I made here: https://forum.arduino.cc/t/mpu6050-is-very-unreliable/1184765?u=pendragon_29

I've been trying to build a balancing robot and the most difficult part is simply reading the current angle from the MPU6050.
As you can see in my previous post when I rotate the sensor quickly the initial output angle will be opposite to the direction I rotated it, before it catches up.
This is also obvious when I quickly push the robot in one direction, the wheels initially rotate to help it fall over before they start rotating in the correct direction to stabilise it, down to the initial angle being the opposite direction.
I was told that this is likely down to a slow loop time but I've now measured it and its only 3ms, surely this is quick enough to get accurate angle values?

I'm racking my brains at this point because apparently the biggest challenge of making a balancing robot is tuning it, not simply getting an accurate angle.

Here's the full code I'm using to control the robot:

#include "Wire.h"

int MPU_addr = 0x68;

int cal_gyro = 1;  

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

float q[4] = {1.0, 0.0, 0.0, 0.0};

float Kp = 30.0;
float Ki = 0.0;

unsigned long now_ms, last_ms = 0; //millis() timers

float pitch;

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

#include <AccelStepper.h>
AccelStepper stepperL(1, 3, 2);
AccelStepper stepperR(1, 5, 4);        // (Type of driver: with 2 pins, STEP, DIR)

float stepperLSpeed;
float stepperRSpeed;

unsigned long currentMillis;
unsigned long prevMillis = 0;     // For calculating derivative gain

unsigned long printMillisPrev = 0;
float printPeriod = 300;

unsigned long loopMillisPrev = 0;
float loopPeriod = 0;

const int stepPinL = 3;
const int stepPinR = 5;
const int dirPinL = 2;
const int dirPinR = 4;
const int motorEnablePin = 6;

const int battVoltPin = A3;
const int battMinVolt = 9;
float battRawSignal, battVolt;

float kp = 60;          // 30
float ki = 0;          // 4
float kd = 40;          // 18 

float setPointOffset = 0;  // -0.7

float pidP, pidI, pidD;
float pidTotal;
float error;
float prevError = 0;
float setPoint = 0;

int kdPeriod = 10;

int timer2_compare_match;

ISR(TIMER2_COMPA_vect) {
  TCNT2 = timer2_compare_match;
  if (battVolt > 10 && pitch > -80 && pitch < 80) {     // Turn off motors once battery voltage drops below 10V or robot falls over
    stepperL.runSpeed(); 
    stepperR.runSpeed(); 
  }
}

void setup() {
  noInterrupts();
  TCCR2A = 0;
  TCCR2B = 0;
  timer2_compare_match = 1000;  // old 1000
  TCNT2 = timer2_compare_match;
  TCCR2B |= (1 << CS22);
  TIMSK2 |= (1 << OCIE1A);
  interrupts();

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

  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);

  pinMode(stepPinL, OUTPUT);
  pinMode(stepPinR, OUTPUT);
  pinMode(dirPinL, OUTPUT);
  pinMode(dirPinR, OUTPUT);
  pinMode(battVoltPin, INPUT);
  pinMode(motorEnablePin, OUTPUT);

  stepperL.setMaxSpeed(2000);
  stepperR.setMaxSpeed(2000);

  digitalWrite(motorEnablePin, LOW);
}

void loop() {
  currentMillis = millis(); 

  static unsigned int i = 0; //loop counter
  static float deltat = 0;  //loop time in seconds
  static unsigned long now = 0, last = 0; //micros() timers
  static long gsum[3] = {0};

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

  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)

  i++;
  if (cal_gyro) {
    gsum[0] += gx; gsum[1] += gy; gsum[2] += gz;
    if (i == 500) {
      cal_gyro = 0;  //turn off calibration and print results

      for (char k = 0; k < 3; k++) G_off[k] = ((float) gsum[k]) / 500.0;

      Serial.print("G_Off: ");
      Serial.print(G_off[0]);
      Serial.print(", ");
      Serial.print(G_off[1]);
      Serial.print(", ");
      Serial.print(G_off[2]);
      Serial.println();
    }
  }
  else {
    Axyz[0] = (float) ax;
    Axyz[1] = (float) ay;
    Axyz[2] = (float) az;

    for (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;

    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);

    pitch = asin(2.0 * (q[0] * q[2] - q[1] * q[3]));
    pitch *= (180.0 / PI) * -1;

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////
    
    /*
    if (pitch < 2 && pitch > -2) {
      pitch *= 0.5;
    }
    */

    pitch -= setPointOffset;    // setPointOffset corrects for robot tending to fall in one direction (for some reason)

    error = setPoint - pitch;      

    
    if (error < 0) {              //Attempt at non linear proportional gain
      error = abs(error);
      pidP = kp * pow(error, 1.1);
      pidP = -pidP;
    } else {
      pidP = kp * pow(error, 1.1);
    }
     

    //pidP = kp * error;
  
    if (pitch > -80 && pitch < 80) {
      pidI += ki * error;
    }

    if (currentMillis - prevMillis > kdPeriod) {
        pidD = kd * ((error - prevError)/(currentMillis - prevMillis));
        prevError = error;
        prevMillis = currentMillis;
    }
    
    pidTotal = pidP + pidI + pidD;

    stepperLSpeed = (pidTotal);
    stepperRSpeed = (pidTotal * -1);     
    
    stepperL.setSpeed(stepperLSpeed);
    stepperR.setSpeed(stepperRSpeed);

    battRawSignal = analogRead(battVoltPin);              // Measure battery voltage
    battVolt = battRawSignal * (12.3/820);
    
    /*
    Serial.print(100);
    Serial.print("  ");
    Serial.print(-100);
    Serial.print("  ");
    Serial.print(error);
    Serial.print("  ");
    Serial.print(pidP);
    Serial.print("  ");
    Serial.print(pidI);
    Serial.print("  ");
    Serial.println(pidD);
    */

    /*
    if (currentMillis - printMillisPrev > printPeriod) {
      Serial.println(loopPeriod);  
      printMillisPrev = currentMillis;
    }

    loopPeriod = currentMillis - loopMillisPrev;
    loopMillisPrev = currentMillis;
    */
  }
}






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;

  tmp = ax * ax + ay * ay + az * az;

  if (tmp > 0.0)
  {

    recipNorm = 1.0 / sqrt(tmp);
    ax *= recipNorm;
    ay *= recipNorm;
    az *= recipNorm;

    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];

    ex = (ay * vz - az * vy);
    ey = (az * vx - ax * vz);
    ez = (ax * vy - ay * vx);

    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;
    }

    gx += Kp * ex;
    gy += Kp * ey;
    gz += Kp * ez;
  }

  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);

  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;
}

Why did you decided to start a new thread? Thread's duplication is not recommended in the forum rules.

I have merged your topics due to them having too much overlap on the same subject matter @pendragon_29.

In the future, please only create one topic for each distinct subject matter and be careful not to cause them to converge into parallel discussions.

The reason is that generating multiple threads on the same subject matter can waste the time of the people trying to help. Someone might spend a lot of time investigating and writing a detailed answer on one topic, without knowing that someone else already did the same in the other topic.

Thanks in advance for your cooperation.

PID tuning is one of the several critical challenges.

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