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.