[adc] Refactor ADS1115 driver (#24428)

This commit is contained in:
Niklas Hauser 2025-03-05 10:36:39 +01:00 committed by GitHub
parent 0ee592f67c
commit 2aecdfe116
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 192 deletions

View File

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

View File

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

View File

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