MCU & FPGA Sensores MPU6050 em projetos reais: do “dado cru” ao ângulo (e preparando terreno para o DMP)

MPU6050 em projetos reais: do “dado cru” ao ângulo (e preparando terreno para o DMP)


Table of Contents

O MPU6050 é uma IMU (Unidade de Medição Inercial) de 6 eixos que integra acelerômetro (3 eixos) e giroscópio (3 eixos) e expõe tudo via I²C. A forma mais confiável de “começar certo” é dominar primeiro a leitura bruta e as conversões (LSB → unidades físicas), porque isso te dá um baseline para validar qualquer etapa posterior (filtros, fusão, DMP e quaternions). A própria InvenSense/TDK documenta o mapa de registradores e a especificação do dispositivo, que é o que vamos seguir aqui. (TDK InvenSense)

Nesta primeira seção eu vou te entregar um “núcleo” em C: inicialização mínima, leitura de acelerômetro/giroscópio e conversão para g, m/s² e °/s. Na próxima seção, eu sigo para inclinação por trigonometria (pitch/roll por acelerômetro), depois integração do giroscópio e, em seguida, DMP + FIFO + quaternions (onde o ganho é enorme em estabilidade e praticidade).


Leitura bruta e conversão para unidades físicas (C, I²C genérico)

A base do MPU6050 é simples: você configura alguns registradores de controle (clock, escalas, taxa de amostragem) e lê 14 bytes contínuos a partir do registrador de aceleração. Esses 14 bytes contêm AX, AY, AZ, temperatura, GX, GY, GZ, cada um em 16 bits com sinal (two’s complement), com byte alto seguido de byte baixo. O endereço do registrador inicial e os registradores de controle usados abaixo estão no “Register Map and Descriptions”. (TDK InvenSense)

Código (driver mínimo) — com callbacks de I²C

O código abaixo não “inventa HAL”. Ele assume que você já tem I²C funcionando e consegue implementar duas funções: i2c_write_reg() e i2c_read_regs(). Isso deixa o exemplo portável para STM32, AVR, ESP32, Linux, etc.

#include <stdint.h>
#include <stdbool.h>

#ifndef MPU6050_I2C_ADDR
// AD0=0 -> 0x68, AD0=1 -> 0x69 (endereço de 7 bits)
#define MPU6050_I2C_ADDR 0x68
#endif

// Registradores principais (MPU-6000/MPU-6050 Register Map)
#define REG_SMPLRT_DIV    0x19
#define REG_CONFIG        0x1A
#define REG_GYRO_CONFIG   0x1B
#define REG_ACCEL_CONFIG  0x1C
#define REG_INT_ENABLE    0x38
#define REG_ACCEL_XOUT_H  0x3B
#define REG_PWR_MGMT_1    0x6B
#define REG_WHO_AM_I      0x75

// Bits e campos úteis
#define PWR1_SLEEP_BIT    6

// FS_SEL (GYRO_CONFIG[4:3]) e AFS_SEL (ACCEL_CONFIG[4:3])
typedef enum {
    MPU6050_GYRO_FS_250  = 0, // ±250 °/s
    MPU6050_GYRO_FS_500  = 1, // ±500 °/s
    MPU6050_GYRO_FS_1000 = 2, // ±1000 °/s
    MPU6050_GYRO_FS_2000 = 3  // ±2000 °/s
} mpu6050_gyro_fs_t;

typedef enum {
    MPU6050_ACCEL_FS_2G  = 0, // ±2 g
    MPU6050_ACCEL_FS_4G  = 1, // ±4 g
    MPU6050_ACCEL_FS_8G  = 2, // ±8 g
    MPU6050_ACCEL_FS_16G = 3  // ±16 g
} mpu6050_accel_fs_t;

typedef struct {
    mpu6050_gyro_fs_t  gyro_fs;
    mpu6050_accel_fs_t accel_fs;

    // fatores de escala (LSB por unidade)
    float accel_lsb_per_g;
    float gyro_lsb_per_dps;
} mpu6050_cfg_t;

typedef struct {
    int16_t ax, ay, az;
    int16_t gx, gy, gz;
    int16_t temp_raw;
} mpu6050_raw_t;

typedef struct {
    // aceleração
    float ax_g, ay_g, az_g;        // em g
    float ax_ms2, ay_ms2, az_ms2;  // em m/s²
    // rotação
    float gx_dps, gy_dps, gz_dps;  // em °/s
} mpu6050_si_t;

// Você implementa essas duas funções na sua plataforma:
extern bool i2c_write_reg(uint8_t addr7, uint8_t reg, uint8_t value);
extern bool i2c_read_regs(uint8_t addr7, uint8_t start_reg, uint8_t *buf, uint16_t len);

static float accel_lsb_per_g_from_fs(mpu6050_accel_fs_t fs) {
    // Do datasheet/register map: sensibilidade depende do AFS_SEL. :contentReference[oaicite:2]{index=2}
    switch (fs) {
        case MPU6050_ACCEL_FS_2G:  return 16384.0f;
        case MPU6050_ACCEL_FS_4G:  return 8192.0f;
        case MPU6050_ACCEL_FS_8G:  return 4096.0f;
        case MPU6050_ACCEL_FS_16G: return 2048.0f;
        default: return 16384.0f;
    }
}

static float gyro_lsb_per_dps_from_fs(mpu6050_gyro_fs_t fs) {
    // Do datasheet/register map: sensibilidade depende do FS_SEL. :contentReference[oaicite:3]{index=3}
    switch (fs) {
        case MPU6050_GYRO_FS_250:  return 131.0f;
        case MPU6050_GYRO_FS_500:  return 65.5f;
        case MPU6050_GYRO_FS_1000: return 32.8f;
        case MPU6050_GYRO_FS_2000: return 16.4f;
        default: return 131.0f;
    }
}

bool mpu6050_init(const mpu6050_cfg_t *cfg) {
    // 1) Acorda o chip e seleciona clock interno padrão (PWR_MGMT_1).
    // Escrever 0 em PWR_MGMT_1 limpa SLEEP e deixa o clock interno ativo. :contentReference[oaicite:4]{index=4}
    if (!i2c_write_reg(MPU6050_I2C_ADDR, REG_PWR_MGMT_1, 0x00)) return false;

    // 2) Configura escalas de acel/gyro nos campos [4:3]
    // ACCEL_CONFIG: AFS_SEL em bits 4:3
    uint8_t accel_cfg = ((uint8_t)cfg->accel_fs & 0x03u) << 3;
    if (!i2c_write_reg(MPU6050_I2C_ADDR, REG_ACCEL_CONFIG, accel_cfg)) return false;

    // GYRO_CONFIG: FS_SEL em bits 4:3
    uint8_t gyro_cfg = ((uint8_t)cfg->gyro_fs & 0x03u) << 3;
    if (!i2c_write_reg(MPU6050_I2C_ADDR, REG_GYRO_CONFIG, gyro_cfg)) return false;

    // 3) (Opcional, mas recomendado) filtro digital passa-baixa (DLPF) e taxa de amostragem
    // CONFIG: DLPF_CFG em bits [2:0]. Ex.: 3 => um corte típico para reduzir ruído (ver tabela no register map). :contentReference[oaicite:5]{index=5}
    if (!i2c_write_reg(MPU6050_I2C_ADDR, REG_CONFIG, 0x03)) return false;

    // SMPLRT_DIV define taxa efetiva: sample_rate = gyro_output_rate / (1 + SMPLRT_DIV).
    // gyro_output_rate é 8 kHz quando DLPF_CFG=0, senão 1 kHz (ver register map). :contentReference[oaicite:6]{index=6}
    if (!i2c_write_reg(MPU6050_I2C_ADDR, REG_SMPLRT_DIV, 0x04)) return false; // exemplo

    // 4) Interrupções ficam para depois (no DMP a gente usa bastante INT + FIFO).
    if (!i2c_write_reg(MPU6050_I2C_ADDR, REG_INT_ENABLE, 0x00)) return false;

    return true;
}

bool mpu6050_read_raw(mpu6050_raw_t *out) {
    uint8_t buf[14];
    if (!i2c_read_regs(MPU6050_I2C_ADDR, REG_ACCEL_XOUT_H, buf, 14)) return false;

    // Cada eixo é big-endian: High, Low
    out->ax = (int16_t)((buf[0] << 8) | buf[1]);
    out->ay = (int16_t)((buf[2] << 8) | buf[3]);
    out->az = (int16_t)((buf[4] << 8) | buf[5]);
    out->temp_raw = (int16_t)((buf[6] << 8) | buf[7]);
    out->gx = (int16_t)((buf[8]  << 8) | buf[9]);
    out->gy = (int16_t)((buf[10] << 8) | buf[11]);
    out->gz = (int16_t)((buf[12] << 8) | buf[13]);

    return true;
}

void mpu6050_convert_to_si(const mpu6050_cfg_t *cfg, const mpu6050_raw_t *raw, mpu6050_si_t *out) {
    const float lsb_per_g   = accel_lsb_per_g_from_fs(cfg->accel_fs);
    const float lsb_per_dps = gyro_lsb_per_dps_from_fs(cfg->gyro_fs);

    // aceleração em g
    out->ax_g = (float)raw->ax / lsb_per_g;
    out->ay_g = (float)raw->ay / lsb_per_g;
    out->az_g = (float)raw->az / lsb_per_g;

    // g -> m/s² (g padrão ~ 9.80665)
    const float g0 = 9.80665f;
    out->ax_ms2 = out->ax_g * g0;
    out->ay_ms2 = out->ay_g * g0;
    out->az_ms2 = out->az_g * g0;

    // giro em °/s
    out->gx_dps = (float)raw->gx / lsb_per_dps;
    out->gy_dps = (float)raw->gy / lsb_per_dps;
    out->gz_dps = (float)raw->gz / lsb_per_dps;
}

O que esse código está fazendo (de verdade)

A inicialização começa acordando o sensor em PWR_MGMT_1. Se o bit SLEEP estiver ativo, o MPU não mede nada; então zerar esse registrador é o jeito mais direto de sair do modo sleep. O mapa de registradores descreve esse registrador e os campos de escala do acelerômetro e giroscópio que usamos em seguida. (TDK InvenSense)

Logo depois, nós configuramos os ranges. Isso é crucial porque a conversão de “número cru” para unidade física depende diretamente disso. Quando você seleciona, por exemplo, ±2 g, o datasheet/regmap define o fator típico 16384 LSB/g; se trocar para ±8 g, vira 4096 LSB/g. O mesmo vale para o giroscópio: em ±250 °/s é comum usar 131 LSB/(°/s), e em ±2000 °/s cai para 16.4 LSB/(°/s). Essas relações são as que tornam a conversão objetiva e auditável. (TDK InvenSense)

A leitura em ACCEL_XOUT_H pega um “bloco coeso” de 14 bytes, o que reduz risco de você ler um eixo “antes” e outro “depois” de uma atualização interna. Esse detalhe parece pequeno, mas evita inconsistências sutis quando você começa a amostrar rápido. A ordem dos registradores nesse bloco (accel, temp, gyro) também está definida no register map. (TDK InvenSense)


Observação importante de bancada (qualidade de módulos no mercado)

Existe muita placa “MPU6050” no mercado com variação de qualidade (inclusive peças suspeitas/fora de especificação), o que aparece na prática como offsets absurdos, ruído fora do padrão ou instabilidade. Se seu baseline “cru” já estiver muito estranho, vale desconfiar do módulo antes de culpar o algoritmo. (Arduino Forum)

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x