303 lines
9.5 KiB
C++
303 lines
9.5 KiB
C++
#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<ADS1115Multiplexer>(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<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
|
|
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
|