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