#include "ads1115_int.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { namespace ads1115_int { static const char *const TAG = "ads1115_int"; static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00; 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() { uint16_t value; if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) { this->mark_failed("Could not read ADS1115 conversion register on setup."); return; } // 1. Create a binary semaphore data_ready_sem = xSemaphoreCreateBinary(); this->alert_pin_->setup(); this->alert_pin_->attach_interrupt(&ADS1115Component::isr_handler, this, gpio::INTERRUPT_FALLING_EDGE); // setup with default values uint16_t config = 0; // Clear single-shot bit // 0b0xxxxxxxxxxxxxxx config |= 0b0000000000000000; // Setup multiplexer // 0bx000xxxxxxxxxxxx config |= ADS1115_MULTIPLEXER_P0_N1 << 12; // Setup Gain // 0bxxxx000xxxxxxxxx config |= ADS1115_GAIN_6P144 << 9; // Set singleshot mode - continuous mode does not make sense with interrupt // 0bxxxxxxx1xxxxxxxx config |= 0b0000000100000000; // Set data rate - 860 samples per second // 0bxxxxxxxx100xxxxx config |= ADS1115_860SPS << 5; // Set comparator mode - hysteresis // 0bxxxxxxxxxxx0xxxx config |= 0b0000000000000000; // Set comparator polarity - active low // 0bxxxxxxxxxxxx0xxx config |= 0b0000000000000000; // Set comparator latch enabled - false // 0bxxxxxxxxxxxxx0xx config |= 0b0000000000000000; // Set comparator que mode - disabled // 0bxxxxxxxxxxxxxx11 config |= 0b0000000000000011; if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) { this->mark_failed("Could not write to ADS1115 config register on setup."); return; } this->prev_config_ = config; if(!this->set_data_ready_mode()) { this->mark_failed("Could not set ADS1115 data ready mode on 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() { uint16_t config = 0x0000; // We do not support multiple ads1115 sharing the same alert/rdy pin, however we still read the ads1115 config register to get the multiplexer value. This allows us to also check the operational status (OS) bit. // If OS bit set, i.e. the conversion is done meaning we can read the data; this is valid only in single shot mode, in continuous mode (which is not supported by this code) we can read data without checking the status bit //ESP_LOGD(TAG, "Config: 0x%04X", this->prev_config_); if(this->read_byte_16(ADS1115_REGISTER_CONFIG, &config)) { //ESP_LOGD(TAG, "Config register: 0x%04X", config); int ads_mux = (config >> 12) & 0b111; bool conversion_done = (config >> 15) == 1; //ESP_LOGD(TAG, "Current MUX setting according to ADS1115: %d", ads_mux); int ads_mux_index = get_mux_index(static_cast(ads_mux)); //ESP_LOGD(TAG, "Current MUX index according to ADS1115: %d", ads_mux_index); if (conversion_done && ads_mux_index != -1) { int16_t raw_value = 0; double v = this->read_data(raw_value, ads_mux_index); if (!std::isnan(v)) { auto sensor_ptr = this->muxdata_[ads_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); } // we have read data for the current mux index, move to next int next_index = next_mux_data_index(ads_mux_index); if(next_index != -1) { this->start_single_shot_conversion(next_index); } } } } int ADS1115Component::get_mux_index(ADS1115Multiplexer multiplexer) { for(int i=0; i<4; i++) { if(this->muxdata_[i].multiplexer == multiplexer) { return i; } } return -1; // not found } 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 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++) { if(this->muxdata_[i].multiplexer == multiplexer) { index = i; break; } if(this->muxdata_[i].multiplexer == ADS1115_MULTIPLEXER_INVALID && index == -1) { index = i; } } if(index != -1) { this->muxdata_[index].multiplexer = multiplexer; this->muxdata_[index].gain = gain; this->muxdata_[index].resolution = resolution; this->muxdata_[index].samplerate = samplerate; } this->muxdata_[index].sensor_ptr = sensor_ptr; return index; } bool ADS1115Component::set_data_ready_mode() { bool success = true; uint16_t config = this->prev_config_; // Set comparator que mode - assert after one conversion // 0bxxxxxxxxxxxxxx00 config &= 0b1111111111111100; if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) { ESP_LOGE(TAG, "Failed to write to ads1115 to set comparator que mode"); success = false; } else { this->prev_config_ = config; if(!this->write_byte_16(ADS1115_REGISTER_HI_TRESH, 0x8000)) { ESP_LOGE(TAG, "Failed to write to ads1115 to set high threshold register for alert pin"); success = false; } else { if(!this->write_byte_16(ADS1115_REGISTER_LO_TRESH, 0x0000)) { ESP_LOGE(TAG, "Failed to write to ads1115 to set low threshold register for alert pin"); success = false; } } } return success; } bool ADS1115Component::start_single_shot_conversion(int mux_index) { auto& muxdata = this->muxdata_[mux_index]; auto multiplexer = muxdata.multiplexer; if (multiplexer != ADS1115_MULTIPLEXER_INVALID) { auto gain = muxdata.gain; auto resolution = muxdata.resolution; auto samplerate = muxdata.samplerate; uint16_t config = this->prev_config_; // Multiplexer // 0bxBBBxxxxxxxxxxxx config &= 0b1000111111111111; config |= (multiplexer & 0b111) << 12; // Gain // 0bxxxxBBBxxxxxxxxx config &= 0b1111000111111111; config |= (gain & 0b111) << 9; // Sample rate // 0bxxxxxxxxBBBxxxxx config &= 0b1111111100011111; config |= (samplerate & 0b111) << 5; // Start conversion config |= 0b1000000000000000; if (!this->write_byte_16(ADS1115_REGISTER_CONFIG, config)) { this->status_set_warning(); return false; } this->prev_config_ = config; return true; } return false; } int ADS1115Component::next_mux_data_index(int start_index) { int result = start_index; do { result = (result + 1) % 4; if(this->muxdata_[result].multiplexer != ADS1115_MULTIPLEXER_INVALID) { return result; } } while(result != start_index); return -1; // no valid mux data found } void ADS1115Component::dump_config() { ESP_LOGCONFIG(TAG, "ADS1115_INT:"); LOG_I2C_DEVICE(this); LOG_PIN(" ALERT Pin:", this->alert_pin_); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); } } } // namespace ads1115_int } // namespace esphome