mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
New component: Add support for bmp581 pressure and temperature sensors (#4657)
This commit is contained in:
parent
cd514b140e
commit
a8fa4b56f9
6 changed files with 990 additions and 0 deletions
|
@ -49,6 +49,7 @@ esphome/components/ble_client/* @buxtronix
|
|||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bmp3xx/* @martgras
|
||||
esphome/components/bmp581/* @kahrendt
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
|
|
0
esphome/components/bmp581/__init__.py
Normal file
0
esphome/components/bmp581/__init__.py
Normal file
596
esphome/components/bmp581/bmp581.cpp
Normal file
596
esphome/components/bmp581/bmp581.cpp
Normal file
|
@ -0,0 +1,596 @@
|
|||
/*
|
||||
* Adds support for Bosch's BMP581 high accuracy pressure and temperature sensor
|
||||
* - Component structure based on ESPHome's BMP3XX component (as of March, 2023)
|
||||
* - Implementation is easier as the sensor itself automatically compensates pressure for the temperature
|
||||
* - Temperature and pressure data is converted via simple divison operations in this component
|
||||
* - IIR filter level can independently be applied to temperature and pressure measurements
|
||||
* - Bosch's BMP5-Sensor-API was consulted to verify that sensor configuration is done correctly
|
||||
* - Copyright (c) 2022 Bosch Sensortec Gmbh, SPDX-License-Identifier: BSD-3-Clause
|
||||
* - This component uses forced power mode only so measurements are synchronized by the host
|
||||
* - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
|
||||
*/
|
||||
|
||||
#include "bmp581.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp581 {
|
||||
|
||||
static const char *const TAG = "bmp581";
|
||||
|
||||
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case Oversampling::OVERSAMPLING_NONE:
|
||||
return LOG_STR("None");
|
||||
case Oversampling::OVERSAMPLING_X2:
|
||||
return LOG_STR("2x");
|
||||
case Oversampling::OVERSAMPLING_X4:
|
||||
return LOG_STR("4x");
|
||||
case Oversampling::OVERSAMPLING_X8:
|
||||
return LOG_STR("8x");
|
||||
case Oversampling::OVERSAMPLING_X16:
|
||||
return LOG_STR("16x");
|
||||
case Oversampling::OVERSAMPLING_X32:
|
||||
return LOG_STR("32x");
|
||||
case Oversampling::OVERSAMPLING_X64:
|
||||
return LOG_STR("64x");
|
||||
case Oversampling::OVERSAMPLING_X128:
|
||||
return LOG_STR("128x");
|
||||
default:
|
||||
return LOG_STR("");
|
||||
}
|
||||
}
|
||||
|
||||
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case IIRFilter::IIR_FILTER_OFF:
|
||||
return LOG_STR("OFF");
|
||||
case IIRFilter::IIR_FILTER_2:
|
||||
return LOG_STR("2x");
|
||||
case IIRFilter::IIR_FILTER_4:
|
||||
return LOG_STR("4x");
|
||||
case IIRFilter::IIR_FILTER_8:
|
||||
return LOG_STR("8x");
|
||||
case IIRFilter::IIR_FILTER_16:
|
||||
return LOG_STR("16x");
|
||||
case IIRFilter::IIR_FILTER_32:
|
||||
return LOG_STR("32x");
|
||||
case IIRFilter::IIR_FILTER_64:
|
||||
return LOG_STR("64x");
|
||||
case IIRFilter::IIR_FILTER_128:
|
||||
return LOG_STR("128x");
|
||||
default:
|
||||
return LOG_STR("");
|
||||
}
|
||||
}
|
||||
|
||||
void BMP581Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BMP581:");
|
||||
|
||||
switch (this->error_code_) {
|
||||
case NONE:
|
||||
break;
|
||||
case ERROR_COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, " Communication with BMP581 failed!");
|
||||
break;
|
||||
case ERROR_WRONG_CHIP_ID:
|
||||
ESP_LOGE(TAG, " BMP581 has wrong chip ID - please verify you are using a BMP 581");
|
||||
break;
|
||||
case ERROR_SENSOR_RESET:
|
||||
ESP_LOGE(TAG, " BMP581 failed to reset");
|
||||
break;
|
||||
case ERROR_SENSOR_STATUS:
|
||||
ESP_LOGE(TAG, " BMP581 sensor status failed, there were NVM problems");
|
||||
break;
|
||||
case ERROR_PRIME_IIR_FAILED:
|
||||
ESP_LOGE(TAG, " BMP581's IIR Filter failed to prime with an initial measurement");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, " BMP581 error code %d", (int) this->error_code_);
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
|
||||
|
||||
if (this->temperature_sensor_) {
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_temperature_level_)));
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
|
||||
}
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_pressure_level_)));
|
||||
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
|
||||
}
|
||||
}
|
||||
|
||||
void BMP581Component::setup() {
|
||||
/*
|
||||
* Setup goes through several stages, which follows the post-power-up procedure (page 18 of datasheet) and then sets
|
||||
* configured options
|
||||
* 1) Soft reboot
|
||||
* 2) Verify ASIC chip ID matches BMP581
|
||||
* 3) Verify sensor status (check if NVM is okay)
|
||||
* 4) Enable data ready interrupt
|
||||
* 5) Write oversampling settings and set internal configuration values
|
||||
* 6) Configure and prime IIR Filter(s), if enabled
|
||||
*/
|
||||
|
||||
this->error_code_ = NONE;
|
||||
ESP_LOGCONFIG(TAG, "Setting up BMP581...");
|
||||
|
||||
////////////////////
|
||||
// 1) Soft reboot //
|
||||
////////////////////
|
||||
|
||||
// Power-On-Reboot bit is asserted if sensor successfully reset
|
||||
if (!this->reset_()) {
|
||||
ESP_LOGE(TAG, "BMP581 failed to reset");
|
||||
|
||||
this->error_code_ = ERROR_SENSOR_RESET;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
// 2) Verify ASIC chip ID matches BMP581 //
|
||||
///////////////////////////////////////////
|
||||
|
||||
uint8_t chip_id;
|
||||
|
||||
// read chip id from sensor
|
||||
if (!this->read_byte(BMP581_CHIP_ID, &chip_id)) {
|
||||
ESP_LOGE(TAG, "Failed to read chip id");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// verify id
|
||||
if (chip_id != BMP581_ASIC_ID) {
|
||||
ESP_LOGE(TAG, "Unknown chip ID, is this a BMP581?");
|
||||
|
||||
this->error_code_ = ERROR_WRONG_CHIP_ID;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// 3) Verify sensor status (check if NVM is okay) //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
if (!this->read_byte(BMP581_STATUS, &this->status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read status register");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// verify status_nvm_rdy bit (it is asserted if boot was successful)
|
||||
if (!(this->status_.bit.status_nvm_rdy)) {
|
||||
ESP_LOGE(TAG, "NVM not ready after boot");
|
||||
|
||||
this->error_code_ = ERROR_SENSOR_STATUS;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// verify status_nvm_err bit (it is asserted if an error is detected)
|
||||
if (this->status_.bit.status_nvm_err) {
|
||||
ESP_LOGE(TAG, "NVM error detected on boot");
|
||||
|
||||
this->error_code_ = ERROR_SENSOR_STATUS;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// 4) Enable data ready interrupt //
|
||||
////////////////////////////////////
|
||||
|
||||
// enable the data ready interrupt source
|
||||
if (!this->write_interrupt_source_settings_(true)) {
|
||||
ESP_LOGE(TAG, "Failed to write interrupt source register");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// 5) Write oversampling settings and set internal configuration values //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// configure pressure readings, if sensor is defined
|
||||
// otherwise, disable pressure oversampling
|
||||
if (this->pressure_sensor_) {
|
||||
this->osr_config_.bit.press_en = true;
|
||||
} else {
|
||||
this->pressure_oversampling_ = OVERSAMPLING_NONE;
|
||||
}
|
||||
|
||||
// write oversampling settings
|
||||
if (!this->write_oversampling_settings_(this->temperature_oversampling_, this->pressure_oversampling_)) {
|
||||
ESP_LOGE(TAG, "Failed to write oversampling register");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// set output data rate to 4 Hz=0x19 (page 65 of datasheet)
|
||||
// - ?shouldn't? matter as this component only uses FORCED_MODE - datasheet is ambiguous
|
||||
// - If in NORMAL_MODE or NONSTOP_MODE, then this would still allow deep standby to save power
|
||||
// - will be written to BMP581 at next requested measurement
|
||||
this->odr_config_.bit.odr = 0x19;
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
/// 6) Configure and prime IIR Filter(s), if enabled //
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
if ((this->iir_temperature_level_ != IIR_FILTER_OFF) || (this->iir_pressure_level_ != IIR_FILTER_OFF)) {
|
||||
if (!this->write_iir_settings_(this->iir_temperature_level_, this->iir_pressure_level_)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR configuration registers");
|
||||
|
||||
this->error_code_ = ERROR_COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->prime_iir_filter_()) {
|
||||
ESP_LOGE(TAG, "Failed to prime the IIR filter with an intiial measurement");
|
||||
|
||||
this->error_code_ = ERROR_PRIME_IIR_FAILED;
|
||||
this->mark_failed();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BMP581Component::update() {
|
||||
/*
|
||||
* Each update goes through several stages
|
||||
* 0) Verify either a temperature or pressure sensor is defined before proceeding
|
||||
* 1) Request a measurement
|
||||
* 2) Wait for measurement to finish (based on oversampling rates)
|
||||
* 3) Read data registers for temperature and pressure, if applicable
|
||||
* 4) Publish measurements to sensor(s), if applicable
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// 0) Verify either a temperature or pressure sensor is defined before proceeding //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// 1) Request a measurement //
|
||||
//////////////////////////////
|
||||
|
||||
ESP_LOGVV(TAG, "Requesting a measurement from sensor");
|
||||
|
||||
if (!this->start_measurement_()) {
|
||||
ESP_LOGW(TAG, "Failed to request forced measurement of sensor");
|
||||
this->status_set_warning();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// 2) Wait for measurement to finish (based on oversampling rates) //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
ESP_LOGVV(TAG, "Measurement is expected to take %d ms to complete", this->conversion_time_);
|
||||
|
||||
this->set_timeout("measurement", this->conversion_time_, [this]() {
|
||||
float temperature = 0.0;
|
||||
float pressure = 0.0;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// 3) Read data registers for temperature and pressure, if applicable //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
if (!this->read_temperature_and_pressure_(temperature, pressure)) {
|
||||
ESP_LOGW(TAG, "Failed to read temperature and pressure measurements, skipping update");
|
||||
this->status_set_warning();
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!this->read_temperature_(temperature)) {
|
||||
ESP_LOGW(TAG, "Failed to read temperature measurement, skipping update");
|
||||
this->status_set_warning();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// 4) Publish measurements to sensor(s), if applicable //
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
if (this->temperature_sensor_) {
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
}
|
||||
|
||||
if (this->pressure_sensor_) {
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
bool BMP581Component::check_data_readiness_() {
|
||||
// - verifies component is not internally in standby mode
|
||||
// - reads interrupt status register
|
||||
// - checks if data ready bit is asserted
|
||||
// - If true, then internally sets component to standby mode if in forced mode
|
||||
// - returns data readiness state
|
||||
|
||||
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
|
||||
ESP_LOGD(TAG, "Data is not ready, sensor is in standby mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t status;
|
||||
|
||||
if (!this->read_byte(BMP581_INT_STATUS, &status)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->int_status_.reg = status;
|
||||
|
||||
if (this->int_status_.bit.drdy_data_reg) {
|
||||
// If in forced mode, then set internal record of the power mode to STANDBY_MODE
|
||||
// - sensor automatically returns to standby mode after completing a forced measurement
|
||||
if (this->odr_config_.bit.pwr_mode == FORCED_MODE) {
|
||||
this->odr_config_.bit.pwr_mode = STANDBY_MODE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BMP581Component::prime_iir_filter_() {
|
||||
// - temporarily disables oversampling for a fast initial measurement; avoids slowing down ESPHome's startup process
|
||||
// - enables IIR filter flushing with forced measurements
|
||||
// - forces a measurement; flushing the IIR filter and priming it with a current value
|
||||
// - disables IIR filter flushing with forced measurements
|
||||
// - reverts to internally configured oversampling rates
|
||||
// - returns success of all register writes/priming
|
||||
|
||||
// store current internal oversampling settings to revert to after priming
|
||||
Oversampling current_temperature_oversampling = (Oversampling) this->osr_config_.bit.osr_t;
|
||||
Oversampling current_pressure_oversampling = (Oversampling) this->osr_config_.bit.osr_p;
|
||||
|
||||
// temporarily disables oversampling for temperature and pressure for a fast priming measurement
|
||||
if (!this->write_oversampling_settings_(OVERSAMPLING_NONE, OVERSAMPLING_NONE)) {
|
||||
ESP_LOGE(TAG, "Failed to write oversampling register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// flush the IIR filter with forced measurements (we will only flush once)
|
||||
this->dsp_config_.bit.iir_flush_forced_en = true;
|
||||
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// forces an intial measurement
|
||||
// - this measurements flushes the IIR filter reflecting written DSP settings
|
||||
// - flushing with this initial reading avoids having the internal previous data aquisition being 0, which
|
||||
// (I)nfinitely affects future values
|
||||
if (!this->start_measurement_()) {
|
||||
ESP_LOGE(TAG, "Failed to request a forced measurement");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait for priming measurement to complete
|
||||
// - with oversampling disabled, the conversion time for a single measurement for pressure and temperature is
|
||||
// ceilf(1.05*(1.0+1.0)) = 3ms
|
||||
// - see page 12 of datasheet for details
|
||||
delay(3);
|
||||
|
||||
if (!this->check_data_readiness_()) {
|
||||
ESP_LOGE(TAG, "IIR priming measurement was not ready");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// disable IIR filter flushings on future forced measurements
|
||||
this->dsp_config_.bit.iir_flush_forced_en = false;
|
||||
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to write IIR source register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// revert oversampling rates to original settings
|
||||
return this->write_oversampling_settings_(current_temperature_oversampling, current_pressure_oversampling);
|
||||
}
|
||||
|
||||
bool BMP581Component::read_temperature_(float &temperature) {
|
||||
// - verifies data is ready to be read
|
||||
// - reads in 3 bytes of temperature data
|
||||
// - returns whether successful, where the the variable parameter contains
|
||||
// - the measured temperature (in degrees Celsius)
|
||||
|
||||
if (!this->check_data_readiness_()) {
|
||||
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[3];
|
||||
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
|
||||
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
|
||||
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &pressure) {
|
||||
// - verifies data is ready to be read
|
||||
// - reads in 6 bytes of temperature data (3 for temeperature, 3 for pressure)
|
||||
// - returns whether successful, where the variable parameters contain
|
||||
// - the measured temperature (in degrees Celsius)
|
||||
// - the measured pressure (in Pa)
|
||||
|
||||
if (!this->check_data_readiness_()) {
|
||||
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[6];
|
||||
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
|
||||
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
|
||||
this->status_set_warning();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
|
||||
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
|
||||
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
|
||||
|
||||
// pressure MSB is in data[5], LSB is in data[4], XLSB in data[3]
|
||||
int32_t raw_press = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
|
||||
pressure = (float) (raw_press / 64.0); // Divide by 2^6=64 for Pa (page 22 of datasheet)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMP581Component::reset_() {
|
||||
// - writes reset command to the command register
|
||||
// - waits for sensor to complete reset
|
||||
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
|
||||
|
||||
// writes reset command to BMP's command register
|
||||
if (!this->write_byte(BMP581_COMMAND, RESET_COMMAND)) {
|
||||
ESP_LOGE(TAG, "Failed to write reset command");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// t_{soft_res} = 2ms (page 11 of datasheet); time it takes to enter standby mode
|
||||
// - round up to 3 ms
|
||||
delay(3);
|
||||
|
||||
// read interrupt status register
|
||||
if (!this->read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
|
||||
ESP_LOGE(TAG, "Failed to read interrupt status register");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Power-On-Reboot bit is asserted if sensor successfully reset
|
||||
return this->int_status_.bit.por;
|
||||
}
|
||||
|
||||
bool BMP581Component::start_measurement_() {
|
||||
// - only pushes the sensor into FORCED_MODE for a reading if already in STANDBY_MODE
|
||||
// - returns whether a measurement is in progress or has been initiated
|
||||
|
||||
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
|
||||
return this->write_power_mode_(FORCED_MODE);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir) {
|
||||
// - ensures data registers store filtered values
|
||||
// - sets IIR filter levels on sensor
|
||||
// - matches other default settings on sensor
|
||||
// - writes configuration to the two relevant registers
|
||||
// - returns success or failure of write to the registers
|
||||
|
||||
// If the temperature/pressure IIR filter is configured, then ensure data registers store the filtered measurement
|
||||
this->dsp_config_.bit.shdw_sel_iir_t = (temperature_iir != IIR_FILTER_OFF);
|
||||
this->dsp_config_.bit.shdw_sel_iir_p = (pressure_iir != IIR_FILTER_OFF);
|
||||
|
||||
// set temperature and pressure IIR filter level to configured values
|
||||
this->iir_config_.bit.set_iir_t = temperature_iir;
|
||||
this->iir_config_.bit.set_iir_p = pressure_iir;
|
||||
|
||||
// enable pressure and temperature compensation (page 61 of datasheet)
|
||||
// - ?only relevant if IIR filter is applied?; the datasheet is ambiguous
|
||||
// - matches BMP's default setting
|
||||
this->dsp_config_.bit.comp_pt_en = 0x3;
|
||||
|
||||
// BMP581_DSP register and BMP581_DSP_IIR registers are successive
|
||||
// - allows us to write the IIR configuration with one command to both registers
|
||||
uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
|
||||
return this->write_bytes(BMP581_DSP, register_data, sizeof(register_data));
|
||||
}
|
||||
|
||||
bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
|
||||
// - updates component's internal setting
|
||||
// - returns success or failure of write to interrupt source register
|
||||
|
||||
this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
|
||||
|
||||
// write interrupt source register
|
||||
return this->write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
|
||||
}
|
||||
|
||||
bool BMP581Component::write_oversampling_settings_(Oversampling temperature_oversampling,
|
||||
Oversampling pressure_oversampling) {
|
||||
// - updates component's internal setting
|
||||
// - returns success or failure of write to Over-Sampling Rate register
|
||||
|
||||
this->osr_config_.bit.osr_t = temperature_oversampling;
|
||||
this->osr_config_.bit.osr_p = pressure_oversampling;
|
||||
|
||||
return this->write_byte(BMP581_OSR, this->osr_config_.reg);
|
||||
}
|
||||
|
||||
bool BMP581Component::write_power_mode_(OperationMode mode) {
|
||||
// - updates the component's internal power mode
|
||||
// - returns success or failure of write to Output Data Rate register
|
||||
|
||||
this->odr_config_.bit.pwr_mode = mode;
|
||||
|
||||
// write odr register
|
||||
return this->write_byte(BMP581_ODR, this->odr_config_.reg);
|
||||
}
|
||||
|
||||
} // namespace bmp581
|
||||
} // namespace esphome
|
222
esphome/components/bmp581/bmp581.h
Normal file
222
esphome/components/bmp581/bmp581.h
Normal file
|
@ -0,0 +1,222 @@
|
|||
// All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bmp581 {
|
||||
|
||||
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
|
||||
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
|
||||
|
||||
// BMP581 Register Addresses
|
||||
enum {
|
||||
BMP581_CHIP_ID = 0x01, // read chip ID
|
||||
BMP581_INT_SOURCE = 0x15, // write interrupt sources
|
||||
BMP581_MEASUREMENT_DATA =
|
||||
0x1D, // read measurement registers, 0x1D-0x1F are temperature XLSB to MSB and 0x20-0x22 are pressure XLSB to MSB
|
||||
BMP581_INT_STATUS = 0x27, // read interrupt statuses
|
||||
BMP581_STATUS = 0x28, // read sensor status
|
||||
BMP581_DSP = 0x30, // write sensor configuration
|
||||
BMP581_DSP_IIR = 0x31, // write IIR filter configuration
|
||||
BMP581_OSR = 0x36, // write oversampling configuration
|
||||
BMP581_ODR = 0x37, // write data rate and power mode configuration
|
||||
BMP581_COMMAND = 0x7E // write sensor command
|
||||
};
|
||||
|
||||
// BMP581 Power mode operations
|
||||
enum OperationMode {
|
||||
STANDBY_MODE = 0x0, // no active readings
|
||||
NORMAL_MODE = 0x1, // read continuously at ODR configured rate and standby between
|
||||
FORCED_MODE = 0x2, // read sensor once (only reading mode used by this component)
|
||||
NONSTOP_MODE = 0x3 // read continuously with no standby
|
||||
};
|
||||
|
||||
// Temperature and pressure sensors can be oversampled to reduce noise
|
||||
enum Oversampling {
|
||||
OVERSAMPLING_NONE = 0x0,
|
||||
OVERSAMPLING_X2 = 0x1,
|
||||
OVERSAMPLING_X4 = 0x2,
|
||||
OVERSAMPLING_X8 = 0x3,
|
||||
OVERSAMPLING_X16 = 0x4,
|
||||
OVERSAMPLING_X32 = 0x5,
|
||||
OVERSAMPLING_X64 = 0x6,
|
||||
OVERSAMPLING_X128 = 0x7
|
||||
};
|
||||
|
||||
// Infinite Impulse Response filter reduces noise caused by ambient disturbances
|
||||
enum IIRFilter {
|
||||
IIR_FILTER_OFF = 0x0,
|
||||
IIR_FILTER_2 = 0x1,
|
||||
IIR_FILTER_4 = 0x2,
|
||||
IIR_FILTER_8 = 0x3,
|
||||
IIR_FILTER_16 = 0x4,
|
||||
IIR_FILTER_32 = 0x5,
|
||||
IIR_FILTER_64 = 0x6,
|
||||
IIR_FILTER_128 = 0x7
|
||||
};
|
||||
|
||||
class BMP581Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; }
|
||||
|
||||
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
|
||||
this->temperature_oversampling_ = temperature_oversampling;
|
||||
}
|
||||
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
|
||||
this->pressure_oversampling_ = pressure_oversampling;
|
||||
}
|
||||
|
||||
void set_temperature_iir_filter_config(IIRFilter iir_temperature_level) {
|
||||
this->iir_temperature_level_ = iir_temperature_level;
|
||||
}
|
||||
void set_pressure_iir_filter_config(IIRFilter iir_pressure_level) { this->iir_pressure_level_ = iir_pressure_level; }
|
||||
|
||||
void set_conversion_time(uint8_t conversion_time) { this->conversion_time_ = conversion_time; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
|
||||
Oversampling temperature_oversampling_;
|
||||
Oversampling pressure_oversampling_;
|
||||
|
||||
IIRFilter iir_temperature_level_;
|
||||
IIRFilter iir_pressure_level_;
|
||||
|
||||
// Stores the sensors conversion time needed for a measurement based on oversampling settings and datasheet (page 12)
|
||||
// Computed in Python during codegen
|
||||
uint8_t conversion_time_;
|
||||
|
||||
// Checks if the BMP581 has measurement data ready by checking the sensor's interrupts
|
||||
bool check_data_readiness_();
|
||||
|
||||
// Flushes the IIR filter and primes an initial reading
|
||||
bool prime_iir_filter_();
|
||||
|
||||
// Reads temperature data from sensor and converts data to measurement in degrees Celsius
|
||||
bool read_temperature_(float &temperature);
|
||||
// Reads temperature and pressure data from sensor and converts data to measurements in degrees Celsius and Pa
|
||||
bool read_temperature_and_pressure_(float &temperature, float &pressure);
|
||||
|
||||
// Soft resets the BMP581
|
||||
bool reset_();
|
||||
|
||||
// Initiates a measurement on sensor by switching to FORCED_MODE
|
||||
bool start_measurement_();
|
||||
|
||||
// Writes the IIR filter configuration to the DSP and DSP_IIR registers
|
||||
bool write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir);
|
||||
|
||||
// Writes whether to enable the data ready interrupt to the interrupt source register
|
||||
bool write_interrupt_source_settings_(bool data_ready_enable);
|
||||
|
||||
// Writes the oversampling settings to the OSR register
|
||||
bool write_oversampling_settings_(Oversampling temperature_oversampling, Oversampling pressure_oversampling);
|
||||
|
||||
// Sets the power mode on the BMP581 by writing to the ODR register
|
||||
bool write_power_mode_(OperationMode mode);
|
||||
|
||||
enum ErrorCode {
|
||||
NONE = 0,
|
||||
ERROR_COMMUNICATION_FAILED,
|
||||
ERROR_WRONG_CHIP_ID,
|
||||
ERROR_SENSOR_STATUS,
|
||||
ERROR_SENSOR_RESET,
|
||||
ERROR_PRIME_IIR_FAILED
|
||||
} error_code_{NONE};
|
||||
|
||||
// BMP581's interrupt source register (address 0x15) to configure which interrupts are enabled (page 54 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t drdy_data_reg_en : 1; // Data ready interrupt enable
|
||||
uint8_t fifo_full_en : 1; // FIFO full interrupt enable
|
||||
uint8_t fifo_ths_en : 1; // FIFO threshold/watermark interrupt enable
|
||||
uint8_t oor_p_en : 1; // Pressure data out-of-range interrupt enable
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} int_source_ = {.reg = 0};
|
||||
|
||||
// BMP581's interrupt status register (address 0x27) to determine ensor's current state (page 58 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t drdy_data_reg : 1; // Data ready
|
||||
uint8_t fifo_full : 1; // FIFO full
|
||||
uint8_t fifo_ths : 1; // FIFO fhreshold/watermark
|
||||
uint8_t oor_p : 1; // Pressure data out-of-range
|
||||
uint8_t por : 1; // Power-On-Reset complete
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} int_status_ = {.reg = 0};
|
||||
|
||||
// BMP581's status register (address 0x28) to determine if sensor has setup correctly (page 58 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t status_core_rdy : 1;
|
||||
uint8_t status_nvm_rdy : 1; // asserted if NVM is ready of operations
|
||||
uint8_t status_nvm_err : 1; // asserted if NVM error
|
||||
uint8_t status_nvm_cmd_err : 1; // asserted if boot command error
|
||||
uint8_t status_boot_err_corrected : 1; // asserted if a boot error has been corrected
|
||||
uint8_t : 2;
|
||||
uint8_t st_crack_pass : 1; // asserted if crack check has executed without detecting a crack
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} status_ = {.reg = 0};
|
||||
|
||||
// BMP581's dsp register (address 0x30) to configure data registers iir selection (page 61 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t comp_pt_en : 2; // enable temperature and pressure compensation
|
||||
uint8_t iir_flush_forced_en : 1; // IIR filter is flushed in forced mode
|
||||
uint8_t shdw_sel_iir_t : 1; // temperature data register value selected before or after iir
|
||||
uint8_t fifo_sel_iir_t : 1; // FIFO temperature data register value secected before or after iir
|
||||
uint8_t shdw_sel_iir_p : 1; // pressure data register value selected before or after iir
|
||||
uint8_t fifo_sel_iir_p : 1; // FIFO pressure data register value selected before or after iir
|
||||
uint8_t oor_sel_iir_p : 1; // pressure out-of-range value selected before or after iir
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} dsp_config_ = {.reg = 0};
|
||||
|
||||
// BMP581's iir register (address 0x31) to configure iir filtering(page 62 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t set_iir_t : 3; // Temperature IIR filter coefficient
|
||||
uint8_t set_iir_p : 3; // Pressure IIR filter coefficient
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} iir_config_ = {.reg = 0};
|
||||
|
||||
// BMP581's OSR register (address 0x36) to configure Over-Sampling Rates (page 64 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t osr_t : 3; // Temperature oversampling
|
||||
uint8_t osr_p : 3; // Pressure oversampling
|
||||
uint8_t press_en : 1; // Enables pressure measurement
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} osr_config_ = {.reg = 0};
|
||||
|
||||
// BMP581's odr register (address 0x37) to configure output data rate and power mode (page 64 of datasheet)
|
||||
union {
|
||||
struct {
|
||||
uint8_t pwr_mode : 2; // power mode of sensor
|
||||
uint8_t odr : 5; // output data rate
|
||||
uint8_t deep_dis : 1; // deep standby disabled if asserted
|
||||
} bit;
|
||||
uint8_t reg;
|
||||
} odr_config_ = {.reg = 0};
|
||||
};
|
||||
|
||||
} // namespace bmp581
|
||||
} // namespace esphome
|
163
esphome/components/bmp581/sensor.py
Normal file
163
esphome/components/bmp581/sensor.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
import math
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PASCAL,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
bmp581_ns = cg.esphome_ns.namespace("bmp581")
|
||||
|
||||
Oversampling = bmp581_ns.enum("Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
"NONE": Oversampling.OVERSAMPLING_NONE,
|
||||
"2X": Oversampling.OVERSAMPLING_X2,
|
||||
"4X": Oversampling.OVERSAMPLING_X4,
|
||||
"8X": Oversampling.OVERSAMPLING_X8,
|
||||
"16X": Oversampling.OVERSAMPLING_X16,
|
||||
"32X": Oversampling.OVERSAMPLING_X32,
|
||||
"64X": Oversampling.OVERSAMPLING_X64,
|
||||
"128X": Oversampling.OVERSAMPLING_X128,
|
||||
}
|
||||
|
||||
IIRFilter = bmp581_ns.enum("IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
"OFF": IIRFilter.IIR_FILTER_OFF,
|
||||
"2X": IIRFilter.IIR_FILTER_2,
|
||||
"4X": IIRFilter.IIR_FILTER_4,
|
||||
"8X": IIRFilter.IIR_FILTER_8,
|
||||
"16X": IIRFilter.IIR_FILTER_16,
|
||||
"32X": IIRFilter.IIR_FILTER_32,
|
||||
"64X": IIRFilter.IIR_FILTER_64,
|
||||
"128X": IIRFilter.IIR_FILTER_128,
|
||||
}
|
||||
|
||||
BMP581Component = bmp581_ns.class_(
|
||||
"BMP581Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
|
||||
def compute_measurement_conversion_time(config):
|
||||
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
|
||||
# - returns a rounded up time in ms
|
||||
|
||||
# Page 12 of datasheet
|
||||
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.7,
|
||||
"4X": 2.9,
|
||||
"8X": 5.4,
|
||||
"16X": 10.4,
|
||||
"32X": 20.4,
|
||||
"64X": 40.4,
|
||||
"128X": 80.4,
|
||||
}
|
||||
|
||||
# Page 12 of datasheet
|
||||
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
|
||||
"NONE": 1.0,
|
||||
"2X": 1.1,
|
||||
"4X": 1.5,
|
||||
"8X": 2.1,
|
||||
"16X": 3.3,
|
||||
"32X": 5.8,
|
||||
"64X": 10.8,
|
||||
"128X": 20.8,
|
||||
}
|
||||
|
||||
pressure_conversion_time = (
|
||||
0.0 # No conversion time necessary without a pressure sensor
|
||||
)
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
pressure_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
temperature_conversion_time = (
|
||||
1.0 # BMP581 always samples the temperature even if only reading pressure
|
||||
)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
|
||||
temperature_config.get(CONF_OVERSAMPLING)
|
||||
]
|
||||
|
||||
# Datasheet indicates a 5% possible error in each conversion time listed
|
||||
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BMP581Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PASCAL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x46))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(
|
||||
var.set_temperature_oversampling_config(
|
||||
temperature_config[CONF_OVERSAMPLING]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
|
||||
)
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
|
||||
|
||||
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))
|
|
@ -1355,6 +1355,14 @@ sensor:
|
|||
name: "Distance"
|
||||
update_interval: 60s
|
||||
i2c_id: i2c_bus
|
||||
- platform: bmp581
|
||||
i2c_id: i2c_bus
|
||||
temperature:
|
||||
name: "BMP581 Temperature"
|
||||
iir_filter: 2x
|
||||
pressure:
|
||||
name: "BMP581 Pressure"
|
||||
oversampling: 128x
|
||||
|
||||
esp32_touch:
|
||||
setup_mode: false
|
||||
|
|
Loading…
Reference in a new issue