Examples
This guide walks through progressively more advanced uses of the TMC51x0 driver. Each example builds on the previous ones, so working through them in order is recommended.
Prerequisites β Before diving in, make sure you have:
- The driver installed and your hardware wired
- A working SPI or UART interface (
MySPIclass in these examples)- Read the Quick Start for the
Result<T>error handling pattern
Error handling note: These examples omit most
Resultchecks for readability. In production code, always check return values β especiallyInitialize(),Enable(), and any call that communicates with the TMC chip. See the Error Handling section in the README for the full pattern, orinc/tmc51x0_result.hppfor all error codes.
Example 1: Basic Positioning Mode
| Difficulty: Beginner | What you learn: Initialization, positioning mode, polling for completion |
Move the motor to a specific angle and wait until it arrives.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "inc/tmc51x0.hpp"
MySPI spi; // Your platform SPI (see platform_integration.md)
tmc51x0::TMC51x0<MySPI> driver(spi);
int main() {
// 1. Initialize -- provide motor specs; IRUN/IHOLD/GLOBAL_SCALER are calculated for you
tmc51x0::DriverConfig cfg{};
cfg.motor_spec.rated_current_ma = 1500; // Motor rated current (from datasheet)
cfg.motor_spec.sense_resistor_mohm = 50; // Sense resistor on your board (mOhm)
cfg.motor_spec.supply_voltage_mv = 24000; // Motor supply voltage (mV)
if (!driver.Initialize(cfg)) { return -1; } // Check -- this can fail on SPI errors
// 2. Enable motor output, then configure motion profile
driver.motorControl.Enable();
driver.rampControl.SetRampMode(tmc51x0::RampMode::POSITIONING);
driver.rampControl.SetMaxSpeed(60.0f, tmc51x0::Unit::RPM); // Peak speed
driver.rampControl.SetAcceleration(5.0f, tmc51x0::Unit::RevPerSec); // Ramp up
driver.rampControl.SetDeceleration(5.0f, tmc51x0::Unit::RevPerSec); // Ramp down
// 3. Command a move -- the ramp generator handles accel/decel automatically
driver.rampControl.SetTargetPosition(180.0f, tmc51x0::Unit::Deg);
// 4. Poll until the internal ramp generator reports "target reached"
while (true) {
auto reached = driver.rampControl.IsTargetReached();
if (reached.IsOk() && reached.Value()) break;
// In FreeRTOS: vTaskDelay(pdMS_TO_TICKS(10));
}
return 0;
}
Whatβs happening
| Step | What the driver does internally |
|---|---|
Initialize(cfg) |
Resets the TMC chip, writes GCONF, calculates and writes IRUN/IHOLD/GLOBAL_SCALER from your motor specs, configures the chopper and StealthChop defaults |
Enable() |
Pulls the hardware enable pin (DRV_ENN) active, powering the motor coils |
SetRampMode(POSITIONING) |
Writes RAMPMODE=0 β the internal ramp generator will decelerate and stop at the target |
SetTargetPosition(180Β°) |
Converts 180Β° to microsteps (using steps_per_rev and MRES) and writes XTARGET |
IsTargetReached() |
Reads the position_reached flag from RAMP_STAT β returns Result<bool> |
Related source: internal_ramp_sinusoidal.cpp β a full ESP32 example using positioning mode with BackAndForthMotion
Example 2: Velocity Mode
| Difficulty: Beginner | What you learn: Continuous rotation, direction control, stopping |
Run the motor at a constant speed β useful for conveyors, fans, or any continuous rotation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "inc/tmc51x0.hpp"
MySPI spi;
tmc51x0::TMC51x0<MySPI> driver(spi);
int main() {
tmc51x0::DriverConfig cfg{};
cfg.motor_spec.rated_current_ma = 1500;
cfg.motor_spec.sense_resistor_mohm = 50;
cfg.motor_spec.supply_voltage_mv = 24000;
if (!driver.Initialize(cfg)) { return -1; }
driver.motorControl.Enable();
// Velocity mode: motor accelerates to target speed and holds it
driver.rampControl.SetRampMode(tmc51x0::RampMode::VELOCITY_POS); // Positive direction
driver.rampControl.SetMaxSpeed(30.0f, tmc51x0::Unit::RPM);
driver.rampControl.SetAcceleration(2.0f, tmc51x0::Unit::RevPerSec);
// Motor now runs continuously at 30 RPM ...
// Reverse direction:
// driver.rampControl.SetRampMode(tmc51x0::RampMode::VELOCITY_NEG);
// Decelerate to stop:
// driver.rampControl.Stop();
return 0;
}
Key differences from positioning mode
- VELOCITY_POS / VELOCITY_NEG: Motor runs indefinitely until you call
Stop()or change mode - No target position: There is no
IsTargetReached()event β you decide when to stop - Direction: Controlled by the ramp mode, not by the sign of the target position
Example 3: StealthChop Silent Operation
| Difficulty: Intermediate | What you learn: StealthChop/SpreadCycle mode switching, velocity thresholds |
StealthChop provides near-silent motor operation at low speeds by using voltage-mode PWM. Above a configurable speed threshold, the driver automatically switches to SpreadCycle for better high-speed torque.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "inc/tmc51x0.hpp"
MySPI spi;
tmc51x0::TMC51x0<MySPI> driver(spi);
int main() {
tmc51x0::DriverConfig cfg{};
cfg.motor_spec.rated_current_ma = 1500;
cfg.motor_spec.sense_resistor_mohm = 50;
cfg.motor_spec.supply_voltage_mv = 24000;
if (!driver.Initialize(cfg)) { return -1; }
// Enable StealthChop -- motor MUST be at standstill when first enabled
driver.motorControl.SetStealthChopEnabled(true);
// Set the crossover speed: below 200 RPM = StealthChop (silent),
// above 200 RPM = SpreadCycle (higher torque)
driver.thresholds.SetModeChangeSpeeds(
200.0f, // TPWMTHRS -- StealthChop/SpreadCycle boundary
0.0f, // TCOOLTHRS -- CoolStep threshold (0 = disabled)
0.0f, // THIGH -- high-speed threshold (0 = disabled)
tmc51x0::Unit::RPM
);
// Move at 30 RPM -- stays in StealthChop (below the 200 RPM threshold)
driver.motorControl.Enable();
driver.rampControl.SetRampMode(tmc51x0::RampMode::POSITIONING);
driver.rampControl.SetMaxSpeed(30.0f, tmc51x0::Unit::RPM);
driver.rampControl.SetAcceleration(2.0f, tmc51x0::Unit::RevPerSec);
driver.rampControl.SetTargetPosition(90.0f, tmc51x0::Unit::Deg);
while (true) {
auto reached = driver.rampControl.IsTargetReached();
if (reached.IsOk() && reached.Value()) break;
}
return 0;
}
How StealthChop works
- AT#1 (standstill tuning): When StealthChop is first enabled, the driver auto-tunes PWM parameters for ~150 ms while the motor is stationary
- AT#2 (motion tuning): During the first motion, PWM gradient is optimized automatically
- Mode switching: Above TPWMTHRS velocity, the driver seamlessly switches to SpreadCycle
Important: StallGuard2 does not work in StealthChop mode. If you need stall detection, either disable StealthChop or set the threshold so the motor is in SpreadCycle during the critical phase.
Related: Advanced Configuration β StealthChop
Example 4: Encoder Closed-Loop Control
| Difficulty: Intermediate | What you learn: Encoder setup, resolution matching, step-loss detection |
An ABN incremental encoder provides position feedback. The TMC51x0 compares encoder position against the internal step counter and flags deviations (step loss).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "inc/tmc51x0.hpp"
MySPI spi;
tmc51x0::TMC51x0<MySPI> driver(spi);
int main() {
tmc51x0::DriverConfig cfg{};
cfg.motor_spec.rated_current_ma = 1500;
cfg.motor_spec.sense_resistor_mohm = 50;
cfg.motor_spec.supply_voltage_mv = 24000;
if (!driver.Initialize(cfg)) { return -1; }
// --- Encoder setup ---
tmc51x0::EncoderConfig enc_cfg{}; // Default: ABN mode
driver.encoder.Configure(enc_cfg);
driver.encoder.SetResolution(
200, // Motor: 200 full steps per revolution (1.8Β° stepper)
4096, // Encoder: 4096 pulses per revolution (PPR)
false // Don't invert encoder direction
);
driver.encoder.SetAllowedDeviation(100); // Flag if >100 steps off
// --- Move and monitor ---
driver.motorControl.Enable();
driver.rampControl.SetRampMode(tmc51x0::RampMode::POSITIONING);
driver.rampControl.SetMaxSpeed(30.0f, tmc51x0::Unit::RPM);
driver.rampControl.SetAcceleration(5.0f, tmc51x0::Unit::RevPerSec);
driver.rampControl.SetTargetPosition(180.0f, tmc51x0::Unit::Deg);
while (true) {
auto reached = driver.rampControl.IsTargetReached();
if (reached.IsOk() && reached.Value()) break;
// Check encoder vs. internal position during motion
auto dev = driver.encoder.IsDeviationWarning();
if (dev.IsOk() && dev.Value()) {
printf("Step loss detected!\n");
driver.encoder.ClearDeviationWarning();
// In production: consider reducing speed or increasing current
}
}
// Final check: compare encoder position to expected
auto enc_pos = driver.encoder.GetPosition();
if (enc_pos.IsOk()) {
printf("Encoder position: %d\n", enc_pos.Value());
}
return 0;
}
How the deviation check works
The TMC51x0 continuously compares X_ENC (encoder position, scaled by ENC_CONST) against XACTUAL (internal step counter). When the difference exceeds ENC_DEVIATION, the enc_dev_warn flag is set in ENC_STATUS. This flag persists until you read and clear it.
Related source: abn_encoder_reader.cpp β continuous encoder position reading on ESP32
Example 5: StallGuard2 Stall Detection
| Difficulty: Intermediate | What you learn: StallGuard2 configuration, load monitoring, stall response |
StallGuard2 measures motor load in real time (value 0β1023). A low value means high load; zero means stall. This enables sensorless homing, overload protection, and CoolStep.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "inc/tmc51x0.hpp"
MySPI spi;
tmc51x0::TMC51x0<MySPI> driver(spi);
int main() {
tmc51x0::DriverConfig cfg{};
cfg.motor_spec.rated_current_ma = 1500;
cfg.motor_spec.sense_resistor_mohm = 50;
cfg.motor_spec.supply_voltage_mv = 24000;
if (!driver.Initialize(cfg)) { return -1; }
// StallGuard2 ONLY works in SpreadCycle mode (not StealthChop!)
driver.motorControl.SetStealthChopEnabled(false);
// Configure StallGuard2 threshold: SGT range is -64 to +63
// Lower = more sensitive (detects lighter loads as stalls)
// Higher = less sensitive (only detects hard stalls)
// Start at 0 and tune from there for your motor + mechanics
driver.stallGuard.ConfigureStallGuard(0, true); // SGT=0, filter=on
// StallGuard2 only reports valid readings above TCOOLTHRS velocity
driver.thresholds.SetModeChangeSpeeds(0.0f, 10.0f, 0.0f, tmc51x0::Unit::RPM);
// Run in velocity mode and monitor load
driver.motorControl.Enable();
driver.rampControl.SetRampMode(tmc51x0::RampMode::VELOCITY_POS);
driver.rampControl.SetMaxSpeed(30.0f, tmc51x0::Unit::RPM);
driver.rampControl.SetAcceleration(5.0f, tmc51x0::Unit::RevPerSec);
while (true) {
auto sg = driver.stallGuard.GetStallGuardResult();
if (sg.IsOk()) {
uint16_t load = sg.Value(); // 0 = stalled, 1023 = no load
if (load < 100) {
driver.rampControl.Stop();
printf("Stall detected! SG_RESULT=%u\n", load);
break;
}
}
}
return 0;
}
Tuning StallGuard2
| SGT value | Effect | Use case |
|---|---|---|
| -20 to -5 | Very sensitive | Sensorless homing (detect light contact) |
| -5 to +5 | Moderate | General stall protection |
| +5 to +30 | Less sensitive | Avoid false triggers on rough mechanics |
Tip: Use
driver.tuning.AutoTuneStallGuard()to automatically find the optimal SGT for your motor and velocity. See Advanced Configuration.
Related source: stallguard_tuning.cpp β automatic SGT tuning on ESP32
Example 6: Bounds Finding and Point-to-Point Fatigue Testing
| Difficulty: Advanced | What you learn: Homing subsystem, bounds finding, repetitive motion |
This example demonstrates the driverβs bounds finding and point-to-point motion capabilities,
as used in the production fatigue test unit (fatigue_test_espnow/main.cpp).
Key concepts
- Bounds Finding β Uses StallGuard2 to detect mechanical limits in both directions, then homes to center
- Point-to-Point Motion β Positioning mode moves between two positions repeatedly for fatigue testing
- Physical Units β All positions and speeds specified in degrees and RPM
Bounds finding
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Homing = tmc51x0::TMC51x0<MySPI>::Homing;
// Configure bounds search parameters
Homing::BoundsOptions opt{};
opt.speed_unit = tmc51x0::Unit::RPM;
opt.position_unit = tmc51x0::Unit::Deg;
opt.search_speed = 30.0f; // Search at 30 RPM
opt.search_span = 360.0f; // Max 360Β° travel per direction
opt.backoff_distance = 5.0f; // Back off 5Β° after hitting a stop
opt.timeout_ms = 10000; // 10 s timeout per direction
opt.search_accel = 5.0f;
opt.accel_unit = tmc51x0::Unit::RevPerSec;
// Home to the center of whatever range is found
Homing::HomeConfig home{};
home.mode = Homing::HomePlacement::AtCenter;
// Execute -- internally: disables StealthChop, enables StallGuard,
// searches negative, then positive, computes span, sets XACTUAL=0 at center
auto result = driver.homing.FindBounds(Homing::BoundsMethod::StallGuard, opt, home);
if (result.IsOk()) {
auto bounds = result.Value();
printf("Bounds: min=%.1fΒ° max=%.1fΒ° span=%.1fΒ°\n",
bounds.min_position, bounds.max_position, bounds.span);
} else {
printf("Bounds finding failed: %s\n", result.ErrorMessage());
}
Point-to-point fatigue motion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// After bounds finding, oscillate between two positions within the safe range
float half_range = bounds.span * 0.4f; // Use 80% of total range
float pos_a = -half_range;
float pos_b = +half_range;
driver.rampControl.SetRampMode(tmc51x0::RampMode::POSITIONING);
driver.rampControl.SetMaxSpeed(60.0f, tmc51x0::Unit::RPM);
driver.rampControl.SetAcceleration(5.0f, tmc51x0::Unit::RevPerSec);
driver.rampControl.SetDeceleration(5.0f, tmc51x0::Unit::RevPerSec);
uint32_t cycle = 0;
bool going_to_b = true;
while (cycle < 10000) {
float target = going_to_b ? pos_b : pos_a;
driver.rampControl.SetTargetPosition(target, tmc51x0::Unit::Deg);
while (true) {
auto reached = driver.rampControl.IsTargetReached();
if (reached.IsOk() && reached.Value()) break;
}
going_to_b = !going_to_b;
if (!going_to_b) cycle++; // One full A->B->A = one cycle
}
See also
- Fatigue Test Documentation β Full ESP-NOW fatigue test guide
- Sensorless Homing Guide β StallGuard2 homing configuration
- Source Code β Production fatigue test implementation
Running the Examples
ESP32 (using build scripts)
1
2
3
4
5
cd examples/esp32
# Build a specific application (see app_config.yml for available apps)
./scripts/build_app.sh fatigue_test_espnow_unit
# Flash and monitor
./scripts/flash_app.sh /dev/ttyACM0
See the ESP32 Examples README for full setup instructions.
Other Platforms
Compile with C++17 support and include the driver headers:
1
g++ -std=c++17 -I path/to/hf-tmc5160-driver/ your_app.cpp -o your_app
Next Steps
| Where to go | What youβll find |
|---|---|
| API Reference | Complete method signatures, return types, and parameter descriptions |
| Platform Integration | How to implement MySPI / MyUART for your board |
| Configuration | DriverConfig fields, chopper tuning, StealthChop parameters |
| ESP32 example source | Production-quality implementations you can build and flash |
| Troubleshooting | Common errors and recovery strategies |
Navigation <- API Reference | Next: Troubleshooting -> | Back to Index