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.