Skip to content

File syn_sensor_fusion.c

File List > sensor > syn_sensor_fusion.c

Go to the documentation of this file

#include "syn_sensor_fusion.h"

#include "../util/syn_assert.h"
#include "../util/syn_matrix.h"

#include <string.h>

void syn_sensor_fusion_init(SYN_SensorFusion *f, q16_t Kp, q16_t Ki, q16_t dt)
{
    SYN_ASSERT(f != NULL);
    if (f == NULL) {
        return;
    }

    memset(f, 0, sizeof(*f));
    f->Kp = Kp;
    f->Ki = Ki;
    f->dt = (dt > 0) ? dt : Q16_FROM_FLOAT(0.01); /* Default 100 Hz */

    f->q.w = Q16_ONE;
    f->q.x = 0;
    f->q.y = 0;
    f->q.z = 0;
}

void syn_sensor_fusion_reset(SYN_SensorFusion *f)
{
    SYN_ASSERT(f != NULL);
    if (f == NULL) {
        return;
    }

    f->q.w = Q16_ONE;
    f->q.x = 0;
    f->q.y = 0;
    f->q.z = 0;
    f->e_int[0] = 0;
    f->e_int[1] = 0;
    f->e_int[2] = 0;
}

SYN_Status syn_sensor_fusion_update(SYN_SensorFusion *f, q16_t gx, q16_t gy, q16_t gz, q16_t ax,
                                    q16_t ay, q16_t az)
{
    if (f == NULL)
        return SYN_INVALID_PARAM;

    q16_t qw = f->q.w;
    q16_t qx = f->q.x;
    q16_t qy = f->q.y;
    q16_t qz = f->q.z;

    /* Compute accelerometer magnitude squared */
    int64_t a_sq = ((int64_t)ax * ax) + ((int64_t)ay * ay) + ((int64_t)az * az);
    a_sq >>= Q16_SHIFT;

    if (a_sq > 0) {
        q16_t a_norm = q16_sqrt((q16_t)a_sq);
        if (a_norm > 0) {
            /* Normalize accelerometer measurements */
            ax = q16_div(ax, a_norm);
            ay = q16_div(ay, a_norm);
            az = q16_div(az, a_norm);

            /* Estimated direction of gravity vector from current quaternion state */
            /* vx = 2*(qx*qz - qw*qy) */
            q16_t vx = q16_mul(Q16_FROM_INT(2), q16_mul(qx, qz) - q16_mul(qw, qy));
            /* vy = 2*(qw*qx + qy*qz) */
            q16_t vy = q16_mul(Q16_FROM_INT(2), q16_mul(qw, qx) + q16_mul(qy, qz));
            /* vz = qw^2 - qx^2 - qy^2 + qz^2 */
            q16_t vz = q16_mul(qw, qw) - q16_mul(qx, qx) - q16_mul(qy, qy) + q16_mul(qz, qz);

            /* Cross product error vector e = a x v */
            q16_t a_vec[3] = {ax, ay, az};
            q16_t v_vec[3] = {vx, vy, vz};
            q16_t e_vec[3];
            syn_vec3_cross(a_vec, v_vec, e_vec);
            q16_t ex = e_vec[0], ey = e_vec[1], ez = e_vec[2];

            /* Accumulate integral error */
            if (f->Ki > 0) {
                f->e_int[0] += q16_mul(ex, f->dt);
                f->e_int[1] += q16_mul(ey, f->dt);
                f->e_int[2] += q16_mul(ez, f->dt);
                gx += q16_mul(f->Ki, f->e_int[0]);
                gy += q16_mul(f->Ki, f->e_int[1]);
                gz += q16_mul(f->Ki, f->e_int[2]);
            }

            /* Apply proportional feedback to gyro rates */
            if (f->Kp > 0) {
                gx += q16_mul(f->Kp, ex);
                gy += q16_mul(f->Kp, ey);
                gz += q16_mul(f->Kp, ez);
            }
        }
    }

    /* Integrate quaternion rate of change: q_dot = 0.5 * q * omega */
    q16_t half_dt = f->dt >> 1;

    q16_t dqw = q16_mul(-q16_mul(qx, gx) - q16_mul(qy, gy) - q16_mul(qz, gz), half_dt);
    q16_t dqx = q16_mul(q16_mul(qw, gx) + q16_mul(qy, gz) - q16_mul(qz, gy), half_dt);
    q16_t dqy = q16_mul(q16_mul(qw, gy) - q16_mul(qx, gz) + q16_mul(qz, gx), half_dt);
    q16_t dqz = q16_mul(q16_mul(qw, gz) + q16_mul(qx, gy) - q16_mul(qy, gx), half_dt);

    qw += dqw;
    qx += dqx;
    qy += dqy;
    qz += dqz;

    /* Fast single-cycle reciprocal square root quaternion normalization */
    int64_t q_sq_64 =
        ((int64_t)qw * qw) + ((int64_t)qx * qx) + ((int64_t)qy * qy) + ((int64_t)qz * qz);
    q16_t q_sq = (q16_t)(q_sq_64 >> Q16_SHIFT);
    q16_t inv_norm = q16_rsqrt(q_sq);
    if (inv_norm > 0) {
        qw = q16_mul(qw, inv_norm);
        qx = q16_mul(qx, inv_norm);
        qy = q16_mul(qy, inv_norm);
        qz = q16_mul(qz, inv_norm);
    }

    f->q.w = qw;
    f->q.x = qx;
    f->q.y = qy;
    f->q.z = qz;

    return SYN_OK;
}

SYN_Status syn_sensor_fusion_get_quaternion(const SYN_SensorFusion *f, SYN_Quaternion *q)
{
    if (f == NULL || q == NULL)
        return SYN_INVALID_PARAM;
    *q = f->q;
    return SYN_OK;
}

SYN_Status syn_sensor_fusion_get_euler(const SYN_SensorFusion *f, SYN_EulerAngles *euler)
{
    if (f == NULL || euler == NULL)
        return SYN_INVALID_PARAM;

    q16_t qw = f->q.w;
    q16_t qx = f->q.x;
    q16_t qy = f->q.y;
    q16_t qz = f->q.z;

    /* Roll: atan2(2*(qw*qx + qy*qz), 1 - 2*(qx^2 + qy^2)) */
    q16_t roll_num = q16_mul(Q16_FROM_INT(2), q16_mul(qw, qx) + q16_mul(qy, qz));
    q16_t roll_den = Q16_ONE - q16_mul(Q16_FROM_INT(2), q16_mul(qx, qx) + q16_mul(qy, qy));
    euler->roll_rad = q16_atan2(roll_num, roll_den);

    /* Pitch: asin(2*(qw*qy - qz*qx)) */
    q16_t pitch_sin = q16_mul(Q16_FROM_INT(2), q16_mul(qw, qy) - q16_mul(qz, qx));
    euler->pitch_rad = q16_asin(pitch_sin);

    /* Yaw: atan2(2*(qw*qz + qx*qy), 1 - 2*(qy^2 + qz^2)) */
    q16_t yaw_num = q16_mul(Q16_FROM_INT(2), q16_mul(qw, qz) + q16_mul(qx, qy));
    q16_t yaw_den = Q16_ONE - q16_mul(Q16_FROM_INT(2), q16_mul(qy, qy) + q16_mul(qz, qz));
    euler->yaw_rad = q16_atan2(yaw_num, yaw_den);

    return SYN_OK;
}