ESPHome 2026.3.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
20static const char *const TAG = "bmp581";
21
22// Oversampling strings indexed by Oversampling enum (0-7): NONE, X2, X4, X8, X16, X32, X64, X128
23PROGMEM_STRING_TABLE(OversamplingStrings, "None", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "");
24
25static const LogString *oversampling_to_str(Oversampling oversampling) {
26 return OversamplingStrings::get_log_str(static_cast<uint8_t>(oversampling), OversamplingStrings::LAST_INDEX);
27}
28
29// IIR filter strings indexed by IIRFilter enum (0-7): OFF, 2, 4, 8, 16, 32, 64, 128
30PROGMEM_STRING_TABLE(IIRFilterStrings, "OFF", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "");
31
32static const LogString *iir_filter_to_str(IIRFilter filter) {
33 return IIRFilterStrings::get_log_str(static_cast<uint8_t>(filter), IIRFilterStrings::LAST_INDEX);
34}
35
37 ESP_LOGCONFIG(TAG, "BMP581:");
38
39 switch (this->error_code_) {
40 case NONE:
41 break;
43 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
44 break;
46 ESP_LOGE(TAG, "Unknown chip ID");
47 break;
49 ESP_LOGE(TAG, "Reset failed");
50 break;
52 ESP_LOGE(TAG, "Get status failed");
53 break;
55 ESP_LOGE(TAG, "IIR Filter failed to prime with initial measurement");
56 break;
57 default:
58 ESP_LOGE(TAG, "Error %d", (int) this->error_code_);
59 break;
60 }
61
62 LOG_UPDATE_INTERVAL(this);
63
64 ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
65
66 if (this->temperature_sensor_) {
67 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
68 ESP_LOGCONFIG(TAG,
69 " IIR Filter: %s\n"
70 " Oversampling: %s",
71 LOG_STR_ARG(iir_filter_to_str(this->iir_temperature_level_)),
72 LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
73 }
74
75 if (this->pressure_sensor_) {
76 LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
77 ESP_LOGCONFIG(TAG,
78 " IIR Filter: %s\n"
79 " Oversampling: %s",
80 LOG_STR_ARG(iir_filter_to_str(this->iir_pressure_level_)),
81 LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
82 }
83}
84
86 /*
87 * Setup goes through several stages, which follows the post-power-up procedure (page 18 of datasheet) and then sets
88 * configured options
89 * 1) Soft reboot
90 * 2) Verify ASIC chip ID matches BMP581
91 * 3) Verify sensor status (check if NVM is okay)
92 * 4) Enable data ready interrupt
93 * 5) Write oversampling settings and set internal configuration values
94 * 6) Configure and prime IIR Filter(s), if enabled
95 */
96
97 this->error_code_ = NONE;
99 // 1) Soft reboot //
101
102 // Power-On-Reboot bit is asserted if sensor successfully reset
103 if (!this->reset_()) {
104 ESP_LOGE(TAG, "Reset failed");
105
106 this->error_code_ = ERROR_SENSOR_RESET;
107 this->mark_failed();
108
109 return;
110 }
111
113 // 2) Verify ASIC chip ID matches BMP581 //
115
116 uint8_t chip_id;
117
118 // read chip id from sensor
119 if (!this->bmp_read_byte(BMP581_CHIP_ID, &chip_id)) {
120 ESP_LOGE(TAG, "Read chip ID failed");
121
122 this->error_code_ = ERROR_COMMUNICATION_FAILED;
123 this->mark_failed();
124
125 return;
126 }
127
128 // verify id
129 if (chip_id != BMP581_ASIC_ID) {
130 ESP_LOGE(TAG, "Unknown chip ID");
131
132 this->error_code_ = ERROR_WRONG_CHIP_ID;
133 this->mark_failed();
134
135 return;
136 }
137
139 // 3) Verify sensor status (check if NVM is okay) //
141
142 if (!this->bmp_read_byte(BMP581_STATUS, &this->status_.reg)) {
143 ESP_LOGE(TAG, "Failed to read status register");
144
145 this->error_code_ = ERROR_COMMUNICATION_FAILED;
146 this->mark_failed();
147
148 return;
149 }
150
151 // verify status_nvm_rdy bit (it is asserted if boot was successful)
152 if (!(this->status_.bit.status_nvm_rdy)) {
153 ESP_LOGE(TAG, "NVM not ready");
154
155 this->error_code_ = ERROR_SENSOR_STATUS;
156 this->mark_failed();
157
158 return;
159 }
160
161 // verify status_nvm_err bit (it is asserted if an error is detected)
162 if (this->status_.bit.status_nvm_err) {
163 ESP_LOGE(TAG, "NVM error detected");
164
165 this->error_code_ = ERROR_SENSOR_STATUS;
166 this->mark_failed();
167
168 return;
169 }
170
172 // 4) Enable data ready interrupt //
174
175 // enable the data ready interrupt source
176 if (!this->write_interrupt_source_settings_(true)) {
177 ESP_LOGE(TAG, "Failed to write interrupt source register");
178
179 this->error_code_ = ERROR_COMMUNICATION_FAILED;
180 this->mark_failed();
181
182 return;
183 }
184
186 // 5) Write oversampling settings and set internal configuration values //
188
189 // configure pressure readings, if sensor is defined
190 // otherwise, disable pressure oversampling
191 if (this->pressure_sensor_) {
192 this->osr_config_.bit.press_en = true;
193 } else {
195 }
196
197 // write oversampling settings
199 ESP_LOGE(TAG, "Failed to write oversampling register");
200
201 this->error_code_ = ERROR_COMMUNICATION_FAILED;
202 this->mark_failed();
203
204 return;
205 }
206
207 // set output data rate to 4 Hz=0x19 (page 65 of datasheet)
208 // - ?shouldn't? matter as this component only uses FORCED_MODE - datasheet is ambiguous
209 // - If in NORMAL_MODE or NONSTOP_MODE, then this would still allow deep standby to save power
210 // - will be written to BMP581 at next requested measurement
211 this->odr_config_.bit.odr = 0x19;
212
216
219 ESP_LOGE(TAG, "Failed to write IIR configuration registers");
220
221 this->error_code_ = ERROR_COMMUNICATION_FAILED;
222 this->mark_failed();
223
224 return;
225 }
226
227 if (!this->prime_iir_filter_()) {
228 ESP_LOGE(TAG, "Failed to prime the IIR filter with an initial measurement");
229
230 this->error_code_ = ERROR_PRIME_IIR_FAILED;
231 this->mark_failed();
232
233 return;
234 }
235 }
236}
237
239 /*
240 * Each update goes through several stages
241 * 0) Verify either a temperature or pressure sensor is defined before proceeding
242 * 1) Request a measurement
243 * 2) Wait for measurement to finish (based on oversampling rates)
244 * 3) Read data registers for temperature and pressure, if applicable
245 * 4) Publish measurements to sensor(s), if applicable
246 */
247
249 // 0) Verify either a temperature or pressure sensor is defined before proceeding //
251
252 if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) {
253 return;
254 }
255
257 // 1) Request a measurement //
259
260 ESP_LOGVV(TAG, "Requesting measurement");
261
262 if (!this->start_measurement_()) {
263 ESP_LOGW(TAG, "Requesting forced measurement failed");
264 this->status_set_warning();
265
266 return;
267 }
268
270 // 2) Wait for measurement to finish (based on oversampling rates) //
272
273 ESP_LOGVV(TAG, "Measurement should take %d ms", this->conversion_time_);
274
275 this->set_timeout("measurement", this->conversion_time_, [this]() {
276 float temperature = 0.0;
277 float pressure = 0.0;
278
280 // 3) Read data registers for temperature and pressure, if applicable //
282
283 if (this->pressure_sensor_) {
284 if (!this->read_temperature_and_pressure_(temperature, pressure)) {
285 ESP_LOGW(TAG, "Failed to read temperature and pressure; skipping update");
286 this->status_set_warning();
287
288 return;
289 }
290 } else {
291 if (!this->read_temperature_(temperature)) {
292 ESP_LOGW(TAG, "Failed to read temperature; skipping update");
293 this->status_set_warning();
294
295 return;
296 }
297 }
298
300 // 4) Publish measurements to sensor(s), if applicable //
302
303 if (this->temperature_sensor_) {
304 this->temperature_sensor_->publish_state(temperature);
305 }
306
307 if (this->pressure_sensor_) {
308 this->pressure_sensor_->publish_state(pressure);
309 }
310
311 this->status_clear_warning();
312 });
313}
314
316 // - verifies component is not internally in standby mode
317 // - reads interrupt status register
318 // - checks if data ready bit is asserted
319 // - If true, then internally sets component to standby mode if in forced mode
320 // - returns data readiness state
321
322 if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
323 ESP_LOGD(TAG, "Data not ready, sensor is in standby mode");
324 return false;
325 }
326
327 uint8_t status;
328
329 if (!this->bmp_read_byte(BMP581_INT_STATUS, &status)) {
330 ESP_LOGE(TAG, "Failed to read interrupt status register");
331 return false;
332 }
333
334 this->int_status_.reg = status;
335
336 if (this->int_status_.bit.drdy_data_reg) {
337 // If in forced mode, then set internal record of the power mode to STANDBY_MODE
338 // - sensor automatically returns to standby mode after completing a forced measurement
339 if (this->odr_config_.bit.pwr_mode == FORCED_MODE) {
340 this->odr_config_.bit.pwr_mode = STANDBY_MODE;
341 }
342
343 return true;
344 }
345
346 return false;
347}
348
350 // - temporarily disables oversampling for a fast initial measurement; avoids slowing down ESPHome's startup process
351 // - enables IIR filter flushing with forced measurements
352 // - forces a measurement; flushing the IIR filter and priming it with a current value
353 // - disables IIR filter flushing with forced measurements
354 // - reverts to internally configured oversampling rates
355 // - returns success of all register writes/priming
356
357 // store current internal oversampling settings to revert to after priming
358 Oversampling current_temperature_oversampling = (Oversampling) this->osr_config_.bit.osr_t;
359 Oversampling current_pressure_oversampling = (Oversampling) this->osr_config_.bit.osr_p;
360
361 // temporarily disables oversampling for temperature and pressure for a fast priming measurement
363 ESP_LOGE(TAG, "Failed to write oversampling register");
364
365 return false;
366 }
367
368 // flush the IIR filter with forced measurements (we will only flush once)
369 this->dsp_config_.bit.iir_flush_forced_en = true;
370 if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
371 ESP_LOGE(TAG, "Failed to write IIR source register");
372
373 return false;
374 }
375
376 // forces an intial measurement
377 // - this measurements flushes the IIR filter reflecting written DSP settings
378 // - flushing with this initial reading avoids having the internal previous data aquisition being 0, which
379 // (I)nfinitely affects future values
380 if (!this->start_measurement_()) {
381 ESP_LOGE(TAG, "Failed to request a forced measurement");
382
383 return false;
384 }
385
386 // wait for priming measurement to complete
387 // - with oversampling disabled, the conversion time for a single measurement for pressure and temperature is
388 // ceilf(1.05*(1.0+1.0)) = 3ms
389 // - see page 12 of datasheet for details
390 delay(3);
391
392 if (!this->check_data_readiness_()) {
393 ESP_LOGE(TAG, "IIR priming measurement was not ready");
394
395 return false;
396 }
397
398 // disable IIR filter flushings on future forced measurements
399 this->dsp_config_.bit.iir_flush_forced_en = false;
400 if (!this->bmp_write_byte(BMP581_DSP, this->dsp_config_.reg)) {
401 ESP_LOGE(TAG, "Failed to write IIR source register");
402
403 return false;
404 }
405
406 // revert oversampling rates to original settings
407 return this->write_oversampling_settings_(current_temperature_oversampling, current_pressure_oversampling);
408}
409
411 // - verifies data is ready to be read
412 // - reads in 3 bytes of temperature data
413 // - returns whether successful, where the the variable parameter contains
414 // - the measured temperature (in degrees Celsius)
415
416 if (!this->check_data_readiness_()) {
417 ESP_LOGW(TAG, "Data not ready, skipping this update");
418 this->status_set_warning();
419
420 return false;
421 }
422
423 uint8_t data[3];
424 if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
425 ESP_LOGW(TAG, "Failed to read measurement");
426 this->status_set_warning();
427
428 return false;
429 }
430
431 // temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
432 int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
433 temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
434
435 return true;
436}
437
439 // - verifies data is ready to be read
440 // - reads in 6 bytes of temperature data (3 for temeperature, 3 for pressure)
441 // - returns whether successful, where the variable parameters contain
442 // - the measured temperature (in degrees Celsius)
443 // - the measured pressure (in Pa)
444
445 if (!this->check_data_readiness_()) {
446 ESP_LOGW(TAG, "Data not ready, skipping this update");
447 this->status_set_warning();
448
449 return false;
450 }
451
452 uint8_t data[6];
453 if (!this->bmp_read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
454 ESP_LOGW(TAG, "Failed to read measurement");
455 this->status_set_warning();
456
457 return false;
458 }
459
460 // temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
461 int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
462 temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
463
464 // pressure MSB is in data[5], LSB is in data[4], XLSB in data[3]
465 int32_t raw_press = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
466 pressure = (float) (raw_press / 64.0); // Divide by 2^6=64 for Pa (page 22 of datasheet)
467
468 return true;
469}
470
472 // - writes reset command to the command register
473 // - waits for sensor to complete reset
474 // - returns the Power-On-Reboot interrupt status, which is asserted if successful
475
476 // writes reset command to BMP's command register
477 if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) {
478 ESP_LOGE(TAG, "Failed to write reset command");
479
480 return false;
481 }
482
483 // t_{soft_res} = 2ms (page 11 of datasheet); time it takes to enter standby mode
484 // - round up to 3 ms
485 delay(3);
486
487 // read interrupt status register
488 if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
489 ESP_LOGE(TAG, "Failed to read interrupt status register");
490
491 return false;
492 }
493
494 // Power-On-Reboot bit is asserted if sensor successfully reset
495 return this->int_status_.bit.por;
496}
497
499 // - only pushes the sensor into FORCED_MODE for a reading if already in STANDBY_MODE
500 // - returns whether a measurement is in progress or has been initiated
501
502 if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
503 return this->write_power_mode_(FORCED_MODE);
504 } else {
505 return true;
506 }
507}
508
509bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir) {
510 // - ensures data registers store filtered values
511 // - sets IIR filter levels on sensor
512 // - matches other default settings on sensor
513 // - writes configuration to the two relevant registers
514 // - returns success or failure of write to the registers
515
516 // If the temperature/pressure IIR filter is configured, then ensure data registers store the filtered measurement
517 this->dsp_config_.bit.shdw_sel_iir_t = (temperature_iir != IIR_FILTER_OFF);
518 this->dsp_config_.bit.shdw_sel_iir_p = (pressure_iir != IIR_FILTER_OFF);
519
520 // set temperature and pressure IIR filter level to configured values
521 this->iir_config_.bit.set_iir_t = temperature_iir;
522 this->iir_config_.bit.set_iir_p = pressure_iir;
523
524 // enable pressure and temperature compensation (page 61 of datasheet)
525 // - ?only relevant if IIR filter is applied?; the datasheet is ambiguous
526 // - matches BMP's default setting
527 this->dsp_config_.bit.comp_pt_en = 0x3;
528
529 // BMP581_DSP register and BMP581_DSP_IIR registers are successive
530 // - allows us to write the IIR configuration with one command to both registers
531 uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
532 return this->bmp_write_bytes(BMP581_DSP, register_data, sizeof(register_data));
533}
534
536 // - updates component's internal setting
537 // - returns success or failure of write to interrupt source register
538
539 this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
540
541 // write interrupt source register
542 return this->bmp_write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
543}
544
546 Oversampling pressure_oversampling) {
547 // - updates component's internal setting
548 // - returns success or failure of write to Over-Sampling Rate register
549
550 this->osr_config_.bit.osr_t = temperature_oversampling;
551 this->osr_config_.bit.osr_p = pressure_oversampling;
552
553 return this->bmp_write_byte(BMP581_OSR, this->osr_config_.reg);
554}
555
557 // - updates the component's internal power mode
558 // - returns success or failure of write to Output Data Rate register
559
560 this->odr_config_.bit.pwr_mode = mode;
561
562 // write odr register
563 return this->bmp_write_byte(BMP581_ODR, this->odr_config_.reg);
564}
565
566} // namespace esphome::bmp581_base
BedjetMode mode
BedJet operating mode.
uint8_t status
Definition bl0942.h:8
virtual void mark_failed()
Mark this component as failed.
void status_set_warning(const char *message=nullptr)
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:429
void status_clear_warning()
bool read_temperature_(float &temperature)
union esphome::bmp581_base::BMP581Component::@42 iir_config_
bool write_interrupt_source_settings_(bool data_ready_enable)
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 dsp_config_
bool write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir)
union esphome::bmp581_base::BMP581Component::@39 int_status_
union esphome::bmp581_base::BMP581Component::@38 int_source_
virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len)=0
union esphome::bmp581_base::BMP581Component::@44 odr_config_
union esphome::bmp581_base::BMP581Component::@40 status_
enum esphome::bmp581_base::BMP581Component::ErrorCode NONE
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)
union esphome::bmp581_base::BMP581Component::@43 osr_config_
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
u_int8_t raw_temp
PROGMEM_STRING_TABLE(OversamplingStrings, "None", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "")
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint16_t temperature
Definition sun_gtil2.cpp:12
uint8_t pressure
Definition tt21100.cpp:7