ESPHome  2024.5.0
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 
17 namespace esphome {
18 namespace bmp581 {
19 
20 static const char *const TAG = "bmp581";
21 
22 static 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 
45 static 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;
80  case ERROR_SENSOR_RESET:
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 
538 bool 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
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition: i2c.h:235
Oversampling pressure_oversampling_
Definition: bmp581.h:93
bool write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir)
Definition: bmp581.cpp:538
bool read_temperature_(float &temperature)
Definition: bmp581.cpp:439
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
uint8_t pressure
Definition: tt21100.cpp:19
bool write_interrupt_source_settings_(bool data_ready_enable)
Definition: bmp581.cpp:564
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:69
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:212
sensor::Sensor * temperature_sensor_
Definition: bmp581.h:89
enum esphome::bmp581::BMP581Component::ErrorCode NONE
sensor::Sensor * pressure_sensor_
Definition: bmp581.h:90
union esphome::bmp581::BMP581Component::@45 odr_config_
bool read_temperature_and_pressure_(float &temperature, float &pressure)
Definition: bmp581.cpp:467
union esphome::bmp581::BMP581Component::@39 int_source_
bool write_power_mode_(OperationMode mode)
Definition: bmp581.cpp:585
void status_clear_warning()
Definition: component.cpp:166
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:151
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void dump_config() override
Definition: bmp581.cpp:68
Oversampling temperature_oversampling_
Definition: bmp581.h:92
uint16_t temperature
Definition: sun_gtil2.cpp:26
union esphome::bmp581::BMP581Component::@41 status_
union esphome::bmp581::BMP581Component::@40 int_status_
u_int8_t raw_temp
uint8_t status
Definition: bl0942.h:23
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition: i2c.h:262
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
union esphome::bmp581::BMP581Component::@42 dsp_config_
bool write_oversampling_settings_(Oversampling temperature_oversampling, Oversampling pressure_oversampling)
Definition: bmp581.cpp:574
union esphome::bmp581::BMP581Component::@43 iir_config_
union esphome::bmp581::BMP581Component::@44 osr_config_
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop=true)
Definition: i2c.h:248