MPU6050 in vertical position

Hello,

I would like to use an MPU6050 to build a one axis servo gimbal. The thing is, I would like to mount the MPU6050 in a vertical position (to fit in a box). Do I have to calibrate it in vertical position or modify the code ?

I think I have to modify the code but I don’t know what part. I googled it but in vain…

PS : I use this code that works perfectly with the PMU6050 in horizontal position

/* http://www.youtube.com/c/electronoobs
 * 
 * This is an example for the 3D printed gimbal project. It uses 
 * the MPU6050 and two S3003 servo motors. This is the PID code for the gimbal. * 
 * Arduino pin    |   MPU6050
 * 5V             |   Vcc
 * GND            |   GND
 * A4             |   SDA
 * A5             |   SCL
 * Scheematic: https://www.electronoobs.com/eng_arduino_tut46_sch1.php 
 */
 
//Includes
#include <Wire.h>
#include <Servo.h>

Servo ServoPitch;

/*MPU-6050 gives you 16 bits data so you have to create some float constants
*to store the data for accelerations and gyro*/

//Gyro Variables
float elapsedTime, time, timePrev;            //Variables for time control
int gyro_error=0;                             //We use this variable to only calculate once the gyro data error
float Gyr_rawX, Gyr_rawY, Gyr_rawZ;           //Here we store the raw data read 
float Gyro_angle_x, Gyro_angle_y;             //Here we store the angle value obtained with Gyro data
float Gyro_raw_error_x, Gyro_raw_error_y;     //Here we store the initial gyro data error

//Acc Variables
int acc_error=0;                              //We use this variable to only calculate once the Acc data error
float rad_to_deg = 180/3.141592654;           //This value is for pasing from radians to degrees values
float Acc_rawX, Acc_rawY, Acc_rawZ;           //Here we store the raw data read 
float Acc_angle_x, Acc_angle_y;               //Here we store the angle value obtained with Acc data
float Acc_angle_error_x, Acc_angle_error_y;   //Here we store the initial Acc data error

float Total_angle_x,Total_angle_y;                          //Here we store the final total angle

//More variables for the code...
int i;
int mot_activated=0;
long activate_count=0;
long des_activate_count=0;

float PWM_pitch = 90;
float offset = -7; //angle offset to compensate IMU/gimbal misalignment

void setup() {
  ServoPitch.attach(5,553,2520); //servo motor for pitch
  
  Wire.begin();   
  //begin the wire comunication
  Wire.beginTransmission(0x68);           //begin, Send the slave adress (in this case 68)              
  Wire.write(0x6B);                       //make the reset (place a 0 into the 6B register)
  Wire.write(0x00);
  Wire.endTransmission(true);             //end the transmission
  //Gyro config
  Wire.beginTransmission(0x68);           //begin, Send the slave adress (in this case 68) 
  Wire.write(0x1B);                       //We want to write to the GYRO_CONFIG register (1B hex)
  Wire.write(0x10);                       //Set the register bits as 00010000 (1000dps full scale)
  Wire.endTransmission(true);             //End the transmission with the gyro
  //Acc config
  Wire.beginTransmission(0x68);           //Start communication with the address found during search.
  Wire.write(0x1C);                       //We want to write to the ACCEL_CONFIG register
  Wire.write(0x10);                       //Set the register bits as 00010000 (+/- 8g full scale range)
  Wire.endTransmission(true); 

  Serial.begin(9600);                     //Remember to set this same baud rate to the serial monitor  
  time = millis();                        //Start counting time in milliseconds
}//end of setup void


void loop() {
    
  /////////////////////////////I M U/////////////////////////////////////
  timePrev = time;  // the previous time is stored before the actual time read
  time = millis();  // actual time read
  elapsedTime = (time - timePrev) / 1000;     
  /*The tiemeStep is the time that elapsed since the previous loop. 
  *This is the value that we will use in the formulas as "elapsedTime" 
  *in seconds. We work in ms so we have to divide the value by 1000 
  to obtain seconds*/
  /*Reed the values that the accelerometre gives.
  * We know that the slave adress for this IMU is 0x68 in
  * hexadecimal. For that in the RequestFrom and the 
  * begin functions we have to put this value.*/   
 //////////////////////////////////////Gyro read/////////////////////////////////////
  Wire.beginTransmission(0x68);            //begin, Send the slave adress (in this case 68) 
  Wire.write(0x43);                        //First adress of the Gyro data
  Wire.endTransmission(false);
  Wire.requestFrom(0x68,4,true);           //We ask for just 4 registers        
  Gyr_rawX=Wire.read()<<8|Wire.read();     //Once again we shif and sum
  Gyr_rawY=Wire.read()<<8|Wire.read();
  /*Now in order to obtain the gyro data in degrees/seconds we have to divide first
  the raw value by 32.8 because that's the value that the datasheet gives us for a 1000dps range*/
  /*---X---*/
  Gyr_rawX = (Gyr_rawX/32.8); 
  /*---Y---*/
  Gyr_rawY = (Gyr_rawY/32.8);  
  /*Now we integrate the raw value in degrees per seconds in order to obtain the angle
  * If you multiply degrees/seconds by seconds you obtain degrees */
    /*---X---*/
  Gyro_angle_x = Gyr_rawX*elapsedTime;
  /*---X---*/
  Gyro_angle_y = Gyr_rawY*elapsedTime;


  //////////////////////////////////////Acc read/////////////////////////////////////
  Wire.beginTransmission(0x68);     //begin, Send the slave adress (in this case 68) 
  Wire.write(0x3B);                 //Ask for the 0x3B register- correspond to AcX
  Wire.endTransmission(false);      //keep the transmission and next
  Wire.requestFrom(0x68,6,true);    //We ask for next 6 registers starting withj the 3B  
  /*We have asked for the 0x3B register. The IMU will send a brust of register.
  * The amount of register to read is specify in the requestFrom function.
  * In this case we request 6 registers. Each value of acceleration is made out of
  * two 8bits registers, low values and high values. For that we request the 6 of them  
  * and just make then sum of each pair. For that we shift to the left the high values 
  * register (<<) and make an or (|) operation to add the low values.
  If we read the datasheet, for a range of+-8g, we have to divide the raw values by 4096*/    
  Acc_rawX=(Wire.read()<<8|Wire.read())/4096.0 ; //each value needs two registres
  Acc_rawY=(Wire.read()<<8|Wire.read())/4096.0 ;
  Acc_rawZ=(Wire.read()<<8|Wire.read())/4096.0 ; 
 /*Now in order to obtain the Acc angles we use euler formula with acceleration values
 after that we substract the error value found before*/  
 /*---X---*/
 Acc_angle_x = (atan((Acc_rawY)/sqrt(pow((Acc_rawX),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
 /*---Y---*/
 Acc_angle_y = (atan(-1*(Acc_rawX)/sqrt(pow((Acc_rawY),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;   


 //////////////////////////////////////Total angle and filter/////////////////////////////////////

 Total_angle_x = 0.98 *(Total_angle_x + Gyro_angle_x) + 0.02*Acc_angle_x;
 Total_angle_y = 0.98 *(Total_angle_y + Gyro_angle_y) + 0.02*Acc_angle_y;
 PWM_pitch = map(Total_angle_y,90,-90,0,180);
 ServoPitch.write(PWM_pitch + offset);
 delay(5);
    
//Uncomment this below for debug
// Serial.print(Total_angle_y);
// Serial.print(",");
 Serial.print(PWM_pitch);
 Serial.print("\n");
    
}//end of void loop

Moved here from the French section of the forum as it is written in English

If you install the MPU6050 in an orientation different than the reference one, you swap some axis ( and their orientation too), providing you make 90° rotations

Choose any convenient accelerometer axis that can be aligned/mounted vertically and modify the code appropriately.

It does not matter that the manufacturer names the axes X, Y and Z, although you need to be aware that each axis will respond slightly differently than another. Be sure to study the data sheet.

UKHeliBob:
Moved here from the French section of the forum as it is written in English

Thank you, sorry for the mistake

fdufnews:
If you install the MPU6050 in an orientation different than the reference one, you swap some axis ( and their orientation too), providing you make 90° rotations

I'll try to and post any news

the code seems to use "Total_angle_y", so likely the Y axis.
try similar formulas with X (or -X) or Z (or -Z). The only difference might be that on the Z axis you will always have G and so you might need to subtract that

I tried this code from https://www.electronoobs.com/eng_robotica_tut6_1.php
I added Z axis in the code and swap X and Z but it is not giving me the correct values.

/* http://www.youtube.com/c/electronoobs
 * 
 * This is an example for the 3D printed gimbal project. It uses 
 * the MPU6050 and two S3003 servo motors. This is the PID code for the gimbal. * 
 * Arduino pin    |   MPU6050
 * 5V             |   Vcc
 * GND            |   GND
 * A4             |   SDA
 * A5             |   SCL
 * Scheematic: https://www.electronoobs.com/eng_arduino_tut46_sch1.php 
 */
 
//Includes
#include <Wire.h>
#include <Servo.h>

Servo ServoPitch;

/*MPU-6050 gives you 16 bits data so you have to create some float constants
*to store the data for accelerations and gyro*/

//Gyro Variables
float elapsedTime, time, timePrev;            //Variables for time control
int gyro_error=0;                             //We use this variable to only calculate once the gyro data error
float Gyr_rawX, Gyr_rawY, Gyr_rawZ;           //Here we store the raw data read 
float Gyro_angle_x, Gyro_angle_y, Gyro_angle_z;  //Here we store the angle value obtained with Gyro data
float Gyro_raw_error_x, Gyro_raw_error_y, Gyro_raw_error_z;     //Here we store the initial gyro data error

//Acc Variables
int acc_error=0;                              //We use this variable to only calculate once the Acc data error
float rad_to_deg = 180/3.141592654;           //This value is for pasing from radians to degrees values
float Acc_rawX, Acc_rawY, Acc_rawZ;           //Here we store the raw data read 
float Acc_angle_x, Acc_angle_y, Acc_angle_z;               //Here we store the angle value obtained with Acc data
float Acc_angle_error_x, Acc_angle_error_y;   //Here we store the initial Acc data error

float Total_angle_x,Total_angle_y,Total_angle_z;                          //Here we store the final total angle

//More variables for the code...
int i;
int mot_activated=0;
long activate_count=0;
long des_activate_count=0;

float PWM_pitch = 90;
float offset = -7; //angle offset to compensate IMU/gimbal misalignment

void setup() {
  ServoPitch.attach(5,553,2520); //servo motor for pitch
  
  Wire.begin();   
  //begin the wire comunication
  Wire.beginTransmission(0x68);           //begin, Send the slave adress (in this case 68)              
  Wire.write(0x6B);                       //make the reset (place a 0 into the 6B register)
  Wire.write(0x00);
  Wire.endTransmission(true);             //end the transmission
  //Gyro config
  Wire.beginTransmission(0x68);           //begin, Send the slave adress (in this case 68) 
  Wire.write(0x1B);                       //We want to write to the GYRO_CONFIG register (1B hex)
  Wire.write(0x10);                       //Set the register bits as 00010000 (1000dps full scale)
  Wire.endTransmission(true);             //End the transmission with the gyro
  //Acc config
  Wire.beginTransmission(0x68);           //Start communication with the address found during search.
  Wire.write(0x1C);                       //We want to write to the ACCEL_CONFIG register
  Wire.write(0x10);                       //Set the register bits as 00010000 (+/- 8g full scale range)
  Wire.endTransmission(true); 

  Serial.begin(9600);                     //Remember to set this same baud rate to the serial monitor  
  time = millis();                        //Start counting time in milliseconds
}//end of setup void


void loop() {
    
  /////////////////////////////I M U/////////////////////////////////////
  timePrev = time;  // the previous time is stored before the actual time read
  time = millis();  // actual time read
  elapsedTime = (time - timePrev) / 1000;     
  /*The tiemeStep is the time that elapsed since the previous loop. 
  *This is the value that we will use in the formulas as "elapsedTime" 
  *in seconds. We work in ms so we have to divide the value by 1000 
  to obtain seconds*/
  /*Reed the values that the accelerometre gives.
  * We know that the slave adress for this IMU is 0x68 in
  * hexadecimal. For that in the RequestFrom and the 
  * begin functions we have to put this value.*/   
 //////////////////////////////////////Gyro read/////////////////////////////////////
  Wire.beginTransmission(0x68);            //begin, Send the slave adress (in this case 68) 
  Wire.write(0x43);                        //First adress of the Gyro data
  Wire.endTransmission(false);
  Wire.requestFrom(0x68,6,true);           //We ask for just 6 registers        
  Gyr_rawX=Wire.read()<<8|Wire.read();     //Once again we shif and sum
  Gyr_rawY=Wire.read()<<8|Wire.read();
  Gyr_rawZ=Wire.read()<<8|Wire.read();

  //Swap axis
  swapFloat(Gyr_rawX,Gyr_rawZ);
  
  /*Now in order to obtain the gyro data in degrees/seconds we have to divide first
  the raw value by 32.8 because that's the value that the datasheet gives us for a 1000dps range*/
  /*---X---*/
  Gyr_rawX = (Gyr_rawX/32.8); 
  /*---Y---*/
  Gyr_rawY = (Gyr_rawY/32.8);
  /*---Z---*/
  Gyr_rawZ = (Gyr_rawZ/32.8);  
  /*Now we integrate the raw value in degrees per seconds in order to obtain the angle
  * If you multiply degrees/seconds by seconds you obtain degrees */
  /*---X---*/
  Gyro_angle_x = Gyr_rawX*elapsedTime;
  /*---Y---*/
  Gyro_angle_y = Gyr_rawY*elapsedTime;
  /*---Z---*/
  Gyro_angle_z = Gyr_rawZ*elapsedTime;


  //////////////////////////////////////Acc read/////////////////////////////////////
  Wire.beginTransmission(0x68);     //begin, Send the slave adress (in this case 68) 
  Wire.write(0x3B);                 //Ask for the 0x3B register- correspond to AcX
  Wire.endTransmission(false);      //keep the transmission and next
  Wire.requestFrom(0x68,6,true);    //We ask for next 6 registers starting with the 3B  
  /*We have asked for the 0x3B register. The IMU will send a burst of register.
  * The amount of register to read is specify in the requestFrom function.
  * In this case we request 6 registers. Each value of acceleration is made out of
  * two 8bits registers, low values and high values. For that we request the 6 of them  
  * and just make then sum of each pair. For that we shift to the left the high values 
  * register (<<) and make an or (|) operation to add the low values.
  If we read the datasheet, for a range of+-8g, we have to divide the raw values by 4096*/    
  Acc_rawX=(Wire.read()<<8|Wire.read())/4096.0 ; //each value needs two registres
  Acc_rawY=(Wire.read()<<8|Wire.read())/4096.0 ;
  Acc_rawZ=(Wire.read()<<8|Wire.read())/4096.0 ;

  //Swap axis
  swapFloat(Acc_rawX,Acc_rawZ);
  
 /*Now in order to obtain the Acc angles we use euler formula with acceleration values
 after that we substract the error value found before*/  
 /*---X---*/
 Acc_angle_x = (atan((Acc_rawY)/sqrt(pow((Acc_rawX),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
 /*---Y---*/
 Acc_angle_y = (atan(-1*(Acc_rawX)/sqrt(pow((Acc_rawY),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
 /*---Z---*/
 Acc_angle_z = (atan((Acc_rawZ)/sqrt(pow((Acc_rawX),2) + pow((Acc_rawY),2)))*rad_to_deg) ;


 //////////////////////////////////////Total angle and filter/////////////////////////////////////

 Total_angle_x = 0.98 *(Total_angle_x + Gyro_angle_x) + 0.02*Acc_angle_x;
 Total_angle_y = 0.98 *(Total_angle_y + Gyro_angle_y) + 0.02*Acc_angle_y;
 Total_angle_z = 0.98 *(Total_angle_z + Gyro_angle_z) + 0.02*Acc_angle_z;
 PWM_pitch = map(Total_angle_y,90,-90,0,180);
 ServoPitch.write(PWM_pitch + offset);
 delay(5);
    
//Uncomment this below for debug
 Serial.print(Total_angle_x);
 Serial.print(",");
 Serial.print(Total_angle_y);
 Serial.print(",");
 Serial.print(Total_angle_z);
// Serial.print(PWM_pitch);
 Serial.print("\n");
    
}//end of void loop

void swapFloat(float &a, float &b) {
  int c = a;
  a = b;
  b = c;
}

you might need to take standard acceleration due to gravity into account now

I don't understand where it is taken into account in his code. The original code from electronoobs seems to work without. Maybe it is already substracted by the IMU itself?

if the MPU was horizontal in the original code then the G axis might have been simply ignored (I'm on my smartphone so can't really test anything)

No worries.

Z total angle was not used but Z acceleration is used to compute Acc angle x and Acc angle y. So I'm confused

Earth’s gravity is a constant acceleration where the force is always pointing down to the centre of the Earth. When the accelerometer is parallel with the gravity, the measured acceleration will be 1G, when the accelerometer is perpendicular with the gravity, it will measure 0G.

So when you lay still and "flat", Z acceleration will measure -1G and X and Y will be 0
if you position the sensor differently with one axis alongside gravitational field, then Z will be 0 and X or Y will be ±1G

I see that in the code that indeed they used the Z acceleration the compute the norm of the acceleration vector projected to (Y,Z) plane

Gyro_angle_y = Gyr_rawY*elapsedTime; 
Acc_angle_y = (atan(-1*(Acc_rawX)/sqrt(pow((Acc_rawY),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
Total_angle_y = 0.98 *(Total_angle_y + Gyro_angle_y) + 0.02*Acc_angle_y;
PWM_pitch = map(Total_angle_y,90,-90,0,180);
ServoPitch.write(PWM_pitch + offset);

You need to find what (X,Y,Z) have been changed to based on the new orientation of your module and adapt the formula accordingly

This is nonsense. 3D angles do not accumulate in this way. For 3D angles, it matters which rotation is performed first.

 Total_angle_x = 0.98 *(Total_angle_x + Gyro_angle_x) + 0.02*Acc_angle_x;
 Total_angle_y = 0.98 *(Total_angle_y + Gyro_angle_y) + 0.02*Acc_angle_y;

Here is a tutorial showing one of several correct ways to use an accelerometer to estimate stationary tilt angles.

The "Z axis" is the axis that is pointing approximately up or down, and the other two are horizontal.

jremington:
This is nonsense. 3D angles do not accumulate in this way.

Since OP stated "one axis servo gimbal" - I assumed 2 axis where physically "locked"

The OP obviously has no clue, and neither did whoever wrote that code. Yet that code shows up everywhere.

jremington:
and neither did whoever wrote that code.

hum... that's an unsubstantiated bold statement.

I think this comes from the idea of simplifying the processing without using a Kalman Filter by building a "Complementary Filter".

That should work relatively well esp. in operations where angles don't change widely or abruptly

It is pretty well explained in this article

you can read some info here too (from someone with a PHD and who has been working in that field) and more research here as well comparing the benefits

[an unsubstantiated bold statement]The code is so obviously wrong that the statement really needs no substantiation.

However, for those who are skeptical, a few minutes spent observing the output angles will reveal that they are meaningless after just a few measurements.

The second link, from the "PhD", covers the one dimensional case, which is trivial. The approach does not work at all in 3D, where the full IMU 3D treatment is required. This is because 3D rotation operations do not commute.

This is one axis gimbal. So not sure about your 3D statement.

there is no doubt that this integrates the gyroscope value over time (subject to drift is known)

Gyro_angle_y = Gyr_rawY*elapsedTime;

and this is the very same formula you had in the tutorial you linked (in a different plane)
Acc_angle_y = (atan(-1*(Acc_rawX)/sqrt(pow((Acc_rawY),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;  which works OK if you are not accelerating.

the next equation is the Complementary Filter as documented in the research papers I linked above.

I'm not here to defend this code specifically but can you elaborate as I don't get it?

Again, in 1D the complementary filter is OK.

The problem in 3D is that the rotations have to be applied successively, and the order of application matters a great deal.

So, you cannot sum up successive X and Y rotations individually to get the equivalent total X and Y rotation. This explained quite well this wikipedia article: Euler angles - Wikipedia

Furthermore, one of these two equations, from the code in the OP, is wrong (take your pick). The result will be correct only if one of the two rotations is zero.

 Acc_angle_x = (atan((Acc_rawY)/sqrt(pow((Acc_rawX),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
 Acc_angle_y = (atan(-1*(Acc_rawX)/sqrt(pow((Acc_rawY),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;

The entire field has moved on, and the complementary filter is really a thing of the past. The modern AHRS systems use a quaternion to represent the 3D orientation, and use quaternion operations to update it.