Skip to content

File syn_foc.c

File List > motor > syn_foc.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_FOC) || SYN_USE_FOC

#include "../common/syn_defs.h"
#include "../util/syn_assert.h"
#include "syn_foc.h"

#define Q16_SQRT3 113512

#define Q16_INV_SQRT3 37837

#define Q16_2_INV_SQRT3 75674

#define Q16_SQRT3_OVER_2 56756

/* ── Clarke transform ───────────────────────────────────────────────────── */

void syn_foc_clarke(const SYN_FOC_ABC *abc, SYN_FOC_AB *ab)
{
    SYN_ASSERT(abc != NULL && ab != NULL);

    /*
     * Equal-amplitude Clarke (simplest form, assumes a + b + c ≈ 0):
     *   α = a
     *   β = (a + 2b) / √3 = (a + 2b) * (1/√3)
     */
    ab->alpha = abc->a;
    ab->beta = q16_mul(abc->a + 2 * abc->b, Q16_INV_SQRT3);
}

void syn_foc_inv_clarke(const SYN_FOC_AB *ab, SYN_FOC_ABC *abc)
{
    SYN_ASSERT(ab != NULL && abc != NULL);

    /*
     * Inverse Clarke:
     *   a = α
     *   b = (-α + √3·β) / 2
     *   c = (-α - √3·β) / 2
     *   where √3 ≈ 1.73205 (113512 in Q16.16)
     */
    q16_t s3b = q16_mul(ab->beta, Q16_SQRT3);

    abc->a = ab->alpha;
    abc->b = (-ab->alpha + s3b) / 2;
    abc->c = (-ab->alpha - s3b) / 2;
}

/* ── Park transform ─────────────────────────────────────────────────────── */

void syn_foc_park(const SYN_FOC_AB *ab, q16_t theta, SYN_FOC_DQ *dq)
{
    SYN_ASSERT(ab != NULL && dq != NULL);

    q16_t cos_t = q16_cos(theta);
    q16_t sin_t = q16_sin(theta);

    /*
     * d =  α·cos(θ) + β·sin(θ)
     * q = −α·sin(θ) + β·cos(θ)
     */
    dq->d = q16_mul(ab->alpha, cos_t) + q16_mul(ab->beta, sin_t);
    dq->q = -q16_mul(ab->alpha, sin_t) + q16_mul(ab->beta, cos_t);
}

void syn_foc_inv_park(const SYN_FOC_DQ *dq, q16_t theta, SYN_FOC_AB *ab)
{
    SYN_ASSERT(dq != NULL && ab != NULL);

    q16_t cos_t = q16_cos(theta);
    q16_t sin_t = q16_sin(theta);

    /*
     * α = d·cos(θ) − q·sin(θ)
     * β = d·sin(θ) + q·cos(θ)
     */
    ab->alpha = q16_mul(dq->d, cos_t) - q16_mul(dq->q, sin_t);
    ab->beta = q16_mul(dq->d, sin_t) + q16_mul(dq->q, cos_t);
}

void syn_foc_park_fast(const SYN_FOC_AB *ab, q16_t theta, SYN_FOC_DQ *dq)
{
    SYN_ASSERT(ab != NULL && dq != NULL);
    q16_t sin_t, cos_t;
    q16_sincos_fast(theta, &sin_t, &cos_t);

    dq->d = q16_mul(ab->alpha, cos_t) + q16_mul(ab->beta, sin_t);
    dq->q = -q16_mul(ab->alpha, sin_t) + q16_mul(ab->beta, cos_t);
}

void syn_foc_inv_park_fast(const SYN_FOC_DQ *dq, q16_t theta, SYN_FOC_AB *ab)
{
    SYN_ASSERT(dq != NULL && ab != NULL);
    q16_t sin_t, cos_t;
    q16_sincos_fast(theta, &sin_t, &cos_t);

    ab->alpha = q16_mul(dq->d, cos_t) - q16_mul(dq->q, sin_t);
    ab->beta = q16_mul(dq->d, sin_t) + q16_mul(dq->q, cos_t);
}

bool syn_foc_field_weakening(q16_t v_d, q16_t v_q, q16_t v_max, q16_t *id_cmd)
{
    SYN_ASSERT(id_cmd != NULL && v_max > 0);
    q16_t v_mag = q16_hypot(v_d, v_q);
    if (v_mag > v_max) {
        q16_t v_err = v_mag - v_max;
        /* Ingest negative d-axis current proportional to voltage excess */
        *id_cmd -= q16_mul(v_err, Q16_FROM_FRAC(1, 2));
        return true;
    }
    return false;
}

/* ── SVPWM ──────────────────────────────────────────────────────────────── */

void syn_foc_svpwm(const SYN_FOC_AB *ab, q16_t v_bus, q16_t *duty_a, q16_t *duty_b, q16_t *duty_c)
{
    SYN_ASSERT(ab != NULL && duty_a != NULL && duty_b != NULL && duty_c != NULL);
    SYN_ASSERT(v_bus > 0);
    if (v_bus <= 0)
        return;

    SYN_FOC_ABC v_ref;
    syn_foc_inv_clarke(ab, &v_ref);
    q16_t va = v_ref.a;
    q16_t vb = v_ref.b;
    q16_t vc = v_ref.c;

    q16_t v_min = SYN_MIN(va, SYN_MIN(vb, vc));
    q16_t v_max = SYN_MAX(va, SYN_MAX(vb, vc));

    q16_t v_offset = -(v_max + v_min) / 2;

    /* Use single reciprocal division for 3-phase PWM normalization */
    q16_t inv_v_bus = q16_inv(v_bus);
    *duty_a = q16_clamp(q16_mul(va + v_offset, inv_v_bus) + Q16_HALF, 0, Q16_ONE);
    *duty_b = q16_clamp(q16_mul(vb + v_offset, inv_v_bus) + Q16_HALF, 0, Q16_ONE);
    *duty_c = q16_clamp(q16_mul(vc + v_offset, inv_v_bus) + Q16_HALF, 0, Q16_ONE);
}

#endif /* SYN_USE_FOC */