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)


Giroscópio na prática: taxa de rotação, integração no tempo e ângulo estável com fusão (filtro complementar)

O giroscópio do MPU6050 mede velocidade angular em cada eixo, tipicamente em graus por segundo (°/s) depois que você converte usando o fator de escala correto. Diferente do acelerômetro, que “vê” a gravidade e por isso entrega uma referência absoluta de inclinação quando o movimento é suave, o giroscópio é excelente para captar mudanças rápidas de orientação. O ponto crítico é que o giroscópio tem bias (offset) e ruído; quando você integra ( \omega(t) ) no tempo para obter ângulo, qualquer pequeno bias vira uma deriva que cresce continuamente.

A integração é conceitualmente simples: se \( \omega \) é a taxa em °/s e \( \Delta t \) é o tempo entre amostras em segundos, então \( \Delta \theta \approx \omega \cdot \Delta t \). O desafio real é garantir \( \Delta t \) bem medido, compensar o bias e, para ficar robusto, “puxar” o ângulo de volta usando o acelerômetro como referência lenta. É exatamente isso que um filtro complementar faz: passa-alta no giroscópio (movimento rápido) e passa-baixa no acelerômetro (referência lenta).

1) Calibrando o bias do giroscópio (do jeito que funciona em bancada)

Antes de integrar, você precisa estimar o bias com o sensor parado alguns segundos. A ideia é bem direta: ler várias amostras, somar, tirar a média, e considerar isso como “zero”. Se o sensor estiver parado, a taxa real deveria ser ~0 °/s; o que sobrar é bias.

#include <stdint.h>

typedef struct {
    float gx_bias_dps;
    float gy_bias_dps;
    float gz_bias_dps;
} mpu6050_gyro_bias_t;

/**
 * @brief Estima bias do giroscópio assumindo o sensor parado.
 *
 * @param read_cb Função que já lê e converte para °/s (use mpu6050_read_raw + convert_to_si).
 * @param n Número de amostras para média (ex.: 500 a 2000 dependendo da sua taxa).
 */
bool mpu6050_estimate_gyro_bias(mpu6050_cfg_t *cfg,
                               mpu6050_gyro_bias_t *bias,
                               uint16_t n)
{
    double sx = 0.0, sy = 0.0, sz = 0.0;

    for (uint16_t i = 0; i < n; i++) {
        mpu6050_raw_t raw;
        mpu6050_si_t si;

        if (!mpu6050_read_raw(&raw)) return false;
        mpu6050_convert_to_si(cfg, &raw, &si);

        sx += si.gx_dps;
        sy += si.gy_dps;
        sz += si.gz_dps;

        // aqui você normalmente espera o período de amostragem (delay ou loop sincronizado)
    }

    bias->gx_bias_dps = (float)(sx / n);
    bias->gy_bias_dps = (float)(sy / n);
    bias->gz_bias_dps = (float)(sz / n);
    return true;
}

O motivo de fazer isso no começo do firmware é que o bias varia com temperatura, montagem e até com a alimentação. Mesmo que você use o DMP depois, entender e validar o bias com leitura crua te dá controle e diagnósticos melhores quando algo “parece errado”.

2) Integração do giroscópio para obter ângulo (roll/pitch) e por que deriva

Agora sim você integra. Aqui eu vou integrar roll e pitch usando \(g_x\) e \(g_y\) (em °/s), subtraindo o bias. O yaw (rotação em torno de Z) também dá para integrar, mas sem magnetômetro ele deriva sem referência absoluta; no MPU6050 puro, yaw só fica “bom” com fusão avançada e ainda assim deriva com o tempo se você não tiver referência externa.

#include <math.h>

typedef struct {
    float roll_deg;
    float pitch_deg;
} mpu6050_angles_t;

/**
 * @brief Integra giroscópio (com bias removido) para atualizar ângulos.
 *
 * @param dt_s Intervalo de amostragem em segundos (muito importante medir bem).
 */
void mpu6050_integrate_gyro(const mpu6050_si_t *si,
                            const mpu6050_gyro_bias_t *bias,
                            float dt_s,
                            mpu6050_angles_t *angles_io)
{
    const float gx = si->gx_dps - bias->gx_bias_dps;
    const float gy = si->gy_dps - bias->gy_bias_dps;

    // Integração Euler: theta(t+dt) = theta(t) + omega * dt
    angles_io->roll_deg  += gx * dt_s;
    angles_io->pitch_deg += gy * dt_s;
}

Se você rodar isso “puro”, vai perceber um comportamento muito típico: no começo parece perfeito, mas depois de alguns segundos/minutos o ângulo começa a “andar sozinho”. Isso é a deriva causada por bias residual e por ruído integrado. Por isso a gente corrige com o acelerômetro em baixa frequência.

3) Filtro complementar: giroscópio rápido + acelerômetro estável (pitch/roll)

O filtro complementar pode ser entendido como uma mistura ponderada: você confia mais no giroscópio para o curto prazo (porque ele é suave e responde rápido) e usa o acelerômetro para “puxar” o valor de volta lentamente (porque ele não deriva, mas sofre com aceleração linear e vibração). Uma forma canônica é:

\[
\theta = \alpha(\theta + \omega \Delta t) + (1-\alpha)\theta_{acc}
\]

onde \(\alpha\) geralmente é algo como 0.98 (mas você ajusta conforme sua taxa de amostragem e ruído).

A seguir, eu amarro tudo: calcula inclinação por acelerômetro (a função da seção anterior), integra gyro, e faz o blend.

#include <math.h>

typedef struct {
    float alpha; // 0..1 (ex.: 0.98)
} mpu6050_comp_filter_t;

void mpu6050_complementary_update(const mpu6050_si_t *si,
                                  const mpu6050_gyro_bias_t *bias,
                                  float dt_s,
                                  const mpu6050_comp_filter_t *f,
                                  mpu6050_angles_t *angles_io)
{
    // 1) Ângulo vindo do acelerômetro (referência lenta)
    mpu6050_tilt_t tilt_acc;
    mpu6050_tilt_from_accel(si->ax_g, si->ay_g, si->az_g, &tilt_acc);

    // 2) Predição pelo giroscópio (movimento rápido)
    const float gx = si->gx_dps - bias->gx_bias_dps;
    const float gy = si->gy_dps - bias->gy_bias_dps;

    const float roll_pred  = angles_io->roll_deg  + gx * dt_s;
    const float pitch_pred = angles_io->pitch_deg + gy * dt_s;

    // 3) Combinação (complementar)
    angles_io->roll_deg  = f->alpha * roll_pred  + (1.0f - f->alpha) * tilt_acc.roll_deg;
    angles_io->pitch_deg = f->alpha * pitch_pred + (1.0f - f->alpha) * tilt_acc.pitch_deg;
}

O que essa função faz, fisicamente, é “deixar o giroscópio mandar” a curto prazo e impedir que o ângulo se perca a longo prazo. Se você usar o sensor num robô, drone pequeno, gimbal simples ou wearables, esse filtro costuma ser a primeira solução realmente utilizável antes de entrar em quaternions. A trigonometria do acelerômetro (com atan2 e a normalização no pitch) é justamente o que fornece a referência lenta que impede a deriva.

4) Medindo o dt direito (onde muita gente erra)

Se o seu dt_s oscilar muito, a integração vira ruído. O ideal é ter um timer de alta resolução e calcular dt_s como diferença entre timestamps. Em STM32, por exemplo, você normalmente pega um contador de microssegundos; em AVR, pode ser micros(); em ESP32, esp_timer_get_time(). O ponto é: o filtro depende de dt_s confiável.

Aqui vai um esqueleto genérico usando microssegundos, para você adaptar na sua plataforma:

extern uint32_t micros32(void); // você implementa: retorna microssegundos (overflow ok)

static float dt_from_micros(uint32_t *t_prev_us)
{
    const uint32_t now = micros32();
    const uint32_t dt_us = (uint32_t)(now - *t_prev_us); // overflow-friendly em unsigned
    *t_prev_us = now;
    return (float)dt_us * 1e-6f;
}

Você chama dt_from_micros() uma vez por loop e usa o retorno como dt_s.

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