Porting¶
Run the driver on a new MCU by writing two small classes: one for SPI, one for CONVST, DRDY and delays.
The driver never calls your SDK directly. It calls six methods on classes you derive from
ads9324::SpiInterface<Self> and ads9324::HostInterface<Self> (in
inc/ads9324_spi_interface.hpp). The base classes use CRTP: the calls are
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 ADS9324
Drawn from: inc/ads9324.hpp ADS9324 public methods; inc/ads9324_spi_interface.hpp SpiInterface, HostInterface; figure module docs/diagrams/figures/driver-layers.py.
What you implement¶
| Method | Class | Contract |
|---|---|---|
transfer(tx, rx, len) |
SpiInterface |
One full-duplex transfer of len bytes with CS low for all of them. tx == nullptr: send zeros. rx == nullptr: discard the received bytes. Return true when the bytes went out, false on a bus error |
ConvstWrite(high) |
HostInterface |
Drive CONVST high (true) or low (false) |
DrdyRead() |
HostInterface |
Return true when DRDY/ALARM is high (conversion ready) |
HasDrdy() const |
HostInterface |
true if DRDY is wired; false makes the driver wait CONV_FALLBACK_US instead |
DelayUs(us) |
HostInterface |
Block for at least us microseconds (1 µs steps during the DRDY poll) |
DelayMs(ms) |
HostInterface |
Block for at least ms milliseconds (30 ms power-up, 1 ms after reset) |
ResetWrite(high) |
your host class, optional | Drive the RESET pin: false = low (chip held in reset), true = released. Only needed for HardwareReset() |
The port¶
// SPI: one CS-low window per call, SPI mode 0, MSB first.
class MySpi : public ads9324::SpiInterface<MySpi> {
public:
// tx == nullptr: clock out zeros. rx == nullptr: discard what arrives on SDOUT.
// Return false on a bus error: the driver then fails the call that sent the frame.
bool transfer(const uint8_t* tx, uint8_t* rx, std::size_t len) {
return platform_spi_transfer(tx, rx, len);
}
};
// Host: the CONVST output, the optional DRDY input and two delays.
class MyHost : public ads9324::HostInterface<MyHost> {
public:
static constexpr int kConvst = 3; // GPIO wired to CONVST (pin 50)
static constexpr int kDrdy = 11; // GPIO wired to DRDY/ALARM (pin 49)
static constexpr int kReset = 12; // GPIO wired to RESET (pin 32); optional
void ConvstWrite(bool high) { platform_gpio_write(kConvst, high); }
bool DrdyRead() { return platform_gpio_read(kDrdy); } // high = conversion done
bool HasDrdy() const { return true; } // false: fixed wait instead
void DelayUs(uint32_t us) { platform_delay_us(us); }
void DelayMs(uint32_t ms) { platform_delay_us(ms * 1000U); }
void ResetWrite(bool high) {
platform_gpio_write(kReset, high);
} // optional: HardwareReset()
};
One class may implement both contracts; pass the same object twice: ADS9324<Bus, Bus> adc(bus, bus).
Rules for the transfer¶
- SPI mode 0, MSB first, up to 60 MHz. Start at 1 to 8 MHz on a new board.
- One CS window per call. CS goes low before the first byte and high after the last. Register frames are 3 bytes; a conversion burst is 32 bytes (16 channels, 16-bit words) or 48 bytes (24-bit words). Never split one call into several CS windows: the chip decodes a register write on the CS rising edge.
- Handle
nullptr. The driver passes real buffers today, but the contract allows either pointer to be null. - Own the bus. Do not share this CS with another device, and do not let another task use the bus in the middle of a register read (two frames).
transfer() returns bool: false tells the driver the transfer failed. The call that sent the frame stops
there: configuration calls return false, ReadDeviceId() returns 0 and the reads return Error::SpiError. The
driver's cached settings change only after the frames that set them went out, and after a failed BANK_SEL frame
the next access selects the bank again.
Return the SDK's status as a bool (== ESP_OK, == HAL_OK): any other return type is rejected at compile time,
so an esp_err_t (0 = success) cannot be read as false. A port written for the earlier contract, with a void
transfer(), still compiles; the driver then treats every transfer as successful.
CONVST and DRDY¶
The driver holds CONVST high for 1 µs and then drives it low; the falling edge samples every channel. The chip
needs only 50 ns high and 50 ns low, so any GPIO is fast enough. DRDY is active high by default; if your board
inverts it, invert it in DrdyRead(), not with SetAlarmPin(..., true).
Without DRDY (HasDrdy() returns false) the driver waits CONV_FALLBACK_US (10 µs) after the falling edge. The
conversion takes 1.25 µs without oversampling, so the default leaves margin.
Optional reset pin¶
If RESET (pin 32) is wired to a GPIO, add void ResetWrite(bool high) to the host class, as the port above does.
The driver detects it at compile time (ADS9324<...>::HasHardwareReset()); it is not part of HostInterface, so a
port without it compiles unchanged and HardwareReset() returns false. Hold RESET high from power-up, for
example with a pull-up, so the chip is not held in reset before your code runs.
Reference implementation¶
The ESP32 examples use esp32_ads9324_bus.hpp: one class that
implements both contracts with the ESP-IDF spi_master driver (hardware CS, one transaction per call), GPIO for
CONVST and DRDY, esp_rom_delay_us() and FreeRTOS delays. 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/, with the driver instantiated over it. - Call
EnsureInitialized(). If it fails, printReadDeviceId():0x0004proves SDI, SCLK, CS and SDOUT end to end. - Short one input and read a snapshot: the count should be within a few tens of LSB of zero.
- Watch
GetDiagnostics().errors: it should stay at zero on a healthy link.
Threads¶
The driver is not thread-safe. Give each ADS9324 one owner task, or guard every call on a driver object (and its port) with one mutex. Several chips on separate buses can run in parallel.