Skip to content

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.