MCU & FPGA Algoritimos Quaternions, Ângulos de Euler e Matrizes de Rotação: Guia Completo para Sistemas Embarcados e IMUs

Quaternions, Ângulos de Euler e Matrizes de Rotação: Guia Completo para Sistemas Embarcados e IMUs


Vetores e Matrizes de Orientação

Se ângulos de Euler são uma descrição “sequencial” de rotações, vetores e matrizes de orientação são uma descrição geométrica direta da atitude do corpo no espaço. Em vez de dizer “girei tantos graus em X, depois Y, depois Z”, você descreve explicitamente para onde apontam os eixos do corpo em relação ao referencial do mundo. Isso é extremamente poderoso em sistemas embarcados, porque a maioria dos problemas reais não envolve “ângulos”, mas sim transformação de vetores: gravidade, aceleração linear, campo magnético, direção de empuxo, força aplicada, entre outros.

Uma matriz de rotação 3×3 pode ser interpretada como três vetores coluna que representam os eixos X, Y e Z do corpo expressos no referencial global. Por exemplo, se R é a matriz de rotação do corpo para o mundo, então:

R = [ x_b y_b z_b ]

onde cada coluna é um vetor unitário. Isso significa que, se você tem um vetor medido no corpo vb, o vetor equivalente no mundo vw é obtido por:

vw = R * vb

Essa operação é fundamental quando queremos remover a gravidade da leitura do acelerômetro. O acelerômetro mede a soma da aceleração linear com o vetor gravidade projetado no referencial do sensor. Se conhecemos a orientação, podemos transformar o vetor gravidade conhecido no mundo (0, 0, g) para o corpo e subtrair.

Vamos construir isso em C de forma clara. Primeiro, definimos uma estrutura para matriz 3×3 e funções básicas de multiplicação matriz-vetor.

#include <stdio.h>

/**
 * @brief Estrutura para matriz 3x3.
 */
typedef struct
{
    float m[3][3];
} RotationMatrix;

/**
 * @brief Estrutura para vetor 3D.
 */
typedef struct
{
    float x;
    float y;
    float z;
} Vector3;

/**
 * @brief Multiplica matriz 3x3 por vetor 3D.
 */
Vector3 mat_vec_mul(RotationMatrix R, Vector3 v)
{
    Vector3 result;

    result.x = R.m[0][0] * v.x + R.m[0][1] * v.y + R.m[0][2] * v.z;
    result.y = R.m[1][0] * v.x + R.m[1][1] * v.y + R.m[1][2] * v.z;
    result.z = R.m[2][0] * v.x + R.m[2][1] * v.y + R.m[2][2] * v.z;

    return result;
}

Agora, vamos construir a matriz de rotação a partir de ângulos de Euler (roll, pitch, yaw) na convenção Z-Y-X. Isso mostra claramente como as representações se conectam.

#include <math.h>

/**
 * @brief Constrói matriz de rotação a partir de ângulos de Euler (Z-Y-X).
 */
RotationMatrix euler_to_matrix(float roll, float pitch, float yaw)
{
    RotationMatrix R;

    float cr = cosf(roll);
    float sr = sinf(roll);
    float cp = cosf(pitch);
    float sp = sinf(pitch);
    float cy = cosf(yaw);
    float sy = sinf(yaw);

    // R = Rz(yaw) * Ry(pitch) * Rx(roll)

    R.m[0][0] = cy * cp;
    R.m[0][1] = cy * sp * sr - sy * cr;
    R.m[0][2] = cy * sp * cr + sy * sr;

    R.m[1][0] = sy * cp;
    R.m[1][1] = sy * sp * sr + cy * cr;
    R.m[1][2] = sy * sp * cr - cy * sr;

    R.m[2][0] = -sp;
    R.m[2][1] = cp * sr;
    R.m[2][2] = cp * cr;

    return R;
}

Com essa matriz, podemos transformar vetores entre referenciais. Suponha que o vetor gravidade no mundo seja:

g_world = (0, 0, 9.81)

Para obter o vetor gravidade no referencial do sensor (corpo), se R transforma corpo → mundo, então precisamos usar a transposta de R (pois matriz de rotação é ortogonal, R⁻¹ = Rᵀ).

/**
 * @brief Transpõe uma matriz 3x3.
 */
RotationMatrix transpose(RotationMatrix R)
{
    RotationMatrix Rt;

    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Rt.m[i][j] = R.m[j][i];
        }
    }

    return Rt;
}

Agora aplicamos:

Vector3 g_world = {0.0f, 0.0f, 9.81f};

RotationMatrix R = euler_to_matrix(roll, pitch, yaw);
RotationMatrix Rt = transpose(R);

// Gravidade projetada no corpo
Vector3 g_body = mat_vec_mul(Rt, g_world);

Esse vetor pode ser subtraído da leitura do acelerômetro para obter apenas aceleração linear.

O ponto crítico aqui é que matrizes são robustas do ponto de vista geométrico, mas acumulam erro numérico quando você integra rotações sucessivas usando giroscópio. Após muitas iterações, os vetores coluna deixam de ser perfeitamente ortogonais, e a matriz começa a “entortar”. Isso exige técnicas de re-ortogonalização (como Gram-Schmidt) ou normalização periódica.

Além disso, matrizes usam 9 floats, enquanto quaternions usam apenas 4, mantendo representação livre de singularidades como o gimbal lock. É exatamente por isso que, internamente, muitos filtros de orientação usam quaternions e só convertem para matriz quando precisam transformar vetores.

Related Post

Triângulos Esféricos: Fundamentos e Aplicações em Navegação, Antenas e Guerra EletrônicaTriângulos Esféricos: Fundamentos e Aplicações em Navegação, Antenas e Guerra Eletrônica

Os triângulos esféricos são fundamentais para aplicações em navegação, guerra eletrônica, comunicações via satélite e sistemas de posicionamento global (GPS). Diferente da trigonometria plana, eles trabalham com lados definidos por

Sistema de Ronda com OneWire e iButton DS1904 usando ESP32: Implementação Completa, Multithread e à Prova de FalhasSistema de Ronda com OneWire e iButton DS1904 usando ESP32: Implementação Completa, Multithread e à Prova de Falhas

Este artigo apresenta uma implementação completa e profissional de um sistema de ronda baseado no barramento OneWire e no iButton DS1904, utilizando o microcontrolador ESP32 com FreeRTOS. Explicamos de forma

Funções de Delay em Microcontroladores: Comparação Completa entre RP2040, ESP32, STM32, AVR, NXP e RenesasFunções de Delay em Microcontroladores: Comparação Completa entre RP2040, ESP32, STM32, AVR, NXP e Renesas

As funções de delay são essenciais no desenvolvimento de firmware, mas cada microcontrolador implementa temporizações de forma distinta, afetando precisão, consumo de energia, multitarefa e estabilidade do sistema. Este artigo