3.5. Configuration Parameters

There are many configurable parameters in the MCAF:

  • control gains
  • time delays
  • ramp rates
  • filter time constants
  • full-scale values (current, voltage, duty cycle, etc.)
  • limits (current, voltage, duty cycle, etc.)

and so on. The velocity control loop is a good example. Algebraically it looks like this:

(3.1)\[I_q = K_p \omega_{err} + \int K_i \omega_{err}\ dt\]

The question becomes: how can we manage parameters such as K_p and K_i for each control loop and take advantage of fixed-point mathematics?

3.5.1. Classic Application Notes

Microchip’s classic application notes, such as AN1292 and AN1078 used calculations expressed via the C preprocessor. For example:

Listing 3.7 from AN1078 UserParms.h
#define DFCY        70000000         // Instruction cycle frequency (Hz)
#define DTCY        (1.0/DFCY)               // Instruction cycle period (sec)
#define DDEADTIME   (unsigned int)(DEADTIMESEC*DFCY) // Dead time in dTcys
#define LOOPTIMEINSEC (1.0/PWMFREQUENCY) // PWM Period = 1.0 / PWMFREQUENCY
#define IRP_PERCALC (unsigned int)(SPEEDLOOPTIME/LOOPTIMEINSEC)      // PWM loops per velocity calculation
// PWM loops necessary for transitioning from open loop to closed loop
#define TRANSITION_STEPS   IRP_PERCALC/4

#define SPEEDLOOPTIME (float)(1.0/SPEEDLOOPFREQ) // Speed Control Period
#define LOOPINTCY    DFCY/PWMFREQUENCY   // Basic loop period in units of Tcy
#define LOCKTIME     (unsigned int)(LOCKTIMEINSEC*(1.0/LOOPTIMEINSEC))
// Time it takes to ramp from zero to MINSPEEDINRPM. Time represented in seconds
#define DELTA_STARTUP_RAMP   (unsigned int)(MINSPEEDINRPM*POLEPAIRS*LOOPTIMEINSEC* \
                                                     LOOPTIMEINSEC*65536*65536/(60*OPENLOOPTIMEINSEC))
// Number of control loops that must execute before the button routine is executed.
#define      BUTPOLLOOPCNT   (unsigned int)(BUTPOLLOOPTIME/LOOPTIMEINSEC)

Advantages of this approach are that the formula used to derive each value is visible.

Disadvantages of this approach revolve around the way the C preprocessor works. The #define approach is purely a text substitution, and the preprocessor does not actually do any calculations; the compiler optimizes by computing any calculations it can do at compile-time (also known as constant folding) but does not see the original code prior to preprocessing. Specific disadvantages are

  • Potential for cryptic or invisible errors due to overflow, type mismatch, or unsafe calculations (lack of parentheses to control precedence, no checking of values out of range). For example:

    #define FOSC 70e6
    #define SLOW_BAUDRATE 38400
    #define FAST_BAUDRATE 3*SLOW_BAUDRATE
    #define BRG_DIVIDER FOSC/16/FAST_BAUDRATE
    U1BRG = BRG_DIVIDER - 1;
    

    This code has an error; the substituted code looks like

    U1BRG = 70e6/16/3*SLOW_BAUDRATE - 1;
    

    which is missing parentheses around 3*SLOW_BAUDRATE

  • No visibility of the intermediate or final computed values

  • No debugging of preprocessor calculations is possible

  • Can cover “built-in” function calls (standard math) but not more complicated calculations (e.g. requiring loops, advanced math functions like erf or Bessel functions, or other numerical analysis)

3.5.2. Motor Control Application Framework

The MCAF approach takes advantage of the code generation features in motorBench® Development Suite. These features use a templating engine; in the MCAF firmware package are files containing parameterized code with placeholder expressions:

Listing 3.8 pi_gains.h.template
// Current loop gains
#define KII  ${INTEGRAL_GAIN_HERE    } // integral gain
#define KIP  ${PROPORTIONAL_GAIN_HERE} // proportional gain

which will get values substituted into the ${SOME_EXPRESSION} blocks and end up looking like

Listing 3.9 pi_gains.h
// Current loop gains
#define KII  3.021 // integral gain
#define KIP  7.413 // proportional gain

This provides a couple of advantages:

  • Calculations can be made at code generation time using mechanisms that can be debugged
  • Intermediate and final results can be made visible
  • Reporting of floating-point / engineering equivalents can be added
  • Almost any computable equation can be used

The sample code above is an example of floating-point parameters; since we tend to use fixed-point calculations, the firmware package templates could look more like

Listing 3.10 pi_gains.h.template
// Current loop gains
#define KII_Q12  <@describe_constant Q12(INTEGRAL_GAIN) />
#define KIP_Q12  <@describe_constant Q12(PROPORTIONAL_GAIN) />

using templating macros that produce both integer counts and an equivalent floating-point value in a comment:

Listing 3.11 pi_gains.h
// Current loop gains
#define KII_Q12  12370   // 3.021 Q12
#define KIP_Q12  30364   // 7.413 Q12

Then we can apply the defined constants in code:

Listing 3.12 pi_controller.c
void control_motor(motor *pmotor)
{
   ...
   int16_t current_error = pmotor->iCmd - pmotor->iMeas;
   pmotor->applied_voltage =
     ((int32_t)CURRENT_LOOP_GAIN_Q12 * current_error) >> 12;
   ...
}

This approach of compile-time separation (splitting configurable parameters into a definition in a template-generated C header file, and usage in one or more C source files) is essentially the one used in the MCAF. In general it has some important characteristics:

  • Good for optimization — the compiler sees the constant value, might be able to speed up the calculation.
  • Bad for modularity — What if we have software driving two motors with different gains? Then we need KII_Q12_MOTOR1 and KII_Q12_MOTOR2 and somehow in our C code we need to determine when to apply each.
  • Bad for development — We can’t use run-time diagnostic tools to try out different gains.
  • Definition and point of use are separated — this is an inevitable casualty of modular design.

We are addressing some of these concerns by taking a slightly different approach, using run-time separation, storing configuration parameters in state variables:

Listing 3.13 pi_controller.c
void control_motor(motor *pmotor)
{
   ...
   int16_t current_error = pmotor->iCmd - pmotor->iMeas;
   pmotor->applied_voltage =
     ((int32_t) pmotor->kp * current_error) >> pmotor->kp_shift;
   ...
}
Listing 3.14 init_motor.c (dynamically generated)
 void init_motor_1(motor *pmotor)
 {
    pmotor->kp = 12370;    // 3.02 Q12
    pmotor->kp_shift = 12;
 }
 void init_motor_2(motor *pmotor)
 {
    pmotor->kp = 15147;    // 1.849 Q13
    pmotor->kp_shift = 13;
 }

This allows a code architecture so that

  • more than one motor can be used with different gains for each
  • variable shift counts can be used, to support a dynamic range that is optimized for each motor.
  • parameters can be changed at run-time during development and testing

The run-time cost is slightly higher than in the case where each line of C code uses a constant fixed at compile-time, rather than a run-time parameter, but is a better approach for the MCAF.

As of MCAF version 1.0, there is a mix of compile-time and run-time separation; we are migrating to use purely run-time separation in future versions. All generated code relating to configurable parameters is located in the parameters/xyz_params.h files.

One drawback of the code generation approach is that it is not very easy to be “transparent”; that is, to show equations of where the calculated values came from. We are working to address this issue in the future.

Sample generated parameters of the MCAF:

Listing 3.15 parameters/operating_params.h
 /* --- commutation slewrate parameters --- */

 /*
  * slewrate_accel:                 7.426 krad/s^2
  *                                70.909 kRPM/s
  * slewrate_decel:                 2.335 krad/s^2
  *                                22.296 kRPM/s
  * t_coastdown:                  248.983 ms
  */

 #define VELOCITY_SLEWRATE_LIMIT1 32000
 #define VELOCITY_SLEWRATE_LIMIT_ACCEL         387      // Q15(  0.01181) =   +7.42063 rad/s       =   +7.42562 rad/s       - 0.0671%
 #define VELOCITY_SLEWRATE_LIMIT_DECEL         122      // Q15(  0.00372) =   +2.33932 rad/s       =   +2.33479 rad/s       + 0.1938%
 #define VELOCITY_COASTDOWN_TIME              4980      // Q0(4980.00000) = +249.00000 ms          = +248.98273 ms          + 0.0069%
Listing 3.16 parameters/foc_params.h
//************** PI Coefficients **************
//// Current loop
// phase margin = 80 deg
// PI phase at crossover = 45.000 deg
// crossover frequency = 2.442 k rad/s (388.596 Hz)
#define KIP                                  9151      // Q15(  0.27927) = +436.37336 mV/A        = +436.37526 mV/A        - 0.0004%
#define KIP_Q                        15
#define KII                                  1117      // Q15(  0.03409) =   +1.06530 kV/A/s      =   +1.06546 kV/A/s      - 0.0152%
#define KII_Q                        15
//// Velocity loop
// phase margin = 50 deg
// PI phase at crossover = 10.000 deg
// crossover frequency = 142.852 rad/s (22.736 Hz)
#define KWP                                 14933      // Q13(  1.82288) =  +25.73362 mA/(rad/s)  =  +25.73375 mA/(rad/s)  - 0.0005%
#define KWP_Q                        13
#define KWI                                  1505      // Q15(  0.04593) = +648.38105 mA/rad      = +648.19737 mA/rad      + 0.0283%
#define KWI_Q                        15