Skip to content

API guide

How to do the common jobs with the driver: initialise, configure, read, program alarms and GPIOs, and handle errors.

Every snippet on this page is compiled by the host CI from tests/docs/api_tasks.cpp, with Adc standing for ads7952::ADS7952<MySpi>. The API reference lists every member.

The shape of the API

One ADS7952<SpiType> object per chip. It owns no hardware: it holds a reference to your port and mirrors what it has programmed into the chip.

Group Calls Returns
Initialise EnsureInitialized(force), IsInitialized() bool
Read ReadChannel(ch), ReadAllChannels(), RawManualFrames(ch, out, n) ReadResult, ChannelReadings, frame count
Modes EnterManualMode(ch), EnterAuto1Mode(reset), EnterAuto2Mode(reset), GetMode() bool
Sequences ProgramAuto1Channels(mask), ProgramAuto2LastChannel(ch) bool
Alarms and GPIOs ProgramAlarm(), ProgramAlarmVoltage(), ProgramGPIO(config), SetGPIOOutputs(bits) bool
Range and power SetRange(range), SetPowerDown(pd) bool
Conversion CountToVoltage(), VoltageToCount(), SetVref(), SetVA(), GetActiveVref() volts, counts

Reads return a struct with an error field and an ok() helper (Error::SpiError when the port's transfer() reported a bus error). The other calls return false for an argument out of range (a channel above 11) or a failed transfer; see Porting.

Initialise

#include "ads7952.hpp"
#include <cstdio>

int read_adc(MySpi& spi) {
  ads7952::ADS7952<MySpi> adc(spi, 2.5f, 5.0f);               // REFP = 2.5 V, +VA = 5 V

  if (!adc.EnsureInitialized()) {                             // 1. discard power-up frame, program sequences
    return -1;
  }

  const ads7952::ReadResult ch3 = adc.ReadChannel(3);         // 2. one channel (Manual mode)
  if (!ch3.ok()) {
    return -1;                                                // Timeout: wiring, SPI mode or CS framing
  }
  std::printf("CH3 = %.3f V (%u counts)\n", ch3.voltage, static_cast<unsigned>(ch3.count));

  const ads7952::ChannelReadings all = adc.ReadAllChannels(); // 3. every channel (Auto-1 mode)
  for (uint8_t ch = 0; ch < 12; ++ch) {
    if (all.hasChannel(ch)) {
      std::printf("CH%u = %.3f V\n", static_cast<unsigned>(ch), all.voltage[ch]);
    }
  }
  return all.ok() ? 0 : -1;
}

EnsureInitialized() discards the invalid first conversion, programs the Auto-1 mask and the Auto-2 last channel from ADS7952_CFG and enters Auto-1. A second call returns true without SPI traffic; pass force = true after the chip was reset or lost power. Every read returns Error::NotInitialized until it has succeeded.

Configure

// Auto-1 scans any subset; Auto-2 scans CH0..last. Program them once after EnsureInitialized().
if (!adc.ProgramAuto1Channels(ads7952::ChannelMask(0, 1, 4, 5))) {
  return false;
}
if (!adc.ProgramAuto2LastChannel(7)) {               // CH0..CH7
  return false;
}

// 0..2 x VREF: only valid while 2 x VREF <= +VA (2.5 V reference, 5 V supply).
if (!adc.SetRange(ads7952::Range::TwoVref)) {        // in effect on the chip when it returns
  return false;
}

Build masks with ads7952::ChannelMask(0, 2, 4), ChannelRangeMask(first, last) or the constants kAllChannels, kEvenChannels, kOddChannels, kFirstFour, kSecondFour, kThirdFour. Bit N is channel N.

SetRange() is in effect on the chip when it returns, in whatever mode the chip is in, and the driver's full scale changes with it. Modes and the frame pipeline shows the two frames it sends.

Read

// One channel: Manual mode, three frames, keyed on the channel address in the reply.
const ads7952::ReadResult r = adc.ReadChannel(4);
if (r.ok()) {
  std::printf("CH%u: %u counts, %.3f V\n", static_cast<unsigned>(r.channel),
              static_cast<unsigned>(r.count), r.voltage);
}

// Every channel of the Auto-1 mask: one frame per channel plus two.
const ads7952::ChannelReadings all = adc.ReadAllChannels();
for (uint8_t ch = 0; ch < ads7952::ChannelReadings::MAX_CHANNELS; ++ch) {
  if (all.hasChannel(ch)) {
    std::printf("CH%u: %.3f V\n", static_cast<unsigned>(ch), all.voltage[ch]);
  }
}
if (!all.ok()) {
  std::printf("%u of the programmed channels arrived\n", static_cast<unsigned>(all.validChannelCount()));
}
Call Frames (healthy) Frames (most) Leaves the chip in
ReadChannel(ch) 3 1 + MANUAL_READ_MAX_FRAMES Manual, on ch
ReadAllChannels() N + 2 1 + N + READ_ALL_MAX_EXTRA_FRAMES Auto-1

N is the number of channels in the Auto-1 mask.

ReadAllChannels() always uses the Auto-1 mask. ChannelReadings::valid_mask says which entries of count[] and voltage[] were filled; on Error::Timeout the channels that did arrive are still valid.

Program alarms and GPIOs

// GPIO0 goes high while any channel is outside its window (active-high output).
ads7952::GPIOConfig gpio;
gpio.alarm_mode = ads7952::GPIO01AlarmMode::GPIO0_HighAndLowAlarm;
gpio.direction_mask = ads7952::gpio::kGPIO2 | ads7952::gpio::kGPIO3;  // GPIO2, GPIO3: outputs
if (!adc.ProgramGPIO(gpio)) {
  return false;
}

// A window of 0.5 V .. 2.0 V on CH0, from volts with the active reference ...
if (!adc.ProgramAlarmVoltage(0, ads7952::AlarmBound::Low, 0.5f) ||
    !adc.ProgramAlarmVoltage(0, ads7952::AlarmBound::High, 2.0f)) {
  return false;
}
// ... or from counts. The chip keeps the top 10 bits (4-count steps).
if (!adc.ProgramAlarm(1, ads7952::AlarmBound::High, 3000)) {
  return false;
}

Each channel has a high and a low threshold. The chip compares the top 10 bits of each conversion with them, so a threshold has a resolution of 4 counts. ProgramAlarmVoltage() converts with the reference that is active when you call it: program the thresholds again after SetRange() or SetVref().

GPIO01AlarmMode DI06:04 GPIO0 GPIO1
GPIO 000 GPIO GPIO
GPIO0_HighAndLowAlarm 001 High or low alarm GPIO
GPIO0_HighAlarm 010 High alarm GPIO
GPIO1_LowAlarm 100 GPIO Low alarm
GPIO1_LowAlarm_GPIO0_HighAlarm 110 High alarm Low alarm

The chip has no code that makes GPIO1 a high-alarm output (SLAS605C Table 11). GPIO1_HighAlarm, the old, misleading name of GPIO1_LowAlarm, is kept as a deprecated alias with the same code.

GPIOConfig::gpio2_as_range_input and gpio3_as_powerdown_input hand the range and power-down control to those pins; reset_all_registers resets every chip register in the next frame. On the VQFN package only GPIO0 exists.

Write outputs and power down

// Each call is in effect on the chip when it returns.
if (!adc.SetGPIOOutputs(ads7952::gpio::kGPIO2)) {    // GPIO2 high, GPIO3 low
  return false;
}
if (!adc.SetPowerDown(ads7952::PowerDown::PowerDown)) {  // the chip is powered down
  return false;
}
if (!adc.SetPowerDown(ads7952::PowerDown::Normal)) {     // awake again
  return false;
}

SetGPIOOutputs() and SetPowerDown() work like SetRange(): when they return, the pins show the new levels, or the chip is powered down (about 1 µA) or awake again. Waking takes 1 µs and costs one invalid conversion, which falls in the frame the driver drops.

Read raw frames

// Bring-up: clock frames by hand and see the address pipeline (CH in DO15:12, count in DO11:0).
uint16_t words[4] = {};
const uint8_t n = adc.RawManualFrames(6, words, 4);
for (uint8_t i = 0; i < n; ++i) {
  std::printf("frame %u: CH%u = %u\n", static_cast<unsigned>(i),
              static_cast<unsigned>(words[i] >> 12), static_cast<unsigned>(words[i] & 0x0FFF));
}

RawManualFrames() sends one Manual frame and n - 1 continue frames and returns every reply untouched. Use it on the bench to see the address pipeline advance; it leaves the chip in Manual mode.

Handle errors

const ads7952::ReadResult r = adc.ReadChannel(2);
switch (r.error) {
case ads7952::Error::Ok:
  break;
case ads7952::Error::NotInitialized:                 // call EnsureInitialized() first
  adc.EnsureInitialized();
  break;
case ads7952::Error::InvalidChannel:                 // channel > 11
  break;
case ads7952::Error::Timeout:                        // the address never came back: check the bus
  adc.EnsureInitialized(true);                       // after a reset or brown-out, program again
  break;
case ads7952::Error::SpiError:                       // the port's transfer() returned false
  break;
default:
  break;
}
Signal Meaning What to do
Error::NotInitialized EnsureInitialized() has not succeeded Call it
Error::InvalidChannel Channel above 11 Fix the caller
Error::Timeout The requested channel address never came back in the frame budget Check SPI mode, CS framing and MISO; re-initialise after a chip reset
Error::SpiError The port's transfer() returned false Check the port and the bus; the read stopped at that frame
A bool call returns false Channel or last channel above 11, or the port's transfer() returned false Fix the caller, or check the bus

ProgrammingFailed and ModeChangeFailed exist in the enum but no call returns them today. Troubleshooting maps symptoms to causes.

Calibrate the conversion

// Software only: the scale used by CountToVoltage(), VoltageToCount() and the results.
adc.SetVref(measured_vref);                          // e.g. 2.497 V measured at REFP
const float v = adc.CountToVoltage(2048);            // with the active reference (range applied)
const uint16_t c = ads7952::VoltageToCount(1.25f, adc.GetActiveVref());
std::printf("2048 counts = %.4f V, 1.25 V = %u counts\n", v, static_cast<unsigned>(c));

SetVref() and SetVA() only change the numbers the driver converts with; they send nothing. Measure REFP once on your board and pass the real value for the best absolute accuracy. Configuration covers the limits these values are clamped to.