From 6f58170acef595f59c19313dcfd52f55cb623988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xose=20P=C3=A9rez?= Date: Sat, 16 Dec 2017 10:00:32 +0100 Subject: [PATCH] Support for energy monitor based on ADS1115, still WIP --- code/espurna/config/general.h | 2 +- code/espurna/config/prototypes.h | 3 + code/espurna/config/sensors.h | 25 +- code/espurna/sensor.ino | 7 +- code/espurna/sensors/EmonADC121Sensor.h | 2 +- code/espurna/sensors/EmonADS1115Sensor.h | 280 +++++++++++++++++++++++ code/espurna/sensors/EmonSensor.h | 9 +- code/platformio.ini | 1 + 8 files changed, 318 insertions(+), 11 deletions(-) create mode 100644 code/espurna/sensors/EmonADS1115Sensor.h diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 4cff76a0..5a3bb2cd 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -667,7 +667,7 @@ PROGMEM const char* const custom_reset_string[] = { #define I2C_SUPPORT 1 // I2C enabled (1.98Kb) #endif -#define I2C_USE_BRZO 1 // Use brzo_i2c library or standard Wire +#define I2C_USE_BRZO 0 // Use brzo_i2c library or standard Wire #ifndef I2C_SDA_PIN #define I2C_SDA_PIN SDA // SDA GPIO (Sonoff => 4) diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 135814c2..6d76cd7a 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -78,6 +78,9 @@ template bool idbSend(const char * topic, unsigned char id, T payloa #if DS18B20_SUPPORT #include #endif +#if EMON_ADS1115_SUPPORT +#include +#endif // ----------------------------------------------------------------------------- // Utils diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index 44df8056..b9de94a4 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -142,8 +142,9 @@ // Energy Monitor //-------------------------------------------------------------------------------- -#define EMON_MAX_SAMPLES 1500 // Max number of samples to get -#define EMON_MAX_TIME 200 // Max time in ms to sample +#define EMON_MAX_SAMPLES 1000 // Max number of samples to get +#define EMON_MAX_TIME 500 // Max time in ms to sample +#define EMON_FILTER_SPEED 512 // Mobile average filter speed #define EMON_MAINS_VOLTAGE 230 // Mains voltage //-------------------------------------------------------------------------------- @@ -170,16 +171,32 @@ //-------------------------------------------------------------------------------- #ifndef EMON_ADC121_SUPPORT -#define EMON_ADC121_SUPPORT 1 // Do not build support by default +#define EMON_ADC121_SUPPORT 0 // Do not build support by default #endif #define EMON_ADC121_I2C_ADDRESS 0x50 // I2C address of the ADC121 -#define EMON_ADC121_MAINS_VOLTAGE 230 // Mains voltage #define EMON_ADC121_CURRENT_RATIO 30 // Current ratio in the clamp (30V/1A) #define EMON_ADC121_ADC_BITS 12 // ADC depth #define EMON_ADC121_REFERENCE_VOLTAGE 3.3 // Reference voltage of the ADC +//-------------------------------------------------------------------------------- +// Energy Monitor based on ADS1115 +// Enable support by passing EMON_ADS1115_SUPPORT=1 build flag +//-------------------------------------------------------------------------------- + +#ifndef EMON_ADS1115_SUPPORT +#define EMON_ADS1115_SUPPORT 1 // Do not build support by default +#endif + +#define EMON_ADS1115_PORT_MASK 0x08 // A0=1 A1=2 A2=4 A4=8 +#define EMON_ADS1115_I2C_ADDRESS 0x48 // I2C address of the ADS1115 + +#define EMON_ADS1115_CURRENT_RATIO 30 // Current ratio in the clamp (30V/1A) +#define EMON_ADS1115_ADC_BITS 16 // ADC depth +#define EMON_ADS1115_GAIN ADS1115_PGA_4P096 +#define EMON_ADS1115_REFERENCE_VOLTAGE 8.192 // Double the gain for peak-to-peak + //-------------------------------------------------------------------------------- // Internal power montior // Enable support by passing ADC_VCC_ENABLED=1 build flag diff --git a/code/espurna/sensor.ino b/code/espurna/sensor.ino index 401421e0..30cf144b 100644 --- a/code/espurna/sensor.ino +++ b/code/espurna/sensor.ino @@ -248,6 +248,11 @@ void sensorInit() { sensorRegister(new EmonADC121Sensor(EMON_ADC121_I2C_ADDRESS, EMON_MAINS_VOLTAGE, EMON_ADC121_ADC_BITS, EMON_ADC121_REFERENCE_VOLTAGE, EMON_ADC121_CURRENT_RATIO)); #endif + #if EMON_ADS1115_SUPPORT + #include "sensors/EmonADS1115Sensor.h" + sensorRegister(new EmonADS1115Sensor(EMON_ADS1115_I2C_ADDRESS, EMON_ADS1115_PORT_MASK, EMON_MAINS_VOLTAGE, EMON_ADS1115_ADC_BITS, EMON_ADS1115_REFERENCE_VOLTAGE, EMON_ADS1115_CURRENT_RATIO)); + #endif + #if COUNTER_SUPPORT if (_sensor_isr == 0xFF) { #include "sensors/EventSensor.h" @@ -354,7 +359,7 @@ void sensorLoop() { { dtostrf(current, 1-sizeof(buffer), decimals, buffer); DEBUG_MSG("[SENSOR] %s - %s: %s%s\n", - magnitude.sensor->name().c_str(), + magnitude.sensor->slot(magnitude.local).c_str(), _sensorTopic(magnitude.type).c_str(), buffer, _sensorUnits(magnitude.type).c_str() diff --git a/code/espurna/sensors/EmonADC121Sensor.h b/code/espurna/sensors/EmonADC121Sensor.h index d46f544e..8f6b031f 100644 --- a/code/espurna/sensors/EmonADC121Sensor.h +++ b/code/espurna/sensors/EmonADC121Sensor.h @@ -56,7 +56,7 @@ class EmonADC121Sensor : public EmonSensor { // Descriptive name of the sensor String name() { - char buffer[20]; + char buffer[30]; snprintf(buffer, sizeof(buffer), "EMON @ ADC121 @ I2C (0x%02X)", _address); return String(buffer); } diff --git a/code/espurna/sensors/EmonADS1115Sensor.h b/code/espurna/sensors/EmonADS1115Sensor.h new file mode 100644 index 00000000..38277d1a --- /dev/null +++ b/code/espurna/sensors/EmonADS1115Sensor.h @@ -0,0 +1,280 @@ +// ----------------------------------------------------------------------------- +// Energy monitor sensor +// ----------------------------------------------------------------------------- + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" +#include "EmonSensor.h" + +#include + +/* +#if I2C_USE_BRZO +#include +#else +#include +#endif + +#define ADS1015_CONVERSIONDELAY (1) +#define ADS1115_CONVERSIONDELAY (8) + +#define ADS1015_BIT_SHIFT (4) +#define ADS1115_BIT_SHIFT (0) + +#define ADS1015_REG_POINTER_MASK (0x03) +#define ADS1015_REG_POINTER_CONVERT (0x00) +#define ADS1015_REG_POINTER_CONFIG (0x01) +#define ADS1015_REG_POINTER_LOWTHRESH (0x02) +#define ADS1015_REG_POINTER_HITHRESH (0x03) + +#define ADS1015_REG_CONFIG_OS_MASK (0x8000) +#define ADS1015_REG_CONFIG_OS_SINGLE (0x8000) // Write: Set to start a single-conversion +#define ADS1015_REG_CONFIG_OS_BUSY (0x0000) // Read: Bit = 0 when conversion is in progress +#define ADS1015_REG_CONFIG_OS_NOTBUSY (0x8000) // Read: Bit = 1 when device is not performing a conversion + +#define ADS1015_REG_CONFIG_MUX_MASK (0x7000) +#define ADS1015_REG_CONFIG_MUX_DIFF_0_1 (0x0000) // Differential P = AIN0, N = AIN1 (default) +#define ADS1015_REG_CONFIG_MUX_DIFF_0_3 (0x1000) // Differential P = AIN0, N = AIN3 +#define ADS1015_REG_CONFIG_MUX_DIFF_1_3 (0x2000) // Differential P = AIN1, N = AIN3 +#define ADS1015_REG_CONFIG_MUX_DIFF_2_3 (0x3000) // Differential P = AIN2, N = AIN3 +#define ADS1015_REG_CONFIG_MUX_SINGLE_0 (0x4000) // Single-ended AIN0 +#define ADS1015_REG_CONFIG_MUX_SINGLE_1 (0x5000) // Single-ended AIN1 +#define ADS1015_REG_CONFIG_MUX_SINGLE_2 (0x6000) // Single-ended AIN2 +#define ADS1015_REG_CONFIG_MUX_SINGLE_3 (0x7000) // Single-ended AIN3 + +#define ADS1015_REG_CONFIG_PGA_MASK (0x0E00) +#define ADS1015_REG_CONFIG_PGA_6_144V (0x0000) // +/-6.144V range = Gain 2/3 +#define ADS1015_REG_CONFIG_PGA_4_096V (0x0200) // +/-4.096V range = Gain 1 +#define ADS1015_REG_CONFIG_PGA_2_048V (0x0400) // +/-2.048V range = Gain 2 (default) +#define ADS1015_REG_CONFIG_PGA_1_024V (0x0600) // +/-1.024V range = Gain 4 +#define ADS1015_REG_CONFIG_PGA_0_512V (0x0800) // +/-0.512V range = Gain 8 +#define ADS1015_REG_CONFIG_PGA_0_256V (0x0A00) // +/-0.256V range = Gain 16 + +#define ADS1015_REG_CONFIG_MODE_MASK (0x0100) +#define ADS1015_REG_CONFIG_MODE_CONTIN (0x0000) // Continuous conversion mode +#define ADS1015_REG_CONFIG_MODE_SINGLE (0x0100) // Power-down single-shot mode (default) + +#define ADS1015_REG_CONFIG_DR_MASK (0x00E0) +#define ADS1015_REG_CONFIG_DR_128SPS (0x0000) // 128 samples per second +#define ADS1015_REG_CONFIG_DR_250SPS (0x0020) // 250 samples per second +#define ADS1015_REG_CONFIG_DR_490SPS (0x0040) // 490 samples per second +#define ADS1015_REG_CONFIG_DR_920SPS (0x0060) // 920 samples per second +#define ADS1015_REG_CONFIG_DR_1600SPS (0x0080) // 1600 samples per second (default) +#define ADS1015_REG_CONFIG_DR_2400SPS (0x00A0) // 2400 samples per second +#define ADS1015_REG_CONFIG_DR_3300SPS (0x00C0) // 3300 samples per second + +#define ADS1015_REG_CONFIG_CMODE_MASK (0x0010) +#define ADS1015_REG_CONFIG_CMODE_TRAD (0x0000) // Traditional comparator with hysteresis (default) +#define ADS1015_REG_CONFIG_CMODE_WINDOW (0x0010) // Window comparator + +#define ADS1015_REG_CONFIG_CPOL_MASK (0x0008) +#define ADS1015_REG_CONFIG_CPOL_ACTVLOW (0x0000) // ALERT/RDY pin is low when active (default) +#define ADS1015_REG_CONFIG_CPOL_ACTVHI (0x0008) // ALERT/RDY pin is high when active + +#define ADS1015_REG_CONFIG_CLAT_MASK (0x0004) // Determines if ALERT/RDY pin latches once asserted +#define ADS1015_REG_CONFIG_CLAT_NONLAT (0x0000) // Non-latching comparator (default) +#define ADS1015_REG_CONFIG_CLAT_LATCH (0x0004) // Latching comparator + +#define ADS1015_REG_CONFIG_CQUE_MASK (0x0003) +#define ADS1015_REG_CONFIG_CQUE_1CONV (0x0000) // Assert ALERT/RDY after one conversions +#define ADS1015_REG_CONFIG_CQUE_2CONV (0x0001) // Assert ALERT/RDY after two conversions +#define ADS1015_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions +#define ADS1015_REG_CONFIG_CQUE_NONE (0x0003) // Disable the comparator and put ALERT/RDY in high state (default) +*/ + +#define EMON_ADS1115_PORTS 4 +#define EMON_ADS1115_MAGNITUDES_PER_PORT 2 + +class EmonADS1115Sensor : public EmonSensor { + + public: + + EmonADS1115Sensor(unsigned char address, unsigned char mask, double voltage, unsigned char bits, double ref, double ratio): EmonSensor(voltage, bits, ref, ratio) { + + // Cache + _address = address; + _mask = mask; + _ports = 0; + while (mask) { + if (mask & 0x01) ++_ports; + mask = mask >> 1; + } + _count = _ports * EMON_ADS1115_MAGNITUDES_PER_PORT; + + // Initialize + _ads = new ADS1115(_address); + _ads->initialize(); + _ads->setMode(ADS1115_MODE_SINGLESHOT); + _ads->setRate(ADS1115_RATE_860); + _ads->setGain(ADS1115_PGA_4P096); + _ads->setConversionReadyPinMode(); + + // warmup + read(_address); + + } + + // Descriptive name of the sensor + String name() { + char buffer[30]; + snprintf(buffer, sizeof(buffer), "EMON @ ADS1115 @ I2C (0x%02X)", _address); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + char buffer[35]; + unsigned char port = getPort(index / EMON_ADS1115_MAGNITUDES_PER_PORT); + snprintf(buffer, sizeof(buffer), "EMON @ ADS1115 (A%d) @ I2C (0x%02X)", port, _address); + return String(buffer); + } + + // Type for slot # index + magnitude_t type(unsigned char index) { + if (index < _count) { + _error = SENSOR_ERROR_OK; + unsigned char port = getPort(index / EMON_ADS1115_MAGNITUDES_PER_PORT); + unsigned char magnitude = index % EMON_ADS1115_MAGNITUDES_PER_PORT; + if (magnitude == 0) return MAGNITUDE_CURRENT; + if (magnitude == 1) return MAGNITUDE_POWER_APPARENT; + //if (magnitude == 2) return MAGNITUDE_ENERGY; + //if (magnitude == 3) return MAGNITUDE_ENERGY_DELTA; + } + _error = SENSOR_ERROR_OUT_OF_RANGE; + return MAGNITUDE_NONE; + } + + void pre() { + //static unsigned long last = 0; + for (unsigned char index=0; index<_ports; index++) { + unsigned char port = getPort(index); + _current[port] = read(port); + //if (last > 0) { + // _delta[port] = _current[port] * _voltage * (millis() - last) / 1000; + //} + //_energy[port] += _delta[port]; + } + //last = millis(); + } + + // Current value for slot # index + double value(unsigned char index) { + + if (index < _count) { + _error = SENSOR_ERROR_OK; + unsigned char port = getPort(index / EMON_ADS1115_MAGNITUDES_PER_PORT); + unsigned char magnitude = index % EMON_ADS1115_MAGNITUDES_PER_PORT; + if (magnitude == 0) return _current[port]; + if (magnitude == 1) return _current[port] * _voltage; + //if (magnitude == 2) return _energy[port]; + //if (magnitude == 3) return _delta[port]; + } + + _error = SENSOR_ERROR_OUT_OF_RANGE; + return 0; + + } + + protected: + + unsigned char getPort(unsigned char index) { + unsigned char count = 0; + unsigned char bit = 1; + for (unsigned char i=0; isetMultiplexer(port + 4); + return _ads->getConversion(true); + } + return 0; + } + + /* + unsigned int readADC(unsigned char port) { + + if (port > 3) return 0; + port = 3; + unsigned int value; + + // Start with default values + uint16_t config = 0; + + config |= ADS1015_REG_CONFIG_CQUE_NONE; // Disable the comparator (default val) + config |= ADS1015_REG_CONFIG_CLAT_NONLAT; // Non-latching (default val) + config |= ADS1015_REG_CONFIG_CPOL_ACTVLOW; // Alert/Rdy active low (default val) + config |= ADS1015_REG_CONFIG_CMODE_TRAD; // Traditional comparator (default val) + config |= ADS1015_REG_CONFIG_DR_1600SPS; // 1600 samples per second (default) + config |= ADS1015_REG_CONFIG_MODE_SINGLE; // Single-shot mode (default) + config |= ADS1015_REG_CONFIG_OS_SINGLE; // Set 'start single-conversion' bit + config |= EMON_ADS1115_GAIN; // Set PGA/voltage range + config |= ((port + 4) << 12); // Set single-ended input channel + + Serial.println(config); + + // Write config register to the ADC + #if I2C_USE_BRZO + uint8_t buffer[3]; + buffer[0] = ADS1015_REG_POINTER_CONFIG; + buffer[1] = config >> 8; + buffer[2] = config & 0xFF; + brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY); + brzo_i2c_write(buffer, 3, false); + //brzo_i2c_end_transaction(); + #else + Wire.beginTransmission(_address); + Wire.write((uint8_t) ADS1015_REG_POINTER_CONFIG); + Wire.write((uint8_t) (config >> 8)); + Wire.write((uint8_t) (config & 0xFF)); + Wire.endTransmission(); + #endif + + // Wait for the conversion to complete + unsigned long start = millis(); + while (millis() - start < ADS1115_CONVERSIONDELAY) delay(1); + + // Read the conversion results + // Shift 12-bit results right 4 bits for the ADS1015 + #if I2C_USE_BRZO + buffer[0] = ADS1015_REG_POINTER_CONVERT; + //brzo_i2c_start_transaction(_address, I2C_SCL_FREQUENCY); + brzo_i2c_write(buffer, 1, false); + brzo_i2c_read(buffer, 2, false); + brzo_i2c_end_transaction(); + value = (buffer[0] & 0x0F) << 8; + value |= buffer[1]; + #else + Wire.beginTransmission(_address); + Wire.write(ADS1015_REG_POINTER_CONVERT); + Wire.endTransmission(); + Wire.requestFrom(_address, (unsigned char) 2); + value = Wire.read() << 8; + value |= Wire.read(); + #endif + + return value; + + } + */ + + ADS1115 * _ads; + + unsigned char _address; + unsigned char _mask; + unsigned char _ports; + double _current[EMON_ADS1115_PORTS] = {0, 0, 0, 0}; + //unsigned long _energy[EMON_ADS1115_PORTS] = {0, 0, 0, 0}; + //unsigned long _delta[EMON_ADS1115_PORTS] = {0, 0, 0, 0}; + + +}; diff --git a/code/espurna/sensors/EmonSensor.h b/code/espurna/sensors/EmonSensor.h index 1ec3f1a3..c2e4c80d 100644 --- a/code/espurna/sensors/EmonSensor.h +++ b/code/espurna/sensors/EmonSensor.h @@ -28,7 +28,7 @@ class EmonSensor : public BaseSensor { #if EMON_DEBUG Serial.print("[EMON] Current ratio: "); Serial.println(ratio); - Serial.print("[EMON] Ref. Voltage: "); Serial.println(_voltage); + Serial.print("[EMON] Ref. Voltage: "); Serial.println(ref); Serial.print("[EMON] ADC Counts: "); Serial.println(_adc_counts); Serial.print("[EMON] Current factor: "); Serial.println(_current_factor); Serial.print("[EMON] Multiplier: "); Serial.println(_multiplier); @@ -69,7 +69,7 @@ class EmonSensor : public BaseSensor { if (sample < min) min = sample; // Digital low pass filter extracts the VDC offset - _pivot = (_pivot + (sample - _pivot) / _adc_counts); + _pivot = (_pivot + (sample - _pivot) / EMON_FILTER_SPEED); filtered = sample - _pivot; // Root-mean-square method @@ -101,7 +101,8 @@ class EmonSensor : public BaseSensor { #endif // Check timing - if (time_span > EMON_MAX_TIME) { + if ((time_span > EMON_MAX_TIME) + || ((time_span < EMON_MAX_TIME) && (_samples < EMON_MAX_SAMPLES))) { _samples = (_samples * EMON_MAX_TIME) / time_span; } @@ -110,7 +111,7 @@ class EmonSensor : public BaseSensor { } double _voltage; - unsigned int _adc_counts; + unsigned long _adc_counts; unsigned int _multiplier = 1; double _current_factor; double _pivot; diff --git a/code/platformio.ini b/code/platformio.ini index 0153bb84..530ff851 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -21,6 +21,7 @@ lib_deps = NtpClientLib OneWire Brzo I2C + I2Cdevlib-ADS1115 EspSoftwareSerial https://bitbucket.org/xoseperez/justwifi.git#1.1.4 https://bitbucket.org/xoseperez/hlw8012.git#1.1.0