ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
bmp581_base.cpp
Go to the documentation of this file.
1/*
2 * Adds support for Bosch's BMP581 high accuracy pressure and temperature sensor
3 * - Component structure based on ESPHome's BMP3XX component (as of March, 2023)
4 * - Implementation is easier as the sensor itself automatically compensates pressure for the temperature
5 * - Temperature and pressure data is converted via simple divison operations in this component
6 * - IIR filter level can independently be applied to temperature and pressure measurements
7 * - Bosch's BMP5-Sensor-API was consulted to verify that sensor configuration is done correctly
8 * - Copyright (c) 2022 Bosch Sensortec Gmbh, SPDX-License-Identifier: BSD-3-Clause
9 * - This component uses forced power mode only so measurements are synchronized by the host
10 * - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
11 */
12
13#include "bmp581_base.h"
14#include "esphome/core/hal.h"
15#include "esphome/core/log.h"
17
19
20ESPHOME_LOG_TAG(TAG, "bmp581");
21
22static constexpr uint32_t MEASUREMENT_TIMEOUT_ID = 0;
23
24// Oversampling strings indexed by Oversampling enum (0-7): NONE, X2, X4, X8, X16, X32, X64, X128
25PROGMEM_STRING_TABLE(OversamplingStrings, "None", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "");
26
27static const LogString *oversampling_to_str(Oversampling oversampling) {
28 return OversamplingStrings::get_log_str(static_cast<uint8_t>(oversampling), OversamplingStrings::LAST_INDEX);
29}
30
31// IIR filter strings indexed by IIRFilter enum (0-7): OFF, 2, 4, 8, 16, 32, 64, 128
32PROGMEM_STRING_TABLE(IIRFilterStrings, "OFF", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "");
33
34static const LogString *iir_filter_to_str(IIRFilter filter) {
35 return IIRFilterStrings::get_log_str(static_cast<uint8_t>(filter), IIRFilterStrings::LAST_INDEX);
36}
37
39 ESP_LOGCONFIG(TAG, "BMP581:");
40
41 LOG_UPDATE_INTERVAL(this);
42
43 ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
44
45 if (this->temperature_sensor_) {
46 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
47 ESP_LOGCONFIG(TAG,
48 " IIR Filter: %s\n"
49 " Oversampling: %s",
50 LOG_STR_ARG(iir_filter_to_str(this->iir_temperature_level_)),
51 LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
52 }
53
54 if (this->pressure_sensor_) {
55 LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
56 ESP_LOGCONFIG(TAG,
57 " IIR Filter: %s\n"
58 " Oversampling: %s",
59 LOG_STR_ARG(iir_filter_to_str(this->iir_pressure_level_)),
60 LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
61 }
62}
63
65 /*
66 * Setup goes through several stages, which follows the post-power-up procedure (page 18 of datasheet) and then sets
67 * configured options
68 * 1) Soft reboot
69 * 2) Verify ASIC chip ID matches BMP581
70 * 3) Verify sensor status (check if NVM is okay)
71 * 4) Enable data ready interrupt
72 * 5) Write oversampling settings and set internal configuration values
73 * 6) Configure and prime IIR Filter(s), if enabled
74 */
75
77 // 1) Soft reboot //
79
80 // Power-On-Reboot bit is asserted if sensor successfully reset
81 if (!this->reset_()) {
82 this->mark_failed(LOG_STR("Reset failed"));
83
84 return;
85 }
86
88 // 2) Verify ASIC chip ID matches BMP581 //
90
91 uint8_t chip_id;
92
93 // read chip id from sensor
94 if (!this->bmp_read_byte(BMP581_CHIP_ID, &chip_id)) {
95 this->mark_failed(LOG_STR("Read chip ID failed"));
96
97 return;
98 }
99
100 // verify id
101 if (chip_id != BMP581_ASIC_ID && chip_id != BMP585_ASIC_ID) {
102 this->mark_failed(LOG_STR("Unknown chip ID"));
103
104 return;
105 }
106
108 // 3) Verify sensor status (check if NVM is okay) //
110
111 if (!this->bmp_read_byte(BMP581_STATUS, &this->status_.reg)) {
112 this->mark_failed(LOG_STR("Failed to read status register"));
113
114 return;
115 }
116
117 // verify status_nvm_rdy bit (it is asserted if boot was successful)
118 if (!(this->status_.bit.status_nvm_rdy)) {
119 this->mark_failed(LOG_STR("NVM not ready"));
120
121 return;
122 }
123
124 // verify status_nvm_err bit (it is asserted if an error is detected)
125 if (this->status_.bit.status_nvm_err) {
126 this->mark_failed(LOG_STR("NVM error detected"));
127
128 return;
129 }
130
132 // 4) Enable data ready interrupt //
134
135 // enable the data ready interrupt source
136 if (!this->write_interrupt_source_settings_(true)) {
137 this->mark_failed(LOG_STR("Failed to write interrupt source register"));
138
139 return;
140 }
141
143 // 5) Write oversampling settings and set internal configuration values //
145
146 // configure pressure readings, if sensor is defined
147 // otherwise, disable pressure oversampling
148 if (this->pressure_sensor_) {
149 this->osr_config_.bit.press_en = true;
150 } else {
152 }
153
154 // write oversampling settings
156 this->mark_failed(LOG_STR("Failed to write oversampling register"));
157
158 return;
159 }
160
161 // set output data rate to 4 Hz=0x19 (page 65 of datasheet)
162 // - ?shouldn't? matter as this component only uses FORCED_MODE - datasheet is ambiguous
163 // - If in NORMAL_MODE or NONSTOP_MODE, then this would still allow deep standby to save power
164 // - will be written to BMP581 at next requested measurement
165 this->odr_config_.bit.odr = 0x19;
166
170
173 this->mark_failed(LOG_STR("Failed to write IIR configuration registers"));
174
175 return;
176 }
177
178 if (!this->prime_iir_filter_()) {
179 this->mark_failed(LOG_STR("Failed to prime the IIR filter with an initial measurement"));
180
181 return;
182 }
183 }
184}
185
187 /*
188 * Each update goes through several stages
189 * 0) Verify either a temperature or pressure sensor is defined before proceeding
190 * 1) Request a measurement
191 * 2) Wait for measurement to finish (based on oversampling rates)
192 * 3) Read data registers for temperature and pressure, if applicable
193 * 4) Publish measurements to sensor(s), if applicable
194 */
195
197 // 0) Verify either a temperature or pressure sensor is defined before proceeding //
199
200 if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) {
201 return;
202 }
203
205 // 1) Request a measurement //
207
208 ESP_LOGVV(TAG, "Requesting measurement");
209
210 if (!this->start_measurement_()) {
211 ESP_LOGW(TAG, "Requesting forced measurement failed");
212 this->status_set_warning();
213
214 return;
215 }
216
218 // 2) Wait for measurement to finish (based on oversampling rates) //
220
221 ESP_LOGVV(TAG, "Measurement should take %d ms", this->conversion_time_);
222
223 this->set_timeout(MEASUREMENT_TIMEOUT_ID, this->conversion_time_, [this]() {
224 float temperature = 0.0;
225 float pressure = 0.0;
226
228 // 3) Read data registers for temperature and pressure, if applicable //
230
231 if (this->pressure_sensor_) {
232 if (!this->read_temperature_and_pressure_(temperature, pressure)) {
233 ESP_LOGW(TAG, "Failed to read temperature and pressure; skipping update");
234 this->status_set_warning();
235
236 return;
237 }
238 } else {
239 if (!this->read_temperature_(temperature)) {
240 ESP_LOGW(TAG, "Failed to read temperature; skipping update");
241 this->status_set_warning();
242
243 return;
244 }
245 }
246
248 // 4) Publish measurements to sensor(s), if applicable //
250
251 if (this->temperature_sensor_) {
252 this->temperature_sensor_->publish_state(temperature);
253 }
254
255 if (this->pressure_sensor_) {
256 this->pressure_sensor_->publish_state(pressure);
257 }
258
259 this->status_clear_warning();
260 });
261}
262
264 // - verifies component is not internally in standby mode
265 // - reads interrupt status register
266 // - checks if data ready bit is asserted
267 // - If true, then internally sets component to standby mode if in forced mode
268 // - returns data readiness state
269
270 if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
271 ESP_LOGD(TAG, "Data not ready, sensor is in standby mode");
272 return false;
273 }
274
275 uint8_t status;
276
277 if (!this->bmp_read_byte(BMP581_INT_STATUS, &status)) {
278 ESP_LOGE(TAG, "Failed to read interrupt status register");
279 return false;
280 }
281
282 this->int_status_.reg = status;
283
284 if (this->int_status_.bit.drdy_data_reg) {
285 // If in forced mode, then set internal record of the power mode to STANDBY_MODE
286 // - sensor automatically returns to standby mode after completing a forced measurement
287 if (this->odr_config_.bit.pwr_mode == FORCED_MODE) {
288 this->odr_config_.bit.pwr_mode = STANDBY_MODE;
289 }
290
291 return true;
292 }
293
294 return false;
295}
296
298 // - temporarily disables oversampling for a fast initial measurement; avoids slowing down ESPHome's startup process
299 // - enables IIR filter flushing with forced measurements
300 // - forces a measurement; flushing the IIR filter and priming it with a current value
301 // - disables IIR filter flushing with forced measurements
302 // - reverts to internally configured oversampling rates
303 // - returns success of all register writes/priming
304
305 // store current internal oversampling settings to revert to after priming
306 Oversampling current_temperature_oversampling = (Oversampling) this->osr_config_.bit.osr_t;
307 Oversampling current_pressure_oversampling = (Oversampling) this->osr_config_.bit.osr_p;
308
309 // temporarily disables oversampling for temperature and pressure for a fast priming measurement
311 ESP_LOGE(TAG, "Failed to write oversampling register");
312
313 return false;
314 }
315
316 // flush the IIR filter with forced measurements (we will only flush once)
317 this->dsp_config_.bit.iir_flush_forced_en = true;
318 if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
319 ESP_LOGE(TAG, "Failed to write IIR source register");
320
321 return false;
322 }
323
324 // forces an intial measurement
325 // - this measurements flushes the IIR filter reflecting written DSP settings
326 // - flushing with this initial reading avoids having the internal previous data aquisition being 0, which
327 // (I)nfinitely affects future values
328 if (!this->start_measurement_()) {
329 ESP_LOGE(TAG, "Failed to request a forced measurement");
330
331 return false;
332 }
333
334 // wait for priming measurement to complete
335 // - with oversampling disabled, the conversion time for a single measurement for pressure and temperature is
336 // ceilf(1.05*(1.0+1.0)) = 3ms
337 // - see page 12 of datasheet for details
338 delay(3);
339
340 if (!this->check_data_readiness_()) {
341 ESP_LOGE(TAG, "IIR priming measurement was not ready");
342
343 return false;
344 }
345
346 // disable IIR filter flushings on future forced measurements
347 this->dsp_config_.bit.iir_flush_forced_en = false;
348 if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
349 ESP_LOGE(TAG, "Failed to write IIR source register");
350
351 return false;
352 }
353
354 // revert oversampling rates to original settings
355 return this->write_oversampling_settings_(current_temperature_oversampling, current_pressure_oversampling);
356}
357
359 // - verifies data is ready to be read
360 // - reads in 3 bytes of temperature data
361 // - returns whether successful, where the the variable parameter contains
362 // - the measured temperature (in degrees Celsius)
363
364 if (!this->check_data_readiness_()) {
365 ESP_LOGW(TAG, "Data not ready, skipping this update");
366 this->status_set_warning();
367
368 return false;
369 }
370
371 uint8_t data[3];
372 if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
373 ESP_LOGW(TAG, "Failed to read measurement");
374 this->status_set_warning();
375
376 return false;
377 }
378
379 // temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
380 int32_t raw_temp = static_cast<int32_t>(encode_uint32(data[2], data[1], data[0], 0)) >> 8;
381 temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
382
383 return true;
384}
385
387 // - verifies data is ready to be read
388 // - reads in 6 bytes of temperature data (3 for temeperature, 3 for pressure)
389 // - returns whether successful, where the variable parameters contain
390 // - the measured temperature (in degrees Celsius)
391 // - the measured pressure (in Pa)
392
393 if (!this->check_data_readiness_()) {
394 ESP_LOGW(TAG, "Data not ready, skipping this update");
395 this->status_set_warning();
396
397 return false;
398 }
399
400 uint8_t data[6];
401 if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
402 ESP_LOGW(TAG, "Failed to read measurement");
403 this->status_set_warning();
404
405 return false;
406 }
407
408 // temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
409 int32_t raw_temp = static_cast<int32_t>(encode_uint32(data[2], data[1], data[0], 0)) >> 8;
410 temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
411
412 // pressure MSB is in data[5], LSB is in data[4], XLSB in data[3]
413 int32_t raw_press = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
414 pressure = (float) (raw_press / 64.0); // Divide by 2^6=64 for Pa (page 22 of datasheet)
415
416 return true;
417}
418
420 // - activates interface (only relevant for SPI mode)
421 // - writes reset command to the command register
422 // - waits for sensor to complete reset
423 // - activates interface (only relevant for SPI mode)
424 // - returns the Power-On-Reboot interrupt status, which is asserted if successful
425
426 // activates communication interface (SPI only)
427 this->activate_interface();
428
429 // writes reset command to BMP's command register
430 if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) {
431 ESP_LOGE(TAG, "Failed to write reset command");
432 return false;
433 }
434
435 // t_{soft_res} = 2ms (page 11 of datasheet); time it takes to enter standby mode
436 // - round up to 3 ms
437 delay(3);
438
439 // reactivates communication interface after reset (SPI only)
440 this->activate_interface();
441
442 // read interrupt status register
443 if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
444 ESP_LOGE(TAG, "Failed to read interrupt status register");
445
446 return false;
447 }
448
449 // power-On-Reboot bit is asserted if sensor successfully reset
450 return this->int_status_.bit.por;
451}
452
454 // - only pushes the sensor into FORCED_MODE for a reading if already in STANDBY_MODE
455 // - returns whether a measurement is in progress or has been initiated
456
457 if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
458 return this->write_power_mode_(FORCED_MODE);
459 } else {
460 return true;
461 }
462}
463
464bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir) {
465 // - ensures data registers store filtered values
466 // - sets IIR filter levels on sensor
467 // - matches other default settings on sensor
468 // - writes configuration to the two relevant registers
469 // - returns success or failure of write to the registers
470
471 // If the temperature/pressure IIR filter is configured, then ensure data registers store the filtered measurement
472 this->dsp_config_.bit.shdw_sel_iir_t = (temperature_iir != IIR_FILTER_OFF);
473 this->dsp_config_.bit.shdw_sel_iir_p = (pressure_iir != IIR_FILTER_OFF);
474
475 // set temperature and pressure IIR filter level to configured values
476 this->iir_config_.bit.set_iir_t = temperature_iir;
477 this->iir_config_.bit.set_iir_p = pressure_iir;
478
479 // enable pressure and temperature compensation (page 61 of datasheet)
480 // - ?only relevant if IIR filter is applied?; the datasheet is ambiguous
481 // - matches BMP's default setting
482 this->dsp_config_.bit.comp_pt_en = 0x3;
483
484 // BMP581_DSP register and BMP581_DSP_IIR registers are successive
485 // - allows us to write the IIR configuration with one command to both registers
486 uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
487 return this->bmp_write_bytes(BMP581_DSP, register_data, sizeof(register_data));
488}
489
491 // - updates component's internal setting
492 // - returns success or failure of write to interrupt source register
493
494 this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
495
496 // write interrupt source register
497 return this->bmp_write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
498}
499
501 Oversampling pressure_oversampling) {
502 // - updates component's internal setting
503 // - returns success or failure of write to Over-Sampling Rate register
504
505 this->osr_config_.bit.osr_t = temperature_oversampling;
506 this->osr_config_.bit.osr_p = pressure_oversampling;
507
508 return this->bmp_write_byte(BMP581_OSR, this->osr_config_.reg);
509}
510
512 // - updates the component's internal power mode
513 // - returns success or failure of write to Output Data Rate register
514
515 this->odr_config_.bit.pwr_mode = mode;
516
517 // write odr register
518 return this->bmp_write_byte(BMP581_ODR, this->odr_config_.reg);
519}
520
521} // namespace esphome::bmp581_base
BedjetMode mode
BedJet operating mode.
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void status_clear_warning()
Definition component.h:291
bool read_temperature_(float &temperature)
union esphome::bmp581_base::BMP581Component::@42 dsp_config_
bool write_interrupt_source_settings_(bool data_ready_enable)
union esphome::bmp581_base::BMP581Component::@43 iir_config_
bool write_oversampling_settings_(Oversampling temperature_oversampling, Oversampling pressure_oversampling)
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data)=0
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len)=0
union esphome::bmp581_base::BMP581Component::@41 status_
union esphome::bmp581_base::BMP581Component::@44 osr_config_
bool write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir)
union esphome::bmp581_base::BMP581Component::@39 int_source_
union esphome::bmp581_base::BMP581Component::@40 int_status_
virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len)=0
union esphome::bmp581_base::BMP581Component::@45 odr_config_
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data)=0
bool read_temperature_and_pressure_(float &temperature, float &pressure)
bool write_power_mode_(OperationMode mode)
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
u_int8_t raw_temp
ESPHOME_LOG_TAG(TAG, "bmp581")
PROGMEM_STRING_TABLE(OversamplingStrings, "None", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "")
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:988
void HOT delay(uint32_t ms)
Definition hal.cpp:85
static void uint32_t
uint16_t temperature
Definition sun_gtil2.cpp:12
uint8_t pressure
Definition tt21100.cpp:7