Got ISR method working with ads1115_int using semaphore.

This commit is contained in:
Chris Stuurman 2026-01-12 22:08:28 +02:00
parent 20d2e5b4ef
commit 602cd9dc0d
8 changed files with 149 additions and 143 deletions

View File

@ -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]))

View File

@ -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<int16_t>(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<int>(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<int16_t>(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()
{

View File

@ -6,6 +6,8 @@
#include <vector>
#include <esphome/core/gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
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:
// Semaphore handle
SemaphoreHandle_t data_ready_sem = NULL;
void setup() override;
void loop() 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
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

View File

@ -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 = (

View File

@ -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() {

View File

@ -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

View File

@ -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);

View File

@ -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