Quickstart¶
Regulate a solenoid current from your MCU: wire the SPI bus and three control pins, write a small port class, call five driver functions.
Before you energise a coil
Use a current-limited VBAT supply for the first run and start with a low setpoint. Every inductive load needs a freewheeling diode from FWDx to its supply (see Hardware). The example drives 500 mA into CH0.
1. Wire it¶
| MCU | TLE92466ED (pin) | Notes |
|---|---|---|
| SPI SCLK, MOSI, MISO | SCK (6), SI (7), SO (9) | Mode 1, 1 MHz to start |
| SPI CS | CSN (5) | One pulse per 32-bit frame |
| GPIO out | RESN (26) | Active low reset |
| GPIO out | EN (27) | Active high enable |
| GPIO in, pull-up | FAULTN (11) | Open drain, active low |
| 3.3 V or 5 V | VIO (8) | The MCU's I/O rail |
| 5 V | VDD (29) | 4.5 V to 5.5 V |
| GND | GND, GNDPx | Common ground |
VBAT (pin 22) is the load supply, 6 V to 18 V. The Hardware page has every pin, the load connection and the timing.
2. Add the driver¶
Add the repository as a submodule and link the hf_tle92466ed CMake target (or include inc/ and build with
C++20). Installation covers CMake and ESP-IDF.
3. Write the port¶
The port is the only platform code. It moves 32-bit frames over SPI, sets RESN and EN, reads FAULTN and waits.
Replace the platform_* calls with your SDK's.
class MySpi : public tle92466ed::SpiInterface<MySpi> {
public:
template <typename T>
using Result = tle92466ed::CommResult<T>;
using Error = tle92466ed::CommError;
using tle92466ed::SpiInterface<MySpi>::Log; // keep the driver's printf-style Log() visible
// SPI peripheral and the three GPIOs. The driver calls this first, from Init().
Result<void> Init() noexcept {
ready_ = platform_spi_init();
return ready_ ? Result<void>{} : tle::unexpected(Error::HardwareNotReady);
}
Result<void> Deinit() noexcept {
ready_ = false;
return {};
}
// One 32-bit frame, MSB first, CSN low for exactly these four bytes.
Result<uint32_t> Transfer32(uint32_t tx) noexcept {
const uint8_t out[4] = {uint8_t(tx >> 24), uint8_t(tx >> 16), uint8_t(tx >> 8), uint8_t(tx)};
uint8_t in[4] = {};
if (!platform_spi_transfer(out, in, sizeof out)) {
return tle::unexpected(Error::TransferError);
}
return (uint32_t(in[0]) << 24) | (uint32_t(in[1]) << 16) | (uint32_t(in[2]) << 8) | in[3];
}
// Several frames back to back: a CSN pulse per frame, the bus held for the whole sequence.
Result<void> TransferMulti(std::span<const uint32_t> tx, std::span<uint32_t> rx) noexcept {
if (tx.size() != rx.size()) {
return tle::unexpected(Error::InvalidParameter);
}
platform_spi_lock();
for (std::size_t i = 0; i < tx.size(); ++i) {
auto r = Transfer32(tx[i]);
if (!r) {
platform_spi_unlock();
return tle::unexpected(r.error());
}
rx[i] = *r;
}
platform_spi_unlock();
return {};
}
// RESN and FAULTN are active low, EN is active high (datasheet pin table).
Result<void> GpioSet(tle92466ed::CtrlPin pin, tle92466ed::GpioSignal s) noexcept {
const bool active = (s == tle92466ed::GpioSignal::ACTIVE);
const bool ok = (pin == tle92466ed::CtrlPin::RESN) ? platform_gpio_write(kResn, !active)
: (pin == tle92466ed::CtrlPin::EN) ? platform_gpio_write(kEn, active)
: false; // FAULTN is an input
return ok ? Result<void>{} : tle::unexpected(Error::InvalidParameter);
}
Result<tle92466ed::GpioSignal> GpioRead(tle92466ed::CtrlPin pin) noexcept {
if (pin != tle92466ed::CtrlPin::FAULTN) {
return tle::unexpected(Error::InvalidParameter);
}
bool level = true;
if (!platform_gpio_read(kFaultn, level)) {
return tle::unexpected(Error::BusError);
}
return level ? tle92466ed::GpioSignal::INACTIVE : tle92466ed::GpioSignal::ACTIVE;
}
Result<void> Delay(uint32_t us) noexcept {
platform_delay_us(us);
return {};
}
Result<void> Configure(const tle92466ed::SPIConfig& /*config*/) noexcept {
return {}; // fixed bus settings in this port
}
bool IsReady() const noexcept {
return ready_;
}
Error GetLastError() const noexcept {
return Error::None;
}
Result<void> ClearErrors() noexcept {
return {};
}
void Log(tle92466ed::LogLevel /*lvl*/, const char* tag, const char* fmt, va_list args) noexcept {
std::printf("[%s] ", tag);
std::vprintf(fmt, args);
std::printf("\n");
}
private:
static constexpr int kResn = 6; // your board's GPIO numbers
static constexpr int kEn = 5;
static constexpr int kFaultn = 16;
bool ready_{false};
};
Porting explains each method, the frame rules and how to check a port.
4. Drive a channel¶
#include "tle92466ed.hpp"
int drive_ch0_at_500_ma(MySpi& spi) {
using tle92466ed::Channel;
tle92466ed::Driver<MySpi> driver(spi);
if (!driver.Init()) { // 1. reset, ICVID, Config Mode
return -1; // VDD, wiring, SPI mode 1
}
if (!driver.ConfigurePwmPeriod(Channel::CH0, 1000.0F) || // 2. 1 kHz PWM,
!driver.ConfigureDither(Channel::CH0, 50.0F, 100.0F)) { // 50 mA dither at 100 Hz
return -2;
}
if (!driver.EnterMissionMode() || // 3. outputs can switch
!driver.SetCurrentSetpoint(Channel::CH0, 500) || // 4. regulate 500 mA
!driver.EnableChannel(Channel::CH0, true)) {
return -3;
}
return 0;
}
What each step does:
Init()pulses RESN, asserts EN, readsICVID(expects0xC1xx), writes a default configuration and leaves the chip in Config Mode. Nothing else works until it succeeds.ConfigurePwmPeriod()sets the channel's PWM target (110 Hz to 4 kHz).ConfigureDither()sets the dither and starts the channel's measurement clock, without which current feedback never updates.EnterMissionMode()setsOP_MODE: channels can be switched on only in Mission Mode.SetCurrentSetpoint()writes the 15-bit target for 500 mA;EnableChannel()setsEN_CH0and the current controller starts regulating.
This example is built by the host CI, so it compiles against the current headers.
If it does not work¶
| Symptom | First thing to check |
|---|---|
Init() returns DeviceNotResponding or HardwareError |
MISO wiring, SPI mode 1, CSN per 32-bit frame, VIO present |
Init() returns FaultDetected |
Critical fault frames: VDD (5 V) missing or out of range, clock fault |
Init() returns WrongDeviceID |
Another device on the bus, or bits shifted by a wrong SPI mode |
EnableChannel() returns WrongMode |
Call EnterMissionMode() first |
Current reads FeedbackNotReady |
Dither clock not configured (ConfigureDither() or ConfigureDitherClock()) |
Troubleshooting has the full list.
Next¶
- API guide: configure channels, read feedback, handle faults.
- Current control: setpoint scaling, PWM, dither and the feedback window.
- Examples: ESP32-S3 applications, from a no-hardware API test to a proportional-valve cycle.