From c5974b8833c89b5b064567da32cb1dff5178156b Mon Sep 17 00:00:00 2001 From: Nicholas Peters Date: Wed, 26 Jan 2022 04:48:51 -0500 Subject: [PATCH] TSL2591 automatic gain control (#3071) --- esphome/components/tsl2591/sensor.py | 25 ++++----- esphome/components/tsl2591/tsl2591.cpp | 72 +++++++++++++++++++++++--- esphome/components/tsl2591/tsl2591.h | 23 +++++++- 3 files changed, 101 insertions(+), 19 deletions(-) diff --git a/esphome/components/tsl2591/sensor.py b/esphome/components/tsl2591/sensor.py index 095a8c886c..1ec37b5f93 100644 --- a/esphome/components/tsl2591/sensor.py +++ b/esphome/components/tsl2591/sensor.py @@ -56,18 +56,19 @@ INTEGRATION_TIMES = { 600: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_600MS, } -TSL2591Gain = tsl2591_ns.enum("TSL2591Gain") +TSL2591ComponentGain = tsl2591_ns.enum("TSL2591ComponentGain") GAINS = { - "1X": TSL2591Gain.TSL2591_GAIN_LOW, - "LOW": TSL2591Gain.TSL2591_GAIN_LOW, - "25X": TSL2591Gain.TSL2591_GAIN_MED, - "MED": TSL2591Gain.TSL2591_GAIN_MED, - "MEDIUM": TSL2591Gain.TSL2591_GAIN_MED, - "400X": TSL2591Gain.TSL2591_GAIN_HIGH, - "HIGH": TSL2591Gain.TSL2591_GAIN_HIGH, - "9500X": TSL2591Gain.TSL2591_GAIN_MAX, - "MAX": TSL2591Gain.TSL2591_GAIN_MAX, - "MAXIMUM": TSL2591Gain.TSL2591_GAIN_MAX, + "1X": TSL2591ComponentGain.TSL2591_CGAIN_LOW, + "LOW": TSL2591ComponentGain.TSL2591_CGAIN_LOW, + "25X": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "MED": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "MEDIUM": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "400X": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, + "HIGH": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, + "9500X": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "MAX": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "MAXIMUM": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "AUTO": TSL2591ComponentGain.TSL2591_CGAIN_AUTO, } @@ -117,7 +118,7 @@ CONFIG_SCHEMA = ( CONF_INTEGRATION_TIME, default="100ms" ): validate_integration_time, cv.Optional(CONF_NAME, default="TLS2591"): cv.string, - cv.Optional(CONF_GAIN, default="MEDIUM"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_GAIN, default="AUTO"): cv.enum(GAINS, upper=True), cv.Optional(CONF_POWER_SAVE_MODE, default=True): cv.boolean, cv.Optional(CONF_DEVICE_FACTOR, default=53.0): cv.float_with_unit( "device_factor", "", True diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 7755437de2..53cd65bd88 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -43,6 +43,8 @@ void TSL2591Component::disable_if_power_saving_() { } void TSL2591Component::setup() { + if (this->component_gain_ == TSL2591_CGAIN_AUTO) + this->gain_ = TSL2591_GAIN_MED; uint8_t address = this->address_; ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address); uint8_t id; @@ -73,26 +75,30 @@ void TSL2591Component::dump_config() { } ESP_LOGCONFIG(TAG, " Name: %s", this->name_); - TSL2591Gain raw_gain = this->gain_; + TSL2591ComponentGain raw_gain = this->component_gain_; int gain = 0; std::string gain_word = "unknown"; switch (raw_gain) { - case TSL2591_GAIN_LOW: + case TSL2591_CGAIN_LOW: gain = 1; gain_word = "low"; break; - case TSL2591_GAIN_MED: + case TSL2591_CGAIN_MED: gain = 25; gain_word = "medium"; break; - case TSL2591_GAIN_HIGH: + case TSL2591_CGAIN_HIGH: gain = 400; gain_word = "high"; break; - case TSL2591_GAIN_MAX: + case TSL2591_CGAIN_MAX: gain = 9500; gain_word = "maximum"; break; + case TSL2591_CGAIN_AUTO: + gain = -1; + gain_word = "auto"; + break; } ESP_LOGCONFIG(TAG, " Gain: %dx (%s)", gain, gain_word.c_str()); TSL2591IntegrationTime raw_timing = this->integration_time_; @@ -129,6 +135,9 @@ void TSL2591Component::process_update_() { if (this->calculated_lux_sensor_ != nullptr) { this->calculated_lux_sensor_->publish_state(lux); } + if (this->component_gain_ == TSL2591_CGAIN_AUTO) { + this->automatic_gain_update(full); + } this->status_clear_warning(); } @@ -183,7 +192,7 @@ void TSL2591Component::set_integration_time(TSL2591IntegrationTime integration_t this->integration_time_ = integration_time; } -void TSL2591Component::set_gain(TSL2591Gain gain) { this->gain_ = gain; } +void TSL2591Component::set_gain(TSL2591ComponentGain gain) { this->component_gain_ = gain; } void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) { this->device_factor_ = device_factor; @@ -366,5 +375,56 @@ float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infr return std::max(lux, 0.0F); } +/** Calculates and updates the sensor gain setting, trying to keep the full spectrum counts near + * the middle of the range + * + * It's hard to tell how far down to turn the gain when it's at the top of the scale, so decrease + * the gain by up to 2 steps if it's near the top to be sure we get a good reading next time. + * Increase gain by max 2 steps per reading. + * + * If integration time is 100 MS, divide the upper thresholds by 2 to account for ADC saturation + * + * @param full_spectrum The ADC reading for TSL2591 channel 0. + * + * 1/3 FS = 21,845 + */ +void TSL2591Component::automatic_gain_update(uint16_t full_spectrum) { + TSL2591Gain new_gain = this->gain_; + uint fs_divider = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS) ? 2 : 1; + + switch (this->gain_) { + case TSL2591_GAIN_LOW: + if (full_spectrum < 54) // 1/3 FS / GAIN_HIGH + new_gain = TSL2591_GAIN_HIGH; + else if (full_spectrum < 875) // 1/3 FS / GAIN_MED + new_gain = TSL2591_GAIN_MED; + break; + case TSL2591_GAIN_MED: + if (full_spectrum < 57) // 1/3 FS / (GAIN_MAX/GAIN_MED) + new_gain = TSL2591_GAIN_MAX; + else if (full_spectrum < 1365) // 1/3 FS / (GAIN_HIGH/GAIN_MED) + new_gain = TSL2591_GAIN_HIGH; + else if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS + new_gain = TSL2591_GAIN_LOW; + break; + case TSL2591_GAIN_HIGH: + if (full_spectrum < 920) // 1/3 FS / (GAIN_MAX/GAIN_HIGH) + new_gain = TSL2591_GAIN_MAX; + else if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS + new_gain = TSL2591_GAIN_LOW; + break; + case TSL2591_GAIN_MAX: + if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS + new_gain = TSL2591_GAIN_MED; + break; + } + + if (this->gain_ != new_gain) { + this->gain_ = new_gain; + this->set_integration_time_and_gain(this->integration_time_, this->gain_); + } + ESP_LOGD(TAG, "Gain setting: %d", this->gain_); +} + } // namespace tsl2591 } // namespace esphome diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index 19352a15c5..d82dbc395f 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -21,6 +21,19 @@ enum TSL2591IntegrationTime { TSL2591_INTEGRATION_TIME_600MS = 0b101, }; +/** Enum listing all gain settings for the TSL2591 component. + * + * Enum constants are used by the component to allow auto gain, not directly to registers + * Higher values are better for low light situations, but can increase noise. + */ +enum TSL2591ComponentGain { + TSL2591_CGAIN_LOW, // 1x + TSL2591_CGAIN_MED, // 25x + TSL2591_CGAIN_HIGH, // 400x + TSL2591_CGAIN_MAX, // 9500x + TSL2591_CGAIN_AUTO +}; + /** Enum listing all gain settings for the TSL2591. * * Specific values of the enum constants are register values taken from the TSL2591 datasheet. @@ -200,6 +213,13 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { */ void disable(); + /** Updates the gain setting based on the most recent full spectrum reading + * + * This gets called on update and tries to keep the ADC readings in the middle of the range + */ + + void automatic_gain_update(uint16_t full_spectrum); + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these. They're for ESPHome integration use.) /** Used by ESPHome framework. */ @@ -213,7 +233,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { /** Used by ESPHome framework. Does NOT actually set the value on the device. */ void set_integration_time(TSL2591IntegrationTime integration_time); /** Used by ESPHome framework. Does NOT actually set the value on the device. */ - void set_gain(TSL2591Gain gain); + void set_gain(TSL2591ComponentGain gain); /** Used by ESPHome framework. */ void setup() override; /** Used by ESPHome framework. */ @@ -230,6 +250,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *visible_sensor_; sensor::Sensor *calculated_lux_sensor_; TSL2591IntegrationTime integration_time_; + TSL2591ComponentGain component_gain_; TSL2591Gain gain_; bool power_save_mode_enabled_; float device_factor_;