[adc] Refactor ADS1115 driver (#24428)

This commit is contained in:
Niklas Hauser
2025-03-05 00:36:39 -09:00
committed by GitHub
parent 0ee592f67c
commit 2aecdfe116
3 changed files with 101 additions and 192 deletions
+36 -130
View File
@@ -42,18 +42,7 @@ int ADS1115::init()
return ret; return ret;
} }
uint8_t config[2] = {}; readChannel(Channel::A0); // prepare for the first measure.
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.
ScheduleOnInterval(SAMPLE_INTERVAL / 4, SAMPLE_INTERVAL / 4); ScheduleOnInterval(SAMPLE_INTERVAL / 4, SAMPLE_INTERVAL / 4);
@@ -62,152 +51,69 @@ int ADS1115::init()
int ADS1115::probe() int ADS1115::probe()
{ {
uint8_t buf[2] = {}; // The ADS1115 has no ID register, so we read out the threshold registers
int ret = readReg(ADDRESSPOINTER_REG_CONFIG, buf, 2); // 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) { if (ret != PX4_OK) {
DEVICE_DEBUG("readReg failed (%i)", ret); DEVICE_DEBUG("lo_thresh read failed (%i)", ret);
return ret; return ret;
} }
if (buf[0] != CONFIG_RESET_VALUE_HIGH || buf[1] != CONFIG_RESET_VALUE_LOW) { ret = readReg(ADDRESSPOINTER_REG_HI_THRESH, parts.high, 2);
DEVICE_DEBUG("ADS1115 not found");
return PX4_ERROR; 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 buf[2];
uint8_t next_mux_reg = CONFIG_HIGH_MUX_P0NG; buf[0] = CONFIG_HIGH_OS_START_SINGLE | uint8_t(ch) | CONFIG_HIGH_PGA_6144 | CONFIG_HIGH_MODE_SS;
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;
buf[1] = CONFIG_LOW_DR_250SPS | CONFIG_LOW_COMP_MODE_TRADITIONAL | CONFIG_LOW_COMP_POL_RESET | 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; CONFIG_LOW_COMP_LAT_NONE | CONFIG_LOW_COMP_QU_DISABLE;
return writeReg(ADDRESSPOINTER_REG_CONFIG, buf, 2); // must write whole register to take effect 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}; 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}; uint8_t buf[2] = {0x00};
readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1); // Pull config register
ChannelSelection channel;
switch ((buf[0] & (uint8_t) 0x70) >> 4) { if (readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1) != PX4_OK) { return Channel::Invalid; }
case 0x04:
channel = A0;
break;
case 0x05: const auto channel{Channel(buf[0] & CONFIG_HIGH_MUX_P3NG)};
channel = A1;
break;
case 0x06: if (readReg(ADDRESSPOINTER_REG_CONVERSATION, buf, 2) != PX4_OK) { return Channel::Invalid; }
channel = A2;
break;
case 0x07: *value = int16_t((buf[0] << 8) | buf[1]);
channel = A3;
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;
}
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; return channel;
} }
+20 -19
View File
@@ -93,9 +93,6 @@
#define CONFIG_LOW_COMP_QU_AFTER4 0x02 #define CONFIG_LOW_COMP_QU_AFTER4 0x02
#define CONFIG_LOW_COMP_QU_DISABLE 0x03 #define CONFIG_LOW_COMP_QU_DISABLE 0x03
#define CONFIG_RESET_VALUE_HIGH 0x85
#define CONFIG_RESET_VALUE_LOW 0x83
using namespace time_literals; using namespace time_literals;
/* /*
@@ -127,35 +124,39 @@ protected:
private: private:
uORB::PublicationMulti<adc_report_s> _to_adc_report{ORB_ID(adc_report)}; uORB::PublicationMulti<adc_report_s> _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{}; 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 // ADS1115 logic part
enum ChannelSelection { enum class Channel : uint8_t {
Invalid = -1, A0 = 0, A1, A2, A3 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 */ constexpr unsigned ch2u(Channel ch) { return (unsigned(ch) >> 4) & 0b11u; }
int setChannel(ChannelSelection ch); constexpr Channel u2ch(unsigned ch) { return Channel((ch << 4) | CONFIG_HIGH_MUX_P0NG); }
/* return true if sample result is valid */ // Set the channel and start a conversion
bool isSampleReady(); 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. * get adc sample. return the channel being measured.
* Invalid indicates sample failure. * Invalid indicates sample failure.
*/ */
ChannelSelection getMeasurement(int16_t *value); Channel getMeasurement(int16_t *value);
/*
* get adc sample and automatically switch to next channel and start another measurement
*/
ChannelSelection cycleMeasure(int16_t *value);
int readReg(uint8_t addr, uint8_t *buf, size_t len); int readReg(uint8_t addr, uint8_t *buf, size_t len);
+44 -42
View File
@@ -45,7 +45,8 @@
ADS1115::ADS1115(const I2CSPIDriverConfig &config) : ADS1115::ADS1115(const I2CSPIDriverConfig &config) :
I2C(config), I2C(config),
I2CSPIDriver(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.device_id = this->get_device_id();
_adc_report.resolution = 32768; _adc_report.resolution = 32768;
@@ -61,6 +62,7 @@ ADS1115::~ADS1115()
{ {
ScheduleClear(); ScheduleClear();
perf_free(_cycle_perf); perf_free(_cycle_perf);
perf_free(_comms_errors);
} }
void ADS1115::exit_and_cleanup() void ADS1115::exit_and_cleanup()
@@ -77,56 +79,55 @@ void ADS1115::RunImpl()
perf_begin(_cycle_perf); 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 (ready == 1) {
if (!_reported_ready_last_cycle) { // I2C transaction success and status register reported conversion as finished
PX4_INFO("ADS1115: reported ready"); if (_ready_counter == 0) { PX4_INFO("ADS1115: reported ready"); }
_reported_ready_last_cycle = true;
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; // Find the next unread channel in the bitmask
ADS1115::ChannelSelection ch = cycleMeasure(&buf); uint8_t next_index{0};
++_channel_cycle_count;
switch (ch) { for (; next_index < 4 && (_channel_cycle_mask & (1u << next_index)); next_index++) {}
case ADS1115::A0:
_adc_report.channel_id[0] = 0;
_adc_report.raw_data[0] = buf;
break;
case ADS1115::A1: readChannel(u2ch(next_index));
_adc_report.channel_id[1] = 1;
_adc_report.raw_data[1] = buf;
break;
case ADS1115::A2: if (_channel_cycle_mask == 0b1111) {
_adc_report.channel_id[2] = 2; _channel_cycle_mask = 0;
_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;
_to_adc_report.publish(_adc_report); _to_adc_report.publish(_adc_report);
} }
} else { } else if (ready == 0) {
if (_reported_ready_last_cycle) { // I2C transaction success but status register reported conversion still in progress
_reported_ready_last_cycle = false; perf_count(_comms_errors);
PX4_ERR("ADS1115: not ready. Device lost?"); // 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); perf_end(_cycle_perf);
@@ -163,6 +164,7 @@ void ADS1115::print_status()
{ {
I2CSPIDriverBase::print_status(); I2CSPIDriverBase::print_status();
perf_print_counter(_cycle_perf); perf_print_counter(_cycle_perf);
perf_print_counter(_comms_errors);
} }
extern "C" int ads1115_main(int argc, char *argv[]) extern "C" int ads1115_main(int argc, char *argv[])