API guide¶
How to do the common jobs with the driver: bring the chip up, configure channels, drive currents, read feedback and handle errors.
Every snippet on this page is compiled by the host CI from
tests/docs/api_tasks.cpp, with Driver standing for
tle92466ed::Driver<MySpi>. The API reference lists every member.
The shape of the API¶
One Driver<Comm> object per chip. Its members fall into these groups:
| Group | Members | Mode |
|---|---|---|
| Bring-up and modes | Init(), EnterMissionMode(), EnterMissionModeChecked(), EnterConfigMode(), IsMissionMode() |
|
| Global configuration | ConfigureGlobal(), SetCrcEnabled(), SetVbatThresholds(), SetVioLevel(), ConfigureClockSource() |
Config |
| Channel setup | SetChannelMode(), ConfigureChannel(), SetParallelOperation() |
Config |
| Current, PWM, dither | SetCurrentSetpoint(), SetCurrentSetpointUa(), ConfigurePwmPeriod(), ConfigureDither(), ConfigureDitherClock() |
Any |
| Switching | EnableChannel(), EnableChannels(), EnableAllChannels(), DisableAllChannels() |
Mission |
| Feedback | GetAverageCurrent(), GetDutyCycle(), ReadChannelFeedback(), SampleChannelCurrents(), ReadAllSupplyVoltages() |
Any |
| Faults | GetAllFaults(), GetDeviceStatus(), GetChannelDiagnostics(), ClearFaults(), IsFault(), SetFaultMask() |
Any |
| Watchdog and identity | ReloadSpiWatchdog(), VerifyDevice(), GetIcVersion(), GetChipId() |
Any |
| Control pins | HoldReset(), ReleaseReset(), Enable(), Disable() |
Any |
| Registers | ReadRegister(), ReadRegisterMulti(), WriteRegister(), ModifyRegister() |
Any |
Channels are tle92466ed::Channel::CH0 to CH5. Every call returns a DriverResult<T> and never throws: test it
with if (!r) and read r.error(), a DriverError.
Initialise¶
bool bring_up(Driver& driver) {
auto r = driver.Init(); // RESN pulse, EN high, ICVID check, defaults; ends in Config Mode
if (!r) {
if (r.error() == DriverError::WrongDeviceID) {
std::printf("not a TLE92466ED (ICVID high byte is not 0xC1)\n");
} else if (r.error() == DriverError::FaultDetected) {
std::printf("critical fault frame: check VDD (5 V) and VIO first\n");
}
return false;
}
return true;
}
Init() initialises your port, pulses RESN (10 ms low, 50 ms settle), sets EN, reads ICVID up to five times,
refuses unprogrammed silicon, writes defaults (every channel ICC, setpoint 0, clock watchdog on, CRC and SPI
watchdog off) and clears the power-on flags. Init(false) skips the RESN pulse, for a retry on a shared bus.
Configure¶
Configure in Config Mode, before EnterMissionMode(): the chip accepts channel modes, the global configuration and
parallel pairs only there.
// In Config Mode: mode, PWM period and dither for one channel.
bool configure_channel(Driver& driver, Channel ch) {
return driver.SetChannelMode(ch, tle92466ed::ChannelMode::ICC) &&
driver.ConfigurePwmPeriod(ch, 500.0F) && // 500 us = 2 kHz
driver.ConfigureDither(ch, 40.0F, 200.0F); // 40 mA at 200 Hz; also starts Tmeas
}
ConfigureChannel() sets everything about a channel in one call (mode, setpoint, slew rate, diagnosis current,
open-load threshold, PWM, dither); Configuration lists the fields of ChannelConfig and
GlobalConfig. Current control gives the ranges and formulas behind the PWM and dither
calls.
Drive a current¶
bool drive(Driver& driver, Channel ch, uint16_t milliamps) {
if (!driver.IsMissionMode() && !driver.EnterMissionMode()) {
return false;
}
return driver.SetCurrentSetpoint(ch, milliamps) && // specified up to 1500 mA
driver.EnableChannel(ch, true);
}
bool stop_all(Driver& driver) {
return driver.DisableAllChannels().has_value();
}
SetCurrentSetpoint() takes milliamperes (SetCurrentSetpointUa() microamperes) and can be called in either mode;
the current flows once the channel is enabled in Mission Mode with EN high. Pass parallel_mode = true for the
master channel of a parallel pair.
Read feedback¶
void report(Driver& driver, Channel ch) {
if (auto i = driver.GetAverageCurrent(ch); i) {
std::printf("I_avg = %u mA\n", *i);
} else if (i.error() == DriverError::FeedbackNotReady) {
std::printf("no measurement window yet (dither clock not running?)\n");
}
if (auto dc = driver.GetDutyCycle(ch); dc) {
std::printf("duty = %u.%02u %%\n", *dc / 100U, *dc % 100U);
}
if (auto v = driver.ReadAllSupplyVoltages(); v) {
std::printf("VBAT %u mV, VIO %u mV, VDD %u mV\n", v->vbat_mV, v->vio_mV, v->vdd_mV);
}
}
GetAverageCurrent() waits for a completed measurement window (up to about 8 ms) and is meant for consoles and
diagnostics. Supplies come back in millivolts. In a control loop, sample instead:
// Control loop: one pipelined burst for several channels, never blocks.
void control_tick(Driver& driver) {
constexpr uint8_t kChannels = 0b000111; // CH0..CH2
auto batch = driver.SampleChannelCurrents(kChannels);
if (!batch) {
return;
}
for (unsigned n = 0; n < 6; ++n) {
if ((batch->valid_mask & (1U << n)) != 0U) {
const int32_t ma = batch->channels[n].iavg_ma; // use it in your current loop
(void)ma;
}
}
// batch->rejected_chains > 0 means replies arrived out of order on the bus.
}
SampleChannelCurrents() reads every requested channel in one pipelined burst and never waits: a channel whose
window is not ready keeps its bit clear in valid_mask and is picked up on the next tick.
Use any register directly¶
bool raw_access(Driver& driver) {
namespace reg = tle92466ed::CentralReg;
auto id = driver.ReadRegister(reg::ICVID); // 0xC1xx: type 0xC1, silicon revision in the low byte
if (!id) {
return false;
}
const uint16_t setpoint =
tle92466ed::GetChannelRegister(Channel::CH1, tle92466ed::ChannelReg::SETPOINT);
return driver.ModifyRegister(setpoint, 0x7FFF, 0x1000).has_value(); // TARGET bits only
}
Central registers are in tle92466ed::CentralReg; channel registers are an offset in ChannelReg added to the
channel's base (GetChannelRegister()). WriteRegister() reads the value back by default; ModifyRegister()
reads, masks and writes. Writes reach only 0x00 to 0x7F (SPI protocol).
Errors¶
DriverError |
Meaning | First thing to check |
|---|---|---|
NotInitialized |
Init() has not succeeded |
Call order |
WrongMode |
Needs Config Mode (configuration) or Mission Mode (enable) | EnterConfigMode() / EnterMissionMode() |
InvalidChannel, InvalidParameter |
Argument out of range | Channel, setpoint scale, PWM period |
DeviceNotResponding |
MISO idle on every attempt | Power, VIO, CSN, MISO wiring |
CRCError |
Replies arrived but none passed CRC and width checks | SPI mode, clock speed, CSN timing, shared bus |
SPIFrameError |
The chip flagged the previous frame (status field) | Frame length (exactly 32 bits), CRC |
FaultDetected |
Critical-fault reply | VDD, internal supplies, clock (SPI protocol) |
WrongDeviceID |
ICVID is not 0xC1xx |
Another device on CSN, wrong SPI mode |
RegisterError |
A write did not read back | Bus integrity, Mission Mode restrictions |
FeedbackNotReady |
No measurement window yet | Dither clock (ConfigureDither()), feedback frozen |
ConfigurationError |
Unprogrammed OTP, or a configuration the chip rejected | The part, the arguments |
TimeoutError |
A wait (feedback, mission mode, BIST) ran out | Timeout argument, chip state |
HardwareError |
Your port returned an error | The port |
Faults the chip detects are not errors of the call; read them with GetAllFaults():
void handle_faults(Driver& driver) {
auto pin = driver.IsFault(); // FAULTN low?
if (!pin || !*pin) {
return;
}
auto report = driver.GetAllFaults();
if (!report) {
return;
}
if (report->vbat_uv || report->vdd_uv) {
std::printf("supply under-voltage\n");
}
for (unsigned n = 0; n < report->channels.size(); ++n) {
const auto& c = report->channels[n];
if (c.open_load || c.short_to_ground || c.overcurrent || c.over_temperature) {
std::printf("CH%u: OL=%d SG=%d OC=%d OT=%d\n", n, c.open_load, c.short_to_ground,
c.overcurrent, c.over_temperature);
}
}
(void)driver.ClearFaults(); // after the cause is fixed; a fault that is still there comes back
}
Diagnostics and faults explains every fault source; Troubleshooting maps symptoms to causes.
Keep the SPI watchdog fed¶
// With GlobalConfig::spi_watchdog_enabled, call this faster than the watchdog runs out.
bool kick_watchdog(Driver& driver) {
return driver.ReloadSpiWatchdog(1000).has_value();
}
Only needed when you enable the watchdog in ConfigureGlobal(); the
timeout table converts WD_TIME to milliseconds.
Threads¶
The driver is not thread-safe. Give each chip one owner task, or guard every call on a driver object (and its port) with one mutex.