Skip to content

API guide

How to do the common jobs with the driver: initialise, configure channels, calibrate, read and handle errors.

Every snippet on this page is compiled by the host CI from tests/docs/api_tasks.cpp, with adc an ads9324::ADS9324<MySpi, MyHost> over the port. The API reference lists every member.

The shape of the API

One ADS9324<Spi, Host> object per chip. Its methods fall into these groups:

Group Methods
Bring-up EnsureInitialized(), IsInitialized(), SoftwareReset(), HardwareReset(), ReadDeviceId(), SetReference()
Channels ConfigureChannel(), ConfigureAllChannels(), GetChannelConfig(), SetChannelOffset(), SetChannelGain()
Calibration SetSystemCorrection(), RunAdcCalibration(), GetAdcCalMode(), OffsetCorrectionFor(), GainCorrectionFor()
Data interface SetDataFormat(), GetDataFormat(), SetDigitalInterface(), SetChannelCountMode(), SetOversampling()
Pins and power SetAlarmPin(), ProgramWindowComparator(), SetPowerDown()
Reading ReadSnapshot(), ReadChannel(), CountToVoltage(), GetDiagnostics()

Configuration calls return bool: false means the channel index was out of range or the call did not complete. Reads return a Snapshot or ReadResult with an error field. Nothing throws or allocates. Channels are numbered 0 to 15 in the API (AIN1 to AIN16 on the chip).

Initialise

if (!adc.EnsureInitialized()) { // 30 ms settle, SW reset, GEN_CFG3 = 0x0032, DEVICE_ID, PGA
                                // defaults
  const uint16_t id = adc.ReadDeviceId();   // 0x0004 on a healthy ADS9324, 0 when SDOUT is dead
  std::printf("ADS9324 init failed, DEVICE_ID = 0x%04X\n", id);
  return false;
}
// Later calls return at once; EnsureInitialized(true) runs the whole sequence again.

EnsureInitialized() runs the datasheet's 1-lane sequence (Registers and SPI frames) and fails when DEVICE_ID is not 0x0004. Every read and every snapshot needs it to have succeeded. Define CONFIG_ADS9324_SKIP_DEVICE_ID_CHECK to skip the ID check during bench bring-up (Configuration).

Reset and reference

// Use the internal 4.096 V reference whatever the REFSEL pin is strapped to.
if (!adc.SetReference(ads9324::ReferenceSource::Internal)) {
  return false;
}
// Pulse the RESET pin (the port has ResetWrite()); fall back to SW_RST otherwise.
const bool reset_ok = Adc::HasHardwareReset() ? adc.HardwareReset() : adc.SoftwareReset();
// Both resets return the reference to RefselPin: select it again.
return reset_ok && adc.SetReference(ads9324::ReferenceSource::Internal);

SoftwareReset() writes SW_RST; HardwareReset() pulses the RESET pin (low for RESET_PULSE_US, then the 30 ms tPU) and needs the optional ResetWrite() hook in the port (HasHardwareReset() tells you at compile time). Either reset returns every register to its reset value; the driver then selects 1-lane SDOUT again and writes the channel defaults, so offsets, gains, the window comparator, the channel-count mode, the reference selection, the oversampling filter and the ADC_CAL correction are back at their reset values. If that reprogramming fails, IsInitialized() turns false.

SetReference() writes ANA_CFG1 (Table 7-3):

ReferenceSource ANA_CFG1 Reference
RefselPin (reset) 0x0000 REFSEL pin: high = internal, low = external on REFIO
Internal 0x0002 Internal 4.096 V, REFSEL ignored
External 0x0003 External on REFIO, REFSEL ignored; without one the chip powers down

Configure the channels

// AIN1..AIN4: ±10 V differential, wide bandwidth for a fast current-sense signal.
ads9324::ChannelConfig current;
current.type = ads9324::InputType::Differential;
current.range = ads9324::InputRange::PlusMinus10V;
current.bandwidth = ads9324::PgaBandwidth::Wide;

// AIN5..AIN16: ±2.5 V single-ended sensors that may come unplugged.
ads9324::ChannelConfig sensor;
sensor.type = ads9324::InputType::SingleEndedOpenWireSafe;
sensor.range = ads9324::InputRange::PlusMinus2V5;

for (uint8_t ch = 0; ch < ads9324::kNumChannels; ++ch) {
  if (!adc.ConfigureChannel(ch, ch < 4 ? current : sensor)) {
    return false;                      // ch out of range, or the bus failed
  }
}
ChannelConfig field Default Values
type Differential Differential, SingleEnded, SingleEndedOpenWireSafe
range PlusMinus5V PlusMinus2V5, PlusMinus5V, PlusMinus6V25, PlusMinus10V, PlusMinus12V5
bandwidth Low Low (25.5 kHz), Wide (280 kHz to 350 kHz)
common_mode_correct false Common-mode error correction

Each ConfigureChannel() call is a read-modify-write of two registers (about eight SPI frames). The driver keeps the last configuration per channel; GetChannelConfig(ch) returns it, and the voltage conversion uses its range. EnsureInitialized() and SoftwareReset() write the compile-time defaults (DefaultChannelConfig(): ±5 V differential, low bandwidth unless changed with CONFIG_ADS9324_*, see Configuration) to all 16 channels, so the chip and the driver agree from the start. PGA and calibration explains the encodings.

Calibrate offset and gain

// Offset: 10-bit two's complement in quarter LSBs (-512..511), added before the gain (Equation
// 1). Channel 0 reads 10 LSB high with its input shorted: subtract 40 quarter LSBs.
if (!adc.SetChannelOffset(0, -40)) {
  return false;
}

// Gain: 14-bit two's complement (-8192..8191); the chip multiplies by (1 + GAN / 65536).
// Channel 0 reads 0.5 % high: GAN = -0.005 * 65536 = -328. Values outside the field return false.
if (!adc.SetChannelGain(0, -328)) {
  return false;
}

The offset is in quarter LSBs, −512 to +511 (−128 to +127.75 LSB); the gain is signed, −8192 to +8191, and multiplies by 1 + GAN / 65536 (about ±12.5 %). A value outside its field returns false and writes nothing. Measure with the real source impedance connected: the correction is for the whole signal chain.

To compute the values from two measurements, OffsetCorrectionFor(measured, expected) returns 4 × (expected − measured) (Equation 1) and GainCorrectionFor(measured, expected) returns (expected − measured) / measured × 65536, rounded (Equation 3). Measure the gain point after the offset is corrected, with the gain correction at 0:

// AIN1 shorted reads +10 LSB; with 4.5 V applied on the ±5 V range it reads 29000 instead of
// 29491 (4.5 / 5 * 32768). The helpers apply Equations 1 and 3 of the datasheet.
const int32_t ofs = ads9324::OffsetCorrectionFor(10, 0);           // -40 quarter LSBs
const int32_t gan = ads9324::GainCorrectionFor(29000 - 10, 29491); // measured after the offset
if (ofs < ads9324::kOffsetMin || ofs > ads9324::kOffsetMax || gan < ads9324::kGainMin ||
    gan > ads9324::kGainMax) {
  return false; // error too large for the registers
}
return adc.SetChannelOffset(0, static_cast<int16_t>(ofs)) &&
       adc.SetChannelGain(0, static_cast<int16_t>(gan));

SetSystemCorrection(offset, gain) switches the application of all offset or all gain registers on or off (OFS_CORR_DIS and GAN_CORR_DIS in the GEN_CFG5 register of both channel banks, sections 7.3.8 and 7.3.9); both are on at reset and the register values are kept while off.

On-chip ADC calibration

// On-chip ADC_CAL: the inputs are disconnected for about 125 ms while the chip measures its own
// offset and gain error. Run it after configuring the channels (it reads their input types).
if (!adc.RunAdcCalibration(ads9324::AdcCalMode::OffsetAndGain)) {
  return false; // bus error, or CALIB_BUSY did not clear
}
// The correction stays applied until a reset or until it is switched off:
// adc.RunAdcCalibration(ads9324::AdcCalMode::Disabled);

RunAdcCalibration(mode) runs the chip's ADC_CAL module once in single-shot mode (section 7.3.10). It turns the digital filters off and sets INT_TRIG_MODE, writes ADC_CAL_MODE and the bank input types (SE_DIFF_MODE_AIN1_8, SE_DIFF_MODE_AIN9_16), pulses ADC_CAL_TRIG for 10 µs, gives CONVST one high-low-high pulse, waits ADC_CAL_WAIT_MS (125 ms, Table 7-7) and polls CALIB_BUSY_FLAG in DEVICE_STATUS for up to ADC_CAL_TIMEOUT_MS. Then CONVST returns low and the oversampling filter is restored. A bank counts as differential when all its eight channels are; RunAdcCalibration(mode, ain1_8, ain9_16) sets the two input types yourself. AdcCalMode::Disabled removes the correction. A reset sets ADC_CAL back to disabled, so run it again after SoftwareReset() or HardwareReset(). Use it when you do not calibrate the system yourself; it reduces the offset error from about ±30 LSB to ±8 LSB in the datasheet's example.

Read

const ads9324::Snapshot snap = adc.ReadSnapshot();   // all 16 channels, same sampling instant
if (snap.ok()) {
  for (uint8_t ch = 0; ch < ads9324::kNumChannels; ++ch) {
    if (snap.hasChannel(ch)) {
      std::printf("AIN%u: %6d  %.4f V\n", ch + 1U, snap.count[ch],
                  static_cast<double>(snap.voltage[ch]));
    }
  }
}

// One channel: still one full conversion; the others are discarded.
const ads9324::ReadResult r = adc.ReadChannel(3);
if (r.ok()) {
  const float again = adc.CountToVoltage(3, r.count);  // same value as r.voltage
  (void)again;
}

ReadSnapshot() is one CONVST edge: all channels are sampled at the same instant. valid_mask has a bit for each channel clocked out, and hasChannel(ch) tests it. ReadChannel() runs a full snapshot and keeps one channel; call ReadSnapshot() once when you need more than one.

The SPI burst is 32 bytes at the default 16-bit word length. At the examples' 8 MHz clock that is 32 µs on the bus; see Hardware for the rate this allows.

Change the data format and word length

// Offset-binary coding on the wire; the driver still returns two's-complement counts.
if (!adc.SetDataFormat(ads9324::DataFormat::OffsetBinary)) {
  return false;
}
// 24-bit words on SDOUT (48-byte bursts); the driver uses the top 16 bits as the code.
return adc.SetDigitalInterface(ads9324::DoutLaneMode::Lane1, ads9324::DoutLength::Bits24, true);

Both calls rewrite GEN_CFG3. Snapshot::count stays two's complement whatever the wire format. Keep DoutLaneMode::Lane1 with data on SDOUT: ReadSnapshot() reads SDOUT only, so another lane setting stops the data from reaching it.

Read fewer channels

SetChannelCountMode(mode, ch_sel) writes ANA_CFG2 so the chip converts and outputs 8, 4 or 2 channels, which shortens the burst.

// Eight channels, group 0: the chip sends AIN1..AIN4 and AIN13..AIN16 (Table 7-12).
if (adc.SetChannelCountMode(ads9324::ChannelCountMode::Eight, 0)) {
  const ads9324::Snapshot snap = adc.ReadSnapshot(); // 16-byte burst
  if (snap.ok() && snap.hasChannel(12)) {            // valid_mask = 0xF00F
    std::printf("AIN13: %d\n", snap.count[12]);      // stored under its own channel
  }
}

The chip outputs a fixed set of channels in these modes, taken from both ends of the device, lowest channel first (Table 7-12). ch_sel is the ADC_CH_SEL field; only its top bits pick the group:

ChannelCountMode ch_sel Channels in the burst
All16 (default) any AIN1 to AIN16
Eight 0b0xx / 0b1xx AIN1 to AIN4 and AIN13 to AIN16 / AIN5 to AIN12
Four 0b00x, 0b01x, 0b10x, 0b11x AIN1, 2, 15, 16 / AIN3, 4, 13, 14 / AIN5, 6, 11, 12 / AIN7 to AIN10
Two 0b000 to 0b111 AIN1 and AIN16, AIN2 and AIN15, ... AIN8 and AIN9

ReadSnapshot() stores each word under its real channel, so count[12] is AIN13 in every mode, and sets only those channels in valid_mask. reg::FrameChannel(mode, ch_sel, word) and reg::FrameChannelMask(mode, ch_sel) give the same mapping. ReadChannel() for a channel the chip does not send returns Error::SpiError.

Average with oversampling

SetOversampling(osr, clock) turns on the chip's block-average filter (section 7.3.11.2): each snapshot returns the mean of N conversions per channel.

// Average 16 conversions per snapshot: +6.5 dB SNR on the ±5 V range, 62.5 kSPS at most (Table
// 7-8). The internal clock runs the 16 conversions from one CONVST edge.
if (!adc.SetOversampling(ads9324::OversamplingRatio::X16)) {
  return false;
}
const ads9324::Snapshot avg = adc.ReadSnapshot(); // averaged codes, same layout as before
(void)avg;

// External clock: the driver pulses CONVST 16 times, at regular intervals (best antialiasing).
if (!adc.SetOversampling(ads9324::OversamplingRatio::X16, ads9324::OversamplingClock::External)) {
  return false;
}
return adc.SetOversampling(ads9324::OversamplingRatio::None); // back to one conversion
OversamplingRatio DIG_FILTER BLK_AVG_OSR SNR, ±5 V (Table 7-8) Highest output rate
None (reset) 0000b 87.5 dB 1 MSPS
X2, X4, X8 0001b, 0010b, 0100b 89.5, 91.2, 92.7 dB 500, 250, 125 kSPS
X16, X32 0111b, 1001b 94.0, 96.7 dB 62.5, 31.25 kSPS
X64, X128 1010b, 1011b 99.1, 101.6 dB 15.6, 7.8 kSPS

X6, X10, X12 and X20 are also valid codes (Table 8-17); OversamplingSamples(osr) returns N.

OversamplingClock DIG_FILTER INT_TRIG_MODE Conversions
Internal (default) 1 One CONVST edge; the chip runs the other N − 1 itself (Figure 7-17)
External 0 ReadSnapshot() pulses CONVST N times, 2 µs apart (Figure 7-16)

The first enabling call writes GEN_CFG5 AVG_MODE_OVR_EN, then AVG_MODE, as section 7.3.11.2 requires; every enabling call then writes DIG_FILTER and pulses DIG_FILT_SYSREF 0 → 1 → 0, high for 2 µs (more than one tCONVST, Figure 7-14), to clear the accumulator. With the internal clock the DRDY timeout and the CONV_FALLBACK_US wait are multiplied by N. A reset returns the filter to None and GetOversampling() follows. A reserved code returns false.

Window comparator and alarm pin

// Window comparator on AIN1. Thresholds are compared with the top 8 bits of the code, so one
// step is 256 LSB: +64 is +FS/2, -16 is -FS/8. Alert while AIN1 is outside that window.
if (!adc.ProgramWindowComparator(0, 64, -16)) {
  return false;
}
// Route the comparator to the DRDY/ALARM pin. DRDY polling then no longer works:
// give the port HasDrdy() == false, or switch back with SetAlarmPin(AlarmPinMode::Drdy).
return adc.SetAlarmPin(ads9324::AlarmPinMode::DwcAlert);
AlarmPinMode DRDY/ALARM carries
Drdy (default) Conversion ready, active high
DwcAlert Any enabled window comparator out of its window
AdcCalDone On-chip calibration finished
OrMasked OR of the alarm flags selected by the mask

SetAlarmPin(mode, true) inverts the pin's polarity. With the pin inverted or muxed away from Drdy, the driver's DRDY poll reads the wrong signal: give the port HasDrdy() == false for that time.

Power down

if (!adc.SetPowerDown(true)) {         // standby: AFE and reference off, about 6.5 mW
  return false;
}
// ... later
return adc.SetPowerDown(false);        // the reference needs time to settle before accurate samples

Standby powers down the analog front ends and the internal reference. The registers keep their values. The reference takes up to 30 ms to settle again (with 1 µF on REFCAP).

Handle errors

const ads9324::Snapshot snap = adc.ReadSnapshot();
switch (snap.error) {
case ads9324::Error::Ok:
  break;
case ads9324::Error::NotInitialized:   // EnsureInitialized() never succeeded
  adc.EnsureInitialized(true);
  break;
case ads9324::Error::Timeout:          // DRDY stayed low for DRDY_TIMEOUT_US
  std::printf("no DRDY: check CONVST and the DRDY/ALARM mux\n");
  break;
default:
  break;
}

const ads9324::Diagnostics d = adc.GetDiagnostics();   // software counters, no SPI traffic
std::printf("snapshots %lu, errors %lu\n", static_cast<unsigned long>(d.snapshots),
            static_cast<unsigned long>(d.errors));
Signal Meaning What to do
EnsureInitialized() returns false Reset frame failed or DEVICE_ID was not 0x0004 Check ReadDeviceId(), then wiring and supplies
Error::NotInitialized A read before a successful EnsureInitialized() Initialise first
Error::Timeout DRDY did not rise within DRDY_TIMEOUT_US (1000 µs) CONVST wiring, DRDY polarity, SetAlarmPin()
Error::InvalidChannel ReadChannel() with a channel above 15 Use 0 to 15
Error::SpiError The port's transfer() returned false, or the requested channel was not in the burst Check the bus; with a reduced channel-count mode, read a channel the chip sends (table)
A configuration call returns false Channel above 15, a value outside its register field, or the port's transfer() returned false Use 0 to 15 and the documented ranges; check the bus

GetDiagnostics() returns the software counters (snapshots, errors) and the cached interface settings without touching the bus.

Bus errors

The port's transfer() returns false on a bus error (Porting); the driver stops the call that sent the frame and leaves its cached settings unchanged. A failed transfer is the only error the bus can report: a wiring fault that still clocks bytes shows up as a wrong DEVICE_ID.

Troubleshooting maps symptoms to causes.