Skip to content

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.