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.