|
1 | 1 | # TMC4671 Class State Machine Documentation |
2 | 2 |
|
3 | | -This document describes the operation of the state machine for the `TMC4671` class, which is responsible for controlling the Trinamic TMC4671 motor driver. |
| 3 | +This document describes the operation of the asynchronous state machine for the `TMC4671` class, responsible for controlling the Trinamic TMC4671 motor driver. |
4 | 4 |
|
5 | 5 | ## 1. Possible States |
6 | 6 |
|
7 | | -The `TMC_ControlState` enum defines all possible states of the machine: |
| 7 | +The `TMC_ControlState` enum defines the primary states of the machine: |
8 | 8 |
|
9 | 9 | ```cpp |
10 | 10 | enum class TMC_ControlState : uint32_t { |
11 | | - uninitialized, |
12 | | - waitPower, |
13 | | - Shutdown, |
14 | | - Running, |
15 | | - EncoderInit, |
16 | | - EncoderFinished, |
17 | | - HardError, |
18 | | - OverTemp, |
19 | | - IndexSearch, |
20 | | - FullCalibration, |
21 | | - ExternalEncoderInit, |
22 | | - Pidautotune, |
23 | | - CoggingCalibration, |
24 | | - SlewRateCalibration |
| 11 | + uninitialized, // Initial startup and hardware detection |
| 12 | + waitPower, // Waiting for VM (Motor Voltage) to be stable |
| 13 | + Shutdown, // Motor stopped, PWM off |
| 14 | + Running, // Main operational state (Torque/Velocity/Position) |
| 15 | + EncoderInit, // Internal encoder alignment sequence |
| 16 | + EncoderFinished, // Encoder ready, waiting for start |
| 17 | + HardError, // Fatal error, requires reset |
| 18 | + OverTemp, // Thermal protection triggered |
| 19 | + IndexSearch, // Searching for encoder index pulse |
| 20 | + FullCalibration, // Complete commissioning routine |
| 21 | + ExternalEncoderInit, // External encoder setup |
| 22 | + Pidautotune, // torque PID auto-tuning |
| 23 | + CoggingCalibration, // Anti-cogging map generation |
| 24 | + SlewRateCalibration, // Maximum current ramp measurement |
| 25 | + NONE // Placeholder for recovery logic |
25 | 26 | }; |
26 | 27 | ``` |
27 | 28 |
|
28 | 29 | ## 2. State Control Variables |
29 | 30 |
|
30 | | -- `TMC_ControlState state`: Contains the current state of the machine. |
31 | | -- `TMC_ControlState laststate`: Saves the previous state before a transition to a temporary state (like a calibration), allowing a return to the normal operating state. |
32 | | -- `TMC_ControlState requestedState`: The desired destination state. The transition only occurs if `allowStateChange` is true. |
33 | | -- `bool allowStateChange`: A lock that prevents state changes during critical operations to ensure their complete execution. |
| 31 | +- `TMC_ControlState state`: The current active state. |
| 32 | +- `TMC_ControlState laststate`: Saves the previous state to allow returning after temporary routines (e.g., calibrations). |
| 33 | +- `TMC_ControlState requestedState`: The next intended state, applied in the next loop tick. |
| 34 | +- `TMC_ControlState postPowerState`: Stores a calibration state to be resumed automatically after a power loss/recovery cycle. |
| 35 | +- `bool allowStateChange`: A lock that prevents external state transitions during critical internal procedures. |
34 | 36 |
|
35 | | -## 3. Transition Mechanism |
| 37 | +## 3. Asynchronous Transition Mechanism |
36 | 38 |
|
37 | 39 | The `changeState(TMC_ControlState newState, bool force = false)` method manages transitions: |
38 | | -- **`force = true`**: The state change is immediate (`state = newState`). Used for critical or internal machine transitions. |
39 | | -- **`force = false`**: The request is stored in `requestedState`. The actual transition occurs in the main `Run()` loop when `allowStateChange` is `true`. |
| 40 | +- **`force = true`**: Immediate state change (`state = newState`). Used for critical failures or internal state machine logic. |
| 41 | +- **`force = false`**: Requests a transition by setting `requestedState`. The change is applied at the beginning of the next `Run()` iteration if `allowStateChange` is `true`. |
40 | 42 |
|
41 | 43 | ## 4. Detailed Description of States and Transitions |
42 | 44 |
|
43 | | -The main logic is implemented in the `Run()` method, which is an infinite loop containing a `switch` on the current state. |
| 45 | +The logic is executed within the `Run()` thread loop. To maintain responsiveness and prevent stack overflows, the state machine is strictly non-blocking. |
44 | 46 |
|
45 | 47 | --- |
46 | 48 |
|
47 | 49 | ### **State: `uninitialized`** |
48 | | -- **Role**: First state on startup. |
49 | | -- **Actions**: |
50 | | - 1. Attempts to communicate with the TMC4671 driver via the `pingDriver()` function. |
51 | | - 2. If communication is successful, calls `initialize()` to write the basic configuration (PWM frequency, motor type, etc.). |
52 | | -- **Transitions**: |
53 | | - - **➡️ `waitPower`**: If `pingDriver()` and `initialize()` succeed. |
54 | | - - **(Stays)**: If `pingDriver()` fails, remains in this state and retries after a delay. |
55 | | -- **Associated Commands**: No direct user commands. This is an automatic initialization step. |
| 50 | +- **Role**: Hardware discovery. |
| 51 | +- **Actions**: Pings the driver and calls `initialize()` to set PWM frequency, motor type, and basic registers. |
| 52 | +- **Transition**: ➡️ `waitPower` on success. |
56 | 53 |
|
57 | 54 | --- |
58 | 55 |
|
59 | 56 | ### **State: `waitPower`** |
60 | | -- **Role**: Wait for the motor power supply to be stable and sufficient. |
61 | | -- **Actions**: |
62 | | - 1. Periodically checks the supply voltage via `hasPower()`. |
63 | | - 2. Once the voltage is stable, calls `initializeWithPower()`, which performs ADC calibration. |
64 | | -- **Transitions**: |
65 | | - - **➡️ `EncoderInit`** or **`ExternalEncoderInit`**: If the power is stable but the encoder is not yet aligned (`!encoderAligned`). |
66 | | - - **➡️ `requestedState`** (usually `Running` or `Shutdown`): If the power is stable and the encoder is already aligned. |
67 | | - - **(Stays)**: As long as `hasPower()` returns `false`. |
68 | | -- **Special Logic**: If a voltage loss is detected in another state (e.g., `Running`), the machine immediately switches to `waitPower` to ensure a clean recovery when the voltage returns. |
69 | | -
|
70 | | ---- |
71 | | -
|
72 | | -### **State: `Shutdown`** |
73 | | -- **Role**: A safe resting state where the motor is stopped. |
| 57 | +- **Role**: Monitors supply voltage (VM). |
74 | 58 | - **Actions**: |
75 | | - 1. Ensures that PWM is disabled (`setPwm(TMC_PwmMode::off)`). |
76 | | - 2. The motor is freewheeling or braked, depending on the hardware configuration. |
77 | | -- **Transitions**: |
78 | | - - **➡️ `Running`**: Triggered by a call to `startMotor()`. |
79 | | -- **Associated Commands**: `stopMotor()` leads to this state. |
| 59 | + 1. Checks `hasPower()`. |
| 60 | + 2. Once stable (5 consecutive successful checks), calls `initializeWithPower()` for ADC calibration. |
| 61 | +- **Post-Power Recovery**: If `postPowerState` is not `NONE`, the machine automatically transitions to that state (e.g., resuming a Cogging calibration that was interrupted by a power cut). |
80 | 62 |
|
81 | 63 | --- |
82 | 64 |
|
83 | 65 | ### **State: `Running`** |
84 | | -- **Role**: The main operational state where the motor is active and controlled. |
85 | | -- **Actions**: |
86 | | - 1. Applies the requested torque, velocity, or position. |
87 | | - 2. Continuously monitors the driver status (`statusCheck()`), temperature (`getTemp()`), and the hardware emergency stop status. |
88 | | - 3. Applies torque compensation (anti-cogging) if enabled. |
89 | | -- **Transitions**: |
90 | | - - **➡️ `waitPower`**: If an undervoltage is detected. |
91 | | - - **➡️ `OverTemp`**: If the temperature exceeds the configured limit. |
92 | | - - **➡️ `Shutdown`**: Triggered by a call to `stopMotor()`. |
93 | | -- **Associated Commands**: `turn()`, `setTargetPos()`, `setTargetVelocity()`. |
| 66 | +- **Role**: Main control loop. |
| 67 | +- **Actions**: Applies real-time torque, velocity, or position. Monitors temperature and driver status flags. |
| 68 | +- **Anti-Cogging**: Applies real-time torque compensation from the Flash-stored map at ~1kHz. |
94 | 69 |
|
95 | 70 | --- |
96 | 71 |
|
97 | | -### **Encoder Initialization States: `EncoderInit` & `ExternalEncoderInit`** |
98 | | -- **Role**: Manage the complex sequence of aligning the encoder with the motor. |
99 | | -- **Actions**: |
100 | | - - This is a "super-state" that executes a sequence of sub-steps: |
101 | | - 1. `estimateABNparams()` / `calibrateAenc()`: Estimates encoder parameters. |
102 | | - 2. `findEncoderIndex()`: Searches for the encoder's index pulse (if configured). |
103 | | - 3. `bangInitEnc()`: Forces an angle on the motor with high current to determine the offset between the electrical position and the encoder position. |
104 | | - 4. `checkEncoder()`: Verifies that the encoder correctly follows the motor's movement. |
105 | | -- **Transitions**: |
106 | | - - **➡️ `EncoderFinished`**: If all steps succeed. |
107 | | - - **➡️ `HardError`**: If a step fails repeatedly. |
108 | | -- **Special Logic**: This state is critical and cannot be interrupted (`allowStateChange = false`). |
| 72 | +### **Calibration Sub-State Machines** |
109 | 73 |
|
110 | | ---- |
| 74 | +To avoid blocking the 1KB TMC thread, long calibrations use internal sub-states: |
111 | 75 |
|
112 | | -### **State: `EncoderFinished`** |
113 | | -- **Role**: A transient state marking the end of encoder initialization. |
114 | | -- **Actions**: |
115 | | - 1. Sets the `encoderAligned` flag to `true`. |
116 | | -- **Transitions**: |
117 | | - - **➡️ `Running`**: If a motor start was requested (`motorEnabledRequested`). |
118 | | - - **➡️ `Shutdown`**: Otherwise, waits in a safe mode. |
119 | | -
|
120 | | ---- |
| 76 | +#### **Cogging Calibration (`CoggingState`)** |
| 77 | +- **Mechanism**: Iterates through `ForwardWait`, `ForwardMeasure`, `BackwardWait`, `BackwardMeasure`, and `Compute`. |
| 78 | +- **Memory Management**: Large calibration arrays (~34KB) are allocated on the **heap** using `std::make_unique<CoggingCalibData>()` only when the calibration starts, and are freed immediately after completion to prevent stack overflow. |
121 | 79 |
|
122 | | -### **Calibration States: `FullCalibration`, `Pidautotune`, `CoggingCalibration`, `SlewRateCalibration`** |
123 | | -- **Role**: Execute specific calibration routines initiated by the user. |
124 | | -- **Actions**: |
125 | | - - Each state executes a specific calibration algorithm (e.g., `calibrateCogging()` measures the motor's detent torque). |
126 | | -- **Transitions**: |
127 | | - - **➡️ `laststate`**: After the calibration is finished, the machine returns to the previous state (usually `Running` or `Shutdown`). |
128 | | -- **Associated Commands**: `calibrate`, `pidautotune`, `calibrateCogging`. |
129 | | -- **Special Logic**: These states are blocking (`allowStateChange = false`). |
130 | | -- **Prerequisites**: For the calibrations to work correctly, the motor must be in a fully initialized state (encoder aligned, etc.) and powered. The state machine checks for power (`hasPower()`) before launching the routine. |
| 80 | +#### **PID Auto-Tune (`PidTuneState`)** |
| 81 | +- **Mechanism**: Iterates through `RampFluxP`, `TuneFluxI_Pulse`, and `TuneFluxI_Measure`. |
| 82 | +- **Asynchronicity**: Replaces `while` loops with tick-based measurements. If power is lost during tuning, it resets to `waitPower` and marks `postPowerState` for resumption. |
131 | 83 |
|
132 | 84 | --- |
133 | 85 |
|
134 | | -### **Error States: `HardError` & `OverTemp`** |
135 | | -- **Role**: Handle critical error conditions. |
136 | | -- **`OverTemp`**: |
137 | | - - **Cause**: Driver temperature is too high. |
138 | | - - **Action**: Immediately stops the motor. |
139 | | - - **Transition**: **➡️ `HardError`**. |
140 | | -- **`HardError`**: |
141 | | - - **Cause**: Unrecoverable error (ADC calibration failure, repeated encoder alignment failure, overheating). |
142 | | - - **Action**: Blocks the state machine. The driver is completely disabled. |
143 | | - - **Transition**: None. A hardware reset is required to exit this state. |
144 | | -
|
145 | | -## 5. Notable Points and Potential "Illogisms" |
146 | | -
|
147 | | -1. **Complex Transition Management**: The `requestedState` and `allowStateChange` system is powerful but can make debugging difficult. A user command may not have an immediate effect if the machine is in a critical state that has locked transitions. |
148 | | -2. **Recovery after Power Loss**: The systematic transition to `waitPower` in case of undervoltage is a good design practice, as it ensures the system will not attempt to operate under unstable conditions and will re-initialize correctly (re-calibrating ADCs, etc.) when power is restored. |
149 | | -3. **`HardError` is a Final State**: Once in the `HardError` state, the system is blocked. This is a safety measure, but it means that no software recovery attempt is possible without a restart. |
150 | | -4. **Encoder Initialization Sequence**: The `encoderInit()` function is the core of the motor's commissioning. It is long and complex, with multiple steps that must succeed. A failure at any stage can lead to an error state, which makes it robust but potentially difficult to diagnose without detailed logs. |
151 | | -5. **Use of `laststate`**: Storing `laststate` is essential for calibration routines. It allows the user to launch a calibration from the `Running` state and then automatically return to it without manual intervention. |
| 86 | +## 5. Memory and Stack Vigilance |
| 87 | +
|
| 88 | +- **Stack Limit**: The TMC thread stack is limited (`TMC_THREAD_MEM = 256` words / 1KB). |
| 89 | +- **Heap Allocation**: Any large data structure (like the `CoggingCalibData` struct) **must** be allocated on the heap. |
| 90 | +- **Safety**: Always initialize local variables (e.g., when reading from Flash) to prevent erratic behavior from uninitialized memory. |
152 | 91 |
|
153 | 92 | --- |
154 | 93 |
|
155 | | -## 6. Interaction with User Commands and Safety |
| 94 | +## 6. FreeRTOS Safety and Critical Sections |
| 95 | +
|
| 96 | +There is a strict orchestration requirement for hardware initialization to avoid FreeRTOS crashes: |
156 | 97 |
|
157 | | -When a user command triggers a long action like a calibration (`calibrateCogging`, `pidautotune`, etc.), the `CommandHandler` calls `changeState()` to request a transition to the corresponding calibration state (e.g., `TMC_ControlState::CoggingCalibration`). |
| 98 | +1. **No Blocking in Critical Sections**: Calls like `xSemaphoreTake` (used in SPI transfers), `vTaskDelay`, or `Task Creation` must **never** occur inside a `cpp_freertos::CriticalSection`. |
| 99 | +2. **Driver Instantiation Pattern**: In `Axis::setDrvType`, the `TMC4671` object is instantiated **outside** the critical section. This allows its constructor to safely use SPI/Semaphores to read Flash and configure registers. |
| 100 | +3. **The Swap**: The critical section is used only for the micro-second duration required to swap the `std::unique_ptr` driver pointers and update the slew rate values. |
| 101 | +4. **Deferred Cleanup**: Old driver objects are deleted **after** exiting the critical section to ensure their destructors do not block while interrupts are disabled. |
158 | 102 |
|
159 | | -The safety of these operations is guaranteed by the following mechanism: |
| 103 | +--- |
160 | 104 |
|
161 | | -1. **State Locking**: At the beginning of each calibration state, the `allowStateChange` variable is set to `false`. This "locks" the state machine, preventing any new command or event (like a power loss) from causing an unexpected state change while the calibration routine is in progress. |
162 | | -2. **Blocking Execution**: The calibration function (`calibrateCogging()`, `measureMaxSlewRate()`, etc.) is called and runs to completion. |
163 | | -3. **Unlocking and Return**: Once the calibration is finished, `allowStateChange` is set back to `true`, and a call to `changeState(laststate, false)` is made. This requests the machine to return cleanly to the state it was in before the calibration began (usually `Running` or `Shutdown`). |
| 105 | +## 7. Interaction with User Commands |
164 | 106 |
|
165 | | -This model ensures that a calibration cannot be interrupted and that the system cannot end up in an inconsistent state following a user command. |
| 107 | +User commands (`calibrate`, `cogging`, `pidautotune`) trigger a `changeState()` request. |
| 108 | +1. The command sets `allowStateChange = false` to lock the machine. |
| 109 | +2. The `Run()` loop processes the calibration sub-states tick-by-tick. |
| 110 | +3. Upon completion (Success or Fail), `allowStateChange` is set to `true`, and the machine returns to `laststate` (usually `Running` or `Shutdown`). |
0 commit comments