Skip to content

API guide

How to do the common jobs with the driver: boot, configure, command, read, batch and handle errors.

Every snippet on this page is compiled by the host CI from tests/docs/api_tasks.cpp, with Driver standing for tmc9660::TMC9660<MySpi> and tmcl for tmc9660::tmcl. The API reference lists every member.

The shape of the API

One TMC9660<Comm> object per chip. Its public members are the chip's subsystems; each one groups the TMCL parameters of one function and offers three levels of access:

Level Example Use it for
configureAuto(Config) velocityControl.configureAuto(vel) Bring-up: one struct, sensible defaults, applied in the right order
Unit-aware calls setTargetVelocityRpm(1500.0, ctx) Commands and readings in RPM, degrees, rad/s
Raw setters and parameters setTargetVelocityRaw(...), writeParameter(...) Anything the chip has that a helper does not cover
Member Owns
motorConfig Motor type, pole pairs, PWM, commutation mode, current limits
currentSensing Current-sense amplifiers, offsets and calibration
gateDriver Gate-driver timing and power-stage protection
feedbackSense Hall, ABN and SPI encoders
torqueFluxControl Torque / flux PI loop, target torque, open-loop modes
velocityControl Velocity PI loop and targets
positionControl Position loop, targets and limits
ramp Trapezoidal ramp generator
stepDir Step / direction input
referenceSearch, brake, iit, stopEvents, protection Homing, brake chopper, I²t, stop conditions, thresholds
telemetry, diagnostics Readings, status and error flags, summaries
script, ramDebug, nvmStorage, heartbeat, globals, gpio, power TMCL scripts, sampling, flash storage, watchdog, global parameters, chip GPIOs, hibernation

Every call returns bool (or a result enum) and never throws. Check it: false means the chip did not accept the value or the transport failed.

Boot the chip

Nothing works until bootloaderInit() succeeds: it resets the chip, writes the bootloader configuration (clock, interfaces, feedback pins) and starts the motor-control firmware in parameter mode.

#include "tmc9660.hpp"

int spin_bldc_at_1000_rpm(MySpi& spi) {
  tmc9660::TMC9660<MySpi> driver(spi);

  tmc9660::BootloaderConfig cfg{};                          // 1. boot into parameter mode
  cfg.boot.boot_mode = tmc9660::bootcfg::BootMode::Parameter;
  cfg.boot.start_motor_control = true;
  if (driver.bootloaderInit(&cfg) != tmc9660::TMC9660<MySpi>::BootloaderInitResult::Success) {
    return -1;                                              // wiring, power or SPI mode
  }

  driver.motorConfig.setType(tmc9660::tmcl::MotorType::BLDC_MOTOR, 7);  // 2. motor: 7 pole pairs
  driver.motorConfig.setPWMFrequency(25000);

  const auto ctx = driver.getMotorContext();                // 3. unit conversions from the chip
  driver.velocityControl.setTargetVelocityRpm(1000.0, ctx); // 4. spin
  return 0;
}

Bootloader covers every field of BootloaderConfig, including SPI encoders and step/dir.

Configure the motor

Configure in this order: motor, feedback, control loops, then select the commutation mode last. The chip runs the mode as soon as you set it.

Driver::MotorConfig::MotorProfile motor{};
motor.motorType = tmcl::MotorType::BLDC_MOTOR;
motor.polePairs = 7;
motor.pwmFrequency_Hz = 25000;
motor.maxPhaseCurrent_A = 3.0f;                 // sets MAX_TORQUE and MAX_FLUX
if (!driver.motorConfig.configureAuto(motor)) {
  return false;
}

Driver::FeedbackSense::HallConfig hall{};       // defaults: no offset, not inverted
if (!driver.feedbackSense.configureAuto(hall)) {
  return false;
}

Driver::VelocityControl::VelocityConfig vel{};
vel.sensorSelection = tmcl::VelocitySensorSelection::SAME_AS_COMMUTATION;
if (!driver.velocityControl.configureAuto(vel)) {
  return false;
}
return driver.motorConfig.setCommutationMode(tmcl::CommutationMode::FOC_HALL_SENSOR);

MotorProfile needs four values (type, pole pairs, PWM frequency, maximum phase current); the rest have defaults. configureAuto() returns false at the first value the chip rejects. Tune the loops afterwards with TorqueFluxConfig, VelocityConfig and PositionConfig; Configuration lists their fields.

Commutation mode When
SYSTEM_OFF Default after reset; outputs off
FOC_OPENLOOP_VOLTAGE_MODE, FOC_OPENLOOP_CURRENT_MODE First spin without feedback, encoder alignment
FOC_HALL_SENSOR BLDC with digital Hall sensors
FOC_ABN Incremental encoder
FOC_SPI_ENC Absolute SPI encoder

Command motion

const auto ctx = driver.getMotorContext();      // read once, reuse for every conversion

driver.velocityControl.setTargetVelocityRpm(1500.0, ctx);
driver.positionControl.setTargetPositionDegMech(90.0, ctx);
driver.torqueFluxControl.setTargetTorque(800);  // mA

driver.velocityControl.stop();                  // ramp to zero, stay in the current mode
driver.motorConfig.setCommutationMode(tmcl::CommutationMode::SYSTEM_OFF);

The MotorContext holds what the unit conversions need: motor type, pole pairs, velocity sensor and scaling. Read it once after configuration and again only when you change one of those. Units and diagnostics explains the conversions and their limits.

Read state

const auto ctx = driver.getMotorContext();

double rpm = 0.0;
if (driver.velocityControl.getActualVelocityRpm(rpm, ctx)) {
  std::printf("velocity %.1f rpm\n", rpm);
}
const float vbus = driver.telemetry.getSupplyVoltage();     // V
const float temp = driver.telemetry.getChipTemperature();   // degrees C
const int16_t current = driver.telemetry.getMotorCurrent(); // mA
std::printf("%.1f V  %.1f C  %d mA\n", vbus, temp, current);

Each read is one TMCL access (two SPI datagrams). To read several values at the same instant, batch them.

Read or write several parameters at once

Driver::ParameterAccess rd[] = {{tmcl::Parameters::ACTUAL_VELOCITY},
                                {tmcl::Parameters::ACTUAL_POSITION},
                                {tmcl::Parameters::GENERAL_STATUS_FLAGS}};
if (driver.readParameters(rd)) {                // 3 accesses, 4 SPI datagrams
  std::printf("v=%ld p=%ld\n", static_cast<long>(static_cast<int32_t>(rd[0].value)),
              static_cast<long>(static_cast<int32_t>(rd[1].value)));
}

A batch pipelines the accesses: N reads cost N + 1 datagrams instead of 2N. Each element carries its own status. Batches accept only idempotent requests (SAP, GAP, SGP, GGP), because a lost reply makes the driver re-issue one. writeParameters(), readGlobalParameters() and writeGlobalParameters() work the same way.

Use any parameter directly

uint32_t maxTorque = 0;
if (driver.readParameter(tmcl::Parameters::MAX_TORQUE, maxTorque)) {   // any axis parameter
  (void)driver.writeParameter(tmcl::Parameters::MAX_TORQUE, 2500);    // mA
}
uint32_t loops = 0;
(void)driver.readGlobal(tmcl::GlobalParamBank0::MAIN_LOOPS, loops);  // bank from the enum type

Axis parameters are tmcl::Parameters; global parameters are tmcl::GlobalParamBank0, GlobalParamBank2 and GlobalParamBank3, and the bank follows from the type, so an axis id cannot be sent to a global bank by mistake. The parameter-mode reference lists every parameter.

Handle errors and faults

tmc9660::diagnostics::MotorSummary s{};
if (driver.diagnostics.summary(s, driver.getMotorContext())) {
  if (s.has_general_error || s.has_gate_driver_error) {
    std::printf("errors: general 0x%08lx gate 0x%08lx\n",
                static_cast<unsigned long>(s.general_error_flags),
                static_cast<unsigned long>(s.gate_driver_error_flags));
    driver.telemetry.clearGeneralErrorFlags(s.general_error_flags);       // write-1-to-clear
    driver.telemetry.clearGateDriverErrorFlags(s.gate_driver_error_flags);
  }
}

tmc9660::GpioSignal fault{};
if (driver.comm().gpioRead(tmc9660::TMC9660CtrlPin::FAULTN, fault) &&
    fault == tmc9660::GpioSignal::ACTIVE) {
  // the chip reports a severe fault or is still bootstrapping
}
Signal Meaning Where to look
A call returns false The chip rejected the value, or the transport failed The log (debugLog), comm().tmclStats()
has_general_error Over-current, over-temperature, supply or loop errors general_error_flags, decoded in Units and diagnostics
has_gate_driver_error Short circuit, gate-drive under-voltage, bootstrap faults gate_driver_error_flags
FAULTN active Bootstrapping, or a severe fault the chip latched Read the flags, fix the cause, clear them

Error flags are write-1-to-clear: clear the bits you read, after you have handled the cause. A flag that comes back at once means the cause is still there. Troubleshooting maps symptoms to causes.

Tune the transport

tmc9660::TmclSpiTiming t{};   // datasheet defaults: no gap, 20 -> 200 us back-off, 60 polls
t.maxPolls = 4;               // in a 1 kHz control task: fail fast, mark the sample stale
spi.setTmclTiming(t);

const auto& stats = spi.tmclStats();  // frames, notReady, staleReplies, recoveries, failures
(void)stats;

The defaults follow the datasheet: no fixed gap, NOT_READY polling with back-off, at most about 11.5 ms per access. In a fast control loop lower maxPolls so a busy chip costs you a stale sample, not a missed deadline. Timing and throughput has the cost model and the measurement app.