Problems Interfacing with MPU-6050 via I2C

Hello,

I recently bought an GY251 breakkout board with the MPU-6050. It is possible to use it via I2C.
This is my first I2C project. Instead of using a libary, I wanted to learn more about the protocol and how to use it.

I tried to write my own code, to interface the MPU-6050 via I2C. I looked up the registers and addresses in the datasheet.

My code does two things:

  1. In the setup, the Scaling of the readings is set.
  2. In the loop, I tried to read some values of the registers.

When reading the values, I only get 0s.
I think that there is a problem there.

As I was writing the code, I was very confused with the Wire.h libary. I tried to replicate the I2C behaviour required by the datasheet with the commands I could use. I think there might be a problem there, as the Wire.h libary does not offer the possability to "just send" an Stop, Start, Read, Write or NACK bit.

I would be very happy if you could take a glance over my code, and explain me what I did wrong.
(code as attachement)

datasheet.png

It makes no sense to post a picture of code or text.

Please read and follow the directions in the "How to use this forum" post, and post both code or text inline, using code or quote tags.

If you want to learn the I2C protocol, the Wire library is the last place to look. And you should not confuse the Arduino I2C hardware module with the protocol specifications.

There are various implementations of "bit bang" or software I2C source code that bring the details of the protocol into the open. Here is one example.

jremington:
It makes no sense to post a picture of code or text.

Ok. Here is the code:

#include <Wire.h>

#define MPU_ADDR 0x68        //IC2 Address of MPU 6050 Module
#define GYRO_CFG_ADDR 0x1B   //Configuration register address off MPU6050
#define ACCL_CFG_ADDR 0x1C   //Configuration register address off MPU6050

#define ACCL_XH_ADDR 0x3B    //Register Addresses for Accelerometer Data
#define ACCL_XL_ADDR 0x3C

#define ACCL_YH_ADDR 0x3D
#define ACCL_YL_ADDR 0x3E

#define ACCL_ZH_ADDR 0x3F
#define ACCL_ZL_ADDR 0x40


#define TEMP_H_ADDR 0x41     //Register Addresses for Thermometer Data
#define TEMP_L_ADDR 0x42


#define GYRO_XH_ADDR 0x43    //Register Addresses for Gyroscope Data
#define GYRO_XL_ADDR 0x44

#define GYRO_YH_ADDR 0x45
#define GYRO_YL_ADDR 0x46

#define GYRO_ZH_ADDR 0x47
#define GYRO_ZL_ADDR 0x48


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

 Wire.beginTransmission(MPU_ADDR);   // Setup the module: Full Scale Range for Gyroscope
 Wire.write(GYRO_CFG_ADDR);
 Wire.write(0x00);
 Wire.endTransmission();

 Wire.beginTransmission(MPU_ADDR); // Setup the module: Full Scale Range for Acclerometer
 Wire.write(ACCL_CFG_ADDR);
 Wire.write(0x00);
 Wire.endTransmission();
 

}

void loop() {

 
  unsigned int GYRO_X;

  Wire.beginTransmission(MPU_ADDR); //start transmission
  Wire.write(ACCL_XH_ADDR);        //send Register Address for acclerometer Data (High X Byte)
  Wire.endTransmission(false);    // resend start condition
  Wire.requestFrom(MPU_ADDR,1);   // request data 

  if (1 <= Wire.available()) { // if one bytes were received
    GYRO_X = Wire.read();  // receive high byte (overwrites previous reading)
    GYRO_X = GYRO_X << 8;    // shift high byte to be high 8 bits
  
   }

  Wire.endTransmission();   // send stop condition


  Wire.beginTransmission(MPU_ADDR); //Start Transmission
  Wire.write(ACCL_XL_ADDR);         //Send Register Address for Acclerometer Data (Low X Byte)
  Wire.endTransmission(false);      // resend start condition
  Wire.requestFrom(MPU_ADDR,1);     // request data 
  
    if (1 <= Wire.available()) { // if one bytes were received
    GYRO_X |= Wire.read();  // receive low byte 
    
   }

  Wire.endTransmission();  // send stop condition

  
  Serial.print("x: ");    // output to serial monitor
  Serial.println(GYRO_X);


  delay(500);
 

}

jremington:
And you should not confuse the Arduino I2C hardware module with the protocol specifications.

I am just trying to read the data from the register, how the Datasheet is telling me.

Here is a minimal example of how to use the <Wire.h> library to read one data register from the MPU-6050.

You can experiment to see if it is necessary to "wake up" the MPU-6050 first, but of course the acceleration value won't make sense if the sensor is not actively collecting data.

    #include<Wire.h>
    const int MPU_addr=0x68;  // I2C address of the MPU-6050
    int AcX;
    void setup(){
      Serial.begin(9600);
      Wire.begin();
      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);

      Wire.beginTransmission(MPU_addr);
      Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
      Wire.endTransmission(false);
      Wire.requestFrom(MPU_addr,1,true);  // request one register
      AcX=Wire.read();  // 0x3B (ACCEL_XOUT_H)    
      Serial.print("AcX (H) = "); Serial.println(AcX);
    }
    void loop(){
    }

jremington:
You can experiment to see if it is necessary to "wake up" the MPU-6050 first

This was the issue. It was required to wake up the MPU, by setting one register. I thought, that the wake state would be the default one.
It turns out, that my code was not that wrong. To someone reading this in the future, these are the instructions and functions I use:

 Wire.beginTransmission(MPU_ADDR);  // wake up the Chip
 Wire.write(PWR_CFG_1_ADDR);         // MPU_ADDR = 0x68  ; PWR_CFG_1_ADDR = 0x6B
 Wire.write(0x00);
 Wire.endTransmission();

Read the bytes:

byte getByte(int slave_address, int reg_address){

  byte output = 0;

  Wire.beginTransmission(slave_address); //start transmission
  Wire.write(reg_address);        //send register address 
  Wire.endTransmission(false);    // resend start condition
  Wire.requestFrom(slave_address,1);   // request data 

  if (1 <= Wire.available()) { // if one bytes were received
    output = Wire.read();  // receive byte 
   }

  Wire.endTransmission();  // send stop condition

  return output;
  
}

Together with this function you are able to easily read the registers for each axis:

int getACCL(char c){

  int output = 0;

  switch (c){
    case 'x':  output = getByte(MPU_ADDR, ACCL_XH_ADDR) << 8;
               output |= getByte(MPU_ADDR, ACCL_XL_ADDR);
               return output;

    case 'y':  output = getByte(MPU_ADDR, ACCL_YH_ADDR) << 8;
               output |= getByte(MPU_ADDR, ACCL_YL_ADDR);
               return output;

    case 'z':  output = getByte(MPU_ADDR, ACCL_ZH_ADDR) << 8;
               output |= getByte(MPU_ADDR, ACCL_ZL_ADDR);
               return output;
  }

 return -1;
}

A Wire.requestFrom() should not be followed by a Wire.endTransmission(). Explanation: common mistakes #2.

Using "if (1 <= Wire.available())" is weird (I know that some official Arduino examples use it :frowning: ).
You can do this:

byte getByte(int slave_address, int reg_address)
{
  byte output = 0;

  Wire.beginTransmission(slave_address); //start
  Wire.write(reg_address);        // set register address
  Wire.endTransmission(false);    // omit the stop to create a repeated start

  Wire.requestFrom(slave_address,1);   // request 1 byte
  if (Wire.available() == 1) {    // received the same amount as requested ?
    output = Wire.read();         // get the received byte
  }

  return output;
}

or this:

byte getByte(int slave_address, int reg_address)
{
  byte output = 0;

  Wire.beginTransmission(slave_address); //start
  Wire.write(reg_address);        // set register address
  Wire.endTransmission(false);    // omit the stop to create a repeated start

  int n = Wire.requestFrom(slave_address,1);   // request 1 byte
  if (n == 1) {                   // received the same amount as requested ?
    output = Wire.read();         // get the received byte
  }

  return output;
}

If your I2C bus is really bad, then you could check the return value of Wire.endTransmission() and the return value of Wire.requestFrom() and, for example, return a 'false' to notify that something is wrong.

Together with this function you are able to easily read the registers for each axis:

There is no reason to use that function. It is slow and wastes program memory.

Here is the minimal example showing how to read ALL the data from the MPU-6050.

    #include<Wire.h>
    const int MPU_addr=0x68;  // I2C address of the MPU-6050
    int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
    void setup(){
      Wire.begin();
      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);
      Serial.begin(9600);
    }
    void loop(){
      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
      AcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)    
      AcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
      AcZ=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
      Tmp=Wire.read()<<8|Wire.read();  // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
      GyX=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
      GyY=Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
      GyZ=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
      Serial.print("AcX = "); Serial.print(AcX);
      Serial.print(" | AcY = "); Serial.print(AcY);
      Serial.print(" | AcZ = "); Serial.print(AcZ);
      Serial.print(" | Tmp = "); Serial.print(Tmp/340.00+36.53);  //equation for temperature in degrees C from datasheet
      Serial.print(" | GyX = "); Serial.print(GyX);
      Serial.print(" | GyY = "); Serial.print(GyY);
      Serial.print(" | GyZ = "); Serial.println(GyZ);
      delay(500);
    }