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:
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:
#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:
// 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
// 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
// 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:
// 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:
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
andKII_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:
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;
...
}
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:
/* --- 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%
//************** 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