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)



Aceleração “do objeto” e movimento: separando gravidade da aceleração linear (e por que integrar vira deriva)

Até aqui você já tem roll e pitch razoavelmente estáveis (via filtro complementar). Isso é o que permite dar o próximo passo de forma coerente: estimar qual parte do acelerômetro é gravidade e subtrair do sinal medido para sobrar a aceleração linear (aquela que realmente vem do movimento do objeto). Sem essa separação, qualquer tentativa de “obter movimento” vai confundir inclinação com aceleração e o resultado vira uma bagunça.

A ideia física é simples: o acelerômetro mede a soma da gravidade projetada no sensor com as acelerações lineares do corpo. Em notação vetorial, no referencial do sensor:

\[
\mathbf{a}{meas} \approx \mathbf{g}{sens} + \mathbf{a}_{lin}
\]

Logo:

\[
\mathbf{a}{lin} \approx \mathbf{a}{meas} – \mathbf{g}_{sens}
\]

O ponto crítico é estimar \(\mathbf{g}_{sens}\). Como você já tem roll e pitch, dá para reconstruir as componentes da gravidade no referencial do sensor usando trigonometria.

Estimando o vetor gravidade no referencial do sensor a partir de roll/pitch

Assumindo a convenção mais comum (roll = rotação em torno de X, pitch = rotação em torno de Y) e tomando o módulo da gravidade como 1 g, uma aproximação prática muito usada (suficiente para separar gravidade em aplicações comuns) é:

\[
g_x = -\sin(\theta)
\]
\[
g_y = \sin(\phi)\cos(\theta)
\]
\[
g_z = \cos(\phi)\cos(\theta)
\]

onde \(\phi\) é o roll e \(\theta\) é o pitch, em radianos. Esses termos são exatamente as projeções do vetor “para baixo” (gravidade) nos eixos do sensor após aplicar inclinações.

Código em C: gravidade estimada e aceleração linear (em g e em m/s²)

Aqui eu assumo que você está atualizando angles (roll/pitch) com o filtro complementar da seção anterior, e que si.ax_g, si.ay_g, si.az_g já estão em g.

#include <math.h>

typedef struct {
    float gx_g, gy_g, gz_g;     // gravidade estimada no sensor (em g)
    float ax_lin_g, ay_lin_g, az_lin_g; // aceleração linear (em g)
    float ax_lin_ms2, ay_lin_ms2, az_lin_ms2; // aceleração linear (em m/s²)
} mpu6050_linacc_t;

static float deg2rad(float deg) {
    return deg * ((float)M_PI / 180.0f);
}

/**
 * @brief Estima gravidade no frame do sensor a partir de roll/pitch e separa aceleração linear.
 *
 * Observação: sem magnetômetro, yaw não entra aqui; para remover gravidade, roll/pitch bastam.
 */
void mpu6050_linear_accel_from_angles(const mpu6050_si_t *si,
                                      const mpu6050_angles_t *angles,
                                      mpu6050_linacc_t *out)
{
    const float roll  = deg2rad(angles->roll_deg);
    const float pitch = deg2rad(angles->pitch_deg);

    // Gravidade estimada (em g) projetada nos eixos do sensor
    const float gx = -sinf(pitch);
    const float gy =  sinf(roll) * cosf(pitch);
    const float gz =  cosf(roll) * cosf(pitch);

    out->gx_g = gx;
    out->gy_g = gy;
    out->gz_g = gz;

    // Aceleração linear (medida - gravidade)
    out->ax_lin_g = si->ax_g - gx;
    out->ay_lin_g = si->ay_g - gy;
    out->az_lin_g = si->az_g - gz;

    // Converte para m/s²
    const float g0 = 9.80665f;
    out->ax_lin_ms2 = out->ax_lin_g * g0;
    out->ay_lin_ms2 = out->ay_lin_g * g0;
    out->az_lin_ms2 = out->az_lin_g * g0;
}

O que você deve ver em repouso, com o sensor parado e com roll/pitch bem estimados, é: ax_lin_g, ay_lin_g, az_lin_g ficando perto de zero (com algum ruído). Quando você desloca o objeto, essas componentes passam a representar aceleração linear real (por exemplo, uma “puxada” para frente aparece em X, uma lateral em Y, etc., dependendo de como o sensor está montado).

Movimento: integrar aceleração linear para velocidade e posição (e o “elefante na sala”)

Em teoria, dá para obter velocidade e posição integrando:

\[
\mathbf{v}(t+\Delta t) = \mathbf{v}(t) + \mathbf{a}_{lin}\Delta t
\]
\[
\mathbf{x}(t+\Delta t) = \mathbf{x}(t) + \mathbf{v}(t)\Delta t
\]

Na prática, em IMUs baratas, isso deriva rápido. O motivo é cruel e inevitável: qualquer offset pequeno na aceleração (mesmo 0.01 g) vira erro de velocidade que cresce linearmente e erro de posição que cresce quadraticamente. Por isso, “odometria inercial pura” costuma funcionar só por janelas curtas ou precisa de correções externas (GPS, visão, UWB, zero-velocity update em passos, contato no chão, etc.).

Mesmo assim, para demonstrar o método corretamente (e você poder medir o tamanho do erro na sua aplicação), segue um integrador simples, com um “truque” mínimo para reduzir lixo: um deadband (zona morta) para zerar acelerações muito pequenas (ruído), e um vazamento (leak) suave na velocidade para não acumular drift tão agressivamente quando o sistema “deveria estar parado”.

typedef struct {
    float vx, vy, vz; // m/s
    float px, py, pz; // m
} mpu6050_motion_t;

/**
 * @brief Integra aceleração linear para velocidade e posição (demonstração).
 *
 * @param deadband_ms2 Acelerações menores que isso são tratadas como zero (reduz ruído).
 * @param vel_leak 0..1 (ex.: 0.001 a 0.01). “Puxa” a velocidade lentamente para zero.
 */
void mpu6050_integrate_motion(const mpu6050_linacc_t *lin,
                              float dt_s,
                              float deadband_ms2,
                              float vel_leak,
                              mpu6050_motion_t *m)
{
    float ax = lin->ax_lin_ms2;
    float ay = lin->ay_lin_ms2;
    float az = lin->az_lin_ms2;

    // Deadband para reduzir integração de ruído
    if (fabsf(ax) < deadband_ms2) ax = 0.0f;
    if (fabsf(ay) < deadband_ms2) ay = 0.0f;
    if (fabsf(az) < deadband_ms2) az = 0.0f;

    // Integra velocidade
    m->vx += ax * dt_s;
    m->vy += ay * dt_s;
    m->vz += az * dt_s;

    // “Leak” (um amortecimento simples contra deriva quando parado)
    // vel_leak pequeno: 0.001..0.01 típico para testes
    m->vx *= (1.0f - vel_leak);
    m->vy *= (1.0f - vel_leak);
    m->vz *= (1.0f - vel_leak);

    // Integra posição
    m->px += m->vx * dt_s;
    m->py += m->vy * dt_s;
    m->pz += m->vz * dt_s;
}

Esse integrador é propositalmente “honesto”: ele mostra o pipeline completo e deixa evidente o comportamento de deriva. Em aplicações reais, você normalmente adiciona pelo menos uma estratégia de correção, como detectar “estado parado” (módulo de aceleração perto de 1 g e giroscópio perto de 0) e então travar a velocidade em zero, ou usar alguma referência externa.

Exemplo de loop completo (amarrando tudo)

Aqui está o esqueleto de como isso fica no seu loop: lê sensor, atualiza ângulo (filtro complementar), separa aceleração linear e integra movimento.

mpu6050_gyro_bias_t bias;
mpu6050_comp_filter_t filt = { .alpha = 0.98f };
mpu6050_angles_t angles = {0};
mpu6050_motion_t motion = {0};

uint32_t t_prev = micros32();

for (;;) {
    const float dt = dt_from_micros(&t_prev);

    mpu6050_raw_t raw;
    mpu6050_si_t si;
    if (!mpu6050_read_raw(&raw)) continue;
    mpu6050_convert_to_si(&cfg, &raw, &si);

    // Atualiza roll/pitch combinando gyro + accel
    mpu6050_complementary_update(&si, &bias, dt, &filt, &angles);

    // Separa gravidade e obtém aceleração linear
    mpu6050_linacc_t lin;
    mpu6050_linear_accel_from_angles(&si, &angles, &lin);

    // Integra (demonstração)
    mpu6050_integrate_motion(&lin, dt, /*deadband*/0.10f, /*vel_leak*/0.002f, &motion);

    // Aqui você pode enviar angles e motion via UART/BLE, etc.
}
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

O Futuro das Câmeras de Alta Velocidade: Sensores, IA e Novas Fronteiras da Captura UltrarrápidaO Futuro das Câmeras de Alta Velocidade: Sensores, IA e Novas Fronteiras da Captura Ultrarrápida

As câmeras de alta velocidade estão evoluindo rapidamente graças a avanços em sensores, ótica, inteligência artificial e fotografia computacional. Este artigo explora como a expansão para espectros como UV, SWIR

0
Adoraria saber sua opinião, comente.x