Skip to content

API guide

How to do the common jobs with the driver: initialise, configure, read, use the interrupt pin, read NVM and handle errors.

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

The shape of the API

One bmp581::Driver<Bus> object per sensor. It holds a reference to your port and the chip id; everything else lives in the chip's registers.

Group Calls Registers
Lifecycle Init(), SoftReset(), Configure(Config), ChipId(), Initialized() CHIP_ID, STATUS, CMD, INT_STATUS
Power mode GetPowerMode(), SetPowerMode(PowerMode) ODR_CONFIG
Oversampling and data rate GetOsrOdr(), SetOsrOdr(OsrOdrConfig), GetEffectiveOsr() OSR_CONFIG, ODR_CONFIG, OSR_EFF
IIR filter GetIir(), SetIir(IirConfig) DSP_CONFIG, DSP_IIR
Interrupts ConfigureInterrupt(InterruptConfig), SelectInterruptSources(InterruptSources), ReadInterruptStatus(), DataReady() INT_CONFIG, INT_SOURCE, INT_STATUS
Data ReadSample(pressure_enabled), DecodeSample(bytes, pressure_enabled) TEMP_DATA, PRESS_DATA
NVM ReadNvm(row) NVM_ADDR, NVM_DATA, CMD
Raw access ReadRegister(reg), WriteRegister(reg, value) any

Every call returns bmp581::DriverResult<T>: .value and .error, and it converts to bool. Check it: false means a transfer failed or the chip did not confirm the step.

Initialise

bool bring_up(Driver& dev) {
  const auto init = dev.Init();             // chip id 0x50 / 0x51, STATUS.nvm_rdy, no NVM error
  if (!init) {
    std::printf("init: %s\n", bmp581::ToString(init.error).data());
    return false;
  }
  const auto reset = dev.SoftReset();       // CMD 0xB6, 2 ms, then CHIP_ID, STATUS, INT_STATUS.por
  if (!reset) {
    std::printf("soft reset: %s\n", bmp581::ToString(reset.error).data());
    return false;
  }
  std::printf("BMP581 chip id 0x%02X, driver %s\n", dev.ChipId(), bmp581::GetDriverVersion());
  return true;
}

Init() is safe to call again: it clears ChipId() first and fills it only on success. SoftReset() returns every register to its reset value, so call it before Configure(), never after. It writes CMD = 0xB6, waits 2 ms (t_soft_res), does the dummy read that selects SPI again on an SPI port, and then checks the reset as the datasheet recommends (section 4.3.9): CHIP_ID not 0, STATUS nvm_rdy = 1 and nvm_err = 0, INT_STATUS por = 1. Over I2C the chip does not acknowledge the reset command; the driver accepts that one missing ACK and reports every other failed transfer (Porting).

Configure

bool configure_low_noise(Driver& dev) {
  bmp581::Config cfg{};
  cfg.osr_odr.osr_pressure = bmp581::Oversampling::X64;
  cfg.osr_odr.osr_temperature = bmp581::Oversampling::X4;
  cfg.osr_odr.odr = bmp581::Odr::Hz10;      // datasheet table 7: at most 20 Hz with these OSRs
  cfg.iir.pressure = bmp581::IirFilter::Coeff7;
  cfg.power_mode = bmp581::PowerMode::Normal;
  if (!dev.Configure(cfg)) {                // standby, OSR / ODR, IIR, then the power mode
    return false;
  }
  const auto eff = dev.GetEffectiveOsr();   // the chip falls back to default OSRs if the ODR is too fast
  return eff && eff.value.odr_valid;
}

Configure() writes standby, then oversampling and data rate, then the IIR filter, then the power mode, and stops at the first failure. Pick a pair of oversampling ratios the data rate allows (Register interface), then check odr_valid: when it is false the chip runs with default ratios, not yours. Configuration has presets for common uses.

Read

bool read_when_ready(Driver& dev, float& pressure_pa, float& temperature_c) {
  const auto ready = dev.DataReady();       // reads INT_STATUS, which clears it
  if (!ready || !ready.value) {
    return false;                           // bus error, or no new sample yet
  }
  const auto s = dev.ReadSample();          // one 6-byte burst from 0x1D
  if (!s) {
    return false;
  }
  pressure_pa = s.value.pressure_pa;        // raw / 64
  temperature_c = s.value.temperature_c;    // raw / 65536
  return true;
}

DataReady() reads INT_STATUS, and reading INT_STATUS clears it. Do not read INT_STATUS anywhere else between two samples, or you lose the flag. ReadSample(false) skips the pressure conversion when you disabled pressure with OsrOdrConfig::pressure_enable = false.

Take one sample on demand

bool one_shot(Driver& dev, MyI2c& bus, bmp581::Sample& out) {
  if (!dev.SetPowerMode(bmp581::PowerMode::Forced)) {   // one conversion, then back to standby
    return false;
  }
  bus.DelayUs(bmp581::timing::kStartupUs + 7000U);      // t_startup + conversion at OSR x8 / x1 (6.4 ms)
  const auto s = dev.ReadSample();
  if (!s) {
    return false;
  }
  out = s.value;
  return true;
}

Forced mode does one conversion and returns to standby: the lowest average current for slow, irregular sampling. The wait is t_startup (3 ms) plus the conversion time for the oversampling you set (datasheet Table 4).

Use the interrupt pin

bool enable_data_ready_pin(Driver& dev) {
  bmp581::InterruptConfig pin{};
  pin.mode = bmp581::IntMode::Pulsed;          // about 105 us pulse per sample
  pin.polarity = bmp581::IntPolarity::ActiveHigh;
  pin.drive = bmp581::IntDrive::PushPull;
  pin.enable = true;
  if (!dev.ConfigureInterrupt(pin)) {          // disables sources and clears INT_STATUS first
    return false;
  }
  bmp581::InterruptSources src{};
  src.data_ready = true;
  return dev.SelectInterruptSources(src).ok();
}

In pulsed mode INT pulses for 90 µs to 121 µs per event; in latched mode it stays active until you read ReadInterruptStatus(). Use open drain when several devices share the line, and pull it up to VDDIO.

Read the NVM user words

bool read_user_words(Driver& dev, uint16_t (&words)[3]) {
  for (uint8_t i = 0; i < 3; ++i) {
    const auto w = dev.ReadNvm(static_cast<uint8_t>(bmp581::nvm::kStartAddr + i));  // rows 0x20..0x22
    if (!w) {
      return false;
    }
    words[i] = w.value;
  }
  return true;
}

The NVM holds three 16-bit user words at rows 0x20 to 0x22. ReadNvm() switches to standby for the read and restores the previous mode. Writing the NVM is not wrapped.

Use any register directly

bool read_revision(Driver& dev, uint8_t& rev) {
  const auto r = dev.ReadRegister(bmp581::reg::kRevId);   // 0x32 on current silicon
  if (!r) {
    return false;
  }
  rev = r.value;
  return true;
}

ReadRegister() and WriteRegister() reach anything the driver does not wrap: FIFO, out-of-range detection, pad drive strength. The addresses and masks are in bmp581_registers.hpp. Write standby-only registers in standby.

Handle errors

const char* advice(bmp581::DriverError e) {
  switch (e) {
    case bmp581::DriverError::None:            return "ok";
    case bmp581::DriverError::NotInitialized:  return "EnsureInitialized() failed: bring the bus up";
    case bmp581::DriverError::Bus:             return "a transfer failed: wiring, address, pull-ups";
    case bmp581::DriverError::InvalidChipId:   return "not a BMP581, or SPI read in I2C mode";
    case bmp581::DriverError::NvmNotReady:     return "NVM boot not finished or NVM error";
    case bmp581::DriverError::PorSoftReset:    return "reset not confirmed by INT_STATUS.por";
    case bmp581::DriverError::NvmInvalidAddr:  return "NVM user rows are 0x20..0x22";
    case bmp581::DriverError::NvmCommandError: return "NVM command rejected (chip not in standby)";
    case bmp581::DriverError::InvalidParameter:
    case bmp581::DriverError::InvalidPowerMode: return "invalid argument";
  }
  return "unknown";
}
DriverError Returned by Meaning
NotInitialized Init() Your port's EnsureInitialized() returned false
Bus any call ReadRegs() or WriteRegs() returned false
InvalidChipId Init(), SoftReset() CHIP_ID read 0, or (Init()) not 0x50 / 0x51
NvmNotReady Init(), SoftReset(), ReadNvm() nvm_rdy clear or nvm_err set, after one soft-reset retry in Init()
PorSoftReset SoftReset() INT_STATUS por not set 2 ms after the reset command
NvmInvalidAddr ReadNvm() Row outside 0x20..0x22
NvmCommandError Init(), SoftReset(), ReadNvm() nvm_cmd_err: an NVM command outside standby
InvalidPowerMode SetPowerMode() A value outside the PowerMode enum

ToString(DriverError) returns the enumerator's name for logs. Troubleshooting maps symptoms to causes.