# 4.5. Test Harness

The test harness represents several runtime parameters, which, in combination with adjustable parameters used elsewhere in the motor control application, can be used to put the controller into certain test modes in order to debug and test proper operation of the application, board, and/or motor. It is tightly coupled to the design of the FOC modules.

## 4.5.1. Overview

The test harness includes the features listed below.

- Operating mode — this determines whether the motor controller operates in its normal mode, or with a subset of the controller features, and is used for accretive testing, so that, for example, PWM outputs can be tested first, then three-phase modulation, then the current loop, then the velocity loop.
- Overrides — these are boolean flags that determine whether certain features such as commutation angle, DC link compensation, or input velocity command to operate normally or receive input from alternate sources.
- Square-wave perturbation — this adds a square wave signal to either velocity, current, or voltage commands.
- Seizures — Setting an appropriate flag causes either the ISR or the main loop to enter an infinite loop, triggering the watchdog timeout (if enabled). This allows testing of the watchdog timer.
- Guards — Test guards prevent accidental activation of test harness features.
- Profiling timestamps — these are 16-bit timer snapshots that can be used for profiling the main control ISR.

Seizures and guards are features that apply to the MCAF firmware as a whole, whereas the remaining features apply to each motor in the system.

## 4.5.2. Accretive Testing

Motor control testing should be possible using firmware that is as equivalent as possible (ideally identical) to the intended release firmware. This prevents errors reproducing a problem in the release firmware because it got out of sync with the test firmware.

The MCAF includes built-in test modes to support so-called “accretive” testing at run-time, allowing engineers to start from low-level tests of the power electronics, and work up through verification of open-loop commutation, current control loop, and finally velocity control loop, all without having to recompile the software. This approach is vital, both when bringing up a new circuit board, and when troubleshooting.

## 4.5.3. Test Harness Parameters

The following table lists test parameters used in various cases. Parameters are found both in the main motor data structure `MCAF_MOTOR_DATA`, and the main system data structure `MCAF_SYSTEM_DATA`, and are hierarchical (e.g. `motor.testing.sqwave.idq.q` for the q-axis current command perturbation shown below).

Parameter | Description
--- | ---
`motor.testing` | Contains testing parameters per motor
`.sqwave` | Contains parameters for square wave generator
`.halfperiod` | Half-period, in PWM cycles, of the test square wave
`.velocity` | Amplitude of velocity command perturbation
`.idq` | Amplitudes of current command perturbation
`.d` | Amplitude of d-axis current command perturbation
`.q` | Amplitude of q-axis current command perturbation
`.vdq` | Amplitudes of voltage command perturbation
`.d` | Amplitude of d-axis voltage command perturbation
`.q` | Amplitude of q-axis voltage command perturbation
`.value` | square-wave signal, 0 or ±1 (0 to disable all perturbations)
`.overrides` | Bit fields (denoted later in this document as `overrides:F` where `F` is the bit field), which control certain test overrides: `velocityCommand`: Controls whether input velocity command comes from normal input, or is held static. `commutation`: Controls whether commutation angle is overridden `dcLinkCompensation`: Controls whether DC link compensation is overridden `stallDetection`: Controls whether stall detection is masked
`.overrideOmegaElectrical` | Controls value of electrical frequency used during commutation overrides
`.operatingMode` | Controls operating mode of the motor
`.operatingMode` | Controls operating mode of the motor
`.timestampReference` | Captured timestamp reference for profiling
`.timestamps` | Captured timestamp values for profiling
`system.testing` | Contains testing parameters per system
`.flags` | Test flags, including seizure flags for the ISR and main loop
`.guard` | Enables test harness functionality

## 4.5.4. Test Guarding

The presence of test modes does represent a miniscule risk for accidental operation in these modes. If not set intentionally, the only way to enter a test mode is for certain state variables located in RAM to change autonomously. This can be considered an improbable event, with one of the following causes:

- random hardware failures
- software implementation errors
- execution of write-memory commands from the diagnostic kernel, which are in turn caused by either
  - communications errors that somehow reproduce exactly the required data
  - malicious sources

Unfortunately, such a change is persistent, and the test modes will not revert back to normal behavior on their own.

### 4.5.4.1. Test Features in Production: Should They Stay or Should They Go?

This raises a philosophical question: should a test harness remain in production code? There are two schools of thought for run-time test features.

- Test features should be left in place in production code
  - Production code is actually the exact code being tested
  - In case of an issue observed later (e.g. customer problem), it facilitates understanding of the issue and can even be activated in the field
- Test features should not be left in place in production code
  - They can take extra software resources (CPU time, program and data memory)
  - In safety-critical systems they increase the risk of unintended behavior
  - Code alterations should be removed upon completion of testing

We believe the choice should be left up to the customer; as a future enhancement, the application framework will facilitate this by allowing removal of test features either through the code generation process or the preprocessor (`#ifdef`). In any case, keeping the alterations used for testing to a minimum is extremely important: if we test codebase X but deliver codebase Y, any differences should not be significant with respect to the features being tested, otherwise they represent a shortcoming of the test process.

### 4.5.4.2. Implementation of Test Guarding

To mitigate the risk of accidentally entering test modes, an optional guard mechanism is provided. Essentially, on each ISR cycle the guard is checked and if not valid, the test parameters are reset to their normal mode of operation. The guard consists of two memory locations, a key and a timeout (`system.testing.guard.key` and `system.testing.guard.timeout`). In order for the guard to be valid, the key must match a given value (`0xD1A6` = “DIAG”). If the timeout has expired, the key is reset to an invalid value (zero), otherwise the timeout is decremented. If the guard is invalid, the test parameters are set to their normal conditions. An example of C code to implement this behavior is shown in Listing 4.2.

Listing 4.2

    #define GUARD_KEY_VALID 0xD1A6
    #define GUARD_KEY_RESET 0x0000
    if (system.testing.guard.timeout == 0)
    {
      system.testing.guard.key = GUARD_KEY_RESET;
    }
    else
    {
      --system.testing.guard.timeout;
    }
    // repeat the following block for each motor
    if (system.testing.guard.key != GUARD_KEY_VALID)
    {
      motor.testing.overrides = 0;
      motor.testing.sqwave.value = 0;
      motor.testing.operatingMode = OM_NORMAL;
    }

The intent is to require minimal execution time, and to disable test modes unless diagnostic tools are used to maintain those test modes in an active condition. The guard key should be set manually; the guard timeout can be automatically set to its maximum value (`0xFFFF`) periodically by diagnostic tools; even at 20kHz, 65536 counts represents 3.2768 seconds and should be enough time to allow reactivation in time, even if there are OS or communication hiccups.

The guard timeout mechanism should be optional: publicly-available tools do not yet support the automatic timeout reset, and in some cases the extra program and data memory usage are not warranted.

Note: MCAF v1.0 implements test guarding but not the test guard timeout mechanism.

## 4.5.5. Test Harness Architecture

### 4.5.5.1. Signal Valves

In order to add test mode hooks with minimal impact on the end application, we introduce the concept of a “signal valve” in a block diagram. This is shown in the following example as a circle with an arrow inside it:

Figure 4.4 Signal valve example

The valve represents a state variable with a choice: in its normal mode of operation, the valve is “open” and data flows normally through the valve, so that in the example in Figure 4.4, the signal \(y\) is determined by input signal \(x\) propagating through functions `f` and `g`. In a test mode, the block or blocks upstream of the valve are not executed, and the valve retains its value, so that real-time diagnostic tools can modify that value as appropriate.

For example, one way to implement it in C is shown in Listing 4.3:

Listing 4.3

     int16_t x, y, z;
     bool y_enable = true;
     void step()
     {
       // executed at a regular rate
       if (y_enable)
       {
         y = g(f(x));
       }
       z = h(y);
     }

This gives us a choice between running our control algorithm in its normal mode of operation (`y = g(f(x)); z = h(y);`) with `x` as an input variable, or in a test mode (`z = h(y);`) with `y` as an input variable.

## 4.5.6. Test harness modes

The behavior shown in Figure 4.5 (reproduced from Figure 4.3) represents the primary signal flow paths used in the motor controller. This does not show the complete functionality of the motor controller; in particular supervisory features like overcurrent and stall detection are not shown. Also note that the shaded area represents a vector controller, and signals within this area have multiple components (dq or alpha-beta or abc).

Figure 4.5 Block diagram of MCAF Field Oriented Control

The behavior of the motor controller can be modified under several different modes, described in the following sections. Refer to Figure 4.5 for the block diagram behavior. The modes are enumerated values that can be assigned to the operatingMode parameter.

### 4.5.6.1. `OM_DISABLED`

The `OM_DISABLED` mode represents a disabled controller with output transistors set to a “minimal impact state”. Valves V2-V5 are all off.

Active features of the motor controller:

- All feedback computations execute normally, including
  - ADC readings
  - Park and Clarke transforms
  - Commutation angle estimator (if possible)
  - Sine and cosine calculation
- Supervisory algorithms (stall detection, overcurrent/overvoltage/etc.)

Inactive features of the motor controller:

- All forward path computations are disabled, including
  - Rate limiters
  - Current and velocity control loops
  - Inverse Park and Clarke transforms
  - Zero Sequence Modulation or Space Vector Modulation

PWM Minimal impact state:

- Output transistors are set to a “minimal impact state”, namely:
  - upper transistors off
  - lower transistors at a minimal duty cycle to maintain bootstrap capacitor charge (or completely off if this is not required)

The behavior in the `OM_DISABLED` state is best implemented as a separate state (e.g. `STATE_TEST_DISABLE`) in the main state machine outside the main state transitions, with `OM_DISABLED` forcing entrance to that state from any other state. Re-entry into other test modes or normal behavior can then be handled in the state machine, allows exiting of the `OM_DISABLED` state to occur cleanly.

### 4.5.6.2. `OM_FORCE_VOLTAGE_PWM`

The `OM_FORCE_VOLTAGE_PWM` mode represents a test mode where the PWM outputs are active and assigned from static motor state variables, shown as \(D_{abc1}\) in Figure 4.5. Valves V2-V5 are all off.

Active/inactive features of the motor controller: Same as `OM_DISABLED`.

Use case: Testing system behavior with constant or slowly-changing PWM duty cycles.

Motor or other load may or may not be connected.

> 1. Using debug tools, set mode to `OM_DISABLED` first
> 2. Set \(D_{abc1}\) to desired values
> 3. Set mode to `OM_FORCE_VOLTAGE_PWM`

The behavior in the `OM_FORCE_VOLTAGE_PWM` is best implemented as a separate state (e.g. `STATE_TEST_ENABLE`) in the main state machine outside the normal state transitions, with `OM_FORCE_VOLTAGE_PWM` forcing entrance to that state from any other state. Overcurrent, or any other severe fault condition, should cause entrance to a disabled state that requires manual re-entry into the enabled state.

Setting `OM_FORCE_VOLTAGE_PWM` without using `OM_DISABLED` first is not forbidden, but may have undesirable consequences, since whichever values were present in \(D_{abc1}\) are “frozen” at the time of mode switch, unless changed via debug tools.

### 4.5.6.3. `OM_FORCE_VOLTAGE_ALPHABETA`

The `OM_FORCE_VOLTAGE_ALPHABETA` mode represents a test mode where the PWM outputs are active along with the ZSM block and inverse Clarke block, which takes its inputs from static motor state variables, shown as \(D_{\alpha\beta}\) in Figure 4.5. Valves V2-V4 are off; valve V5 is on.

Active/inactive features of the motor controller: Same as `OM_FORCE_VOLTAGE_PWM`, but inverse Clarke and ZSM are now active.

Use case: Testing system behavior with the inverse Clarke transform active. (rare)

Otherwise this mode is similar to `OM_FORCE_VOLTAGE_PWM` and should be handled in a special state (`STATE_TEST_ENABLE`) outside the normal state transitions.

### 4.5.6.4. `OM_FORCE_VOLTAGE_DQ`

The `OM_FORCE_VOLTAGE_DQ` mode represents a test mode where the PWM outputs are active along with the ZSM block, inverse Clarke, and inverse Park block, which takes its inputs from static motor state variables, shown as \(V_{dq0}\) in Figure 4.5. Valves V2-V3 are off; valves V4-V5 are on.

Active/inactive features of the motor controller: Same as `OM_FORCE_VOLTAGE_ALPHABETA`, but inverse Park, DC link compensation, and dq-voltage perturbation are now active.

Use case: Testing system behavior with the inverse Park transform active. This represents open-loop sinusoidal voltages applied line-to-line. Commutation angle can either be done through the normal feedback mechanism, or via forced commutation. DC link voltage compensation can be active or inactive.

Otherwise this mode is similar to `OM_FORCE_VOLTAGE_PWM` and should be handled in a special state (`STATE_TEST_ENABLE`) outside the normal state transitions.

### 4.5.6.5. `OM_FORCE_CURRENT`

The `OM_FORCE_CURRENT` mode represents a test mode where the PWM outputs are active along with the forward path and the current controller, which takes its input command from static motor state variables, shown as \(I_{dq,\text{ref0}}\) in Figure 4.5. Valve V2 is off; valves V3-V5 are on.

This can be considered a “torque mode”, since controlling \(I_{dq}\) effectively dictates motor torque. Because of this, unless the input command is small or there is a stiff mechanical load present, the motor will generally accelerate to its full speed.

Active/inactive features of the motor controller: Same as `OM_FORCE_VOLTAGE_DQ`, but the current controller and its input filter are now active.

Use case: Testing system behavior with a current loop active. Commutation angle can either be done through the normal feedback mechanism, or via forced commutation.

Otherwise this mode is similar to `OM_FORCE_VOLTAGE_PWM` and should be handled in a special state (`STATE_TEST_ENABLE`) outside the normal state transitions.

Note: Because this is a test mode, the normal motor startup process does not operate, and no automatic transition occurs from open-loop commutation to closed-loop commutation. This mode should either be enabled after the motor startup has occurred, or commutation behavior will have to be managed manually.

### 4.5.6.6. `OM_NORMAL`

The `OM_NORMAL` mode represents the default operating mode of velocity control. All features are active, including the main state machine.

## 4.5.7. Interaction with the System State Machine

Figure 4.2 shows the system state machine and its interaction with the operating mode. If the operating mode is `OM_NORMAL`, the state machine should revert to its reset state, where it can safely re-engage in its normal behavior. If the operating mode is `OM_DISABLED`, the state machine should enter `STATE_TEST_DISABLE` state, where a new test operating mode can put it into the `STATE_TEST_ENABLE` state with a clean transition. In order to support certain rare test cases, direct entry into `STATE_TEST_ENABLE` is allowed but is discouraged.

## 4.5.8. Test Harness Switches (Overrides)

Test harness switches are boolean flags stored in an `overrides` field.

### 4.5.8.1. Input Velocity Command Override

If the bitfield `overrides:velocityCommand` is false, valve V1 is enabled and the velocity command comes from its normal source.

If `overrides:velocityCommandd` is true, the velocity command is static and may be changed via debug tools.

### 4.5.8.2. Commutation Override

If the bitfield `overrides:commutation` is false, commutation angle is computed in its normal manner.

If `overrides:commutation` is true, the commutation angle is updated by accumulating \(\omega_{e,\text{override}}\) each PWM cycle. A fixed commutation angle may be achieved by setting \(\omega_{e,\text{override}} = 0\) and setting the commutation angle to the desired value, using debug tools.

In code this may look like:

Listing 4.4

     if (pmotor->testing.overrides & TEST_FLAG_COMMUTATION)
     {
       pmotor->thetaElectrical += pmotor->testing.overrideOmegaElectrical;
     }
     else
     {
       ... update electrical angle normally ...
     }

Note: The transition of commutation angle from forced commutation to normal commutation is an abrupt transition, and is likely to cause transient disturbances in motor current.

### 4.5.8.3. DC Link Voltage Compensation

If `overrides:dcLinkCompensation` is false, DC link compensation occurs normally.

If `overrides:dcLinkCompensation` is true, valve V6 is off and DC link compensation occurs using a value \(R_V\) that can be either manually adjusted or left at a fixed value.

### 4.5.8.4. Stall Detect Override

If `overrides:stallDetection` is false, all logic related to stall detection occurs normally.

If `overrides:stallDetection` is true, stall detection itself is not changed, but a detected stall condition is ignored.

This is useful for running certain tests that would otherwise cause a detected stall condition (whether real or false) to trigger a transition in the state machine and disrupt the intended test.

## 4.5.9. Perturbation Signals

Perturbation signals may be created using the test harness, which allows step response and disturbance rejection testing for the different controllers.

The state variable `sqwave` is a structure containing several members to generate a square wave perturbation signal. The field `sqwave.value` either remains at 0 (its default value) or toggles back and forth between +1 and -1 every `sqwave.halfperiod` test harness updates. For example, if the test harness update is executed during the control ISR at 20kHz, and `sqwave.halfperiod` is 8, then the half-period is 400μs and the resulting square wave frequency is 1.25kHz (= 1/800μs)

If `sqwave.value` is set to any other value, it will be limited in magnitude to 1. The code to do this is found in `MCAF_TestPerturbationUpdate`:

Listing 4.5

    inline static void MCAF_TestPerturbationUpdate(MCAF_MOTOR_TEST_MANAGER *ptest)
    {
        const int16_t value = ptest->sqwave.value;
        /* update test perturbation waveform */
        if (value != 0)
        {
            /*
             * Ensure square wave value maintains sign and is +/- 1:
             * v >> 15 computes as 0 for positive numbers and -1 for negative numbers.
             * To map to +/- 1, we just have to multiply by 2 and add 1
             * (note: (v>>14) + 1 won't work)
             */
            const int16_t valueLimited = (value >> 15)*2+1;

            /*
             * Reverse sign after N cycles (N = sqwave.halfperiod)
             */
            if (++ptest->sqwave.count >= ptest->sqwave.halfperiod)
            {
                ptest->sqwave.value = -valueLimited;
                ptest->sqwave.count = 0;
            }
            else
            {
                ptest->sqwave.value = valueLimited;
            }
        }
    }

The `sqwave.value` field is then multiplied by one of several amplitudes elsewhere in the code to setup square wave perturbation signals of variable amplitude and added into the appropriate point of the calculation:

Signal (see Figure 4.5) | Amplitude variable(s) | Description
--- | --- | ---
\(\omega_{m,\text{pert}}\) | `sqwave.omega` | Mechanical velocity
\(I_{dq,\text{pert}}\) | `sqwave.idq.d` `sqwave.idq.q` | FOC current command
\(V_{dq,\text{pert}}\) | `sqwave.V.d` `sqwave.V.q` | FOC voltage command (output of current loop)

This technique allows all test perturbation signals to be disabled by setting `sqwave.value = 0`, keeping the execution time of the test harness to a minimum (including guard processing).

## 4.5.10. Seizures

To test the watchdog timer, the test harness includes flags that can be set at runtime, to enter an infinite `while (true)` loop in either the main loop or ISR. The test guards prevent accidental activation.

## 4.5.11. Profiling Timestamps

An array of 16-bit timestamps is part of the test harness state structure. These timestamps are recorded in various places for use in profiling the main control ISR via real-time diagnostic tools. Sections of the `state_machine` module contain code such as the following:

> Listing 4.6

>     MCAF_CaptureTimestamp(&pmotor->testing, MCTIMESTAMP_STATEMACH_START);

This uses one of the timers to count at the system clock rate, so that the timer value indicates the elapsed system clock time.

The various `MCTIMESTAMP_xxxx` enum values are defined in `test_harness_timestamps.h`.

Timestamp recording is disabled by default, but can be enabled via `#define MCAF_TEST_PROFILING`.

## 4.5.12. Example use cases

This section shows several use cases and how to achieve those use cases with the test harness described in this document. Our entry assumptions are that in each case:

- We start with normal operation with `system.testing.guard.key = 0`
- The motor is at rest (not rotating)
- A debug tool is in use which can write variables to the target
- The debug tool can regularly assign `system.testing.guard.timeout = 0xFFFF` as described in Test Guarding, or the guard timeout feature is not in use
- We start each test case by setting `system.testing.guard.key = 0xD1A6` as described in Test Guarding

### 4.5.12.1. Fixed-frequency sinusoidal current control

1. `motor.testing.operatingMode = OM_DISABLED`
2. `motor.testing.overrideCommutation = true`
3. `motor.testing.overrideOmegaElectrical =` [some desired value of electrical frequency]
4. `idqCmdRaw =` [some desired value of dq current]
5. `motor.testing.operatingMode = OM_FORCE_CURRENT`

### 4.5.12.2. Current/torque control mode

1. `motor.testing.operatingMode = OM_DISABLED`
2. `idqCmdRaw =` [some desired value of dq current]
3. `motor.testing.operatingMode = OM_FORCE_CURRENT`

### 4.5.12.3. Normal operation bypassing the external control (potentiometer)

Note that the normal side effects of state machines and fault handling are still active here (e.g. open-loop → closed-loop transition, stall detection and retry, etc.) since we do not enter a test operating mode.

1. `motor.testing.overrideVelCmd = true`
2. `omegaCmd =` [some desired value of motor velocity in terms of electrical frequency]

### 4.5.12.4. Square wave velocity command disturbances to a velocity controller with fixed velocity command

This is the same as the previous case, but with a disturbance.

1. `motor.testing.overrideVelCmd = true`
2. `omegaCmd =` [some desired value of motor velocity in terms of electrical frequency]
3. `motor.testing.sqwave.halfPeriod =` [desired value of half period]
4. `motor.testing.sqwave.omega =` [some desired value of velocity amplitude]

### 4.5.12.5. Square wave current command disturbances to a velocity controller with fixed velocity command

Same as the previous section, but with a disturbance to the d and/or q currents. This case keeps the velocity roughly constant while allowing tests of the current step response. Often this is appropriate since a pure current controller will cause the motor to stall or reach its maximum operating speed and saturate.

1. `motor.testing.overrideVelCmd = true`
2. `omegaCmd =` [some desired value of motor velocity in terms of electrical frequency]
3. `motor.testing.sqwave.halfPeriod =` [desired value of half period]
4. `motor.testing.sqwave.idq.q =` [some desired value of current amplitude]
5. `motor.testing.sqwave.idq.d =` [some desired value of current amplitude]

### 4.5.12.6. Square wave current command disturbances to a current controller with fixed current command

This is the same as the current control mode, but with a disturbance to the d and/or q currents.

1. `motor.testing.operatingMode = OM_DISABLED`
2. `idqCmdRaw =` [some desired value of dq current]
3. `motor.testing.operatingMode = OM_FORCE_CURRENT`
4. `motor.testing.sqwave.halfPeriod =` [desired value of half period]
5. `motor.testing.sqwave.idq.q =` [some desired value of current amplitude]
6. `motor.testing.sqwave.idq.d =` [some desired value of current amplitude]

## 4.5.13. Implementation Notes

### 4.5.13.1. Test harness feature support

Certain features described in this section have not been implemented in v1.0 of the MCAF test harness, but are planned for future versions:

- Test guarding timeout
- Low-level test modes
  - OM_FORCE_VOLTAGE_PWM
  - OM_FORCE_VOLTAGE_ALPHABETA
- Current loop prefilter is not implemented
- Forward path of block diagram in Figure 4.5 shows DC link voltage compensation which is not yet present:
  - Multiply between \(V_{dq}\) and \(D_{dq}\) is not present
  - Output of inverse Clarke transform and SVM blocks are currently voltage, rather than a duty cycle
  - `overrides:DCLinkCompensation` therefore has no effect

### 4.5.13.2. Modules

Module | Files | Description | Comments
--- | --- | --- | ---
`test_harness` | `test_harness.c` `test_harness.h` | Test harness |
`test_harness_timestamps` | `test_harness_timestamps.c` `test_harness_timestamps.h` | Test harness logic for profiling execution times |