Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

STM32 Control of MPU6050 for Six‑Axis Data and Euler Angles Using DMP

Tech 1

STM32 Control of MPU6050 for Six‑Axis Data and Euler Angles Using DMP

The MPU6050 combines a 3‑axis gyroscope, a 3‑axis accelerometer, and an embedded Digital Motion Processor (DMP). The auxiliary I2C port can host an external magnetometer to extend to nine axes. When the DMP is enabled, quaternions and fused orinetation can be produced on-chip, reducing MCU load.

  • Output formats: rotation matrix, quaternion, and optionally Euler angles via host computation
  • Gyroscope ranges: ±250/±500/±1000/±2000 °/s (131 LSB/(°/s) at ±250)
  • Accelerometer ranges: ±2/±4/±8/±16 g
  • 1 kB FIFO, Digital Low-Pass Filter (DLPF), on-chip temperature sansor
  • I2C up to 400 kHz; VLOGIC as low as 1.8 V; typical GYRO current ≈ 5 mA

Pin usage for six-axis output typically requries: VCC (3.3 V), GND, SCL, SDA, and AD0. AD0 selects the I2C adress (0x68 when low, 0x69 when high). INT can be used for data-ready or DMP interrupts. XCL/XDA are used only when an external sensor is connected to the auxiliary bus.

Key registers (7-bit address 0x68 when AD0=0):

  • Power Management 1 (0x6B)
    • DEVICE_RESET, SLEEP, TEMP_DIS, CLKSEL[2:0] (commonly use PLL with X‑gyro)
  • Gyro Config (0x1B)
    • FS_SEL[1:0] sets gyro full-scale
  • Accel Config (0x1C)
    • AFS_SEL[1:0] sets accel full-scale
  • Sample Rate Divider (0x19)
    • Sample Rate = Gyro Output Rate / (1 + SMPLRT_DIV)
  • Config (0x1A)
    • DLPF settings; typical choice is half of sample rate
  • FIFO Enable (0x23)
    • Per-sensor FIFO enables (often unused for simple polled reads)
  • Temperature (0x41/0x42)
    • Temp(°C) = 36.53 + raw/340

Firmware layout on STM32:

  • A software I2C (bit‑bang) layer to talk to the sensor
  • Register access helpers for single and burst read/writes
  • Device init and configuration (FS ranges, rate, DLPF)
  • DMP integration (InvenSense driver port) to fetch quaternions
  • Quaternion to Euler angle conversion on the MCU

I2C bit‑bang layer (PB10=SCL, PB11=SDA; AD0 on PA15):

Header (imu_i2c.h):

#ifndef IMU_I2C_H
#define IMU_I2C_H

#include "stm32f10x.h"
#include "delay.h"

/* PB11 dynamic direction control for SDA */
#define IMU_SDA_IN()   {GPIOB->CRH &= 0xFFFF0FFF; GPIOB->CRH |= 8 << 12;}   /* Input with pull-up */
#define IMU_SDA_OUT()  {GPIOB->CRH &= 0xFFFF0FFF; GPIOB->CRH |= 3 << 12;}   /* Push-pull output */

#define IMU_SCL_HIGH()   GPIO_SetBits(GPIOB, GPIO_Pin_10)
#define IMU_SCL_LOW()    GPIO_ResetBits(GPIOB, GPIO_Pin_10)
#define IMU_SDA_HIGH()   GPIO_SetBits(GPIOB, GPIO_Pin_11)
#define IMU_SDA_LOW()    GPIO_ResetBits(GPIOB, GPIO_Pin_11)

#define IMU_AD0_HIGH()   GPIO_SetBits(GPIOA, GPIO_Pin_15)
#define IMU_AD0_LOW()    GPIO_ResetBits(GPIOA, GPIO_Pin_15)

#define IMU_SDA_READ()   GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)

static inline void imu_i2c_delay(void) { delay_us(2); }

void imu_i2c_init(void);
void imu_i2c_start(void);
void imu_i2c_stop(void);
uint8_t imu_i2c_wait_ack(void);
void imu_i2c_send_ack(void);
void imu_i2c_send_nack(void);
void imu_i2c_write_byte(uint8_t byte);
uint8_t imu_i2c_read_byte(uint8_t ack);

#endif

Source (imu_i2c.c):

#include "imu_i2c.h"

/* SCL:PB10, SDA:PB11, AD0:PA15 */
void imu_i2c_init(void)
{
    GPIO_InitTypeDef gpio;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    /* Free PA15 from JTAG */
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

    /* PB10/PB11 outputs default high */
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    gpio.GPIO_Mode  = GPIO_Mode_Out_PP;
    gpio.GPIO_Pin   = GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_Init(GPIOB, &gpio);

    /* PA15 as AD0 control */
    gpio.GPIO_Pin = GPIO_Pin_15;
    gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &gpio);

    IMU_SCL_HIGH();
    IMU_SDA_HIGH();
    IMU_AD0_LOW(); /* I2C address = 0x68 */
}

void imu_i2c_start(void)
{
    IMU_SDA_OUT();
    IMU_SDA_HIGH();
    IMU_SCL_HIGH();
    imu_i2c_delay();
    IMU_SDA_LOW();
    imu_i2c_delay();
    IMU_SCL_LOW();
}

void imu_i2c_stop(void)
{
    IMU_SDA_OUT();
    IMU_SDA_LOW();
    IMU_SCL_HIGH();
    imu_i2c_delay();
    IMU_SDA_HIGH();
    imu_i2c_delay();
}

uint8_t imu_i2c_wait_ack(void)
{
    uint16_t t = 0;
    IMU_SDA_IN();
    IMU_SDA_HIGH(); /* release */
    IMU_SCL_HIGH();
    imu_i2c_delay();
    while (IMU_SDA_READ()) {
        if (++t > 500) {
            imu_i2c_stop();
            return 1; /* NACK timeout */
        }
    }
    IMU_SCL_LOW();
    return 0;
}

void imu_i2c_send_ack(void)
{
    IMU_SDA_OUT();
    IMU_SDA_LOW();
    imu_i2c_delay();
    IMU_SCL_HIGH();
    imu_i2c_delay();
    IMU_SCL_LOW();
}

void imu_i2c_send_nack(void)
{
    IMU_SDA_OUT();
    IMU_SDA_HIGH();
    imu_i2c_delay();
    IMU_SCL_HIGH();
    imu_i2c_delay();
    IMU_SCL_LOW();
}

void imu_i2c_write_byte(uint8_t byte)
{
    IMU_SDA_OUT();
    for (uint8_t i = 0; i < 8; ++i) {
        IMU_SCL_LOW();
        if (byte & 0x80) IMU_SDA_HIGH(); else IMU_SDA_LOW();
        byte <<= 1;
        imu_i2c_delay();
        IMU_SCL_HIGH();
        imu_i2c_delay();
    }
    IMU_SCL_LOW();
}

uint8_t imu_i2c_read_byte(uint8_t ack)
{
    uint8_t data = 0;
    IMU_SDA_IN();
    for (uint8_t i = 0; i < 8; ++i) {
        IMU_SCL_LOW();
        imu_i2c_delay();
        IMU_SCL_HIGH();
        data <<= 1;
        if (IMU_SDA_READ()) data |= 0x01;
        imu_i2c_delay();
    }
    if (ack) imu_i2c_send_ack(); else imu_i2c_send_nack();
    return data;
}

MPU6050 register helpers and configuraton:

Header (mpu6050.h):

#ifndef MPU6050_IFACE_H
#define MPU6050_IFACE_H

#include "stm32f10x.h"
#include "imu_i2c.h"

#define MPU_I2C_ADDR       0x68

/* Selected register addresses */
#define MPU_SAMPLE_RATE_REG   0x19
#define MPU_CFG_REG           0x1A
#define MPU_GYRO_CFG_REG      0x1B
#define MPU_ACCEL_CFG_REG     0x1C
#define MPU_FIFO_EN_REG       0x23
#define MPU_INTBP_CFG_REG     0x37
#define MPU_INT_EN_REG        0x38
#define MPU_ACCEL_XOUTH_REG   0x3B
#define MPU_ACCEL_XOUTL_REG   0x3C
#define MPU_ACCEL_YOUTH_REG   0x3D
#define MPU_ACCEL_YOUTL_REG   0x3E
#define MPU_ACCEL_ZOUTH_REG   0x3F
#define MPU_ACCEL_ZOUTL_REG   0x40
#define MPU_TEMP_OUTH_REG     0x41
#define MPU_TEMP_OUTL_REG     0x42
#define MPU_GYRO_XOUTH_REG    0x43
#define MPU_GYRO_XOUTL_REG    0x44
#define MPU_GYRO_YOUTH_REG    0x45
#define MPU_GYRO_YOUTL_REG    0x46
#define MPU_GYRO_ZOUTH_REG    0x47
#define MPU_GYRO_ZOUTL_REG    0x48
#define MPU_USER_CTRL_REG     0x6A
#define MPU_PWR_MGMT1_REG     0x6B
#define MPU_PWR_MGMT2_REG     0x6C
#define MPU_DEVICE_ID_REG     0x75

/* I2C accessors */
uint8_t imu_read_reg8(uint8_t reg);
uint8_t imu_write_reg8(uint8_t reg, uint8_t val);
uint8_t imu_burst_read(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf);
uint8_t imu_burst_write(uint8_t addr, uint8_t reg, uint8_t len, const uint8_t *buf);

/* High-level API */
uint8_t imu_init(void);
uint8_t imu_set_gyro_fsr(uint8_t fsr);
uint8_t imu_set_accel_fsr(uint8_t fsr);
uint8_t imu_set_lpf(uint16_t hz);
uint8_t imu_set_rate(uint16_t hz);
short   imu_get_temperature(void);
uint8_t imu_get_gyro_raw(short *gx, short *gy, short *gz);
uint8_t imu_get_accel_raw(short *ax, short *ay, short *az);

#endif

Source (mpu6050.c):

#include "mpu6050.h"
#include "delay.h"

static void imu_select_addr_low(void)
{
    /* AD0 already configured in imu_i2c_init */
    IMU_AD0_LOW();
}

uint8_t imu_read_reg8(uint8_t reg)
{
    uint8_t val;
    imu_i2c_start();
    imu_i2c_write_byte((MPU_I2C_ADDR << 1) | 0);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 0xFF; }
    imu_i2c_write_byte(reg);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 0xFF; }
    imu_i2c_start();
    imu_i2c_write_byte((MPU_I2C_ADDR << 1) | 1);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 0xFF; }
    val = imu_i2c_read_byte(0);
    imu_i2c_stop();
    return val;
}

uint8_t imu_write_reg8(uint8_t reg, uint8_t val)
{
    imu_i2c_start();
    imu_i2c_write_byte((MPU_I2C_ADDR << 1) | 0);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    imu_i2c_write_byte(reg);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    imu_i2c_write_byte(val);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    imu_i2c_stop();
    return 0;
}

uint8_t imu_burst_read(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)
{
    imu_i2c_start();
    imu_i2c_write_byte((addr << 1) | 0);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    imu_i2c_write_byte(reg);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    imu_i2c_start();
    imu_i2c_write_byte((addr << 1) | 1);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    while (len--) {
        *buf++ = imu_i2c_read_byte(len ? 1 : 0);
    }
    imu_i2c_stop();
    return 0;
}

uint8_t imu_burst_write(uint8_t addr, uint8_t reg, uint8_t len, const uint8_t *buf)
{
    imu_i2c_start();
    imu_i2c_write_byte((addr << 1) | 0);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    imu_i2c_write_byte(reg);
    if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    while (len--) {
        imu_i2c_write_byte(*buf++);
        if (imu_i2c_wait_ack()) { imu_i2c_stop(); return 1; }
    }
    imu_i2c_stop();
    return 0;
}

uint8_t imu_set_gyro_fsr(uint8_t fsr)
{
    /* fsr: 0=±250,1=±500,2=±1000,3=±2000 dps */
    return imu_write_reg8(MPU_GYRO_CFG_REG, fsr << 3);
}

uint8_t imu_set_accel_fsr(uint8_t fsr)
{
    /* fsr: 0=±2g,1=±4g,2=±8g,3=±16g */
    return imu_write_reg8(MPU_ACCEL_CFG_REG, fsr << 3);
}

uint8_t imu_set_lpf(uint16_t hz)
{
    /* Map to DLPF config codes (per datasheet) */
    uint8_t cfg;
    if (hz >= 188) cfg = 1;
    else if (hz >= 98) cfg = 2;
    else if (hz >= 42) cfg = 3;
    else if (hz >= 20) cfg = 4;
    else if (hz >= 10) cfg = 5;
    else cfg = 6;
    return imu_write_reg8(MPU_CFG_REG, cfg);
}

uint8_t imu_set_rate(uint16_t hz)
{
    if (hz > 1000) hz = 1000;
    if (hz < 4) hz = 4;
    uint8_t div = (uint8_t)(1000 / hz - 1);
    if (imu_write_reg8(MPU_SAMPLE_RATE_REG, div)) return 1;
    return imu_set_lpf(hz / 2);
}

short imu_get_temperature(void)
{
    uint8_t b[2];
    imu_burst_read(MPU_I2C_ADDR, MPU_TEMP_OUTH_REG, 2, b);
    int16_t raw = ((int16_t)b[0] << 8) | b[1];
    float t = 36.53f + ((float)raw) / 340.0f;
    return (short)(t * 100.0f); /* centi-degrees C */
}

uint8_t imu_get_gyro_raw(short *gx, short *gy, short *gz)
{
    uint8_t b[6];
    if (imu_burst_read(MPU_I2C_ADDR, MPU_GYRO_XOUTH_REG, 6, b)) return 1;
    *gx = (int16_t)(((uint16_t)b[0] << 8) | b[1]);
    *gy = (int16_t)(((uint16_t)b[2] << 8) | b[3]);
    *gz = (int16_t)(((uint16_t)b[4] << 8) | b[5]);
    return 0;
}

uint8_t imu_get_accel_raw(short *ax, short *ay, short *az)
{
    uint8_t b[6];
    if (imu_burst_read(MPU_I2C_ADDR, MPU_ACCEL_XOUTH_REG, 6, b)) return 1;
    *ax = (int16_t)(((uint16_t)b[0] << 8) | b[1]);
    *ay = (int16_t)(((uint16_t)b[2] << 8) | b[3]);
    *az = (int16_t)(((uint16_t)b[4] << 8) | b[5]);
    return 0;
}

uint8_t imu_init(void)
{
    uint8_t who;

    imu_i2c_init();
    imu_select_addr_low();

    /* Reset then wake */
    imu_write_reg8(MPU_PWR_MGMT1_REG, 0x80);
    delay_ms(100);
    imu_write_reg8(MPU_PWR_MGMT1_REG, 0x00);

    /* Disable interrupts/FIFO, set INT active low */
    imu_write_reg8(MPU_INT_EN_REG, 0x00);
    imu_write_reg8(MPU_USER_CTRL_REG, 0x00);
    imu_write_reg8(MPU_FIFO_EN_REG, 0x00);
    imu_write_reg8(MPU_INTBP_CFG_REG, 0x80);

    /* WHO_AM_I should read 0x68 with AD0 low */
    who = imu_read_reg8(MPU_DEVICE_ID_REG);
    if (who != MPU_I2C_ADDR) return 1;

    /* Clock = PLL with X‑gyro; enable all axes */
    imu_write_reg8(MPU_PWR_MGMT1_REG, 0x01);
    imu_write_reg8(MPU_PWR_MGMT2_REG, 0x00);

    /* FS ranges and rate */
    if (imu_set_gyro_fsr(3)) return 2;     /* ±2000 dps */
    if (imu_set_accel_fsr(0)) return 3;    /* ±2 g */
    if (imu_set_rate(50)) return 4;        /* 50 Hz */

    return 0;
}

DMP integration (using InvenSense drivers):

Provide the I2C and delay hooks expected by the DMP library:

/* Map to local I2C helpers used by InvenSense drivers */
#define i2c_write  imu_burst_write
#define i2c_read   imu_burst_read
#define delay_ms   delay_ms

Initialization of the DMP:

#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"

/* Orientation matrix (example for board alignment); adjust to your mount */
static const signed char gyro_orientation[9] = {
     1, 0, 0,
     0, 1, 0,
     0, 0, 1
};

#define DEFAULT_MPU_HZ  50

uint8_t dmp_startup(void)
{
    uint8_t r;

    /* Basic device init already done by imu_init() */
    if (mpu_init() != 0) return 10;

    r = mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL);      if (r) return 1;
    r = mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL);   if (r) return 2;
    r = mpu_set_sample_rate(DEFAULT_MPU_HZ);                if (r) return 3;
    r = dmp_load_motion_driver_firmware();                  if (r) return 4;
    r = dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation));
    if (r) return 5;
    r = dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT |
                           DMP_FEATURE_TAP |
                           DMP_FEATURE_ANDROID_ORIENT |
                           DMP_FEATURE_SEND_RAW_ACCEL |
                           DMP_FEATURE_SEND_CAL_GYRO |
                           DMP_FEATURE_GYRO_CAL);
    if (r) return 6;
    r = dmp_set_fifo_rate(DEFAULT_MPU_HZ);                  if (r) return 7;
    r = run_self_test();                                    if (r) return 8;
    r = mpu_set_dmp_state(1);                               if (r) return 9;
    return 0;
}

Getting Euler angles from the DMP quaternion:

#include <math.h>

uint8_t dmp_read_euler(float *pitch, float *roll, float *yaw)
{
    /* DMP writes quaternions in q30 format */
    const float q30 = 1073741824.0f; /* 2^30 */
    long q[4];
    unsigned long ts;
    short g[3], a[3];
    short sensors;
    unsigned char more;

    if (dmp_read_fifo(g, a, q, &ts, &sensors, &more)) return 1;
    if (!(sensors & INV_WXYZ_QUAT)) return 2;

    float q0 = q[0] / q30;
    float q1 = q[1] / q30;
    float q2 = q[2] / q30;
    float q3 = q[3] / q30;

    /* ZYX convention: pitch (x), roll (y), yaw (z) in degrees */
    *pitch = asinf(-2.0f * (q1 * q3 - q0 * q2)) * 57.29578f;
    *roll  = atan2f( 2.0f * (q2 * q3 + q0 * q1),
                    1.0f - 2.0f * (q1 * q1 + q2 * q2)) * 57.29578f;
    *yaw   = atan2f( 2.0f * (q1 * q2 + q0 * q3),
                     q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.29578f;
    return 0;
}

Example main() using the Standard Peripheral Library:

#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
#include "mpu6050.h"
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
#include <stdio.h>

int main(void)
{
    float pitch = 0, roll = 0, yaw = 0;
    short ax, ay, az;
    short gx, gy, gz;
    short centiC;

    NVIC_PriorityGroupConfig(2);
    delay_init();
    USART1_Init(115200);

    if (imu_init() != 0) {
        printf("IMU init failed\n");
        while (1) {}
    }
    if (dmp_startup() != 0) {
        printf("DMP init failed\n");
        while (1) {}
    }

    while (1) {
        if (dmp_read_euler(&pitch, &roll, &yaw) == 0) {
            centiC = imu_get_temperature();
            imu_get_accel_raw(&ax, &ay, &az);
            imu_get_gyro_raw(&gx, &gy, &gz);
            printf("pitch:%0.2f roll:%0.2f yaw:%0.2f temp:%0.2fC\r\n",
                   pitch, roll, yaw, centiC / 100.0f);
        }
        delay_ms(100);
    }
}

Notes:

  • If PA15 is used for AD0, disable JTAG with GPIO_Remap_SWJ_JTAGDisable to free the pin
  • Ensure I2C lines have pull-ups when bit‑banging
  • DLPF and sample rate must be selected with aliasing in mind; a common choice is DLPF ≈ rate/2
  • The orientation matrix must reflect the physical board mounting to obtain correct Euler angles

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.