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