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