Inclinação por acelerômetro: pitch e roll com trigonometria (vetor gravidade + atan2)
Quando o MPU6050 está parado (ou em movimento lento, sem acelerações lineares fortes), o acelerômetro mede principalmente o vetor da gravidade projetado nos eixos do sensor. Em outras palavras, o que você lê como \(a_x, a_y, a_z\) (em g ou m/s²) é, na maior parte do tempo, a “sombra” do vetor gravidade em cada eixo. A inclinação do corpo aparece porque, ao girar o sensor no espaço, a gravidade “muda de eixo” no referencial do sensor.
A trigonometria entra assim: se você imaginar o vetor gravidade com módulo próximo de 1 g, então os ângulos de inclinação podem ser obtidos pela relação entre componentes desse vetor. O motivo de usarmos atan2 e não atan é que atan2(y, x) resolve corretamente o quadrante (sinais) e se comporta melhor perto de divisões por zero. O resultado sai em radianos, e então convertemos para graus multiplicando por \(180/\pi\).
Modelo geométrico (sem inventar “mágica”)
Assumindo a convenção comum para IMUs: X para frente, Y para a esquerda e Z para cima (isso depende de como sua placa está desenhada, mas a matemática funciona igual), os ângulos mais usados são:
- Roll (ϕ): rotação em torno do eixo X (como “inclinar a cabeça para o lado”).
- Pitch (θ): rotação em torno do eixo Y (como “olhar para cima/baixo”).
Com base nas projeções do vetor gravidade, uma forma clássica e robusta de obter os ângulos pelo acelerômetro é:
\[
\phi = \text{atan2}(a_y, a_z)
\]
\[
\theta = \text{atan2}(-a_x, \sqrt{a_y^2 + a_z^2})
\]
Essas expressões surgem ao considerar que o vetor gravidade, no referencial do sensor, pode ser visto como um vetor 3D cuja direção determina a inclinação. O termo \(\sqrt{a_y^2 + a_z^2}\) funciona como uma “normalização parcial” que deixa o cálculo do pitch mais estável quando \(a_z\) se aproxima de zero.
Código em C: calcula pitch/roll a partir dos dados convertidos
Aqui eu vou usar a struct mpu6050_si_t que a gente já criou na seção anterior (valores em g). Se você estiver usando m/s², também funciona — o importante é que as proporções sejam preservadas. A única coisa que muda é que, em m/s², o vetor gravidade tem módulo ~9.80665 em repouso, mas a trigonometria usa as razões, então dá no mesmo.
#include <math.h>
#include <stdint.h>
typedef struct {
float roll_deg; // ϕ
float pitch_deg; // θ
} mpu6050_tilt_t;
/**
* @brief Converte radianos para graus.
*/
static float rad2deg(float rad) {
return rad * (180.0f / (float)M_PI);
}
/**
* @brief Estima roll e pitch usando apenas o acelerômetro.
*
* Pressuposto: aceleração linear pequena (sensor parado ou movimento suave).
* Se houver aceleração forte (ex.: chacoalhar, arrancar, frear), o ângulo “distorce”.
*/
void mpu6050_tilt_from_accel(const float ax_g,
const float ay_g,
const float az_g,
mpu6050_tilt_t *out)
{
// Roll (ϕ): rotação em torno de X. Usa componentes Y e Z do vetor gravidade.
const float roll_rad = atan2f(ay_g, az_g);
// Pitch (θ): rotação em torno de Y.
// O denominador sqrt(ay^2 + az^2) estabiliza o cálculo.
const float denom = sqrtf(ay_g * ay_g + az_g * az_g);
const float pitch_rad = atan2f(-ax_g, denom);
out->roll_deg = rad2deg(roll_rad);
out->pitch_deg = rad2deg(pitch_rad);
}
Por que isso funciona (ligando matemática ao “mundo físico”)
Em repouso ideal, você tem aproximadamente \(\sqrt{a_x^2 + a_y^2 + a_z^2} \approx 1g\). Se o sensor estiver “de barriga para cima”, \(a_z\) tende a estar próximo de (+1) e \(a_x, a_y\) próximos de 0. Ao inclinar, parte dessa gravidade “escorre” para \(a_x\) e \(a_y\). O atan2 compara dois catetos do triângulo que você forma ao projetar esse vetor no plano adequado. No roll, por exemplo, você está olhando para o plano YZ: o ângulo no plano YZ é exatamente a inclinação em torno de X. No pitch, você olha o plano X contra a magnitude do “resto” (YZ) para obter um ângulo mais bem condicionado.
Um jeito prático de validar no osciloscópio/serial é conferir se, em repouso, o módulo fica próximo de 1 g e se os ângulos respondem “certinho” quando você inclina lentamente. Se você inclinar rápido ou der uma sacudida, o acelerômetro mede gravidade + aceleração linear, então a estimativa de ângulo “contamina” — isso não é bug, é a física do sensor.
Exemplo de uso (amarrando com a seção anterior)
Supondo que você já leu raw e converteu para si (como na primeira seção), o uso típico fica assim:
mpu6050_raw_t raw;
mpu6050_si_t si;
mpu6050_tilt_t tilt;
if (mpu6050_read_raw(&raw)) {
mpu6050_convert_to_si(&cfg, &raw, &si);
mpu6050_tilt_from_accel(si.ax_g, si.ay_g, si.az_g, &tilt);
// Agora você tem tilt.roll_deg e tilt.pitch_deg
// Ex.: imprimir via UART, enviar por BLE, etc.
}