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.