/* DCLF32.h - C2000 Digital Control Library
 *
 * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
 * ALL RIGHTS RESERVED
 */

#ifndef _C_DCLF32_H
#define _C_DCLF32_H

#ifdef __cplusplus
extern "C" {
#endif

//! \file           DCLF32.h
//! \brief          Contains the public interface to the FPU32
//!                 Digital Controller Library functions

#ifdef __TMS320C28XX__
#include <math.h>
#endif

#include "DCL.h"
#include <complex.h>
#include <stdint.h>


//--- Linear PID controller --------------------------------------------------

//! \brief          Defines the DCL_PID shadow parameter set
//!
typedef volatile struct dcl_pid_sps {
    float32_t Kp;       //!< Proportional gain
    float32_t Ki;       //!< Integral gain
    float32_t Kd;       //!< Derivative gain
    float32_t Kr;       //!< Set point weight
    float32_t c1;       //!< D path filter coefficient 1
    float32_t c2;       //!< D path filter coefficient 2
    float32_t Umax;     //!< Upper saturation limit
    float32_t Umin;     //!< Lower saturation limit
} DCL_PID_SPS;

//! \brief          Defines default values to initialize the DCL_PID shadow structure
//!
#define PID_SPS_DEFAULTS { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f }

//! \brief          Defines the DCL_PID active controller structure
//!
typedef volatile struct dcl_pid {
    float32_t Kp;       //!< Proportional gain
    float32_t Ki;       //!< Integral gain
    float32_t Kd;       //!< Derivative gain
    float32_t Kr;       //!< Set point weight
    float32_t c1;       //!< D path filter coefficient 1
    float32_t c2;       //!< D path filter coefficient 2
    float32_t d2;       //!< D path filter intermediate storage 1
    float32_t d3;       //!< D path filter intermediate storage 2
    float32_t i10;      //!< I path intermediate storage
    float32_t i14;      //!< Intermediate saturation storage
    float32_t Umax;     //!< Upper saturation limit
    float32_t Umin;     //!< Lower saturation limit
    DCL_PID_SPS *sps;   //!< Pointer to the shadow parameter set
    DCL_CSS *css;       //!< Pointer to the common support structure
} DCL_PID;

//! \brief          Defines default values to initialize the DCL_PID structure
//!
#define PID_DEFAULTS {  1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, \
                        0.0f, 1.0f, 1.0f, -1.0f, \
                        NULL_ADDR, NULL_ADDR }

//! \brief          Resets PID internal storage data
//! \param[in] p    Pointer to the DCL_PID structure
//! \return         None
//!
static inline void DCL_resetPID(DCL_PID *p)
{
    uint16_t v;

    v = DCL_DISABLE_INTS;
    p->d2 = p->d3 = p->i10 = 0.0f;
    p->i14 = 1.0f;
    p->css->err = ERR_NONE;
    DCL_RESTORE_INTS(v);
}

//! \brief          Loads PID tuning parameters
//! \param[in] p    Pointer to the active DCL_PID controller structure
//! \return         None
//!
static inline void DCL_updatePID(DCL_PID *p)
{
    uint16_t v;

#ifdef DCL_ERROR_HANDLING_ENABLED
    float32_t tau = (2.0f - p->sps->c1 * p->css->T) / (2.0f * p->sps->c1);
    float32_t ec2 = p->sps->c1 * (p->css->T - 2.0f * tau) / 2.0f;
    p->css->err |= !F32_IS_VALUE(p->sps->c2, ec2) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (p->sps->Umax <= p->sps->Umin) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (p->css->T <= 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= ((p->sps->Kp < 0.0f) || (p->sps->Ki < 0.0f) || (p->sps->Kd < 0.0f) || (p->sps->Kr < 0.0f)) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        p->Kp = p->sps->Kp;
        p->Ki = p->sps->Ki;
        p->Kd = p->sps->Kd;
        p->Kr = p->sps->Kr;
        p->c1 = p->sps->c1;
        p->c2 = p->sps->c2;
        p->Umax = p->sps->Umax;
        p->Umin = p->sps->Umin;
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads PID tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_PID controller structure
//! \return         None
//!
extern void DCL_fupdatePID(DCL_PID *p);

//! \brief          Loads the derivative path filter shadow coefficients
//!                 Note: new coefficients take effect when DCL_updatePID() is called
//! \param[in] p    Pointer to the DCL_PID structure
//! \param[in] fc   The desired filter bandwidth in Hz
//! \return         None
//!
static inline void DCL_setPIDfilterBW(DCL_PID *p, float32_t fc)
{
    float32_t tau;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= ((fc >= 1.0f / (2.0f * p->css->T)) || (fc <= 0.0f)) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    tau = 1.0f / (2.0f * CONST_PI_32 * fc);
    p->sps->c1 = 2.0f / (p->css->T + 2.0f * tau);
    p->sps->c2 = (p->css->T - 2.0f * tau) / (p->css->T + 2.0f * tau);
}

//! \brief          Loads the PID derivative path filter active coefficients
//!                 Note: new coefficients take effect immediately.  SPS &
//!                 CSS contents are unaffected.
//! \param[in] p    Pointer to the DCL_PID structure
//! \param[in] fc   The desired filter bandwidth in Hz
//! \param[in] T    The controller update rate in seconds
//! \return         None
//!
static inline void DCL_setActivePIDfilterBW(DCL_PID *p, float32_t fc, float32_t T)
{
    float32_t tau;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= ((fc >= 1.0f / (2.0f * T)) || (fc <= 0.0f)) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    tau = 1.0f / (2.0f * CONST_PI_32 * fc);
    p->c1 = 2.0f / (T + 2.0f * tau);
    p->c2 = (T - 2.0f * tau) / (T + 2.0f * tau);
}

//! \brief          Returns the active derivative path filter bandwidth in Hz
//! \param[in] p    Pointer to the DCL_PID structure
//! \return         The filter bandwidth in Hz
//!
static inline float32_t DCL_getPIDfilterBW(DCL_PID *p)
{
    float32_t tau = ((2.0f - p->c1 * p->css->T) / (2.0f * p->c1));
    return(1.0f / (2.0f * CONST_PI_32 * tau));
}

//! \brief          Configures a series PID controller in ZPK form
//!                 Note: parameters take effect after call to DCL_updatePID()
//!                 Only z1, z2 & p2 considered.  p1 = 1 assumed.
//! \param[in] p    Pointer to the active DCL_PID controller structure
//! \param[in] q    Pointer to the DCL_ZPK3 structure
//! \return         None
//!
static inline void DCL_loadSeriesPIDasZPK(DCL_PID *p, DCL_ZPK3 *q)
{
    float32_t   tau, b2, b1, b0, detA, Vi, Vd;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (!F32_IS_VALUE(cimagf(q->z1) + cimagf(q->z2), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (!F32_IS_VALUE(cimagf(q->p2), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (crealf(q->p2) > DCL_C2_LIMIT_32) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (q->K < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->c2 = -q->p2;
    tau = -(p->css->T / 2.0f) * (p->sps->c2 - 1) / (p->sps->c2 + 1);
    p->sps->c1 = 2.0f / (p->css->T + 2.0f * tau);
    b2 = q->K;
    b1 = -q->K * (float32_t) crealf(q->z1 + q->z2);
    b0 = q->K * (float32_t) crealf(q->z1 * q->z2);
    detA = 1.0f + (2.0f * p->sps->c2) + (p->sps->c2 * p->sps->c2);
    p->sps->Kp = ((p->sps->c2 * b2) - b1 - ((p->sps->c2 + 2.0f) * b0)) / detA;
    Vi = (p->sps->c2 + 1.0f) * (b2 + b1 + b0) / detA;
    Vd = ((p->sps->c2 * p->sps->c2 * b2) - (p->sps->c2 * b1) + b0) / detA;
    p->sps->Ki = Vi / p->sps->Kp;
    p->sps->Kd = Vd / p->sps->Kp;
}

//! \brief          Configures a parallel PID controller in ZPK form
//!                 Note: parameters take effect after call to DCL_updatePID()
//!                 Only z1, z2 & p2 considered.  p1 = 1 assumed.
//! \param[in] p    Pointer to the active DCL_PID controller structure
//! \param[in] q    Pointer to the DCL_ZPK3 structure
//! \return         None
//!
static inline void DCL_loadParallelPIDasZPK(DCL_PID *p, DCL_ZPK3 *q)
{
    float32_t   tau, b2, b1, b0, detA;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (!F32_IS_VALUE(cimagf(q->z1) + cimagf(q->z2), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (!F32_IS_VALUE(cimagf(q->p2), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (crealf(q->p2) > DCL_C2_LIMIT_32) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (q->K < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->c2 = -q->p2;
    tau = -(p->css->T / 2.0f) * (p->sps->c2 - 1) / (p->sps->c2 + 1);
    p->sps->c1 = 2.0f / (p->css->T + 2.0f * tau);
    b2 = q->K;
    b1 = -q->K * (float32_t) crealf(q->z1 + q->z2);
    b0 = q->K * (float32_t) crealf(q->z1 * q->z2);
    detA = 1.0f + (2.0f * p->sps->c2) + (p->sps->c2 * p->sps->c2);
    p->sps->Kp = ((p->sps->c2 * b2) - b1 - ((p->sps->c2 + 2.0f) * b0)) / detA;
    p->sps->Ki = (p->sps->c2 + 1.0f) * (b2 + b1 + b0) / detA;
    p->sps->Kd = ((p->sps->c2 * p->sps->c2 * b2) - (p->sps->c2 * b1) + b0) / detA;
}

//! \brief          Executes an ideal form PID controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_PID structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \param[in] lk   External output clamp flag
//! \return         The control effort
//!
extern float32_t DCL_runPID_C1(DCL_PID *p, float32_t rk, float32_t yk, float32_t lk);

//! \brief          Executes an ideal form PID controller on the FPU32
//! \param[in] p    Pointer to the DCL_PID structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \param[in] lk   External output clamp flag
//! \return         The control effort
//!
static inline float32_t DCL_runPID_C2(DCL_PID *p, float32_t rk, float32_t yk, float32_t lk)
{
    float32_t v1, v4, v5, v8, v9, v10, v12;

    v5 = (p->Kr * rk) - yk;
    v8 = ((rk - yk) * p->Ki * p->Kp * p->i14) + p->i10;
    p->i10 = v8;
    v1 = yk * p->Kd * p->c1;
    v4 = v1 - p->d2 - p->d3;
    p->d2 = v1;
    p->d3 = v4 * p->c2;
    v9 = ((v5 - v4) * p->Kp) + v8;
    v10 = (v9 > p->Umax) ? p->Umax : v9;
    v10 = (v10 < p->Umin) ? p->Umin : v10;
    v12 = (v10 == v9) ? 1.0f : 0.0f;
    p->i14 = v12 * lk;

#ifdef DCL_TESTPOINTS_ENABLED
    p->css->tpt = v4;
#endif

    return(v10);
}

//! \brief          Executes an parallel form PID controller on the FPU32
//! \param[in] p    Pointer to the DCL_PID structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \param[in] lk   External output clamp flag
//! \return         The control effort
//!
static inline float32_t DCL_runPID_C3(DCL_PID *p, float32_t rk, float32_t yk, float32_t lk)
{
    float32_t v1, v4, v5, v6, v8, v9, v10, v12;

    v5 = rk - yk;
    v6 = v5 * p->Kp;
    v8 = v5 * p->Ki * p->i14 + p->i10;
    p->i10 = v8;
    v1 = v5 * p->Kd * p->c1;
    v4 = v1 - p->d2 - p->d3;
    p->d2 = v1;
    p->d3 = v4 * p->c2;
    v9 = v6 + v8 + v4;
    v10 = (v9 > p->Umax) ? p->Umax : v9;
    v10 = (v10 < p->Umin) ? p->Umin : v10;
    v12 = (v10 == v9) ? 1.0f : 0.0f;
    p->i14 = v12 * lk;

#ifdef DCL_TESTPOINTS_ENABLED
    p->css->tpt = v8;
#endif

    return(v10);
}

//! \brief          Executes a parallel form PID controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_PID structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \param[in] lk   External output clamp flag
//! \return         The control effort
//!
extern float32_t DCL_runPID_C4(DCL_PID *p, float32_t rk, float32_t yk, float32_t lk);


//--- Linear PI controller ---------------------------------------------------

//! \brief          Defines the DCL_PI shadow parameter set
//!
typedef volatile struct dcl_pi_sps {
    float32_t Kp;       //!< Proportional gain
    float32_t Ki;       //!< Integral gain
    float32_t Umax;     //!< Upper control saturation limit
    float32_t Umin;     //!< Lower control saturation limit
    float32_t Imax;     //!< Upper integrator saturation limit
    float32_t Imin;     //!< Lower integrator saturation limit
} DCL_PI_SPS;

//! \brief          Defines default values to initialize the DCL_PI shadow structure
//!
#define PI_SPS_DEFAULTS { 1.0f, 0.0f, 1.0f, -1.0f, 1.0f, -1.0f }

//! \brief          Defines the DCL_PI controller structure
//!
typedef volatile struct dcl_pi {
    float32_t Kp;       //!< Proportional gain
    float32_t Ki;       //!< Integral gain
    float32_t i10;      //!< I storage
    float32_t Umax;     //!< Upper control saturation limit
    float32_t Umin;     //!< Lower control saturation limit
    float32_t i6;       //!< Saturation storage
    float32_t i11;      //!< I storage
    float32_t Imax;     //!< Upper integrator saturation limit
    float32_t Imin;     //!< Lower integrator saturation limit
    DCL_PI_SPS *sps;    //!< Pointer to the shadow parameter set
    DCL_CSS *css;       //!< Pointer to the common support structure
} DCL_PI;

//! \brief  Defines default values to initialize the PI structure
//!
#define PI_DEFAULTS { 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, \
                      NULL_ADDR, NULL_ADDR }

//! \brief          Resets PID internal storage data
//! \param[in] p    Pointer to the DCL_PI structure
//! \return         None
//!
static inline void DCL_resetPI(DCL_PI *p)
{
    uint16_t v;

    v = DCL_DISABLE_INTS;
    p->i10 = 0.0f;
    p->i6 = 1.0f;
    p->i11 = 0.0f;
    p->css->err = ERR_NONE;
    DCL_RESTORE_INTS(v);
}

//! \brief          Loads active PI controller parameters
//! \param[in] p    Pointer to the active DCL_PI controller structure
//! \return         None
//!
static inline void DCL_updatePI(DCL_PI *p)
{
    uint16_t v;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (p->sps->Umax <= p->sps->Umin) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (p->css->T <= 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (p->sps->Kp < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (p->sps->Ki < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        p->Kp = p->sps->Kp;
        p->Ki = p->sps->Ki;
        p->Umax = p->sps->Umax;
        p->Umin = p->sps->Umin;
        p->Imax = p->sps->Imax;
        p->Imin = p->sps->Imin;
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads PI tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_PI controller structure
//! \return         None
//!
extern void DCL_fupdatePI(DCL_PI *p);

//! \brief          Configures a series PI controller in "zero-pole-gain" form
//!                 Note: new settings take effect after DCL_updatePI()
//!                 Only z1 considered in DCL_ZPK3, other poles & zeros ignored
//!                 Zero frequency assumed to be in Hz
//! \param[in] p    Pointer to the DCL_PI controller structure
//! \param[in] q    Pointer to the ZPK3 structure
//! \return         None
//!
static inline void DCL_loadSeriesPIasZPK(DCL_PI *p, DCL_ZPK3 *q)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (q->K < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (crealf(q->z1) < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (crealf(q->z1) > (1.0f / (2.0f * p->css->T))) ? ERR_PARAM_WARN : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->Kp = q->K * (float32_t) crealf(q->z1);
    p->sps->Ki = (q->K / p->sps->Kp) - 1.0f;
}

//! \brief          Configures a parallel PI controller in "zero-pole-gain" form
//!                 Note: new settings take effect after DCL_updatePI()
//! \param[in] p    Pointer to the active DCL_PI controller structure
//! \param[in] q    Pointer to the ZPK3 structure
//! \return         None
//!
static inline void DCL_loadParallelPIasZPK(DCL_PI *p, DCL_ZPK3 *q)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (q->K < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (crealf(q->z1) < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (crealf(q->z1) > (1.0f / (2.0f * p->css->T))) ? ERR_PARAM_WARN : ERR_NONE;

    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif
    p->sps->Kp = q->K * (float32_t) crealf(q->z1);
    p->sps->Ki = q->K - p->sps->Kp;
}

//! \brief          Executes a series form PI controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_PI structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \return         The control effort
//!
extern float32_t DCL_runPI_C1(DCL_PI *p, float32_t rk, float32_t yk);

//! \brief          Executes an inline series form PI controller on the FPU32
//! \param[in] p    Pointer to the DCL_PI structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \return         The control effort
//!
static inline float32_t DCL_runPI_C2(DCL_PI *p, float32_t rk, float32_t yk)
{
    float32_t v2, v4, v5, v9;

    v2 = p->Kp * (rk - yk);
    v4 = p->i10 + (p->Ki * p->i6 * v2);
    v5 = v2 + v4;
    v9 = (v5 > p->Umax) ? p->Umax : v5;
    v9 = (v9 < p->Umin) ? p->Umin : v9;
    p->i10 = v4;
    p->i6 = (v5 == v9) ? 1.0f : 0.0f;

#ifdef DCL_TESTPOINTS_ENABLED
    p->css->tpt = v5;
#endif

    return(v9);
}

//! \brief          Executes a parallel form PI controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_PI structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \return         The control effort
//!
static inline float32_t DCL_runPI_C3(DCL_PI *p, float32_t rk, float32_t yk)
{
    float32_t v1, v2, v4, v5, v9;

    v1 = rk - yk;
    v2 = p->Kp * v1;
    v4 = (v1 * p->Ki * p->i6) + p->i10;
    p->i10 = v4;
    v5 = v2 + v4;
    v9 = (v5 > p->Umax) ? p->Umax : v5;
    v9 = (v9 < p->Umin) ? p->Umin : v9;
    p->i6 = (v5 == v9) ? 1.0f : 0.0f;

#ifdef DCL_TESTPOINTS_ENABLED
    p->css->tpt = v5;
#endif

    return(v9);
}

//! \brief          Executes a parallel form PI controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_PI structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \return         The control effort
//!
extern float32_t DCL_runPI_C4(DCL_PI *p, float32_t rk, float32_t yk);

//! \brief          Executes an parallel form PI controller on the FPU32
//!                 Contains enhanced AWR logic
//! \param[in] p    Pointer to the DCL_PI structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \return         The control effort
//!
static inline float32_t DCL_runPI_C5(DCL_PI *p, float32_t rk, float32_t yk)
{
    float32_t v1, v5, v7, v8;
    uint16_t l11, l12, l14, l17, l18, l19;

    v1 = rk - yk;
    v5 = (v1 * p->Ki * p->i6) + p->i10;
    p->i10 = v5;
    v7 = (v1 * p->Kp) + v5;
    v8 = (v7 > p->Umax) ? p->Umax : v7;
    v8 = (v8 < p->Umin) ? p->Umin : v8;
    l17 = ((v7 - v8) == 0) ? 1U : 0U;
    l11 = (v5 >= p->Imax) ? 1U : 0U;
    l12 = (v5 <= p->Imin) ? 1U : 0U;
    l19 = (v5 > 0) ? 1U : 0U;
    l14 = (v1 > 0) ? 1U : 0U;
    l18 = l17 & (!(l11 | l12) | (l19 ^ l14));
    p->i6 = (l18 == 0U) ? 0.0f : 1.0f;

#ifdef DCL_TESTPOINTS_ENABLED
    p->css->tpt = v7;
#endif

    return(v8);
}

//! \brief          Executes a series form PI controller with Tustin integrator
//!                 on the FPU32.
//! \param[in] p    Pointer to the DCL_PI structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \return         The control effort
//!
static inline float32_t DCL_runPI_C6(DCL_PI *p, float32_t rk, float32_t yk)
{
    float32_t v2, v4, v5, v8, v9;

    v2 = (rk - yk) * p->Kp;
    v8 = v2 * p->Ki * p->i6;
    v4 = v8 + p->i11 + p->i10;
    v5 = v2 + v4;
    p->i10 = v4;
    p->i11 = v8;
    v9 = (v5 > p->Umax) ? p->Umax : v5;
    v9 = (v9 < p->Umin) ? p->Umin : v9;
    p->i6 = (v5 == v9) ? 1.0f : 0.0f;

#ifdef DCL_TESTPOINTS_ENABLED
    p->css->tpt = v4;
#endif

    return(v9);
}

//! \brief          Executes a series form PI controller with Tustin integrator
//!                 on the FPU32. Implemented as assembly module.
//! \param[in] p    Pointer to the DCL_PI structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback value
//! \return         The control effort
//!
extern float32_t DCL_runPI_C7(DCL_PI *p, float32_t rk, float32_t yk);


//--- Linear PI2 controller --------------------------------------------------

//! \brief          Defines the DCL_PI2 shadow parameter set
//!
typedef volatile struct dcl_pi2_sps {
    float32_t Kp;       //!< Proportional gain
    float32_t Ki;       //!< Integral gain
    float32_t Umax;     //!< Upper saturation limit
    float32_t Umin;     //!< Lower saturation limit
} DCL_PI2_SPS;

#define PI2_SPS_DEFAULTS { 1.0f, 0.0f, 1.0f, -1.0f }

//! \brief          Defines the DCL_PI2 controller structure
//!
typedef volatile struct dcl_pi2 {
    float32_t Kp;       //!< Proportional gain
    float32_t Ki;       //!< Integral gain
    float32_t i6;       //!< Integrator 1 storage
    float32_t i9;       //!< Integrator 2 storage
    float32_t i12;      //!< Saturation 1 storage
    float32_t i13;      //!< Saturation 2 storage
    float32_t Umax;     //!< Upper saturation limit
    float32_t Umin;     //!< Lower saturation limit
    DCL_PI2_SPS *sps;   //!< Pointer to the shadow parameter set
    DCL_CSS *css;       //!< Pointer to the common support structure
} DCL_PI2;

//! \brief  Defines default values to initialize the DCL_PI2 structure
//!
#define PI2_DEFAULTS { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, -1.0f, \
        NULL_ADDR, NULL_ADDR }

//! \brief          Resets the integrators of the PI2 controller
//! \param[in] p    Pointer to the DCL_PI2 structure
//! \return         None
//!
static inline void DCL_resetPI2(DCL_PI2 *p)
{
    uint16_t v;

    v = DCL_DISABLE_INTS;
    p->i6 = p->i9 = 0.0f;
    p->i12 = p->i13 = 1.0f;
    p->css->err = ERR_NONE;
    DCL_RESTORE_INTS(v);
}

//! \brief          Loads active controller parameters
//! \param[in] p    Pointer to the DCL_PI2 controller structure
//! \return         None
//!
static inline void DCL_updatePI2(DCL_PI2 *p)
{
    uint16_t v;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (p->sps->Umax <= p->sps->Umin) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (p->css->T <= 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (p->sps->Kp < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (p->sps->Ki < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        p->Ki = p->sps->Ki;
        p->Kp = p->sps->Kp;
        p->Umax = p->sps->Umax;
        p->Umin = p->sps->Umin;
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads PI2 tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_PI2 controller structure
//! \return         None
//!
extern void DCL_fupdatePI2(DCL_PI2 *p);

//! \brief          Executes an inline series form PI2 controller on the FPU32
//! \param[in] p    Pointer to the DCL_PI2 structure
//! \param[in] rk   The controller set-point reference
//! \param[in] yk   The measured feedback
//! \return         The control effort
//!
static inline float32_t DCL_runPI2_C1(DCL_PI2 *p, float32_t rk, float32_t yk)
{
    float32_t v1, v2, v5, v8, v10, v11, v14;
    uint16_t l1, l2, l3, l4, l5, l6;

    v1 = rk - yk;
    v2 = p->Kp * v1;
    v5 = (v1 * p->Ki * p->i12) + p->i6;
    p->i6 = v5;
    v8 = (v5 * p->i13) + p->i9;
    p->i9 = v8;
    v10 = v2 + v8;
    v11 = (v10 > p->Umax) ? p->Umax : v10;
    v11 = (v11 < p->Umin) ? p->Umin : v11;
    v14 = v10 - v11;
    l1 = (v1 > 0.0f) ? 1U : 0U;
    l2 = (v14 > 0.0f) ? 1U : 0U;
    l3 = (v14 == 0.0f) ? 1U : 0U;
    l4 = (v5 > 0.0f) ? 1U : 0U;
    l5 = l3 | (l1 ^ l2);
    l6 = l3 | (l4 ^ l2);
    p->i12 = (l5 == 1U) ? 1.0f : 0.0f;
    p->i13 = (l6 == 1U) ? 1.0f : 0.0f;

#ifdef DCL_TESTPOINTS_ENABLED
    p->css->tpt = v8;
#endif

    return(v11);
}


//--- Direct Form 1 - 1st order ----------------------------------------------

//! \brief          Defines the DCL_DF11 shadow parameter set
//!
typedef volatile struct dcl_df11_sps {
    float32_t b0;   //!< b0
    float32_t b1;   //!< b1
    float32_t a1;   //!< a1
} DCL_DF11_SPS;

#define DF11_SPS_DEFAULTS { 0.5f, 0.5f, 1.0f }

//! \brief          Defines the DCL_DF11 controller structure
//!
typedef volatile struct dcl_df11 {
    float32_t b0;   //!< b0
    float32_t b1;   //!< b1
    float32_t a1;   //!< a1
    float32_t d1;   //!< e(k-1)
    float32_t d2;   //!< u(k-1)
    DCL_DF11_SPS *sps;   //!< Pointer to the shadow parameter set
    DCL_CSS *css;   //!< Pointer to the common support structure
} DCL_DF11;

//! \brief          Defines default values to initialize the DCL_DF11 structure
//!
#define DF11_DEFAULTS { 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, \
                        NULL_ADDR, NULL_ADDR }

//! \brief          Resets the DCL_DF11 controller
//! \param[in] p    Pointer to the DCL_DF11 structure
//! \return         None
//!
static inline void DCL_resetDF11(DCL_DF11 *p)
{
    uint16_t v;

    v = DCL_DISABLE_INTS;
    p->d1 = p->d2 = 0.0f;
    p->css->err = ERR_NONE;
    DCL_RESTORE_INTS(v);
}

//! \brief          Updates active coefficients in the DF11 structure
//! \param[in] p    Pointer to the DCL_DF11 controller structure
//! \return         None
//!
static inline void DCL_updateDF11(DCL_DF11 *p)
{
    uint16_t v;

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        p->b0 = p->sps->b0;
        p->b1 = p->sps->b1;
        p->a1 = p->sps->a1;
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads DF11 tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_DF11 controller structure
//! \return         None
//!
extern void DCL_fupdateDF11(DCL_DF11 *p);

//! \brief          Determines stability of the shadow DF11 compensator
//! \param[in] p    Pointer to the DCL_DF11 controller structure
//! \return         'true' if the pole has magnitude less than 1, 'false' otherwise
//!
static inline bool DCL_isStableDF11(DCL_DF11 *p)
{
    return(DCL_isStablePn1(p->sps->a1));
}

//! \brief          Loads the DF11 shadow coefficients from a ZPK3 description
//!                 Note: new settings take effect after DCL_updateDF11()
//!                 Only real z1 & p1 considered: all other roots ignored
//! \param[in] p    Pointer to the DCL_DF11 controller structure
//! \param[in] q    Pointer to the ZPK3 structure
//! \return         None
//!
static inline void DCL_loadDF11asZPK(DCL_DF11 *p, DCL_ZPK3 *q)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= !(F32_IS_VALUE(cimagf(q->z1), 0.0f)) ? ERR_PARAM_WARN : ERR_NONE;
    p->css->err |= !(F32_IS_VALUE(cimagf(q->p1), 0.0f)) ? ERR_PARAM_WARN : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->b0 = q->K;
    p->sps->b1 = -q->K * (float32_t) crealf(q->z1);
    p->sps->a1 = (float32_t) -crealf(q->p1);
}

//! \brief          Loads compensator coefficients to emulate series form PI
//!                 Note: new settings take effect after DCL_updateDF11()
//! \param[in] p    Pointer to the DCL_DF11 controller structure
//! \param[in] Kp   Proportional gain
//! \param[in] Ki   Integral gain
//! \param[in] T    Sampling period in seconds
//! \return         None
//!
static inline void DCL_loadDF11asPI(DCL_DF11 *p, float32_t Kp, float32_t Ki)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (Kp < 0.0f) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (Ki < 0.0f) ? ERR_PARAM_INVALID : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->b0 = Kp * (Ki * p->css->T + 2.0f) / 2.0f;
    p->sps->b1 = Kp * (Ki * p->css->T - 2.0f) / 2.0f;
    p->sps->a1 = -1.0f;
}

//! \brief          Executes a 1st order Direct Form 1 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF11 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
extern float32_t DCL_runDF11_C1(DCL_DF11 *p, float32_t ek);

//! \brief          Executes a 1st order Direct Form 1 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF11 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
static inline float32_t DCL_runDF11_C2(DCL_DF11 *p, float32_t ek)
{
    p->d2 = (ek * p->b0) + (p->d1 * p->b1) - (p->d2 * p->a1);
    p->d1 = ek;

    return(p->d2);
}


//--- Direct Form 1 - 3rd order ----------------------------------------------

//! \brief          Defines the DCL_DF13 shadow parameter set
//!
typedef volatile struct dcl_df13_sps {
    float32_t b0;  //!< b0
    float32_t b1;  //!< b1
    float32_t b2;  //!< b2
    float32_t b3;  //!< b3
    float32_t a0;  //!< a0
    float32_t a1;  //!< a1
    float32_t a2;  //!< a2
    float32_t a3;  //!< a3
} DCL_DF13_SPS;

#define DF13_SPS_DEFAULTS { 0.25f, 0.25f, 0.25f, 0.25f, 1.0f, 0.0f, 0.0f, 0.0f }

//! \brief          Defines the DCL_DF13 controller structure
//!
typedef volatile struct dcl_df13 {
    // coefficients
    float32_t b0;   //!< b0
    float32_t b1;   //!< b1
    float32_t b2;   //!< b2
    float32_t b3;   //!< b3
    float32_t a0;   //!< a0
    float32_t a1;   //!< a1
    float32_t a2;   //!< a2
    float32_t a3;   //!< a3

    //data
    float32_t d0;   //!< e(k)
    float32_t d1;   //!< e(k-1)
    float32_t d2;   //!< e(k-2)
    float32_t d3;   //!< e(k-3)
    float32_t d4;   //!< u(k)
    float32_t d5;   //!< u(k-1)
    float32_t d6;   //!< u(k-2)
    float32_t d7;   //!< u(k-3)

    DCL_DF13_SPS *sps;   //!< Pointer to the shadow parameter set
    DCL_CSS *css;   //!< Pointer to the common support structure
} DCL_DF13;

//! \brief          Defines default values to initialize the DCL_DF13 structure
//!
#define DF13_DEFAULTS { 0.25f, 0.25f, 0.25f, 0.25f, 1.0f, 0.0f, 0.0f, 0.0f, \
                        0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, \
                        NULL_ADDR, NULL_ADDR }

//! \brief          Resets internal controller data to zero
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \return         None
//!
static inline void DCL_resetDF13(DCL_DF13 *p)
{
    uint16_t v;

    v = DCL_DISABLE_INTS;
    p->d0 = p->d1 = p->d2 = p->d3 = p->d4 = p->d5 = p->d6 = p->d7 = 0.0f;
    p->css->err = ERR_NONE;
    DCL_RESTORE_INTS(v);
}

//! \brief          Copies coefficients from one DF13 structure to another
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \return         None
//!
static inline void DCL_updateDF13(DCL_DF13 *p)
{
    uint16_t v;

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        p->b0 = p->sps->b0;
        p->b1 = p->sps->b1;
        p->b2 = p->sps->b2;
        p->b3 = p->sps->b3;
        p->a0 = p->sps->a0;
        p->a1 = p->sps->a1;
        p->a2 = p->sps->a2;
        p->a3 = p->sps->a3;
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads DF13 tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_DF13 controller structure
//! \return         None
//!
extern void DCL_fupdateDF13(DCL_DF13 *p);

//! \brief          Determines stability of the shadow compensator
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \return         'true' if all poles have magnitude less than 1, 'false' otherwise
//!
static inline bool DCL_isStableDF13(DCL_DF13 *p)
{
    return(DCL_isStablePn3(1.0f, p->sps->a1, p->sps->a2, p->sps->a3));
}

//! \brief          Loads the DF13 shadow coefficients from a ZPK3 description
//!                 Note: new settings take effect after DCL_updateDF13()
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \param[in] q    Pointer to the DCL_ZPK3 structure
//! \return         None
//!
static inline void DCL_loadDF13asZPK(DCL_DF13 *p, DCL_ZPK3 *q)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (!F32_IS_VALUE((cimagf(q->z1) + cimagf(q->z2) + cimagf(q->z3)), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (!F32_IS_VALUE((cimagf(q->p1) + cimagf(q->p2) + cimagf(q->p3)), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->b0 = q->K;
    p->sps->b1 = -q->K * (float32_t) crealf(q->z1 + q->z2 + q->z3);
    p->sps->b2 = q->K * (float32_t) crealf((q->z1 * q->z2) + (q->z1 * q->z3) + (q->z2 * q->z3));
    p->sps->b3 = -q->K * (float32_t) crealf(q->z1 * q->z2 * q->z3);
    p->sps->a0 = 1.0f;
    p->sps->a1 = (float32_t) -crealf(q->p1 + q->p2 + q->p3);
    p->sps->a2 = (float32_t) crealf((q->p1 * q->p2) + (q->p1 * q->p3) + (q->p2 * q->p3));
    p->sps->a3 = (float32_t) -crealf(q->p1 * q->p2 * q->p3);
}

//! \brief          Executes a full 3rd order Direct Form 1 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
extern float32_t DCL_runDF13_C1(DCL_DF13 *p, float32_t ek);

//! \brief          Executes an immediate 3rd order Direct Form 1 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \param[in] ek   The servo error
//! \param[in] vk   The partial pre-computed control effort
//! \return         The control effort
//!
extern float32_t DCL_runDF13_C2(DCL_DF13 *p, float32_t ek, float32_t vk);

//! \brief          Executes a partial pre-computed 3rd order Direct Form 1 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \param[in] ek   The servo error
//! \param[in] uk   The controller output in the previous sample interval
//! \return         The control effort
//!
extern float32_t DCL_runDF13_C3(DCL_DF13 *p, float32_t ek, float32_t uk);

//! \brief          Executes a full 3rd order Direct Form 1 controller on the FPU32
//!                 Implemented as inline C function
//!                 Note: d0 not used
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
static inline float32_t DCL_runDF13_C4(DCL_DF13 *p, float32_t ek)
{
    p->d4 = (ek * p->b0) + (p->d1 * p->b1) + (p->d2 * p->b2) + (p->d3 * p->b3) - (p->d5 * p->a1) - (p->d6 * p->a2) - (p->d7 * p->a3);
    p->d3 = p->d2;
    p->d2 = p->d1;
    p->d1 = ek;
    p->d7 = p->d6;
    p->d6 = p->d5;
    p->d5 = p->d4;

    return(p->d4);
}

//! \brief          Executes an immediate 3rd order Direct Form 1 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \param[in] ek   The servo error
//! \param[in] vk   The partial pre-computed control effort
//! \return         The control effort
//!
static inline float32_t DCL_runDF13_C5(DCL_DF13 *p, float32_t ek, float32_t vk)
{
    p->d4 = (ek * p->b0) + vk;

    return(p->d4);
}

//! \brief          Executes a partial pre-computed 3rd order Direct Form 1 controller on the FPU32
//!                 Implemented as inline C function
//!                 Note: d0 not used
//! \param[in] p    Pointer to the DCL_DF13 controller structure
//! \param[in] ek   The servo error
//! \param[in] uk   The controller output in the previous sample interval
//! \return         The control effort
//!
static inline float32_t DCL_runDF13_C6(DCL_DF13 *p, float32_t ek, float32_t uk)
{
    float32_t v9;

    v9 = (ek * p->b1) + (p->d1 * p->b2) + (p->d2 * p->b3) - (uk * p->a1) - (p->d5 * p->a2) - (p->d6 * p->a3);
    p->d2 = p->d1;
    p->d1 = ek;
    p->d6 = p->d5;
    p->d5 = uk;

    return(v9);
}


//--- Direct Form 2 - 2nd order ----------------------------------------------

//! \brief          Defines the DCL_DF22 shadow parameter set
//!
typedef volatile struct dcl_df22_sps {
    float32_t b0;   //!< b0
    float32_t b1;   //!< b1
    float32_t b2;   //!< b2
    float32_t a1;   //!< a1
    float32_t a2;   //!< a2
} DCL_DF22_SPS;

#define DF22_SPS_DEFAULTS { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }

//! \brief          Defines the DCL_DF22 controller structure
//!
typedef volatile struct dcl_df22 {
    float32_t b0;   //!< b0
    float32_t b1;   //!< b1
    float32_t b2;   //!< b2
    float32_t a1;   //!< a1
    float32_t a2;   //!< a2
    float32_t x1;   //!< x1
    float32_t x2;   //!< x2
    DCL_DF22_SPS *sps;  //!< Pointer to the shadow parameter set
    DCL_CSS *css;   //!< Pointer to the common support structure
} DCL_DF22;

//! \brief          Defines default values to initialize the DCL_DF22 structure
//!
#define DF22_DEFAULTS { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, \
                        NULL_ADDR, NULL_ADDR }

//! \brief          Resets internal DF22 controller data to zero
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \return         None
//!
static inline void DCL_resetDF22(DCL_DF22 *p)
{
    uint16_t v;

    v = DCL_DISABLE_INTS;
    p->x1 = p->x2 = 0.0f;
    p->css->err = ERR_NONE;
    DCL_RESTORE_INTS(v);
}

//! \brief          Updates active coefficients from shadow set
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \return         None
//!
static inline void DCL_updateDF22(DCL_DF22 *p)
{
    uint16_t v;

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        p->b0 = p->sps->b0;
        p->b1 = p->sps->b1;
        p->b2 = p->sps->b2;
        p->a1 = p->sps->a1;
        p->a2 = p->sps->a2;
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads DF22 tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_DF22 controller structure
//! \return         None
//!
extern void DCL_fupdateDF22(DCL_DF22 *p);

//! \brief          Determines stability of the shadow compensator
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \return         'true' if both poles have magnitude less than 1, 'false' otherwise
//!
static inline bool DCL_isStableDF22(DCL_DF22 *p)
{
    return(DCL_isStablePn2(1.0f, p->sps->a1, p->sps->a2));
}

//! \brief          Loads the DF22 shadow coefficients from a ZPK3 description
//!                 Note: new settings take effect after DCL_updateDF22()
//!                 Only z1, z2, p1 & p2 considered: z3 & p3 ignored
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] q    Pointer to the DCL_ZPK3 structure
//! \return         None
//!
static inline void DCL_loadDF22asZPK(DCL_DF22 *p, DCL_ZPK3 *q)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (!F32_IS_VALUE((cimagf(q->z1) + cimagf(q->z2)), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (!F32_IS_VALUE((cimagf(q->p1) + cimagf(q->p2)), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->b0 = q->K;
    p->sps->b1 = -q->K * (float32_t) crealf(q->z1 + q->z2);
    p->sps->b2 = q->K * (float32_t) crealf(q->z1 * q->z2);
    p->sps->a1 = (float32_t) -crealf(q->p1 + q->p2);
    p->sps->a2 = (float32_t) crealf(q->p1 * q->p2);
}

//! \brief          Loads the DF22 shadow coefficients from damping ratio and un-damped
//!                 natural frequency using sample rate in CSS.
//!                 Note: new settings take effect after DCL_updateDF22()
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] z    The damping ratio
//! \param[in] wn   The un-damped natural frequency in rad/s
//! \return         None
//!
static inline void DCL_loadDF22asZwn(DCL_DF22 *p, float32_t z, float32_t wn)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (z < 0.0f) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (wn < 0.0f) ? ERR_PARAM_INVALID : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    float32_t n = 1.0f / (4.0f + 4.0f*z*wn*p->css->T + wn*wn*p->css->T*p->css->T);
    p->sps->b0 = wn*wn*p->css->T*p->css->T*n;
    p->sps->b1 = 2.0f*p->sps->b0;
    p->sps->b2 = p->sps->b0;
    p->sps->a1 = (2.0f*wn*wn*p->css->T*p->css->T - 8.0f) * n;
    p->sps->a2 = (4.0f - 4.0f*z*wn*p->css->T + wn*wn*p->css->T*p->css->T) * n;
}

//! \brief          Loads the shadow DF22 compensator coefficients to emulate a parallel form PID
//!                 Note: new settings take effect after DCL_updateDF22()
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] Kp   Proportional gain
//! \param[in] Ki   Integral gain
//! \param[in] Kd   Derivative gain
//! \param[in] fc   Derivative path filter bandwidth in Hz
//! \return         None
//!
static inline void DCL_loadDF22asParallelPID(DCL_DF22 *p, float32_t Kp, float32_t Ki, float32_t Kd, float32_t fc)
{
    float32_t   Kdp, tau, c1, c2;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (Kp < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (Ki < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (Kd < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (fc < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (fc > (1.0f / (2.0f * p->css->T))) ? ERR_PARAM_WARN : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    tau = 1.0f / (2.0f * CONST_PI_32 * fc);
    c1 = 2.0f / (p->css->T + 2.0f*tau);
    c2 = c1 * (p->css->T - 2.0f*tau) / 2.0f;
    Kdp = Kd * c1;
    p->sps->b0 = Kp + Ki + Kdp;
    p->sps->b1 = Kp*(c2 - 1) + Ki*c2 - 2.0f*Kdp;
    p->sps->b2 = -c2*Kp + Kdp;
    p->sps->a1 = c2 - 1;
    p->sps->a2 = -c2;
}

//! \brief          Loads the shadow DF22 compensator coefficients to emulate a series form PID
//!                 Note: new settings take effect after DCL_updateDF22()
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] Kp   Proportional gain
//! \param[in] Ki   Integral gain
//! \param[in] Kd   Derivative gain
//! \param[in] fc   Derivative path filter bandwidth in Hz
//! \return         None
//!
static inline void DCL_loadDF22asSeriesPID(DCL_DF22 *p, float32_t Kp, float32_t Ki, float32_t Kd, float32_t fc)
{
    float32_t   Kdp, tau, c1, c2;

#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (Kp < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (Ki < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= (Kd < 0.0f) ? ERR_PARAM_RANGE : ERR_NONE;
    p->css->err |= ((fc < 0.0f) || (fc > (1.0f / (2.0f * p->css->T)))) ? ERR_PARAM_RANGE : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    tau = 1 / (2.0f * CONST_PI_32 * fc);
    c1 = 2.0f / (p->css->T + 2.0f*tau);
    c2 = c1 * (p->css->T - 2.0f*tau) / 2.0f;
    Kdp = Kd * c1;
    p->sps->b0 = Kp * (1 + Ki + Kdp);
    p->sps->b1 = Kp * (c2 - 1 + Ki*c2 - 2*Kdp);
    p->sps->b2 = Kp * (-c2 + Kdp);
    p->sps->a1 = c2 - 1;
    p->sps->a2 = -c2;
}

//! \brief          Executes a full 2nd order Direct Form 2 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
extern float32_t DCL_runDF22_C1(DCL_DF22 *p, float32_t ek);

//! \brief          Executes an immediate 2nd order Direct Form 2 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
extern float32_t DCL_runDF22_C2(DCL_DF22 *p, float32_t ek);

//! \brief          Executes a partial pre-computed 2nd order Direct Form 2 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] ek   The servo error
//! \param[in] uk   The controller output in the previous sample interval
//!
extern void DCL_runDF22_C3(DCL_DF22 *p, float32_t ek, float32_t uk);

//! \brief          Executes a full 2nd order Direct Form 2 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
static inline float32_t DCL_runDF22_C4(DCL_DF22 *p, float32_t ek)
{
    float32_t v7;

    v7 = (ek * p->b0) + p->x1;
    p->x1 = (ek * p->b1) + p->x2 - (v7 * p->a1);
    p->x2 = (ek * p->b2) - (v7 * p->a2);

    return(v7);
}

//! \brief          Executes an immediate 2nd order Direct Form 2 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
static inline float32_t DCL_runDF22_C5(DCL_DF22 *p, float32_t ek)
{
    return((ek * p->b0) + p->x1);
}

//! \brief          Executes a partial pre-computed 2nd order Direct Form 2 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF22 controller structure
//! \param[in] ek   The servo error
//! \param[in] uk   The controller output in the previous sample interval
//!
static inline void DCL_runDF22_C6(DCL_DF22 *p, float32_t ek, float32_t uk)
{
    p->x1 = (ek * p->b1) + p->x2 - (uk * p->a1);
    p->x2 = (ek * p->b2) - (uk * p->a2);
}


//--- Direct Form 2 - 3rd order ----------------------------------------------

//! \brief          Defines the DCL_DF23 shadow parameter set
//!
typedef volatile struct dcl_df23_sps {
    float32_t b0;   //!< b0
    float32_t b1;   //!< b1
    float32_t b2;   //!< b2
    float32_t b3;   //!< b3
    float32_t a1;   //!< a1
    float32_t a2;   //!< a2
    float32_t a3;   //!< a3
} DCL_DF23_SPS;

#define DF23_SPS_DEFAULTS { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }

//! \brief          Defines the DCL_DF23 controller structure
//!
typedef volatile struct dcl_df23 {
    // coefficients
    float32_t b0;   //!< b0
    float32_t b1;   //!< b1
    float32_t b2;   //!< b2
    float32_t b3;   //!< b3
    float32_t a1;   //!< a1
    float32_t a2;   //!< a2
    float32_t a3;   //!< a3

    // data
    float32_t x1;   //!< x1
    float32_t x2;   //!< x2
    float32_t x3;   //!< x3

    DCL_DF23_SPS *sps; //!< Pointer to the shadow parameter set
    DCL_CSS *css;   //!< Pointer to the common support structure
} DCL_DF23;

//! \brief          Defines default values to initialize the DCL_DF23 structure
//!
#define DF23_DEFAULTS { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, \
                        0.0f, 0.0f, 0.0f, NULL_ADDR, NULL_ADDR }

//! \brief          Resets internal DF23 controller data to zero
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \return         None
//!
static inline void DCL_resetDF23(DCL_DF23 *p)
{
    uint16_t v;

    v = DCL_DISABLE_INTS;
    p->x1 = p->x2 = p->x3 = 0.0f;
    p->css->err = ERR_NONE;
    DCL_RESTORE_INTS(v);
}

//! \brief          Copies coefficients from one DF23 structure to another
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \return         None
//!
static inline void DCL_updateDF23(DCL_DF23 *p)
{
    uint16_t v;

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        p->b0 = p->sps->b0;
        p->b1 = p->sps->b1;
        p->b2 = p->sps->b2;
        p->b3 = p->sps->b3;
        p->a1 = p->sps->a1;
        p->a2 = p->sps->a2;
        p->a3 = p->sps->a3;
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads DF23 tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_DF23 controller structure
//! \return         None
//!
extern void DCL_fupdateDF23(DCL_DF23 *p);

//! \brief          Determines stability of the shadow compensator
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \return         'true' if all poles have magnitude less than 1, 'false' otherwise
//!
static inline bool DCL_isStableDF23(DCL_DF23 *p)
{
    return(DCL_isStablePn3(1.0f, p->sps->a1, p->sps->a2, p->sps->a3));
}

//! \brief          Loads the DF23 shadow coefficients from a ZPK3 description
//!                 Note: new settings take effect after DCL_updateDF23()
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \param[in] q    Pointer to the DCL_ZPK3 structure
//! \return         None
//!
static inline void DCL_loadDF23asZPK(DCL_DF23 *p, DCL_ZPK3 *q)
{
#ifdef DCL_ERROR_HANDLING_ENABLED
    p->css->err |= (!F32_IS_VALUE((cimagf(q->z1) + cimagf(q->z2) + cimagf(q->z3)), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    p->css->err |= (!F32_IS_VALUE((cimagf(q->p1) + cimagf(q->p2) + cimagf(q->p3)), 0.0f)) ? ERR_PARAM_INVALID : ERR_NONE;
    if (p->css->err)
    {
        DCL_GET_ERROR_LOC(p->css);
        DCL_RUN_ERROR_HANDLER(p->css);
    }
#endif

    p->sps->b0 = q->K;
    p->sps->b1 = -q->K * (float32_t) crealf(q->z1 + q->z2 + q->z3);
    p->sps->b2 = q->K * (float32_t) crealf((q->z1 * q->z2) + (q->z1 * q->z3) + (q->z2 * q->z3));
    p->sps->b3 = -q->K * (float32_t) crealf(q->z1 * q->z2 * q->z3);
    p->sps->a1 = (float32_t) -crealf(q->p1 + q->p2 + q->p3);
    p->sps->a2 = (float32_t) crealf((q->p1 * q->p2) + (q->p1 * q->p3) + (q->p2 * q->p3));
    p->sps->a3 = (float32_t) -crealf(q->p1 * q->p2 * q->p3);
}

//! \brief          Executes a full 3rd order Direct Form 2 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
extern float32_t DCL_runDF23_C1(DCL_DF23 *p, float32_t ek);

//! \brief          Executes an immediate 3rd order Direct Form 2 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
extern float32_t DCL_runDF23_C2(DCL_DF23 *p, float32_t ek);

//! \brief          Executes a partial pre-computed 3rd order Direct Form 2 controller on the FPU32
//!                 Implemented as an external assembly module
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \param[in] ek   The servo error
//! \param[in] uk   The controller output in the previous sample interval
//!
extern void DCL_runDF23_C3(DCL_DF23 *p, float32_t ek, float32_t uk);

//! \brief          Executes a full 3rd order Direct Form 2 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
static inline float32_t DCL_runDF23_C4(DCL_DF23 *p, float32_t ek)
{
    float32_t v7;

    v7 = (ek * p->b0) + p->x1;
    p->x1 = (ek * p->b1) + p->x2 - (v7 * p->a1);
    p->x2 = (ek * p->b2) + p->x3 - (v7 * p->a2);
    p->x3 = (ek * p->b3) - (v7 * p->a3);

    return(v7);
}

//! \brief          Executes an immediate 3rd order Direct Form 2 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \param[in] ek   The servo error
//! \return         The control effort
//!
static inline float32_t DCL_runDF23_C5(DCL_DF23 *p, float32_t ek)
{
    return((ek * p->b0) + p->x1);
}

//! \brief          Executes a partial pre-computed 3rd order Direct Form 2 controller on the FPU32
//!                 Implemented as inline C function
//! \param[in] p    Pointer to the DCL_DF23 controller structure
//! \param[in] ek   The servo error
//! \param[in] uk   The controller output in the previous sample interval
//!
static inline void DCL_runDF23_C6(DCL_DF23 *p, float32_t ek, float32_t uk)
{
    p->x1 = (ek * p->b1) + p->x2 - (uk * p->a1);
    p->x2 = (ek * p->b2) + p->x3 - (uk * p->a2);
    p->x3 = (ek * p->b3) - (uk * p->a3);
}


//--- Direct Form 2 - clamp --------------------------------------------------

//! \brief          Saturates a control variable and returns 1 if either limit is exceeded.
//!                 Implemented as an external assembly module.
//!
//! \details        Can be used to saturate a pre-computed Direct Form 2 controller.
//!                 If the immediate result is in range it can be used, otherwise
//!                 it can be clamped and the next partial pre-computation skipped.
//!                 An example of use with a pre-computed DF22 controller follows:
//!
//! \code
//! uk = DCL_runDF22_C2(&arma2, rk);                // immediate result from pre-computed controller
//! i = DCL_runClamp_C1(&uk, 1.0f, -1.0f);          // clamp immediate result to +/-1.0
//! // ...use uk here...
//! if (0 == i)                                     // if immediate result is in range...
//! {
//!     DCL_runDF22_C3(&arma2, rk, uk);             // ...pre-compute the next partial result
//! }
//! \endcode
//!
//! \param[in] data The address of the data variable
//! \param[in] Umax The upper limit
//! \param[in] Umin The lower limit
//! \return         Returns 0 if (Umin < data < Umax), else 1
//!
extern uint16_t DCL_runClamp_C1(float32_t *data, float32_t Umax, float32_t Umin);

//! \brief          Saturates a control variable and returns 1 if either limit is exceeded
//! \param[in] data The address of the data variable
//! \param[in] Umax The upper limit
//! \param[in] Umin The lower limit
//! \return         Returns 0 if (Umin < data < Umax), else 1
//!
static inline uint16_t DCL_runClamp_C2(float32_t *data, float32_t Umax, float32_t Umin)
{
    float32_t iv = *(data);

    *(data) = (*(data) > Umax) ? Umax : *(data);
    *(data) = (*(data) < Umin) ? Umin : *(data);

    return(((iv < Umax) && (iv > Umin)) ? 0U : 1U);
}

//--- Gain Scheduler Module --------------------------------------------------

//! \brief          Number of piecewise linear sections
//!
#define GSM_N   8

//! \brief          Defines the shadow DCL_GSM structure
//!
typedef volatile struct dcl_gsm_sps {
    float32_t   m[GSM_N];       //!< sector gain array
    float32_t   c[GSM_N+1];     //!< sector offset array
} DCL_GSM_SPS;

#define GSM_SPS_DEFAULTS { { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, \
        { 0.0f, 0.125f, 0.25f, 0.375f, 0.5f, 0.625f, 0.75f, 0.875f, 1.0f } }

//! \brief          Defines the DCL_GSM structure
//!                 Consists of gains and offsets for each linear section
//!
typedef volatile struct dcl_gsm {
    float32_t   m[GSM_N];       //!< sector gain array
    float32_t   c[GSM_N+1];     //!< sector offset array
    float32_t   h;              //!< interval size
    DCL_GSM_SPS *sps;           //!< Pointer to the shadow parameter set
    DCL_CSS *css;               //!< Pointer to the common support structure
} DCL_GSM;

//! \brief          Defines default values for the GSM structure (GSM_N=8)
//!
#define GSM_DEFAULTS { { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }, \
                       { 0.0f, 0.125f, 0.25f, 0.375f, 0.5f, 0.625f, 0.75f, 0.875f, 1.0f }, \
                         1.0f/GSM_N, NULL_ADDR, NULL_ADDR }

//! \brief          Resets the active GSM parameters to unity gain
//!                 SPS is unaffected
//! \param[in] p    Pointer to the active DCL_GSM controller structure
//! \return         None
//!
static inline void DCL_resetGSM(DCL_GSM *p)
{
    uint16_t j, v;

    v = DCL_DISABLE_INTS;
    for (j=0; j<GSM_N; j++)
    {
        p->m[j] = 1.0f;
        p->c[j] = (float32_t) j * p->h;
    }
    p->c[GSM_N] = 1.0f;
    DCL_RESTORE_INTS(v);
}

//! \brief          Loads the active GSM parameters
//! \param[in] p    Pointer to the active DCL_GSM controller structure
//! \return         None
//!
static inline void DCL_updateGSM(DCL_GSM *p)
{
    uint16_t j, v;

    if (p->css->sts & STS_UPDATE_PENDING)
    {
        v = DCL_DISABLE_INTS;
        for (j=0; j<GSM_N; j++)
        {
            p->m[j] = p->sps->m[j];
            p->c[j] = p->sps->c[j];
        }
        p->c[GSM_N] = p->sps->c[GSM_N];
        DCL_RESTORE_INTS(v);
        DCL_CLEAR_UPDATE_REQUEST(p);
    }
}

//! \brief          Loads GSM tuning parameters
//!                 Implemented as assembly module
//! \param[in] p    Pointer to the active DCL_GSM controller structure
//! \return         None
//!
extern void DCL_fupdateGSM(DCL_GSM *p);

//! \brief          Computes the shadow scheduler gains from the offset selection
//!                 Active gains are unaffected until DCL_updateGSM() is called
//! \param[in] p    Pointer to DCL_GSM structure
//! \return         None
//!
static inline void DCL_loadGSMgains(DCL_GSM *p)
{
    uint16_t j;

    for (j=0; j<GSM_N; j++)
    {
        p->sps->m[j] = (p->sps->c[j+1] - p->sps->c[j]) * (float32_t) GSM_N;
    }
}

//! \brief          Computes the shadow scheduler offsets from the gain selection
//!                 Active offsets are unaffected until DCL_updateGSM() is called
//! \param[in] p    Pointer to DCL_GSM structure
//! \return         None
//!
static inline void DCL_loadGSMoffsets(DCL_GSM *p)
{
    uint16_t j;

    for (j=0; j<=GSM_N; j++)
    {
        p->sps->c[j] = p->sps->c[j-1] + (p->sps->m[j-1] * p->h);
    }
    p->sps->c[0] = 0.0f;
}

//! \brief          Runs the Gain Scheduler Module
//! \param[in] p    Pointer to DCL_GSM structure
//! \param[in] x    Input
//! \return         Control effort
//!
static inline float32_t DCL_runGSM_C1(DCL_GSM *p, float32_t x)
{
    uint16_t sector;
    float32_t sgnx, modx, rval;

    sgnx = (x >= 0.0f) ? 1.0f : -1.0f;
    modx = fabsf(x);

    if (modx >= 1.0f)
    {
        rval = p->c[GSM_N] * sgnx;
    }
    else
    {
        sector = (uint16_t) (modx * (float32_t) GSM_N);
        rval = (p->m[sector] * (modx - (sector * p->h)) + p->c[sector]) * sgnx;
    }

    return(rval);
}

//----------------------------------------------------------------------------

#ifdef __cplusplus
}
#endif // extern "C"

#endif // _C_DCLF32_H

/* end of file */
