Quickstart¶
Talk to a PF1550 from your MCU: wire the I2C bus, write a small port class, identify the chip and read its rails.
Reading is safe; writing changes the board's supplies
This example only reads. The PF1550 powers the board it sits on, often including the MCU that talks to it: a wrong rail write can brown out the processor or over-volt a peripheral. Read the Portenta H7 profiles page before you apply a profile.
1. Wire it¶
| MCU | PF1550 | Notes |
|---|---|---|
| I2C SDA | SDA (pin 2) | Pull up to VDDIO; up to 400 kHz |
| I2C SCL | SCL (pin 3) | Pull up to VDDIO |
| GPIO out (optional) | STANDBY (pin 7) | High moves RUN to STANDBY (default polarity) |
| GPIO in (optional) | INTB (pin 9) | Open drain, active low: pull it up |
| GND | GND | Common ground |
The PF1550 answers on I2C once it has VSYS and its I/O supply VDDIO, so the host does not have to be the processor the PMIC supplies: an external jig can configure it first. Hardware has the full pin list and the electrical limits.
2. Add the driver¶
Add the repository as a submodule and link the hf::pf1550 CMake target (or add inc/ and the generated
pf1550_version.h to your include path and build with C++20). Installation covers CMake and
ESP-IDF.
3. Write the port¶
The port is the only platform code. It reads and writes one register, brings the bus up and sets the strap pins.
Replace the platform_* calls with your SDK's.
class MyI2c : public pf1550::BusInterface<MyI2c> {
public:
// Bring the bus up once; the driver calls this before it first talks to the chip.
bool EnsureInitialized() noexcept {
if (!ready_) {
ready_ = platform_i2c_init();
}
return ready_;
}
// One register write: [addr+W] [reg] [data] STOP. Return false on a NACK.
bool Write(uint8_t addr, uint8_t reg, const uint8_t* data, size_t len) noexcept {
if (len != 1) {
return false; // the driver writes one register at a time
}
const uint8_t frame[2] = {reg, data[0]};
return platform_i2c_write(addr, frame, sizeof frame);
}
// One register read: [addr+W] [reg] repeated START [addr+R] [data] STOP.
bool Read(uint8_t addr, uint8_t reg, uint8_t* data, size_t len) noexcept {
return platform_i2c_write_read(addr, ®, 1, data, len);
}
// Strap pins wired to the MCU. Active means: STANDBY high (standby), USB enables high.
void GpioSet(pf1550::CtrlPin pin, pf1550::GpioSignal signal) noexcept {
const int gpio = gpioFor(pin);
if (gpio >= 0) {
platform_gpio_write(gpio, signal == pf1550::GpioSignal::Active);
}
}
// Optional: profiles wait between some writes. Without it, the waits are skipped.
void DelayUs(uint32_t us) noexcept {
platform_delay_us(us);
}
private:
static int gpioFor(pf1550::CtrlPin pin) noexcept {
switch (pin) {
case pf1550::CtrlPin::Standby: return 10; // your board's GPIO numbers; -1 = not wired
case pf1550::CtrlPin::UsbVbusEn: return 11;
case pf1550::CtrlPin::UsbOtgEn: return 12;
default: return -1;
}
}
bool ready_ = false;
};
Porting explains each method and the strap-pin levels.
4. Read the rails¶
#include <cstdio>
#include "pf1550.hpp"
int bring_up_pmic(MyI2c& bus) {
pf1550::PF1550<MyI2c> pmic(&bus); // 7-bit address 0x08
if (!pmic.EnsureInitialized()) { // 1. bus up, DEVICE_ID == 0x7C
return -1; // wiring, pull-ups or address
}
pf1550::PmicState state{}; // 2. where is the state machine?
if (!pmic.ReadPmicState(state) || state != pf1550::PmicState::Run) {
return -2;
}
pf1550::DiagnosticSnapshot snap{}; // 3. SW1..LDO3: set point and enable
if (!pmic.ReadDiagnosticSnapshot(snap)) {
return -3;
}
for (size_t i = 0; i <= static_cast<size_t>(pf1550::RailId::Ldo3); ++i) {
const auto id = static_cast<pf1550::RailId>(i);
std::printf("%s %u mV %s\n", pf1550::RailName(id), snap.rails[i].set_voltage_mv,
snap.rails[i].enabled_run ? "on" : "off");
}
return 0;
}
What each step does:
EnsureInitialized()calls your port'sEnsureInitialized(), reads DEVICE_ID (register 0x00) and checks it is 0x7C. Later calls return at once.ReadPmicState()decodes STATE_INFO (0x67).Runmeans the start-up sequence has finished and the OTP-enabled regulators are on.ReadDiagnosticSnapshot()reads about 30 registers: identity, state, charger and VBUS sense, every rail's set point and enables, and the latched faults.
This example is built by the host CI, so it compiles against the current headers.
If it does not work¶
| Symptom | First thing to check |
|---|---|
EnsureInitialized() returns false, DeviceNotFound set |
Pull-ups, SDA / SCL swapped, the 7-bit address (0x08, not 0x10) |
WrongDeviceId set |
Another device answers at 0x08, or the part's OTP moved its address |
| Rails read 0 mV | The snapshot read failed part-way: check read_ok and GetErrorFlags() |
| A rail's set point does not match its measured voltage | SW1 / SW2 DVS mode (Rails and OTP) |
Next¶
- API guide: profiles, rail settings, status, faults and OTP.
- Portenta H7 profiles: what the two built-in profiles write and in which order.
- Examples: ESP32-C6 applications for probing, register dumps, diagnostics and provisioning.