Embedded MCU's Kinetic Sensor/Modules Sensor/Module Interfacing Sensors and Modules SPI Modules STM32 MCU's STM32F1

In the last blog I talked about the basics of MPU6050, this time let’s see how to interface it with the STM32 based controller and get the values of accelerometer and Gyro sensor.

But before  starting first, let’s see the I2C block and the STM32 I2C block to get some idea of the basic structure of the I2C protocol.

I2C BLock

Figure 1: I2C Hardware Circuit

The above circuit diagram shows the hardware connection of the I2C protocol in open drain configuration. The external pull up resistors are being used to make the circuit work efficiently. These days the controllers come with the internal pull up resistors so while writing the code for the same these resistors can be pulled up in the configuration. 

Before proceeding further lets understand certain terms like pull up, pull down, open drain etc.

While using an I/O pin, in the digital domain it has values as LOGIC 1 (HIGH), LOGIC 0 (LOW) and Z-STATE (HIGH IMPEDANCE or FLOATING or TRI-STATE). The purpose of pull up and pull down is to maintain these states of the pin while working.

PULL-UP means that the pin is internally connected to the power supply via a resistor and is read as LOGIC 1.

PULL-DOWN means that the pin is internally connected to the ground via a resistor and is read as LOGIC 0.

In between the two in the configuration, when the pin is neither pull-up nor pull-down and the analog value cannot be represented in the logic value, this state is termed as Z-STATE where there is a floating value.

  

 

Figure 2: Pull-up and Pull-Down Configuration

Now let’s understand the Push-Pull and Open Drain configurations.

This configuration consist of a pair of complementary transistors which works as:

  • For LOGIC 0, the transistor connected to the ground is turned on to sink an electric current from the external circuit.
  • For LOGIC 1, the transistor connected to the power supply is turned on, and it provides an  electric current to the external circuit connected to the output pin.

 

The slave address of the MPU-6050 id 0b110100x, a 7-bit long. Here the LSB bit i.e.x in the 7-bit long address is determined by the logic level at AD0 pin.If x=0 i.e., pin AD0 is Logic LOW otherwise Logic HIGH.

Figure 3: Push-Pull Configuration

 

Figure 4: The Logic 0 and 1 for Push-Pull Configuration

 

This configuration consist of a single transistors which works as:

  • For LOGIC 0, the transistor connected to the ground is turned on to sink an electric current from the external circuit.
  • For LOGIC 1, it cannot supply any electric current to the external load because the output pin is floating, connected to neither the power supply nor the ground.

 

Hence the Open-Drain has two states LOGIC 0 (LOW) and LOGIC 1 (Z-STATE). This configuration use external pull up resistor.

Figure 5: Open-Drain Configuration

 

Figure 6: The Logic 0 and 1 for Open-Drain Configuration

I2C STM32 Functional Block

Let’s analyze the functional block diagram of the STM32.

As shown in the figure, the I2C protocol consists of SDA and SCL lines along with an additional pin SMBA used in SMB protocol known as System Management Bus.

The figure shows that there is a single Data Register along with the single Shift Register as the protocol is half -duplex. The address block is also there and a comparator to compare the addresses.

The control logic consists of Control Register (CR 1 and CR2) and Status Registers (SR1 and SR2)  along with the Clock Control Register for generating the clock for the communication.

 

Figure 7: I2C Block Diagram

I2C Implementation Of MPU6050 With STM32

Figure 9: Pin Connections

Above is the pin connection of the MPU6050 with the STM32F411CE.

Now set up the stm32 environment as follows:

Now starting with the code create a mpu6050.h file in the Inc folder and mpu6050.c file in Scr folder.

We are all done. Let’s start with the code.

In total there are around 80 registers in the IC but during the code all the registers are not required there are certain sets of registers which we are going to use which we will be defining in the header file mpu6050.h. These registers are:

 

Name of the Register

Address of the Register (Hex) 

Function

REG_MPU6050_ADDR

0xD0

It is the device address for the MPU6050 module

REG_SMPLRT_DIV

0x19

This register specifies the divider from the gyroscope output rate used to generate the Sample Rate for the MPU-6050.

REG_GYRO_CONFIG 

0x1B

This register is used to trigger gyroscope self-test and configure the gyroscopes’ full scale range.

REG_ACCEL_CONFIG

0x1C

This register is used to trigger the accelerometer self test and configure the accelerometer full scale range. This register also configures the Digital High Pass Filter (DHPF).

REG_ACCEL_XOUT_H

0x3B

These registers store the most recent accelerometer measurements. 

REG_TEMP_OUT_H 

0x41

These registers store the most recent temperature sensor measurement.

REG_GYRO_XOUT_H 

0x43

These registers store the most recent gyroscope measurements.

REG_PWR_MGMT_1 

0x6B

This register allows the user to configure the power mode and clock source. It also provides a bit for resetting the entire device, and a bit for disabling the temperature sensor.

REG_WHO_AM_I 

0x75

This register is used to verify the identity of the device. The contents of WHO_AM_I are the upper 6 bits of the MPU-60X0’s 7-bit I2C address. The least significant bit of the MPU-60X0’s I2C address is determined by the value of the AD0 pin. The value of the AD0 pin is not reflected in this register.

 

Apart from these registers we have two structures and the function definitions that we are using in the main file.

 

Lets now jump directly towards the mpu6050.c file and see how things are working.

In this phase, keep in mind the 3 things.

  1. Initializing the mpu6050.
  2. Read the RAW values of accelerometer and gyroscope
  3. Convert the RAR values to ‘g’ and ‘dps’ for Accelerometer and Gyroscope respectively.

 

We will be using the Serial Wire Monitor (SWM) to see the output for the same.

Below is the algorithm for MPU_Init().

Firstly, we will verify the device by checking the presence of the sensor with REG_WHO_AM_I (0x75) using HAL_I2C_Mem_Write ()

   

HAL_I2C_Mem_Write () function has the following parameters:

  • hi2c: Pointer to a I2C_HandleTypeDef structure that contains the configuration information for the specified I2C. 

  • DevAddress: Target device address: The device 7 bits address value in datasheet must be shifted to the left before calling the interface

  • MemAddress: Internal memory address 

  • MemAddSize: Size of internal memory address 

  • pData: Pointer to data buffer 

  • Size: Amount of data to be sent

  • Timeout: Timeout duration

In this case the sensor should return 0x68.

If the sensor successfully returns 0x68, then we need to wake up the sensor which is done by REG_PWR_MGMT_1 (0x6B) register by writing 0x00 to it. The clock is set up to 8MHz.

         

Next we will set the Data Output rate or simply the Sampling Rate for the data and for that we will use the REG_SMPLRT_DIV (0x19) register.  We have set the sampling rate to 1KHz.

As shown in the formula, where the Gyroscope Output Rate is 8KHz so to make Sample Rate = 1KHz, SMPLRT_DIV should be equal to 7.

Further, configure the Full Scale range of Accelerometer and Gyroscope for REG_GYRO_CONFIG (0x1B) and REG_ACCEL_CONFIG (0x1C) registers by writing 0x00.

The REG_ACCEL_CONFIG are XA_ST=0,YA_ST=0,ZA_ST=0, FS_SEL=0 -> ± 2g.

        

Similarly, REG_GYRO_CONFIG are XG_ST=0,YG_ST=0,ZG_ST=0, FS_SEL=0 -> ± 250 °/s

      

The code for the same is as follows:

 

void MPU_Init(void)

{

Uint8_t Check;

       uint8_t Data;

       HAL_I2C_Mem_Read (&hi2c1, MPU6050_ADDR, REG_WHO_AM_I, 1, &Check, 1, 1000);

      if (Check == 0x68)  

     {

           Data = 0;

           HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, REG_PWR_MGMT_1, 1,&Data, 1, 1000);

 

           Data = 0x07;

           HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, REG_SMPLRT_DIV, 1, &Data, 1, 1000);

 

           Data = 0x00;

           HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, REG_ACCEL_CONFIG, 1, &Data, 1, 1000);

 

           Data = 0x00;

           HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, REG_GYRO_CONFIG, 1, &Data, 1, 1000);

     }

}

With this we are code with the initialization of the module. Next we move to read the values of Accelerometer and Gyroscope.

Below is the algorithm for MPU_Accel_Read():

Firstly let’s discuss the HAL_I2C_Mem_Read(). It has the following parameters:

  • hi2c: Pointer to a I2C_HandleTypeDef structure that contains the configuration information for the specified I2C. 
  • DevAddress: Target device address: The device 7 bits address value in datasheet must be shifted to the left before calling the interface 
  • MemAddress: Internal memory address 
  • MemAddSize: Size of internal memory address
  •  pData: Pointer to data buffer 
  • Size: Amount of data to be sent 
  • Timeout: Timeout duration

Here we will use REG_ACCEL_XOUT_H (0x3B – 0x40).

 

Here it is clearly being observed that it is a 16 bit data being stored as [15:8] and [7:0] in the HIGHER and LOWER ACCEL_XOUT respectively.

Hence we need a 6 bytes array to store the lower and higher values of X, Y, Z axis respectively. 

Also we will be doing a bitwise left shift operation along with bit wise OR to get the 16 bit RAW data.

For example ACCEL_XOUT_H is 11000110 and ACCEL_XOUT_L is 00101110 then ACCEL_XOUT will be (11001110 << 8 | 00101110) = 1100111000101110 a 16 bit RAW value.

Why I am constantly saying this is a RAW value is because you can see in the diagram that for a Full-Scale ‘2g’ the sensitivity is 16834 LSB/g. So to get the actual value we need to divide this RAW value by 16384.

The code for the same is as follows:

void MPU_Accel_Read_(MPU6050_ACCEL_t *Mpu_Accel)

{

        uint8_t Read_Data[6];

 

        HAL_I2C_Mem_Read (&hi2c1, MPU6050_ADDR, REG_ACCEL_XOUT_H, 1, Read_Data, 6, 1000);

 

        int16_t Accel_X_RAW = (int16_t)(Rec_Data[0] << 8 | Read_Data [1]);

        int16_t Accel_Y_RAW = (int16_t)(Rec_Data[2] << 8 | Read_Data [3]);

        int16_t Accel_Z_RAW = (int16_t)(Rec_Data[4] << 8 | Read_Data [5]);

 

        Mpu_Accel -> Accel_X = (Accel_X_RAW)/16384.0;

        Mpu_Accel -> Accel_Y = (Accel_Y_RAW)/16384.0;

        Mpu_Accel -> Accel_Z = (Accel_Z_RAW)/16384.0;

}

Similar is the case for Gyroscope Readings.

Below is the algorithm for the same.

Here we will use REG_GYRO_XOUT_H (0x43 – 0x48).

Here it is clearly being observed that it is a 16 bit data being stored as [15:8] and [7:0] in the HIGHER and LOWER GYRO_XOUT respectively.

Hence we need a 6 bytes array to store the lower and higher values of X, Y, Z axis respectively. 

Also we will be doing a bitwise left shift operation along with bit wise OR to get the 16 bit RAW data.

Lastly, the RAW value for a Full-Scale ‘250 dps’ sensitivity is131 LSB/dps. So to get the actual value we need to divide this RAW value by 131..

The code is as follows:

void MPU_Gyro_Read (MPU6050_GYRO_t *Mpu_Gyro)

{

        uint8_t Read_Data[6];

 

        HAL_I2C_Mem_Read (&hi2c1, MPU6050_ADDR, REG_GYRO_XOUT_H, 1, Read_Data, 6, 1000);

 

        int16_t Gyro_X_RAW = (int16_t)(Rec_Data[0] << 8 | Read_Data [1]);

        int16_t Gyro_Y_RAW = (int16_t)(Rec_Data[2] << 8 | Read_Data [3]);

        int16_t Gyro_Z_RAW = (int16_t)(Rec_Data[4] << 8 | Read_Data [5]);

 

        Mpu_Gyro -> Gyro_X = (Gyro_X_RAW)/131.0;

        Mpu_Gyro -> Gyro_Y = (Gyro_Y_RAW)/131.0;

        Mpu_Gyro -> Gyro_Z = (Gyro_Z_RAW)/131.0;

}

Hooray, We are completed with the I2C implementation and understated the protocol with MPU6050 along with HAL APIs.

About Author

Kunal Gupta
Author: Kunal Gupta

Author

Kunal Gupta

Leave a comment

Stay Updated With Us

Error: Contact form not found.

      Blog