diff --git a/platformio.ini b/platformio.ini index 3f562ab33..2c7973ecf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -257,6 +257,8 @@ lib_deps = https://github.com/Sensirion/arduino-i2c-scd30/archive/1.1.1.zip # renovate: datasource=github-tags depName=arduino-sht packageName=sensirion/arduino-sht https://github.com/Sensirion/arduino-sht/archive/refs/tags/v1.2.6.zip + # renovate: datasource=custom.pio depName=Adafruit ADS1X15 packageName=adafruit/library/Adafruit ADS1X15 Library + https://github.com/adafruit/Adafruit_ADS1X15/archive/refs/tags/2.6.2.zip # renovate: datasource=github-tags depName=Adafruit DS248x packageName=adafruit/Adafruit_DS248x https://github.com/adafruit/Adafruit_DS248x/archive/refs/tags/1.2.0.zip diff --git a/src/configuration.h b/src/configuration.h index 0ed4dd893..795856955 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -301,6 +301,10 @@ along with this program. If not, see . #define LTR553ALS_ADDR 0x23 #define SEN5X_ADDR 0x69 #define SCD30_ADDR 0x61 +#define ADS1X15_ADDR 0x48 +#define ADS1X15_ADDR_ALT1 0x49 +#define ADS1X15_ADDR_ALT2 0x4A +#define ADS1X15_ADDR_ALT3 0x4B #define DS248X_ADDR 0x18 // same as MCP9808_ADDR, STK8BXX_ADDR and LIS3DH_ADDR #define DS248X_ADDR_ALT1 0x19 // same as LIS3DH_ADDR_ALT and BMA423_ADDR #define DS248X_ADDR_ALT2 0x1A // same as CST328_ADDR @@ -311,7 +315,6 @@ along with this program. If not, see . #define DS248X_ADDR_ALT7 0x1F // same as BBQ10_KB_ADDR #define HM330X_ADDR 0x40 - // ----------------------------------------------------------------------------- // ACCELEROMETER // ----------------------------------------------------------------------------- diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 70f848b33..46d5395a6 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -99,13 +99,14 @@ class ScanI2C SFA30, CW2015, SCD30, - ADS1115, + ADS1X15, + ADS1X15_ALT, IIS2MDCTR, ISM330DHCX, SPA06, DS248X, HM330X - } DeviceType; + } DeviceType; // typedef uint8_t DeviceAddress; typedef enum I2CPort { diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 21a74b759..af484eb15 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -1040,10 +1040,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; } + // ADS1X15 default config register is 8583h registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); - if (registerValue == 0x8583 || registerValue == 0x8580) { - type = ADS1115; - logFoundDevice("ADS1115 ADC", (uint8_t)addr.address); + if (registerValue == 0x8583 || registerValue == 0x8580 || registerValue == 0xf700) { + type = ADS1X15; + logFoundDevice("ADS1X15 ADC", (uint8_t)addr.address); break; } @@ -1052,6 +1053,19 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; } + case ADS1X15_ADDR_ALT1: + case ADS1X15_ADDR_ALT2: + case ADS1X15_ADDR_ALT3: { + // ADS1X15 default config register is 8583h + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); + if (registerValue == 0x8583 || registerValue == 0x8580 || registerValue == 0xf700) { + type = ADS1X15_ALT; + logFoundDevice("ADS1X15_ALT", (uint8_t)addr.address); + break; + } + break; + } + default: LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address); } diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 9a6076b28..aa985c909 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -131,6 +131,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/BH1750Sensor.h" #endif +#if __has_include() +#include "Sensor/ADS1X15Sensor.h" +#endif + #if __has_include() #include "Sensor/DS248XSensor.h" #endif @@ -347,6 +351,10 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::BH1750); #endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::ADS1X15); + addSensor(i2cScanner, ScanI2C::DeviceType::ADS1X15_ALT); +#endif #if __has_include() // TODO Can we scan for multiple sensors connected on the same bus? addSensor(i2cScanner, ScanI2C::DeviceType::SHTXX); @@ -759,21 +767,48 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.time = getTime(); bool validTelemetry = getEnvironmentTelemetry(&m); + if (validTelemetry) { - LOG_INFO("Send: barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f", - m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current, - m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity, - m.variant.environment_metrics.temperature); - LOG_INFO("Send: voltage=%f, IAQ=%d, distance=%f, lux=%f", m.variant.environment_metrics.voltage, - m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); + if (m.variant.environment_metrics.has_temperature || m.variant.environment_metrics.has_relative_humidity || + m.variant.environment_metrics.has_barometric_pressure) + LOG_INFO("Send: barometric_pressure=%fkPa, relative_humidity=%f%RH, temperature=%fdegC", + m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.relative_humidity, + m.variant.environment_metrics.temperature); - LOG_INFO("Send: wind speed=%fm/s, direction=%d degrees, weight=%fkg", m.variant.environment_metrics.wind_speed, - m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight); + if (m.variant.environment_metrics.has_voltage || m.variant.environment_metrics.has_current || + m.variant.environment_metrics.has_iaq || m.variant.environment_metrics.has_gas_resistance) + LOG_INFO("Send: voltage=%f, current=%f, IAQ=%d, gas_resistance=%f", m.variant.environment_metrics.voltage, + m.variant.environment_metrics.current, m.variant.environment_metrics.iaq, + m.variant.environment_metrics.gas_resistance); - LOG_INFO("Send: radiation=%fµR/h", m.variant.environment_metrics.radiation); + if (m.variant.environment_metrics.has_distance || m.variant.environment_metrics.has_lux) + LOG_INFO("Send: distance=%f, lux=%f", m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); - LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature, - m.variant.environment_metrics.soil_moisture); + if (m.variant.environment_metrics.has_wind_speed || m.variant.environment_metrics.has_wind_direction) + LOG_INFO("Send: wind speed=%fm/s, direction=%d degrees", m.variant.environment_metrics.wind_speed, + m.variant.environment_metrics.wind_direction); + + if (m.variant.environment_metrics.has_weight) + LOG_INFO("Send: weight=%fkg", m.variant.environment_metrics.weight); + + if (m.variant.environment_metrics.has_radiation) + LOG_INFO("Send: radiation=%fµR/h", m.variant.environment_metrics.radiation); + + if (m.variant.environment_metrics.has_soil_temperature || m.variant.environment_metrics.has_soil_moisture) + LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature, + m.variant.environment_metrics.soil_moisture); + + if (m.variant.environment_metrics.has_adc_voltage_ch0 || m.variant.environment_metrics.has_adc_voltage_ch1 || + m.variant.environment_metrics.has_adc_voltage_ch2 || m.variant.environment_metrics.has_adc_voltage_ch3) + LOG_INFO("Send: adc_ch0=%f, adc_ch1=%f, adc_ch2=%f, adc_ch3=%f", m.variant.environment_metrics.adc_voltage_ch0, + m.variant.environment_metrics.adc_voltage_ch1, m.variant.environment_metrics.adc_voltage_ch2, + m.variant.environment_metrics.adc_voltage_ch3); + + if (m.variant.environment_metrics.has_adc_voltage_ch4 || m.variant.environment_metrics.has_adc_voltage_ch5 || + m.variant.environment_metrics.has_adc_voltage_ch6 || m.variant.environment_metrics.has_adc_voltage_ch7) + LOG_INFO("Send: adc_ch4=%f, adc_ch5=%f, adc_ch6=%f, adc_ch7=%f", m.variant.environment_metrics.adc_voltage_ch4, + m.variant.environment_metrics.adc_voltage_ch5, m.variant.environment_metrics.adc_voltage_ch6, + m.variant.environment_metrics.adc_voltage_ch7); meshtastic_MeshPacket *p = allocDataProtobuf(m); if (!p) { diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index b00672d2d..60fe00c38 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -272,10 +272,15 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.time = getTime(); bool validTelemetry = getPowerTelemetry(&m); if (validTelemetry) { - LOG_INFO("Send: ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " - "ch3_voltage=%f, ch3_current=%f", - m.variant.power_metrics.ch1_voltage, m.variant.power_metrics.ch1_current, m.variant.power_metrics.ch2_voltage, - m.variant.power_metrics.ch2_current, m.variant.power_metrics.ch3_voltage, m.variant.power_metrics.ch3_current); + LOG_INFO("Send: ch1_voltage=%f, ch2_voltage=%f, ch3_voltage=%f", m.variant.power_metrics.ch1_voltage, + m.variant.power_metrics.ch2_voltage, m.variant.power_metrics.ch3_voltage); + + bool hasAnyCurrent = m.variant.power_metrics.has_ch1_current || m.variant.power_metrics.has_ch2_current || + m.variant.power_metrics.has_ch3_current; + if (hasAnyCurrent) { + LOG_INFO("Send: ch1_current=%f, ch2_current=%f, ch3_current=%f", m.variant.power_metrics.ch1_current, + m.variant.power_metrics.ch2_current, m.variant.power_metrics.ch3_current); + } sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp new file mode 100644 index 000000000..1b22e6358 --- /dev/null +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp @@ -0,0 +1,175 @@ +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "ADS1X15Sensor.h" +#include "TelemetrySensor.h" +#include + +ADS1X15Sensor::ADS1X15Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_ADS1X15, "ADS1X15") {} + +bool ADS1X15Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + LOG_INFO("Init sensor: %s (address: 0x%x)", sensorName, dev->address.address); + + _bus = bus; + _port = dev->address.port; + _address = dev->address.address; + _deviceType = dev->type; + +#ifdef ADS1X15_I2C_CLOCK_SPEED + reClockI2C.setup(_bus, _port); + reClockI2C.setClock(ADS1X15_I2C_CLOCK_SPEED); +#endif /* ADS1X15_I2C_CLOCK_SPEED */ + + status = ads1x15.begin(_address, _bus); + +#ifdef ADS1X15_I2C_CLOCK_SPEED + reClockI2C.restoreClock(); +#endif /* ADS1X15_I2C_CLOCK_SPEED */ + + initI2CSensor(); + + return status; +} + +struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(uint8_t ch) +{ + struct _ADS1X15Measurement measurement; + + // Reset gain + ads1x15.setGain(GAIN_TWOTHIRDS); + double voltage_range = 6.144; + + // Get value with full range + uint16_t value = ads1x15.readADC_SingleEnded(ch); + + // Dynamic gain, to increase resolution of low voltage values + // If value is under 4.096v increase the gain depending on voltage + if (value < 21845) { + if (value > 10922) { + + // 1x gain, 4.096V + ads1x15.setGain(GAIN_ONE); + voltage_range = 4.096; + + } else if (value > 5461) { + + // 2x gain, 2.048V + ads1x15.setGain(GAIN_TWO); + voltage_range = 2.048; + + } else if (value > 2730) { + + // 4x gain, 1.024V + ads1x15.setGain(GAIN_FOUR); + voltage_range = 1.024; + + } else if (value > 1365) { + + // 8x gain, 0.25V + ads1x15.setGain(GAIN_EIGHT); + voltage_range = 0.512; + + } else { + + // 16x gain, 0.125V + ads1x15.setGain(GAIN_SIXTEEN); + voltage_range = 0.256; + } + + // Get the value again + value = ads1x15.readADC_SingleEnded(ch); + } + + measurement.voltage = (float)value / 32768 * voltage_range; + + return measurement; +} + +struct _ADS1X15Measurements ADS1X15Sensor::getMeasurements() +{ + struct _ADS1X15Measurements measurements; + + // ADS1X15 has 4 channels starting from 0 + for (int i = 0; i < 4; i++) { + measurements.measurements[i] = getMeasurement(i); + } + + return measurements; +} + +bool ADS1X15Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + // Done here and not in getMeasurements to avoid the back-and-forth 4-8 times one after the other +#ifdef ADS1X15_I2C_CLOCK_SPEED + reClockI2C.setClock(ADS1X15_I2C_CLOCK_SPEED); +#endif /* ADS1X15_I2C_CLOCK_SPEED */ + + struct _ADS1X15Measurements m = getMeasurements(); + +#ifdef ADS1X15_I2C_CLOCK_SPEED + reClockI2C.restoreClock(); +#endif /* ADS1X15_I2C_CLOCK_SPEED */ + + switch (_deviceType) { + case ScanI2C::DeviceType::ADS1X15: { + measurement->variant.environment_metrics.has_adc_voltage_ch0 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch1 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch2 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch3 = true; + + measurement->variant.environment_metrics.adc_voltage_ch0 = m.measurements[0].voltage; + measurement->variant.environment_metrics.adc_voltage_ch1 = m.measurements[1].voltage; + measurement->variant.environment_metrics.adc_voltage_ch2 = m.measurements[2].voltage; + measurement->variant.environment_metrics.adc_voltage_ch3 = m.measurements[3].voltage; + + LOG_DEBUG( + "Got %s readings: adc_voltage_ch0=%f, adc_voltage_ch1=%f, adc_voltage_ch2=%f, adc_voltage_ch3=%f", sensorName, + measurement->variant.environment_metrics.adc_voltage_ch0, measurement->variant.environment_metrics.adc_voltage_ch1, + measurement->variant.environment_metrics.adc_voltage_ch2, measurement->variant.environment_metrics.adc_voltage_ch3); + + break; + } + case ScanI2C::DeviceType::ADS1X15_ALT: { + measurement->variant.environment_metrics.has_adc_voltage_ch4 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch5 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch6 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch7 = true; + + measurement->variant.environment_metrics.adc_voltage_ch4 = m.measurements[0].voltage; + measurement->variant.environment_metrics.adc_voltage_ch5 = m.measurements[1].voltage; + measurement->variant.environment_metrics.adc_voltage_ch6 = m.measurements[2].voltage; + measurement->variant.environment_metrics.adc_voltage_ch7 = m.measurements[3].voltage; + + LOG_DEBUG( + "Got %s readings: adc_voltage_ch4=%f, adc_voltage_ch5=%f, adc_voltage_ch6=%f, adc_voltage_ch7=%f", sensorName, + measurement->variant.environment_metrics.adc_voltage_ch4, measurement->variant.environment_metrics.adc_voltage_ch5, + measurement->variant.environment_metrics.adc_voltage_ch6, measurement->variant.environment_metrics.adc_voltage_ch7); + + break; + } + default: { + measurement->variant.environment_metrics.has_adc_voltage_ch0 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch1 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch2 = true; + measurement->variant.environment_metrics.has_adc_voltage_ch3 = true; + + measurement->variant.environment_metrics.adc_voltage_ch0 = m.measurements[0].voltage; + measurement->variant.environment_metrics.adc_voltage_ch1 = m.measurements[1].voltage; + measurement->variant.environment_metrics.adc_voltage_ch2 = m.measurements[2].voltage; + measurement->variant.environment_metrics.adc_voltage_ch3 = m.measurements[3].voltage; + + LOG_DEBUG( + "Got %s readings: adc_voltage_ch0=%f, adc_voltage_ch1=%f, adc_voltage_ch2=%f, adc_voltage_ch3=%f", sensorName, + measurement->variant.environment_metrics.adc_voltage_ch0, measurement->variant.environment_metrics.adc_voltage_ch1, + measurement->variant.environment_metrics.adc_voltage_ch2, measurement->variant.environment_metrics.adc_voltage_ch3); + + break; + } + } + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h new file mode 100644 index 000000000..4d56e2db7 --- /dev/null +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h @@ -0,0 +1,52 @@ +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../detect/ReClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +#define ADS1X15_I2C_CLOCK_SPEED 100000 +// ADS1X15 has no practical way to be detected. Use this to toggle +// between ADS1015 (0) or ADS1115 (1) +#ifndef MESHTASTIC_ADC_ADS1115 +#define MESHTASTIC_ADC_ADS1115 1 +#endif + +class ADS1X15Sensor : public TelemetrySensor +{ + private: +#if MESHTASTIC_ADC_ADS1115 + Adafruit_ADS1115 ads1x15{}; +#else + Adafruit_ADS1015 ads1x15{}; +#endif + +#ifdef ADS1X15_I2C_CLOCK_SPEED + ReClockI2C reClockI2C; +#endif + ScanI2C::DeviceType _deviceType{}; + + // get a single measurement for a channel + struct _ADS1X15Measurement getMeasurement(uint8_t ch); + + // get all measurements for all channels + struct _ADS1X15Measurements getMeasurements(); + + public: + ADS1X15Sensor(); + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +struct _ADS1X15Measurement { + float voltage; +}; + +struct _ADS1X15Measurements { + // ADS1X15 has 4 channels + struct _ADS1X15Measurement measurements[4]; +}; + +#endif