From 2aecdfe116519fbd915714947c09898bd8b82b68 Mon Sep 17 00:00:00 2001 From: Niklas Hauser <121870655+niklaut@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:36:39 +0100 Subject: [PATCH] [adc] Refactor ADS1115 driver (#24428) --- src/drivers/adc/ads1115/ADS1115.cpp | 166 +++++------------------ src/drivers/adc/ads1115/ADS1115.h | 39 +++--- src/drivers/adc/ads1115/ads1115_main.cpp | 88 ++++++------ 3 files changed, 101 insertions(+), 192 deletions(-) diff --git a/src/drivers/adc/ads1115/ADS1115.cpp b/src/drivers/adc/ads1115/ADS1115.cpp index 5ce4ae3504..25c8e92956 100644 --- a/src/drivers/adc/ads1115/ADS1115.cpp +++ b/src/drivers/adc/ads1115/ADS1115.cpp @@ -42,18 +42,7 @@ int ADS1115::init() return ret; } - uint8_t config[2] = {}; - config[0] = CONFIG_HIGH_OS_NOACT | CONFIG_HIGH_MUX_P0NG | CONFIG_HIGH_PGA_6144 | CONFIG_HIGH_MODE_SS; - config[1] = CONFIG_LOW_DR_250SPS | CONFIG_LOW_COMP_MODE_TRADITIONAL | CONFIG_LOW_COMP_POL_RESET | - CONFIG_LOW_COMP_LAT_NONE | CONFIG_LOW_COMP_QU_DISABLE; - ret = writeReg(ADDRESSPOINTER_REG_CONFIG, config, 2); - - if (ret != PX4_OK) { - PX4_ERR("writeReg failed (%i)", ret); - return ret; - } - - setChannel(ADS1115::A0); // prepare for the first measure. + readChannel(Channel::A0); // prepare for the first measure. ScheduleOnInterval(SAMPLE_INTERVAL / 4, SAMPLE_INTERVAL / 4); @@ -62,152 +51,69 @@ int ADS1115::init() int ADS1115::probe() { - uint8_t buf[2] = {}; - int ret = readReg(ADDRESSPOINTER_REG_CONFIG, buf, 2); + // The ADS1115 has no ID register, so we read out the threshold registers + // and check their default values. We cannot use the config register, as + // this is changed by this driver. Note the default value is in BE. + static constexpr uint32_t DEFAULT{0xFF7F0080}; + union { + struct { + uint8_t low[2]; + uint8_t high[2]; + } parts; + uint32_t threshold{}; + }; + int ret = readReg(ADDRESSPOINTER_REG_LO_THRESH, parts.low, 2); if (ret != PX4_OK) { - DEVICE_DEBUG("readReg failed (%i)", ret); + DEVICE_DEBUG("lo_thresh read failed (%i)", ret); return ret; } - if (buf[0] != CONFIG_RESET_VALUE_HIGH || buf[1] != CONFIG_RESET_VALUE_LOW) { - DEVICE_DEBUG("ADS1115 not found"); - return PX4_ERROR; + ret = readReg(ADDRESSPOINTER_REG_HI_THRESH, parts.high, 2); + + if (ret != PX4_OK) { + DEVICE_DEBUG("hi_thresh read failed (%i)", ret); + return ret; } - return PX4_OK; + if (threshold == DEFAULT) { + return PX4_OK; + } + + DEVICE_DEBUG("ADS1115 not found"); + return PX4_ERROR; } -int ADS1115::setChannel(ADS1115::ChannelSelection ch) +int ADS1115::readChannel(ADS1115::Channel ch) { - uint8_t buf[2] = {}; - uint8_t next_mux_reg = CONFIG_HIGH_MUX_P0NG; - - switch (ch) { - case A0: - next_mux_reg = CONFIG_HIGH_MUX_P0NG; - break; - - case A1: - next_mux_reg = CONFIG_HIGH_MUX_P1NG; - break; - - case A2: - next_mux_reg = CONFIG_HIGH_MUX_P2NG; - break; - - case A3: - next_mux_reg = CONFIG_HIGH_MUX_P3NG; - break; - - default: - assert(false); - break; - } - - buf[0] = CONFIG_HIGH_OS_START_SINGLE | next_mux_reg | CONFIG_HIGH_PGA_6144 | CONFIG_HIGH_MODE_SS; + uint8_t buf[2]; + buf[0] = CONFIG_HIGH_OS_START_SINGLE | uint8_t(ch) | CONFIG_HIGH_PGA_6144 | CONFIG_HIGH_MODE_SS; buf[1] = CONFIG_LOW_DR_250SPS | CONFIG_LOW_COMP_MODE_TRADITIONAL | CONFIG_LOW_COMP_POL_RESET | CONFIG_LOW_COMP_LAT_NONE | CONFIG_LOW_COMP_QU_DISABLE; return writeReg(ADDRESSPOINTER_REG_CONFIG, buf, 2); // must write whole register to take effect } -bool ADS1115::isSampleReady() +int ADS1115::isSampleReady() { uint8_t buf[1] = {0x00}; - if (readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1) != 0) { return false; } // Pull config register + if (readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1) != PX4_OK) { return -1; } // Pull config register - return (buf[0] & (uint8_t) 0x80); + return (buf[0] & (uint8_t) 0x80) ? 1 : 0; } -ADS1115::ChannelSelection ADS1115::getMeasurement(int16_t *value) +ADS1115::Channel ADS1115::getMeasurement(int16_t *value) { uint8_t buf[2] = {0x00}; - readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1); // Pull config register - ChannelSelection channel; - switch ((buf[0] & (uint8_t) 0x70) >> 4) { - case 0x04: - channel = A0; - break; + if (readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1) != PX4_OK) { return Channel::Invalid; } - case 0x05: - channel = A1; - break; + const auto channel{Channel(buf[0] & CONFIG_HIGH_MUX_P3NG)}; - case 0x06: - channel = A2; - break; + if (readReg(ADDRESSPOINTER_REG_CONVERSATION, buf, 2) != PX4_OK) { return Channel::Invalid; } - case 0x07: - channel = A3; - break; + *value = int16_t((buf[0] << 8) | buf[1]); - default: - return Invalid; - } - - readReg(ADDRESSPOINTER_REG_CONVERSATION, buf, 2); - uint16_t raw_adc_val = buf[0] * 256 + buf[1]; - - if (raw_adc_val & (uint16_t) 0x8000) { // Negetive value - raw_adc_val = ~raw_adc_val + 1; // 2's complement - *value = -raw_adc_val; - - } else { - *value = raw_adc_val; - } - - return channel; -} - -ADS1115::ChannelSelection ADS1115::cycleMeasure(int16_t *value) -{ - uint8_t buf[2] = {0x00}; - readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1); // Pull config register - ChannelSelection channel; - uint8_t next_mux_reg = CONFIG_HIGH_MUX_P0NG; - - switch ((buf[0] & (uint8_t) 0x70) >> 4) { - case 0x04: - channel = A0; - next_mux_reg = CONFIG_HIGH_MUX_P1NG; - break; - - case 0x05: - channel = A1; - next_mux_reg = CONFIG_HIGH_MUX_P2NG; - break; - - case 0x06: - channel = A2; - next_mux_reg = CONFIG_HIGH_MUX_P3NG; - break; - - case 0x07: - channel = A3; - next_mux_reg = CONFIG_HIGH_MUX_P0NG; - break; - - default: - return Invalid; - } - - readReg(ADDRESSPOINTER_REG_CONVERSATION, buf, 2); - uint16_t raw_adc_val = buf[0] * 256 + buf[1]; - - if (raw_adc_val & (uint16_t) 0x8000) { // Negetive value - raw_adc_val = ~raw_adc_val + 1; // 2's complement - *value = -raw_adc_val; - - } else { - *value = raw_adc_val; - } - - buf[0] = CONFIG_HIGH_OS_START_SINGLE | next_mux_reg | CONFIG_HIGH_PGA_6144 | CONFIG_HIGH_MODE_SS; - buf[1] = CONFIG_LOW_DR_250SPS | CONFIG_LOW_COMP_MODE_TRADITIONAL | CONFIG_LOW_COMP_POL_RESET | - CONFIG_LOW_COMP_LAT_NONE | CONFIG_LOW_COMP_QU_DISABLE; - writeReg(ADDRESSPOINTER_REG_CONFIG, buf, 2); // must write whole register to take effect return channel; } diff --git a/src/drivers/adc/ads1115/ADS1115.h b/src/drivers/adc/ads1115/ADS1115.h index 79a31198c9..634a659df8 100644 --- a/src/drivers/adc/ads1115/ADS1115.h +++ b/src/drivers/adc/ads1115/ADS1115.h @@ -93,9 +93,6 @@ #define CONFIG_LOW_COMP_QU_AFTER4 0x02 #define CONFIG_LOW_COMP_QU_DISABLE 0x03 -#define CONFIG_RESET_VALUE_HIGH 0x85 -#define CONFIG_RESET_VALUE_LOW 0x83 - using namespace time_literals; /* @@ -127,35 +124,39 @@ protected: private: - uORB::PublicationMulti _to_adc_report{ORB_ID(adc_report)}; + uORB::PublicationMulti _to_adc_report{ORB_ID(adc_report)}; - static const hrt_abstime SAMPLE_INTERVAL{50_ms}; + static const hrt_abstime SAMPLE_INTERVAL{50_ms}; adc_report_s _adc_report{}; - perf_counter_t _cycle_perf; + perf_counter_t _cycle_perf; + perf_counter_t _comms_errors; - int _channel_cycle_count{0}; + uint8_t _channel_cycle_mask{0}; - bool _reported_ready_last_cycle{false}; + static constexpr uint8_t MAX_READY_COUNTER{20}; + uint8_t _ready_counter{MAX_READY_COUNTER}; // ADS1115 logic part - enum ChannelSelection { - Invalid = -1, A0 = 0, A1, A2, A3 + enum class Channel : uint8_t { + A0 = CONFIG_HIGH_MUX_P0NG, + A1 = CONFIG_HIGH_MUX_P1NG, + A2 = CONFIG_HIGH_MUX_P2NG, + A3 = CONFIG_HIGH_MUX_P3NG, + Invalid = 0xff, }; - /* set multiplexer to specific channel */ - int setChannel(ChannelSelection ch); - /* return true if sample result is valid */ - bool isSampleReady(); + constexpr unsigned ch2u(Channel ch) { return (unsigned(ch) >> 4) & 0b11u; } + constexpr Channel u2ch(unsigned ch) { return Channel((ch << 4) | CONFIG_HIGH_MUX_P0NG); } + // Set the channel and start a conversion + int readChannel(Channel ch); + // return 1 if sample result is valid else 0 or -1 if I2C transaction failed + int isSampleReady(); /* * get adc sample. return the channel being measured. * Invalid indicates sample failure. */ - ChannelSelection getMeasurement(int16_t *value); - /* - * get adc sample and automatically switch to next channel and start another measurement - */ - ChannelSelection cycleMeasure(int16_t *value); + Channel getMeasurement(int16_t *value); int readReg(uint8_t addr, uint8_t *buf, size_t len); diff --git a/src/drivers/adc/ads1115/ads1115_main.cpp b/src/drivers/adc/ads1115/ads1115_main.cpp index 2132082c66..1967b58263 100644 --- a/src/drivers/adc/ads1115/ads1115_main.cpp +++ b/src/drivers/adc/ads1115/ads1115_main.cpp @@ -45,7 +45,8 @@ ADS1115::ADS1115(const I2CSPIDriverConfig &config) : I2C(config), I2CSPIDriver(config), - _cycle_perf(perf_alloc(PC_ELAPSED, MODULE_NAME": single-sample")) + _cycle_perf(perf_alloc(PC_ELAPSED, MODULE_NAME": single-sample")), + _comms_errors(perf_alloc(PC_COUNT, MODULE_NAME": comms errors")) { _adc_report.device_id = this->get_device_id(); _adc_report.resolution = 32768; @@ -61,6 +62,7 @@ ADS1115::~ADS1115() { ScheduleClear(); perf_free(_cycle_perf); + perf_free(_comms_errors); } void ADS1115::exit_and_cleanup() @@ -77,56 +79,55 @@ void ADS1115::RunImpl() perf_begin(_cycle_perf); - _adc_report.timestamp = hrt_absolute_time(); + const int ready = isSampleReady(); - if (isSampleReady()) { // whether ADS1115 is ready to be read or not - if (!_reported_ready_last_cycle) { - PX4_INFO("ADS1115: reported ready"); - _reported_ready_last_cycle = true; + if (ready == 1) { + // I2C transaction success and status register reported conversion as finished + if (_ready_counter == 0) { PX4_INFO("ADS1115: reported ready"); } + + if (_ready_counter < MAX_READY_COUNTER) { _ready_counter++; } + + int16_t value; + Channel ch = getMeasurement(&value); + + if (ch != Channel::Invalid) { + // Store current readings and mark channel as read + const unsigned index{ch2u(ch)}; + _adc_report.channel_id[index] = index; + _adc_report.raw_data[index] = value; + _channel_cycle_mask |= 1u << index; + + } else { + // we will retry the same channel again + perf_count(_comms_errors); } - int16_t buf; - ADS1115::ChannelSelection ch = cycleMeasure(&buf); - ++_channel_cycle_count; + // Find the next unread channel in the bitmask + uint8_t next_index{0}; - switch (ch) { - case ADS1115::A0: - _adc_report.channel_id[0] = 0; - _adc_report.raw_data[0] = buf; - break; + for (; next_index < 4 && (_channel_cycle_mask & (1u << next_index)); next_index++) {} - case ADS1115::A1: - _adc_report.channel_id[1] = 1; - _adc_report.raw_data[1] = buf; - break; + readChannel(u2ch(next_index)); - case ADS1115::A2: - _adc_report.channel_id[2] = 2; - _adc_report.raw_data[2] = buf; - break; - - case ADS1115::A3: - _adc_report.channel_id[3] = 3; - _adc_report.raw_data[3] = buf; - break; - - default: - PX4_DEBUG("ADS1115: undefined behaviour"); - setChannel(ADS1115::A0); - --_channel_cycle_count; - break; - } - - if (_channel_cycle_count == 4) { // ADS1115 has 4 channels - _channel_cycle_count = 0; + if (_channel_cycle_mask == 0b1111) { + _channel_cycle_mask = 0; _to_adc_report.publish(_adc_report); } - } else { - if (_reported_ready_last_cycle) { - _reported_ready_last_cycle = false; - PX4_ERR("ADS1115: not ready. Device lost?"); - } + } else if (ready == 0) { + // I2C transaction success but status register reported conversion still in progress + perf_count(_comms_errors); + // Reset the channel to unstick the device + readChannel(Channel::A0); + + } else if (ready == -1) { + if (_ready_counter == 1) { PX4_ERR("ADS1115: device lost"); } + + if (_ready_counter > 0) { _ready_counter--; } + + perf_count(_comms_errors); + // Reset the channel to unstick the device + readChannel(Channel::A0); } perf_end(_cycle_perf); @@ -151,7 +152,7 @@ parameter, and is disabled by default. If enabled, internal ADCs are not used. )DESCR_STR"); - + PRINT_MODULE_USAGE_NAME("ads1115", "driver"); PRINT_MODULE_USAGE_COMMAND("start"); PRINT_MODULE_USAGE_PARAMS_I2C_SPI_DRIVER(true, false); @@ -163,6 +164,7 @@ void ADS1115::print_status() { I2CSPIDriverBase::print_status(); perf_print_counter(_cycle_perf); + perf_print_counter(_comms_errors); } extern "C" int ads1115_main(int argc, char *argv[])