Porting¶
Run the driver on a new MCU by writing one class: the SPI interface, with one method.
The driver never calls your SDK directly. It calls transfer() on a class you derive from
ads7952::SpiInterface<Self> (in inc/ads7952_spi_interface.hpp). The base
class uses CRTP: the call is resolved at compile time, so there are no virtual functions, no vtable and nothing to
allocate.
Figure description and sources
From top to bottom. Your application talks to one ADS7952
Drawn from: inc/ads7952.hpp ADS7952 public calls; inc/ads7952_spi_interface.hpp SpiInterface; figure module docs/diagrams/figures/driver-layers.py.
What you implement¶
| Method | Contract |
|---|---|
bool transfer(const uint8_t* tx, uint8_t* rx, std::size_t len) |
Assert CS, clock len bytes out of tx while reading len bytes into rx, release CS. Return true when the frame went out, false on a bus error. The driver always passes len = 2 and both buffers |
There is nothing else: no GPIOs, no delays, no logging. The chip has no reset or data-ready pin, and its timing is set by SCLK and CS.
The port¶
class MySpi : public ads7952::SpiInterface<MySpi> {
public:
// One 16-bit frame: SPI mode 0, MSB first, CS low for both bytes and high again afterwards.
// Return false on a bus error: the driver call that sent the frame then fails.
bool transfer(const uint8_t* tx, uint8_t* rx, std::size_t len) {
return platform_spi_transfer(tx, rx, len);
}
};
Rules the transfer must follow:
- SPI mode 0, MSB first, at most 20 MHz.
- One frame per call. CS goes low before the first clock and high after the 16th; never split the two bytes into separate CS cycles, never merge two calls into one. The chip samples its input on the CS falling edge.
- CS high for at least 20 ns between frames (tw1). Most SPI peripherals give far more.
- Byte order as given.
tx[0]is DI15:8 and goes first;rx[0]receives DO15:8.
Bus errors¶
Return false from transfer() when your SDK reports a failure. The driver stops the call that sent the frame:
EnsureInitialized(), the Enter...Mode(), Program...() and Set...() calls return false, ReadChannel() and
ReadAllChannels() return Error::SpiError, and RawManualFrames() returns the number of frames that got through.
The driver's mirrored state (mode, sequence masks, range, power-down, GPIO outputs) changes only when the frames
that change it went out.
Return a bool, not your SDK's status code: the driver rejects any other return type at compile time, so an
esp_err_t (where 0 is success) cannot be read as false. A port written for the earlier contract, with a void
transfer(), still compiles; the driver treats it as never failing.
Reference implementation¶
The ESP32 examples use esp32_ads7952_bus.hpp: an ESP-IDF
spi_master device in mode 0 with hardware CS, one spi_device_transmit() per frame. Start from it for any
ESP-IDF target.
Check your port¶
- Build it: the port on this page is compiled by the host CI from
tests/docs/my_spi.hpp, with the driver instantiated over it. - Call
EnsureInitialized(), thenRawManualFrames(5, words, 4):words[2] >> 12must be 5. If the address never matches, look at the bus with a logic analyser (mode, CS framing, byte order). - Put a known voltage on one input and compare
ReadChannel()with a meter. Use the measured REFP withSetVref(). - Read two channels with very different voltages in turn: if the second reads close to the first, the source impedance is too high for your SCLK rate (Hardware).
Threads¶
The driver is not thread-safe. Give each ADS7952 one owner task, or guard every call on a driver object (and its
port) with one mutex. If the SPI bus is shared, your SDK's bus lock must cover a whole transfer() call.