API guide¶
How to do the common jobs with the driver: initialise, move, read, detect stalls, home, use an encoder and handle faults.
Every snippet on this page is compiled by the host CI from
tests/docs/api_tasks.cpp, with Driver standing for tmc51x0::TMC51x0<MySpi>
and Unit for tmc51x0::Unit. The API reference lists every member.
The shape of the API¶
One TMC51x0<Comm> object per chip. Its public members are the chip's subsystems:
| Member | Use it to |
|---|---|
rampControl |
Set the ramp mode, targets, speeds and accelerations; read position and speed |
motorControl |
Enable and disable the bridges, set currents, chopper, StealthChop, CoolStep, DcStep |
thresholds |
Set the speeds at which StealthChop, CoolStep and high-speed mode switch |
powerStage |
Set gate drive and short protection (TMC5160) |
switches, events |
Configure reference switches, latching, position compare; clear ramp events |
encoder |
Configure and read an ABN encoder, detect step loss |
stallGuard, tuning, homing |
Detect stalls, tune the threshold, find the ends of travel |
status, io |
Read faults, temperature, load, input pins, chip version |
communication, uartConfig |
Daisy-chain position, UART node address, clock |
printer |
Print decoded registers during bring-up |
Every call that touches the bus returns Result<T>. It converts to bool (true = success), and has
Value(), ValueOr(default), Error() and ErrorMessage():
ErrorCode |
Meaning |
|---|---|
COMM_ERROR |
The port failed, a UART CRC was wrong, or a daisy-chain length was invalid |
NOT_INITIALIZED |
Called before Initialize() succeeded |
INVALID_VALUE |
A parameter is out of range (or a pin your port does not support) |
INVALID_STATE |
Not possible in the chip's mode, for example a ramp call with SD_MODE high |
TIMEOUT, CANCELLED |
A homing or tuning routine timed out or was cancelled |
HARDWARE_ERROR, SHORT_CIRCUIT, OPEN_LOAD, OVERTEMP_WARNING, OVERTEMP_SHUTDOWN |
A fault reported by the chip |
UNSUPPORTED |
The port or the chip variant does not have this feature |
Initialise¶
tmc51x0::DriverConfig cfg{};
cfg.motor_spec.steps_per_rev = 200; // 1.8° motor
cfg.motor_spec.rated_current_ma = 2000; // RMS, from the motor datasheet
cfg.motor_spec.run_current_ma = 1500; // 0 = rated current
cfg.motor_spec.hold_current_ma = 500; // 0 = 30 % of run current
cfg.motor_spec.sense_resistor_mohm = 50; // 0.05 ohm
cfg.motor_spec.supply_voltage_mv = 24000;
cfg.chopper.mres = tmc51x0::MicrostepResolution::MRES_256;
auto r = driver.Initialize(cfg, /*verbose=*/false);
if (!r) {
std::printf("init failed: %s\n", r.ErrorMessage());
return false;
}
return driver.motorControl.Enable().IsOk();
Initialize() applies the whole DriverConfig: currents from motor_spec, power stage, chopper, StealthChop,
global configuration, ramp defaults, reference switches and encoder. Pass verbose = false to skip the
configuration table it logs. Configuration lists the fields. The destructor disables the
bridges.
Move to a position¶
driver.rampControl.SetRampMode(tmc51x0::RampMode::POSITIONING);
driver.rampControl.SetMaxSpeed(120.0f, Unit::RPM);
driver.rampControl.SetAccelerations(4.0f, 4.0f, Unit::RevPerSec); // accel, decel in rev/s²
driver.rampControl.SetTargetPosition(90.0f, Unit::Deg); // absolute
driver.rampControl.MoveRelative(-45.0f, Unit::Deg); // from the current target
driver.rampControl.SetTargetPosition(200.0f, Unit::Steps); // full steps: one turn of a 1.8° motor
SetTargetPosition() starts the move as soon as XTARGET is written. MoveRelative() adds to the current
target. Poll IsTargetReached() (or watch position_reached in SPI_STATUS) for completion.
Motion and units explains the ramp and the units.
Run at a speed¶
driver.rampControl.SetRampMode(tmc51x0::RampMode::VELOCITY_POS); // or VELOCITY_NEG
driver.rampControl.SetAcceleration(2.0f, Unit::RevPerSec);
driver.rampControl.SetMaxSpeed(1.5f, Unit::RevPerSec); // runs until told otherwise
driver.rampControl.Stop(); // decelerate to zero with AMAX
In velocity mode the sign comes from the ramp mode, not from the speed. Stop() decelerates to zero.
Read state¶
auto pos = driver.rampControl.GetCurrentPosition(Unit::Deg); // XACTUAL
auto rpm = driver.rampControl.GetCurrentSpeed(Unit::RPM); // VACTUAL
auto [err, standstill] = driver.rampControl.IsStandstill(); // structured binding
if (pos && rpm && err == tmc51x0::ErrorCode::OK) {
std::printf("%.1f deg, %.1f rpm, %s\n", pos.Value(), rpm.Value(), standstill ? "stopped" : "moving");
}
const uint16_t load = driver.stallGuard.GetStallGuardResult().ValueOr(0); // SG_RESULT, 0..1023
std::printf("load %u\n", load);
Each read is one register access (two SPI datagrams). status has decoded readings: IsOvertempWarning(),
IsStealthChopActive(), GetActualCurrentScale(), GetLostSteps() and more.
Detect stalls¶
driver.motorControl.SetStealthChopEnabled(false); // StallGuard2 needs SpreadCycle
tmc51x0::StallGuardConfig sg{};
sg.threshold = 4; // SGT, -64..63: higher = less sensitive
sg.enable_filter = true;
sg.min_velocity = 0.5f; // TCOOLTHRS: ignore stalls below this speed
sg.velocity_unit = Unit::RevPerSec;
sg.stop_on_stall = true; // sg_stop in SW_MODE
if (!driver.stallGuard.ConfigureStallGuard(sg)) {
return false;
}
return driver.stallGuard.IsStallDetected().ValueOr(false);
tmc51x0::StallGuardTuningResult result{};
auto r = driver.tuning.AutoTuneStallGuard(60.0f, result, // target speed, in RPM below
-20, 20, // SGT search range
2.0f, // acceleration, rev/s²
30.0f, 90.0f, // speeds the SGT must also work at
Unit::RPM, Unit::RevPerSec,
0.3f); // 30 % current while tuning
if (r && result.tuning_success) {
tmc51x0::StallGuardConfig sg{};
sg.threshold = result.optimal_sgt;
driver.stallGuard.ConfigureStallGuard(sg);
}
StallGuard and homing explains the rules StallGuard2 needs and what the tuning does.
Home the axis¶
using Homing = Driver::Homing;
Homing::BoundsOptions opt{};
opt.speed_unit = Unit::RPM;
opt.position_unit = Unit::Deg;
opt.search_speed = 30.0f; // RPM
opt.search_span = 720.0f; // at most two turns each way
opt.backoff_distance = 5.0f; // degrees off the hard stop
opt.timeout_ms = 10000;
Homing::HomeConfig home{};
home.mode = Homing::HomePlacement::AtCenter; // XACTUAL = 0 in the middle of the travel
auto bounds = driver.homing.FindBounds(Homing::BoundsMethod::StallGuard, opt, home);
if (bounds && bounds.Value().bounded) {
std::printf("travel %.1f .. %.1f deg\n", bounds.Value().min_bound, bounds.Value().max_bound);
}
Use an encoder¶
tmc51x0::EncoderConfig enc{};
enc.pulses_per_rev = 4000; // counts per revolution after quadrature
driver.encoder.Configure(enc);
driver.encoder.SetResolution(200, 4000, false); // motor full steps, encoder counts, inverted
driver.encoder.SetAllowedDeviation(4); // full steps before deviation_warn
auto x_enc = driver.encoder.GetPosition(); // X_ENC
if (driver.encoder.IsDeviationWarning().ValueOr(false)) {
std::printf("step loss: encoder at %ld\n", static_cast<long>(x_enc.ValueOr(0)));
driver.encoder.ClearDeviationWarning();
}
SetResolution() writes ENC_CONST so X_ENC counts in the same microsteps as XACTUAL; it uses a binary
prescaler when the ratio is exact and a decimal one otherwise. SetAllowedDeviation() takes full steps and arms
deviation_warn in ENC_STATUS.
Handle errors and faults¶
bool drv_err = false;
bool uv_cp = false;
auto reset = driver.status.GetGlobalStatus(drv_err, uv_cp); // GSTAT: true = reset occurred
if (reset.IsOk() && (reset.Value() || drv_err || uv_cp)) {
bool overtemp = false, prewarning = false;
driver.status.GetTemperatureStatus(overtemp, prewarning);
bool s2g_a = false, s2g_b = false;
driver.status.IsShortToGround(s2g_a, s2g_b);
bool ola = false, olb = false;
driver.status.CheckOpenLoad(ola, olb);
std::printf("reset=%d drv_err=%d uv_cp=%d ot=%d otpw=%d s2g=%d/%d ol=%d/%d\n", reset.Value(), drv_err,
uv_cp, overtemp, prewarning, s2g_a, s2g_b, ola, olb);
driver.status.ClearGlobalStatus(); // write 1 to clear, after the fix
}
| Signal | Meaning | Where to look |
|---|---|---|
| A call returns an error | The port failed, a CRC was wrong, or the chip is in the wrong mode | ErrorMessage(), your port's log |
GSTAT.reset |
The chip was reset (power-up, VS or VCC_IO undervoltage) | Run Initialize() again |
GSTAT.drv_err |
Overtemperature or short circuit switched the bridges off | DRV_STATUS: ot, s2ga / s2gb, s2vsa / s2vsb |
GSTAT.uv_cp |
Charge-pump undervoltage | VS too low, or a charge-pump capacitor missing |
DRV_STATUS.otpw |
Overtemperature pre-warning | Reduce current or improve cooling |
DRV_STATUS.ola / olb |
Open load | Motor wiring |
GSTAT is write-1-to-clear: clear it after you fixed the cause. A short-circuit shutdown stays latched until
the driver is disabled, by DRV_ENN (motorControl.Disable()) or by TOFF = 0. After an overtemperature shutdown
the bridges stay off until the chip has cooled below the pre-warning threshold. The open-load flags are
informative only and can trigger falsely in fast motion and at standstill: check them during slow motion.
Access a register directly¶
auto& comm = driver.GetComm();
const uint8_t where = driver.communication.GetDaisyChainPosition(); // SPI: chain position
auto drv_status = comm.ReadRegister(tmc51x0::Registers::DRV_STATUS, where);
if (drv_status) {
std::printf("DRV_STATUS = 0x%08lX\n", static_cast<unsigned long>(drv_status.Value()));
}
comm.WriteRegister(tmc51x0::Registers::TPOWERDOWN, 10, where); // write-only register
Communication protocols has the register map.