Skip to content

API guide

How to do the common jobs with the driver: initialise, configure channels, switch them, run bridges, read faults and detect plunger movement.

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

The shape of the API

One MAX22200<SpiType> object per chip. Its calls work at three levels:

Level Example Use it for
A whole channel ConfigureChannel(ch, ChannelConfig) Bring-up: every CFG_CHx field in one write
One setting in real units SetHoldCurrentMa(ch, 120), SetHitTimeMs(ch, 20) Changing one value of a configured channel
Raw registers ReadRegister32(bank, v), WriteRegister8(bank, v) Anything the helpers do not cover
Area Calls
Start and stop Initialize(), Deinitialize(), EnableDevice(), DisableDevice()
Channels ConfigureChannel(), ConfigureChannelCdr(), ConfigureChannelVdr(), GetChannelConfig()
Switching EnableChannel(), DisableChannel(), SetChannelsOn(), SetFullBridgeState()
Real units SetHit/HoldCurrentMa(), ...CurrentA(), ...CurrentPercent(), ...DutyPercent(), SetHitTimeMs()
STATUS ReadStatus(), WriteStatus()
Faults ReadFaultRegister(), ClearChannelFaults(), ClearAllFaults(), GetFaultPinState()
DPM ConfigureDpm(), SetDpmEnabledChannels(), ReadDpmConfig(), WriteDpmConfig()

Every call that reaches the chip returns DriverStatus and never throws. Check it: anything but OK means the value was rejected or the transfer failed (Architecture).

Initialise

Driver driver(spi);
driver.SetBoardConfig(max22200::BoardConfig(15.0f, false));   // RREF 15 kOhm: IFS = 1000 mA

const DriverStatus st = driver.Initialize();
if (st != DriverStatus::OK) {
  // INITIALIZATION_ERROR: Initialize() or Configure() of the port failed.
  // COMMUNICATION_ERROR: a transfer failed, or the chip answered COMER three times.
  std::printf("MAX22200 init failed: %d\n", static_cast<int>(st));
  return false;
}

Initialize() follows the datasheet's programming flow (Figure 6): it calls your port's Initialize() and Configure(10 MHz, mode 0, MSB first), raises ENABLE, waits 0.5 ms, reads STATUS to clear the power-up UVM flag, writes STATUS with ACTIVE = 1, every channel off and COMER masked (other masks and pair modes as read: their reset values after power-up), waits 2.5 ms (tWU, ACTIVE = 1 to outputs active), then reads STATUS again. A COMER reply restarts the sequence, up to three times.

Set the board's full-scale current before you configure a CDR channel. BoardConfig() without arguments assumes 1000 mA (RREF = 15 kΩ); a wrong IFS scales every current by the same error. Because Initialize() waits tWU, the outputs switch as soon as it returns; it takes at least 3 ms.

Deinitialize(), also called by the destructor, turns every channel off, clears ACTIVE and lowers ENABLE.

Configure a channel

Configure in this order: pair modes in STATUS (if any), then each channel, then switch channels on. Drive mode and side change only while every channel is off.

max22200::ChannelConfig cfg;                    // defaults: CDR, low side, FMAIN_DIV4, all checks off
cfg.drive_mode = max22200::DriveMode::CDR;
cfg.side_mode = max22200::SideMode::LOW_SIDE;
cfg.hit_setpoint = 600.0f;                      // mA, peak current during the HIT time
cfg.hold_setpoint = 200.0f;                     // mA, after the HIT time
cfg.hit_time_ms = 30.0f;                        // rounded to a multiple of 40 / fCHOP
cfg.chop_freq = max22200::ChopFreq::FMAIN_DIV4; // 25 kHz with FREQM = 0
cfg.hit_current_check_enabled = true;           // HHF flag if 600 mA is not reached in 30 ms
cfg.open_load_detection_enabled = true;         // OLF flag if the coil is disconnected

if (driver.ConfigureChannel(0, cfg) != DriverStatus::OK) {
  return false;   // INVALID_PARAMETER: channel > 7, IFS = 0, or SRC above 50 kHz
}
// High-side load (to GND): VDR only. Setpoints are duty cycle in percent, written as HIT[6:0] = percent.
auto cfg = max22200::ChannelConfig::makeSolenoidVdr(100.0f, 40.0f, 50.0f);
cfg.side_mode = max22200::SideMode::HIGH_SIDE;
if (driver.ConfigureChannel(1, cfg) != DriverStatus::OK) {
  return false;
}

max22200::DutyLimits limits;                    // deltaMIN / deltaMAX for this channel's fCHOP and SRC
if (driver.GetDutyLimits(1, limits) == DriverStatus::OK) {
  std::printf("duty %u..%u %%\n", limits.min_percent, limits.max_percent);
}

ChannelConfig holds every CFG_CHx field; Configuration lists them. The presets makeSolenoidCdr(hit_ma, hold_ma, hit_ms) and makeSolenoidVdr(hit_pct, hold_pct, hit_ms) fill a low-side channel at FMAIN_DIV4; ConfigureChannelCdr() and ConfigureChannelVdr() do the same in one call with the other fields as arguments.

Switch channels

// Each call is one 8-bit write of STATUS[31:24] (ONCH): two short SPI transfers.
if (driver.EnableChannel(0) != DriverStatus::OK) return false;
if (driver.SetChannelsOn(0b0000'0101) != DriverStatus::OK) return false;   // channels 0 and 2 on
if (driver.DisableAllChannels() != DriverStatus::OK) return false;

All switching writes STATUS[31:24], the ONCH byte, with an 8-bit access. The driver keeps the last ONCH value, so EnableChannel(2) leaves the other channels as they were. A channel whose trigger_from_pin is set follows its TRIG pin instead of ONCH.

Change a setting while running

// Change one setting of a running channel: read-modify-write of CFG_CH0 in real units.
if (driver.SetHoldCurrentMa(0, 120) != DriverStatus::OK) return false;

uint32_t hold_ma = 0;
if (driver.GetHoldCurrentMa(0, hold_ma) == DriverStatus::OK) {
  std::printf("hold %lu mA\n", static_cast<unsigned long>(hold_ma));   // quantised to IFS / 127
}

Each Set...() call reads CFG_CHx, changes one field and writes all 32 bits back. HOLD current, chopping frequency and SRC can change while the channel is on; drive mode and side cannot. The ...A() variants take amperes, the ...CurrentPercent() variants a fraction of IFS. The setters never change the drive mode: the current setters work on a CDR channel and the duty setters on a VDR channel, and on a channel in the other mode they return INVALID_PARAMETER without writing anything. Select the mode with ConfigureChannel(), ConfigureChannelCdr() or ConfigureChannelVdr().

Run a full bridge

// Channels 2 and 3 as one full bridge (a brushed DC motor or a latching valve).
// Change CMxy only while both channels are off.
max22200::StatusConfig status;
if (driver.ReadStatus(status) != DriverStatus::OK) return false;
status.channels_on_mask &= static_cast<uint8_t>(~0b0000'1100);
status.channel_pair_mode_32 = max22200::ChannelMode::HBRIDGE;
if (driver.WriteStatus(status) != DriverStatus::OK) return false;

// Forward runs from CFG_CH3, reverse from CFG_CH2 (datasheet Table 7).
if (driver.ConfigureChannelCdr(3, 500, 300, 10.0f) != DriverStatus::OK) return false;
if (driver.ConfigureChannelCdr(2, 500, 300, 10.0f) != DriverStatus::OK) return false;

const uint8_t pair = 1;                         // 0: CH0/CH1, 1: CH2/CH3, 2: CH4/CH5, 3: CH6/CH7
if (driver.SetFullBridgeState(pair, max22200::FullBridgeState::Forward) != DriverStatus::OK) return false;
return driver.SetFullBridgeState(pair, max22200::FullBridgeState::Brake) == DriverStatus::OK;
FullBridgeState OUTx / OUTy Settings from
HiZ (ONCHy = 0, ONCHx = 0) Hi-Z / Hi-Z
Forward (0, 1) VM / regulated CFG_CHy
Reverse (1, 0) regulated / VM CFG_CHx
Brake (1, 1) GND / GND

x is the even channel of the pair, y the odd one. In PARALLEL mode the even channel's configuration drives both outputs and the odd channel's is ignored; switch both channels on together.

Handle faults

bool pin_active = false;                        // FAULT pin, read through the port
driver.GetFaultPinState(pin_active);

max22200::StatusConfig status;                  // global flags; reading clears UVM and OVT
if (driver.ReadStatus(status) == DriverStatus::OK && status.hasFault()) {
  if (status.hasUndervoltage()) std::printf("VM dropped below UVLO\n");
  if (status.hasOvertemperature()) std::printf("thermal shutdown\n");
}

max22200::FaultStatus per_channel;              // which channel, which fault
if (driver.ReadFaultRegister(per_channel) == DriverStatus::OK && per_channel.hasFault()) {
  for (uint8_t ch = 0; ch < 8; ++ch) {
    if (per_channel.hasOvercurrentOnChannel(ch)) {
      std::printf("OCP on OUT%u: channel turned off\n", ch);
      driver.DisableChannel(ch);                // clear ONCH before turning it on again
    }
  }
  driver.ClearChannelFaults(per_channel.channelsWithAnyFault());   // MAX22200A: only these bits
}

if (driver.GetLastFaultByte() == max22200::StatusReg::FAULT_BYTE_COMER) {
  std::printf("last command byte hit a communication error\n");
}
Signal Meaning What to do
UVM in STATUS VM fell below about 4 V; always set at power-up Reading STATUS clears it
OVT in STATUS Thermal shutdown at 145 °C; outputs off until it cools Reading STATUS clears it
COMER in STATUS or GetLastFaultByte() = 0x04 The chip ignored a transfer with a wrong clock count Check Transfer() lengths and CS
OCP bit in FAULT Output above 1.1 A; that channel turned off Clear ONCH, clear the flag, find the short
HHF bit in FAULT HIT current not reached in the HIT time (if hit_current_check_enabled) Coil resistance, VM, HIT level
OLF bit in FAULT Load disconnected (if open_load_detection_enabled); clears when the output turns on Wiring
DPM bit in FAULT No current dip in the HIT phase: the plunger did not move Stuck valve, DPM thresholds
FAULT pin low Any unmasked fault above Read STATUS and FAULT

STATUS bits OCP, OLF, HHF and DPM summarise FAULT; reading STATUS does not clear them. Mask a fault from the FAULT pin with the ..._masked fields of StatusConfig and WriteStatus().

driver.SetFaultCallback(&on_fault, nullptr);
max22200::FaultStatus faults;
driver.ReadFaultRegister(faults);               // calls on_fault() once per set FAULT bit

SetFaultCallback(fn, user_data) registers a callback the driver calls for every fault it reads: once per set bit of a FAULT read (ReadFaultRegister(), ReadFaultRegisterSelectiveClear(), ClearChannelFaults(), ClearAllFaults()) with the channel and OCP, HHF, OLF or DPM, and for OVT, UVM and COMER of a STATUS read after Initialize() with channel 0xFF. The driver does not poll: read FAULT when the FAULT pin goes low. The callback runs in the caller's context, so keep it short. SetStateChangeCallback() is stored but not called by the driver.

Detect plunger movement

// Per channel: DPM_EN on channels 0 and 1 (low-side only). Do this first: the debounce below is
// converted with these channels' chopping frequency.
if (driver.SetDpmEnabledChannels(0b0000'0011) != DriverStatus::OK) return false;
// Global DPM settings: search for the dip above 150 mA, dip of at least 40 mA lasting 0.4 ms.
if (driver.ConfigureDpm(150.0f, 40.0f, 0.4f) != DriverStatus::OK) return false;
// A DPM flag in FAULT[7:0] after a HIT means no dip was seen: the plunger did not move.

DPM looks for the dip a moving plunger causes in the HIT current. The search starts above the start current and ends at the HIT level or the end of the HIT time; a dip counts when it is deeper than the threshold and longer than the debounce time. The settings are global; DPM_EN is per channel and low-side only. The datasheet asks for a current slope below 700 mA/ms above the start current. The debounce is a count of chopping periods (DPM_TDEB / fCHOP), and fCHOP is per channel: ConfigureDpm(start, threshold, debounce) converts it with the chop_freq of the channels that have DPM enabled, so enable DPM first. With no DPM channel it uses FMAIN_DIV4; DPM channels at different frequencies return INVALID_PARAMETER. Pass the frequency as a fourth argument to choose it yourself.

Use a register directly

uint32_t cfg0 = 0;
if (driver.ReadRegister32(max22200::RegBank::CFG_CH0, cfg0) != DriverStatus::OK) return false;

// 8-bit access reaches bits [31:24] only: for CFG_CHx that is HFS and HOLD[6:0].
const uint8_t hold_raw = 40;                    // 40 / 127 x IFS
return driver.WriteRegister8(max22200::RegBank::CFG_CH0,
                             static_cast<uint8_t>((cfg0 >> 24) & 0x80u) | hold_raw) == DriverStatus::OK;

Banks are max22200::RegBank::STATUS, CFG_CH0 to CFG_CH7, FAULT and CFG_DPM; bit positions are in the StatusReg, CfgChReg, FaultReg and CfgDpmReg namespaces. An 8-bit access reaches bits [31:24] only. A raw STATUS write bypasses the driver's cache: call ReadStatus() afterwards.

Count transfers

const max22200::DriverStatistics s = driver.GetStatistics();
std::printf("%lu transfers, %lu failed (%.1f %% ok)\n", static_cast<unsigned long>(s.total_transfers),
            static_cast<unsigned long>(s.failed_transfers), static_cast<double>(s.getSuccessRate()));

Only total_transfers and failed_transfers are counted; fault_events, state_changes and uptime_ms stay 0.