API guide¶
How to do the common jobs with the driver: identify the chip, apply a profile, set rails, read status, take a diagnostic snapshot, handle faults and read OTP.
Every snippet on this page is compiled by the host CI from
tests/docs/api_tasks.cpp, with Driver standing for pf1550::PF1550<MyI2c>. The
API reference lists every member.
The shape of the API¶
One PF1550<Bus> object per PMIC, constructed from a pointer to your port and the 7-bit address (default
kDefaultI2cAddress, 0x08). Every member returns bool (or fills an out-parameter) and never throws. A false
also sets a bit in the sticky error flags, which stay set until you clear them.
| Task | Members |
|---|---|
| Identify | EnsureInitialized(), IsInitialized(), ReadDeviceId(), VerifyDevice(), GetI2cAddress() |
| Profiles | ApplyProfile(span), ApplyPortentaH7DefaultProfile(), ApplyPortentaH7CarrierProfile() |
| Rails and limits | SetSwVoltageMv(), SetSwVoltage(), SetLdoVoltage(), SetVbusCurrentLimitMa(), SetSwDvsEnabled() |
| Strap pins | SetPowerMode(), SetUsbRails() |
| Status | ReadPmicStatus(), ReadPmicState(), ReadChargerState(), ReadInterruptCategory() |
| Faults and interrupts | ReadLatchedFaults(), ClearLatchedFaults(), SetInterruptMaskAll() |
| Diagnostics | ReadDiagnosticSnapshot(), RefreshStatusSnapshot(), RunPowerSelfTest() |
| OTP and raw access | ReadOtpByte(), ReadOtpRegion(), ReadRegister(), WriteRegister() |
| Errors | GetErrorFlags(), ClearErrorFlags(mask), GetDriverVersion() |
Identify the chip¶
if (!pmic.EnsureInitialized()) {
const uint16_t err = pmic.GetErrorFlags();
if (err & static_cast<uint16_t>(Driver::Error::WrongDeviceId)) {
std::printf("not a PF1550 at 0x%02X\n", pmic.GetI2cAddress());
} else {
std::printf("no answer at 0x%02X (errors 0x%04X)\n", pmic.GetI2cAddress(), err);
}
return false;
}
EnsureInitialized() brings your bus up, reads DEVICE_ID and accepts only 0x7C. After the first success it
returns true without touching the bus. The other members do not require it, but call it first so a wiring
problem shows up as DeviceNotFound rather than as a failed rail write.
Apply a profile¶
pmic.SetPowerMode(pf1550::PowerMode::Run); // STANDBY strap low
pmic.SetUsbRails(true, true); // USB_VBUS_EN, USB_OTG_EN high
if (!pmic.ApplyPortentaH7CarrierProfile()) { // 23 writes, SW1 first, LDO2_CTRL last
return false; // stopped at the first write that failed
}
A profile is applied in table order, waiting each entry's delay_us through your port's DelayUs(). It stops at
the first write that fails and sets Error::ProfileApply; the writes before it have already taken effect.
Portenta H7 profiles lists every write of the built-in tables; Configuration
shows how to write your own.
Set rails and limits¶
bool ok = true;
ok &= pmic.SetLdoVoltage(2, pf1550::LdoVoltageCode::V1_8); // LDO2_VOLT = 0x00
pmic.SetSwDvsEnabled(2, true); // this part's SW2 runs with DVS
ok &= pmic.SetSwVoltageMv(2, 1200); // SW2_VOLT = 0x30 (0.6 V + 48 x 12.5 mV)
ok &= pmic.SetVbusCurrentLimitMa(500); // VBUS_INLIM_CNFG = 0x70
ok &= pmic.WriteRegister(pf1550::Register::Sw2Ctrl,
pf1550::SwCtrlBits::kEn | pf1550::SwCtrlBits::kStbyEn);
SetLdoVoltage(n, code) writes the RUN set point of LDO n (1 to 3). SetSwVoltageMv(n, mv) and
SetSwVoltage(n, code) set the RUN voltage of SW1 or SW2, and only when that buck runs with DVS enabled (declared
with SetSwDvsEnabled()): they write the DVS code for the voltage. They refuse with Error::InvalidParam, writing
nothing, for SW3 and for a DVS-disabled SW1 / SW2, whose voltage registers are read-only, and for a voltage that
is not on the DVS grid (0.6 V to 1.3875 V in 12.5 mV steps). Any other index is rejected the same way. Enables have no helper: build the control byte from SwCtrlBits or
LdoCtrlBits and use WriteRegister().
SwVoltageCode names a voltage
SwVoltageCode values are DVS-disabled codes. SetSwVoltage() uses the voltage the code stands for
(V1_1 is 1.1 V) and converts it to the DVS encoding, so codes above 1.3875 V are refused. Rails and
OTP has both tables.
Read status¶
pf1550::PmicState state{};
pf1550::ChargerState charger{};
uint8_t category = 0;
if (!pmic.ReadPmicState(state) || !pmic.ReadChargerState(charger) ||
!pmic.ReadInterruptCategory(category)) {
return false;
}
std::printf("%s, charger %s, INT_CATEGORY 0x%02X\n", pf1550::PmicStateName(state),
pf1550::ChargerStateName(charger), category);
ReadPmicState() decodes STATE_INFO (0x67) into PmicState: Wait, Run, Standby, Sleep, RegsDisable
or Unknown. ReadChargerState() decodes CHG_SNS (0x87) into ChargerState (pre-charge, fast charge CC / CV,
end of charge, done, faults, off) and can return the raw byte too. PmicStateName(), ChargerStateName() and
RailName() give log-friendly strings.
Read a diagnostic snapshot¶
pf1550::DiagnosticSnapshot snap{};
if (!pmic.ReadDiagnosticSnapshot(snap)) { // about 30 reads: identity, rails, faults
return false; // fields that were read are still filled in
}
for (size_t i = 0; i < static_cast<size_t>(pf1550::RailId::Vsnvs); ++i) {
const auto& r = snap.rails[i];
std::printf("%-4s %4u mV run=%d stby=%d fault=%d\n", pf1550::RailName(static_cast<pf1550::RailId>(i)),
r.set_voltage_mv, r.enabled_run, r.enabled_stby, r.stat_latched);
}
// Between full reads, refresh only what changes on its own (about 14 reads).
return pmic.RefreshStatusSnapshot(snap);
DiagnosticSnapshot holds identity (DEVICE_ID, OTP_FLAVOR, SILICON_REV), INT_CATEGORY, the decoded state and
charger state, VBUS sense, the decoded VBUS input limit, the aggregated FaultFlags, and one RailDiagnostics per
rail: raw control and voltage bytes, set point in mV, RUN and STANDBY enables, latched and live fault marks. The
VSNVS entry holds the raw VSNVS_CTRL byte, 3000 mV and both enables set: VSNVS is a fixed, always-on output. When
a read fails, read_ok is false and Error::DiagnosticRead is set, but every field that was read is kept for
logging.
RefreshStatusSnapshot() re-reads only what changes without a register write (interrupts, state, charger, VBUS,
faults), at about half the bus time of a full read. A monitor polling at 20 Hz can do a full read once a second
and refreshes in between.
Handle errors and faults¶
uint8_t category = 0;
if (!pmic.ReadInterruptCategory(category)) {
return false;
}
if (category != 0) { // something latched since the last clear
pf1550::FaultFlags faults{};
if (!pmic.ReadLatchedFaults(faults)) {
return false;
}
if (faults.Has(pf1550::FaultFlags::kLdo2Fault)) {
std::printf("LDO2 current limit\n");
}
// Handle the cause first, then re-arm: write 1 to every status bit.
pmic.ClearLatchedFaults();
}
| Signal | Meaning | Where to look |
|---|---|---|
A call returns false |
NACK, bad argument or a failed read | GetErrorFlags(): I2cWrite, I2cRead, InvalidParam, DeviceNotFound, WrongDeviceId, NotInitialized, ProfileApply, DiagnosticRead, SelfTestFailed |
| INT_CATEGORY non-zero | A group has a latched interrupt | ReadLatchedFaults(), or the STAT register with ReadRegister() |
| INTB low | An unmasked interrupt is pending | Masks power up set: SetInterruptMaskAll(false) unmasks every group |
kVbusInval in FaultFlags |
VBUS_VALID_SNS is clear: no valid USB or adapter input | Expected on battery; ranked kInfo |
STAT bits are write-1-to-clear: ClearLatchedFaults() writes 0xFF to every STAT register. Clear after you have
handled the cause; a bit that comes back at once means the cause is still there. The sticky driver flags are
separate: ClearErrorFlags() clears them.
ReadLatchedFaults() decodes the STAT registers at the data sheet's bit positions (Register
map). PWRUP_I and PWRDN_I (a finished RUN / STANDBY / SLEEP transition) are
events, not faults, and have no FaultFlags bit; read MISC_INT_STAT0 if you need them. kWdiAssert and
kResetBmcu are never set: the PF1550 has no such interrupt bits.
Run the boot self-test¶
pf1550::SelfTestResult result{};
const bool pass = pmic.RunPowerSelfTest(result);
std::printf("self-test %s, worst %s\n", pass ? "PASS" : "FAIL",
pf1550::FaultSeverityName(result.worst_severity));
RunPowerSelfTest() takes a full snapshot and passes only when DEVICE_ID is 0x7C, the PMIC is in RUN, every SW
and LDO rail is enabled in RUN and no latched fault ranks above kInfo. The ranking is
FaultSeverityPortentaH7(): it assumes the Portenta H7's wiring (MCU on SW3, LDO1 and LDO3 critical). On another
board, rank result.faults yourself.
Read OTP and raw registers¶
uint8_t otp[pf1550::OtpRegion::kEndExclusive - pf1550::OtpRegion::kStart] = {};
if (!pmic.ReadOtpRegion(otp, sizeof otp)) { // 0x1C..0x36, about 2 ms per byte
return false;
}
uint8_t flavor = 0;
pmic.ReadRegister(pf1550::Register::OtpFlavor, flavor); // 0x00: OTP not burned (A0EP)
ReadOtpByte() writes the three unlock keys, the OTP address to FMRADDR, waits 2 ms with DelayUs() and reads
FMRDATA. ReadOtpRegion() repeats it and refuses addresses past 0x36. ReadRegister() and WriteRegister() take
any pf1550::Register; the Register map lists them.