Skip to content

File syn_foc_observer.c

File List > motor > syn_foc_observer.c

Go to the documentation of this file

#include "syn_foc_observer.h"

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

#include <string.h>

SYN_Status syn_foc_observer_init(SYN_FOCObserver *obs, const SYN_FOCObserverConfig *cfg)
{
    if (obs == NULL || cfg == NULL)
        return SYN_INVALID_PARAM;
    if (cfg->R <= 0 || cfg->L <= 0 || cfg->G <= 0 || cfg->dt <= 0)
        return SYN_INVALID_PARAM;

    memset(obs, 0, sizeof(*obs));
    obs->cfg = *cfg;

    return SYN_OK;
}

void syn_foc_observer_reset(SYN_FOCObserver *obs)
{
    if (obs == NULL)
        return;

    obs->I_alpha_est = 0;
    obs->I_beta_est = 0;
    obs->E_alpha_est = 0;
    obs->E_beta_est = 0;
    obs->theta_e = 0;
    obs->omega_e = 0;
    obs->pll_i_accum = 0;
}

SYN_Status syn_foc_observer_update(SYN_FOCObserver *obs, q16_t V_alpha, q16_t V_beta, q16_t I_alpha,
                                   q16_t I_beta)
{
    if (obs == NULL)
        return SYN_INVALID_PARAM;

    q16_t R = obs->cfg.R;
    q16_t L = obs->cfg.L;
    q16_t G = obs->cfg.G;
    q16_t dt = obs->cfg.dt;

    /* Current estimation error: I_est - I_meas */
    q16_t err_ia = obs->I_alpha_est - I_alpha;
    q16_t err_ib = obs->I_beta_est - I_beta;

    /* Sliding mode switching control with linear boundary layer */
    q16_t Z_alpha = q16_clamp(q16_mul(G, err_ia), -G, G);
    q16_t Z_beta = q16_clamp(q16_mul(G, err_ib), -G, G);

    /* Current estimator integration: dI = (V - R*I_est - Z) * (dt / L) */
    q16_t dt_over_L = q16_div(dt, L);

    q16_t delta_I_alpha = q16_mul(V_alpha - q16_mul(R, obs->I_alpha_est) - Z_alpha, dt_over_L);
    q16_t delta_I_beta = q16_mul(V_beta - q16_mul(R, obs->I_beta_est) - Z_beta, dt_over_L);

    obs->I_alpha_est += delta_I_alpha;
    obs->I_beta_est += delta_I_beta;

    /* Lowpass filter sliding control output Z to extract estimated back-EMF */
    q16_t alpha_lpf = Q16_FROM_FLOAT(0.15); /* LPF smoothing factor */
    obs->E_alpha_est += q16_mul(alpha_lpf, Z_alpha - obs->E_alpha_est);
    obs->E_beta_est += q16_mul(alpha_lpf, Z_beta - obs->E_beta_est);

    /* Software PLL Phase Detector with normalized back-EMF vector */
    int64_t E_sq = ((int64_t)obs->E_alpha_est * obs->E_alpha_est) +
                   ((int64_t)obs->E_beta_est * obs->E_beta_est);
    E_sq >>= Q16_SHIFT;

    q16_t pll_err = 0;
    q16_t min_E_sq = Q16_FROM_FLOAT(0.0001);

    if (E_sq > min_E_sq) {
        q16_t E_mag = q16_sqrt((q16_t)E_sq);
        if (E_mag > 0) {
            q16_t E_alpha_n = q16_div(obs->E_alpha_est, E_mag);
            q16_t E_beta_n = q16_div(obs->E_beta_est, E_mag);

            /* If starting up from zero speed, seed angle and initial speed estimate */
            if (obs->omega_e == 0 && obs->pll_i_accum == 0) {
                obs->theta_e = q16_atan2(-obs->E_alpha_est, obs->E_beta_est);
                obs->pll_i_accum = Q16_FROM_INT(80);
            }

            q16_t cos_th = q16_cos(obs->theta_e);
            q16_t sin_th = q16_sin(obs->theta_e);

            pll_err = -q16_mul(E_alpha_n, cos_th) - q16_mul(E_beta_n, sin_th);
        }
    }

    /* PI loop filter with anti-windup clamp (using 64-bit int to prevent Q16 underflow) */
    int64_t Ki_err = ((int64_t)obs->cfg.Ki_pll * pll_err) >> Q16_SHIFT;
    q16_t delta_i = (q16_t)((Ki_err * dt) >> Q16_SHIFT);

    obs->pll_i_accum += delta_i;
    q16_t max_speed = Q16_FROM_INT(2000);
    obs->pll_i_accum = q16_clamp(obs->pll_i_accum, -max_speed, max_speed);

    obs->omega_e =
        q16_clamp(obs->pll_i_accum + q16_mul(obs->cfg.Kp_pll, pll_err), -max_speed, max_speed);

    /* Position integrator: theta = theta + omega * dt, wrapped to [0, 2pi) */
    obs->theta_e += q16_mul(obs->omega_e, dt);
    while (obs->theta_e >= Q16_2_PI) {
        obs->theta_e -= Q16_2_PI;
    }
    while (obs->theta_e < 0) {
        obs->theta_e += Q16_2_PI;
    }

    return SYN_OK;
}

q16_t syn_foc_observer_get_angle(const SYN_FOCObserver *obs)
{
    if (obs == NULL)
        return 0;
    return obs->theta_e;
}

q16_t syn_foc_observer_get_speed(const SYN_FOCObserver *obs)
{
    if (obs == NULL)
        return 0;
    return obs->omega_e;
}