From 602cd9dc0dcc052536bd5b63491f305716bd7d2d Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 12 Jan 2026 22:08:28 +0200 Subject: [PATCH] Got ISR method working with ads1115_int using semaphore. --- components/ads1115_int/__init__.py | 5 +- components/ads1115_int/ads1115_int.cpp | 221 +++++++++--------- components/ads1115_int/ads1115_int.h | 31 ++- components/ads1115_int/sensor/__init__.py | 6 +- .../ads1115_int/sensor/ads1115_int_sensor.cpp | 5 +- .../ads1115_int/sensor/ads1115_int_sensor.h | 2 +- .../ads1115_pol/sensor/ads1115_pol_sensor.cpp | 4 +- sthome-ut8.yaml | 18 +- 8 files changed, 149 insertions(+), 143 deletions(-) diff --git a/components/ads1115_int/__init__.py b/components/ads1115_int/__init__.py index a5d0ab2..cbeb6a5 100644 --- a/components/ads1115_int/__init__.py +++ b/components/ads1115_int/__init__.py @@ -12,7 +12,7 @@ MULTI_CONF = True ads1115_int_ns = cg.esphome_ns.namespace("ads1115_int") ADS1115Component = ads1115_int_ns.class_("ADS1115Component", cg.Component, i2c.I2CDevice) -CONF_CONTINUOUS_MODE = "continuous_mode" +#CONF_CONTINUOUS_MODE = "continuous_mode" CONF_ADS1115_ID = "ads1115_id" CONFIG_SCHEMA = ( @@ -35,6 +35,5 @@ async def to_code(config): alert_rdy = await cg.gpio_pin_expression( config.get(CONF_ALERT_RDY_PIN) ) - if alert_rdy is not None: - cg.add(var.set_alert_pin(alert_rdy)) + cg.add(var.set_alert_pin(alert_rdy)) # cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE])) diff --git a/components/ads1115_int/ads1115_int.cpp b/components/ads1115_int/ads1115_int.cpp index 91f175b..cb235c6 100644 --- a/components/ads1115_int/ads1115_int.cpp +++ b/components/ads1115_int/ads1115_int.cpp @@ -11,10 +11,13 @@ static const uint8_t ADS1115_REGISTER_CONFIG = 0x01; static const uint8_t ADS1115_REGISTER_LO_TRESH = 0x02; static const uint8_t ADS1115_REGISTER_HI_TRESH = 0x03; -void ADS1115Component::setup() { +void ADS1115Component::setup() +{ uint16_t value; + // 1. Create a binary semaphore + data_ready_sem = xSemaphoreCreateBinary(); this->alert_pin_->setup(); - this->alert_pin_->attach_interrupt(&ADS1115Component::isr, this, gpio::INTERRUPT_FALLING_EDGE); + this->alert_pin_->attach_interrupt(&ADS1115Component::isr_handler, this, gpio::INTERRUPT_FALLING_EDGE); if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) { this->mark_failed(); return; @@ -32,15 +35,9 @@ void ADS1115Component::setup() { // 0bxxxx000xxxxxxxxx config |= ADS1115_GAIN_6P144 << 9; - if (this->continuous_mode_) { - // Set continuous mode - // 0bxxxxxxx0xxxxxxxx - config |= 0b0000000000000000; - } else { - // Set singleshot mode - // 0bxxxxxxx1xxxxxxxx - config |= 0b0000000100000000; - } + // Set singleshot mode - continuous mode does not make sense with interrupt + // 0bxxxxxxx1xxxxxxxx + config |= 0b0000000100000000; // Set data rate - 860 samples per second // 0bxxxxxxxx100xxxxx @@ -72,8 +69,106 @@ void ADS1115Component::setup() { return; } } +// ISR function to handle interrupt from alert pin +// MUST be fast and non-blocking +void IRAM_ATTR ADS1115Component::isr_handler(ADS1115Component *arg) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(arg->data_ready_sem, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); // Switch to the waiting task immediately + } +} + +void ADS1115Component::loop() +{ + // Check the semaphore (0 timeout means non-blocking) + if (xSemaphoreTake(data_ready_sem, 0) == pdTRUE) { + // Perform the I2C read here safely outside of ISR + this->read_request_next(); + } +} + +// we read data for the current mux index, then set up the next conversion +void ADS1115Component::read_request_next() +{ + int mux_index = this->mux_data_index_ % 4; + auto multiplexer = this->muxdata_[mux_index].multiplexer; + if(multiplexer == ADS1115_MULTIPLEXER_INVALID) { + ESP_LOGE(TAG, "MUX data not set for index %d", mux_index); + } + else { + int16_t raw_value = 0; + double v = this->read_data(raw_value, mux_index); + if (!std::isnan(v)) { + auto sensor_ptr = this->muxdata_[mux_index].sensor_ptr; + ESP_LOGV(TAG, "'% -18s': Raw=% 6d %fV", sensor_ptr->get_name().c_str(), raw_value, v); + sensor_ptr->publish_state(v); + } + } + if(next_mux_data_index() != -1) { + this->start_single_shot_conversion(); + } +} + +double ADS1115Component::read_data(int16_t& raw_value, int mux_index) +{ + raw_value = 0; + uint16_t raw_conversion; + if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) { + this->status_set_warning(); + return NAN; + } + ADS1115Resolution resolution = this->muxdata_[mux_index].resolution; + // Adjust for 12-bit resolution if needed + if (resolution == ADS1015_12_BITS) { + bool negative = (raw_conversion >> 15) == 1; + + // shift raw_conversion as it's only 12-bits, left justified + raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS); + + // check if number was negative in order to keep the sign + if (negative) { + // the number was negative + // 1) set the negative bit back + raw_conversion |= 0x8000; + // 2) reset the former (shifted) negative bit + raw_conversion &= 0xF7FF; + } + } + auto signed_conversion = static_cast(raw_conversion); + raw_value = signed_conversion; + double millivolts; + double divider = (resolution == ADS1115_16_BITS) ? 32768.0f : 2048.0f; + switch (this->muxdata_[mux_index].gain) { + case ADS1115_GAIN_6P144: + millivolts = (signed_conversion * 6144) / divider; + break; + case ADS1115_GAIN_4P096: + millivolts = (signed_conversion * 4096) / divider; + break; + case ADS1115_GAIN_2P048: + millivolts = (signed_conversion * 2048) / divider; + break; + case ADS1115_GAIN_1P024: + millivolts = (signed_conversion * 1024) / divider; + break; + case ADS1115_GAIN_0P512: + millivolts = (signed_conversion * 512) / divider; + break; + case ADS1115_GAIN_0P256: + millivolts = (signed_conversion * 256) / divider; + break; + default: + millivolts = NAN; + } + + this->status_clear_warning(); + return millivolts / 1e3f; +} + // Method to set mux data for later use, i.e. when data is to be read -void ADS1115Component::set_mux_data(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution, ADS1115Samplerate samplerate, sensor::Sensor *sensor_ptr) +int ADS1115Component::set_mux_data(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution, ADS1115Samplerate samplerate, sensor::Sensor *sensor_ptr) { int index = -1; for(int i=0; i<4; i++) { @@ -90,8 +185,9 @@ void ADS1115Component::set_mux_data(ADS1115Multiplexer multiplexer, ADS1115Gain this->muxdata_[index].gain = gain; this->muxdata_[index].resolution = resolution; this->muxdata_[index].samplerate = samplerate; - this->muxdata_[index].sensor_ptr = sensor_ptr; } + this->muxdata_[index].sensor_ptr = sensor_ptr; + return index; } bool ADS1115Component::set_data_ready_mode() @@ -113,42 +209,17 @@ bool ADS1115Component::set_data_ready_mode() } return true; } -// ISR function to handle interrupt from alert pin -// only function that sets data_ready_ flags -void IRAM_ATTR ADS1115Component::isr(ADS1115Component *arg) -{ - arg->update_sensor(); -} -void ADS1115Component::update_sensor() { - int mux_index = this->mux_data_index_; - ADS1115Multiplexer multiplexer = this->muxdata_[mux_index].multiplexer; - if (multiplexer != ADS1115_MULTIPLEXER_INVALID) { - int16_t raw_value = 0; - double v = this->read_data(raw_value, mux_index); - auto sensor_ptr = this->muxdata_[mux_index].sensor_ptr; - if (!std::isnan(v)) { - if(this->count_ % 10 == 0) { - ESP_LOGI(TAG, "'% -18s': Raw=% 6d %fV", sensor_ptr->get_name().c_str(), raw_value, v); - } - this->count_++; - ESP_LOGD(TAG, "'%s': Got Voltage=%fV", sensor_ptr->get_name().c_str(), v); - sensor_ptr->publish_state(v); - } - } - if(next_mux_data_index() != -1) { - this->start_single_shot_conversion(); - } -} bool ADS1115Component::start_single_shot_conversion() { int mux_index = this->mux_data_index_; - ADS1115Multiplexer multiplexer = this->muxdata_[mux_index].multiplexer; + auto& muxdata = this->muxdata_[mux_index]; + auto multiplexer = muxdata.multiplexer; if (multiplexer != ADS1115_MULTIPLEXER_INVALID) { - ADS1115Gain gain = this->muxdata_[mux_index].gain; - ADS1115Resolution resolution = this->muxdata_[mux_index].resolution; - ADS1115Samplerate samplerate = this->muxdata_[mux_index].samplerate; + auto gain = muxdata.gain; + auto resolution = muxdata.resolution; + auto samplerate = muxdata.samplerate; uint16_t config = this->prev_config_; // Multiplexer // 0bxBBBxxxxxxxxxxxx @@ -188,70 +259,6 @@ int ADS1115Component::next_mux_data_index() } while(this->mux_data_index_ != start_index); return -1; // no valid mux data found } -// call only when data_ready_ is true -double ADS1115Component::read_data(int16_t& raw_value, int mux_index) -{ - ADS1115Multiplexer multiplexer = this->muxdata_[mux_index].multiplexer; - raw_value = 0; - if(multiplexer == ADS1115_MULTIPLEXER_INVALID) { - ESP_LOGE(TAG, "MUX data not set for index %d", mux_index); - return NAN; - } - ADS1115Gain gain = this->muxdata_[mux_index].gain; - ADS1115Resolution resolution = this->muxdata_[mux_index].resolution; - - uint16_t raw_conversion; - if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &raw_conversion)) { - this->status_set_warning(); - return NAN; - } - //ESP_LOGI(TAG, "'mux:%d': raw=<0x%04X>", static_cast(multiplexer), raw_conversion); - - if (resolution == ADS1015_12_BITS) { - bool negative = (raw_conversion >> 15) == 1; - - // shift raw_conversion as it's only 12-bits, left justified - raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS); - - // check if number was negative in order to keep the sign - if (negative) { - // the number was negative - // 1) set the negative bit back - raw_conversion |= 0x8000; - // 2) reset the former (shifted) negative bit - raw_conversion &= 0xF7FF; - } - } - auto signed_conversion = static_cast(raw_conversion); - raw_value = signed_conversion; - double millivolts; - double divider = (resolution == ADS1115_16_BITS) ? 32768.0f : 2048.0f; - switch (gain) { - case ADS1115_GAIN_6P144: - millivolts = (signed_conversion * 6144) / divider; - break; - case ADS1115_GAIN_4P096: - millivolts = (signed_conversion * 4096) / divider; - break; - case ADS1115_GAIN_2P048: - millivolts = (signed_conversion * 2048) / divider; - break; - case ADS1115_GAIN_1P024: - millivolts = (signed_conversion * 1024) / divider; - break; - case ADS1115_GAIN_0P512: - millivolts = (signed_conversion * 512) / divider; - break; - case ADS1115_GAIN_0P256: - millivolts = (signed_conversion * 256) / divider; - break; - default: - millivolts = NAN; - } - - this->status_clear_warning(); - return millivolts / 1e3f; -} void ADS1115Component::dump_config() { diff --git a/components/ads1115_int/ads1115_int.h b/components/ads1115_int/ads1115_int.h index 24b742d..edac85f 100644 --- a/components/ads1115_int/ads1115_int.h +++ b/components/ads1115_int/ads1115_int.h @@ -6,6 +6,8 @@ #include #include +#include +#include namespace esphome { namespace ads1115_int { @@ -47,6 +49,7 @@ enum ADS1115Samplerate { ADS1115_860SPS = 0b111 }; +// we will force single-shot mode for interrupt operation class ADS1115Component : public Component, public i2c::I2CDevice { private: struct mux_data_item { @@ -54,36 +57,30 @@ class ADS1115Component : public Component, public i2c::I2CDevice { ADS1115Gain gain; ADS1115Resolution resolution; ADS1115Samplerate samplerate; - sensor::Sensor *sensor_ptr; // ADS1115Sensor *sensor_ptr + sensor::Sensor *sensor_ptr; mux_data_item() { multiplexer = ADS1115_MULTIPLEXER_INVALID; } }; - void request_data(); int next_mux_data_index(); - void update_sensor(); + void read_request_next(); int mux_data_index_{0}; mux_data_item muxdata_[4]; - uint64_t count_{0}; - - public: - void setup() override; - void dump_config() override; - /// HARDWARE_LATE setup priority - // we will force single-shot mode for interrupt operation; kept continuous_mode_ for ease of reversal if needed - //void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; } - // Method to read converted data + public: + // Semaphore handle + SemaphoreHandle_t data_ready_sem = NULL; + void setup() override; + void loop() override; + void dump_config() override; double read_data(int16_t& raw_value, int mux_index); bool start_single_shot_conversion(); void set_alert_pin(InternalGPIOPin *pin) { alert_pin_ = pin; } bool set_data_ready_mode(); - void set_mux_data(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution, ADS1115Samplerate samplerate, sensor::Sensor *sensor_ptr); - + int set_mux_data(ADS1115Multiplexer multiplexer, ADS1115Gain gain, ADS1115Resolution resolution, ADS1115Samplerate samplerate, sensor::Sensor *sensor_ptr); + protected: uint16_t prev_config_{0}; - bool continuous_mode_{false}; InternalGPIOPin *alert_pin_; - static void isr(ADS1115Component *arg); - + static void IRAM_ATTR isr_handler(ADS1115Component *arg); }; } // namespace ads1115_int diff --git a/components/ads1115_int/sensor/__init__.py b/components/ads1115_int/sensor/__init__.py index 10a09e0..e90de1f 100644 --- a/components/ads1115_int/sensor/__init__.py +++ b/components/ads1115_int/sensor/__init__.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import sensor, voltage_sampler +from esphome.components import sensor #, voltage_sampler import esphome.config_validation as cv from esphome.const import ( CONF_GAIN, @@ -14,7 +14,7 @@ from esphome.const import ( from .. import CONF_ADS1115_ID, ADS1115Component, ads1115_int_ns -AUTO_LOAD = ["voltage_sampler"] +#AUTO_LOAD = ["voltage_sampler"] DEPENDENCIES = ["ads1115_int"] ADS1115Multiplexer = ads1115_int_ns.enum("ADS1115Multiplexer") @@ -58,7 +58,7 @@ SAMPLERATE = { } ADS1115Sensor = ads1115_int_ns.class_( - "ADS1115Sensor", sensor.Sensor, cg.Component, voltage_sampler.VoltageSampler + "ADS1115Sensor", sensor.Sensor, cg.Component #, voltage_sampler.VoltageSampler ) CONFIG_SCHEMA = ( diff --git a/components/ads1115_int/sensor/ads1115_int_sensor.cpp b/components/ads1115_int/sensor/ads1115_int_sensor.cpp index 8575d71..8a3cbaf 100644 --- a/components/ads1115_int/sensor/ads1115_int_sensor.cpp +++ b/components/ads1115_int/sensor/ads1115_int_sensor.cpp @@ -7,14 +7,13 @@ namespace ads1115_int { static const char *const TAG = "ads1115_int.sensor"; -void ADS1115Sensor::loop() { +void ADS1115Sensor::loop() +{ if(this->first_reading_) { this->parent_->set_mux_data(this->multiplexer_, this->gain_, this->resolution_, this->samplerate_, this); - //delay(100); this->first_reading_ = false; this->parent_->start_single_shot_conversion(); // this will set off the first conversion; subsequent conversions will be triggered by the ISR } - // Data reading and publishing is handled in the parent component's update_sensor() method } void ADS1115Sensor::dump_config() { diff --git a/components/ads1115_int/sensor/ads1115_int_sensor.h b/components/ads1115_int/sensor/ads1115_int_sensor.h index ea159ad..e9722d7 100644 --- a/components/ads1115_int/sensor/ads1115_int_sensor.h +++ b/components/ads1115_int/sensor/ads1115_int_sensor.h @@ -22,7 +22,6 @@ class ADS1115Sensor : public sensor::Sensor, void set_gain(ADS1115Gain gain) { this->gain_ = gain; } void set_resolution(ADS1115Resolution resolution) { this->resolution_ = resolution; } void set_samplerate(ADS1115Samplerate samplerate) { this->samplerate_ = samplerate; } - void dump_config() override; protected: @@ -31,6 +30,7 @@ class ADS1115Sensor : public sensor::Sensor, ADS1115Resolution resolution_; ADS1115Samplerate samplerate_; bool first_reading_{true}; + }; } // namespace ads1115 diff --git a/components/ads1115_pol/sensor/ads1115_pol_sensor.cpp b/components/ads1115_pol/sensor/ads1115_pol_sensor.cpp index 9395076..a47e8cf 100644 --- a/components/ads1115_pol/sensor/ads1115_pol_sensor.cpp +++ b/components/ads1115_pol/sensor/ads1115_pol_sensor.cpp @@ -15,9 +15,9 @@ void ADS1115Sensor::update() { int16_t raw_value = -1000; float v = this->sample(raw_value); if (!std::isnan(v)) { - if(this->count_ % 10 == 0) { + // if(this->count_ % 10 == 0) { ESP_LOGI(TAG, "'% -18s': Raw=% 6d %fV", this->get_name().c_str(), raw_value, v); - } + // } this->count_++; ESP_LOGD(TAG, "'%s': Got Voltage=%fV", this->get_name().c_str(), v); this->publish_state(v); diff --git a/sthome-ut8.yaml b/sthome-ut8.yaml index af3fed6..f036a67 100644 --- a/sthome-ut8.yaml +++ b/sthome-ut8.yaml @@ -211,18 +211,20 @@ debug: # Enable logging logger: - level: INFO + level: DEBUG initial_level: INFO logs: canbus: INFO - i2c: INFO - i2c.idf: INFO + i2c: DEBUG + i2c.idf: DEBUG uart: INFO light: INFO sensor: INFO ds1307: INFO text_sensor: INFO ads1115.sensor: INFO + ads1115_pol.sensor: INFO + ads1115_int.sensor: DEBUG modbus: INFO modbus_controller: INFO modbus_controller.sensor: INFO @@ -266,7 +268,6 @@ ads1115: id: ads1115_4A continuous_mode: true -ads1115_pol: - address: 0x49 id: ads1115_49 continuous_mode: true @@ -274,12 +275,15 @@ ads1115_pol: ads1115_int: - address: 0x48 id: ads1115_48 + # continuous_mode: true alert_rdy_pin: number: GPIO3 mode: input: true pullup: true + + spi: - id: spi_bus0 clk_pin: GPIO18 @@ -1548,7 +1552,7 @@ sensor: # NB! Keep all ads1115 sample rates the same. Update intervals should be more than or equal to 1/sample_rate # ads1115_48 - - platform: ads1115_pol + - platform: ads1115 multiplexer: 'A2_A3' gain: 2.048 # 4.096 ads1115_id: ads1115_49 @@ -1617,7 +1621,7 @@ sensor: accuracy_decimals: 8 unit_of_measurement: "A" icon: "mdi:current" - #update_interval: 8ms #5ms + # update_interval: 8ms #5ms #filters: # - lambda: return x * x; # - sliding_window_moving_average: @@ -1641,7 +1645,7 @@ sensor: # - lambda: |- # ESP_LOGI("geyser", "Geyser current detected. Geyser was energised."); # - - platform: ads1115_pol + - platform: ads1115 multiplexer: A0_A1 gain: 2.048 # 4.096 ads1115_id: ads1115_49