From 7cac609c06de3ebe9481716e384204f41d69006f Mon Sep 17 00:00:00 2001 From: Dimitry Date: Fri, 21 Oct 2022 04:25:36 +0300 Subject: [PATCH] Add ADS1115 usermod (#2752) Co-authored-by: Christian Schwinne --- platformio.ini | 5 + usermods/ADS1115_v2/ChannelSettings.h | 15 ++ usermods/ADS1115_v2/readme.md | 10 + usermods/ADS1115_v2/usermod_ads1115.h | 255 ++++++++++++++++++++++++++ wled00/const.h | 1 + wled00/usermods_list.cpp | 8 + 6 files changed, 294 insertions(+) create mode 100644 usermods/ADS1115_v2/ChannelSettings.h create mode 100644 usermods/ADS1115_v2/readme.md create mode 100644 usermods/ADS1115_v2/usermod_ads1115.h diff --git a/platformio.ini b/platformio.ini index f128ed6a..ee68b263 100644 --- a/platformio.ini +++ b/platformio.ini @@ -115,6 +115,8 @@ build_flags = -D DECODE_LG=true -DWLED_USE_MY_CONFIG ; -D USERMOD_SENSORSTOMQTT + #For ADS1115 sensor uncomment following + ; -D USERMOD_ADS1115 build_unflags = @@ -177,6 +179,9 @@ lib_deps = ; adafruit/Adafruit BMP280 Library @ 2.1.0 ; adafruit/Adafruit CCS811 Library @ 1.0.4 ; adafruit/Adafruit Si7021 Library @ 1.4.0 + #For ADS1115 sensor uncomment following + ; adafruit/Adafruit BusIO @ 1.13.2 + ; adafruit/Adafruit ADS1X15 @ 2.4.0 extra_scripts = ${scripts_defaults.extra_scripts} diff --git a/usermods/ADS1115_v2/ChannelSettings.h b/usermods/ADS1115_v2/ChannelSettings.h new file mode 100644 index 00000000..26538a14 --- /dev/null +++ b/usermods/ADS1115_v2/ChannelSettings.h @@ -0,0 +1,15 @@ +#include "wled.h" + +namespace ADS1115 +{ + struct ChannelSettings { + const String settingName; + bool isEnabled; + String name; + String units; + const uint16_t mux; + float multiplier; + float offset; + uint8_t decimals; + }; +} \ No newline at end of file diff --git a/usermods/ADS1115_v2/readme.md b/usermods/ADS1115_v2/readme.md new file mode 100644 index 00000000..9b778809 --- /dev/null +++ b/usermods/ADS1115_v2/readme.md @@ -0,0 +1,10 @@ +# ADS1115 16-Bit ADC with four inputs + +This usermod will read from an ADS1115 ADC. The voltages are displayed in the Info section of the web UI. + +Configuration is all completed via the Usermod menu. There are no settings to set in code! + +## Installation + +Add the build flag `-D USERMOD_ADS1115` to your platformio environment. +Uncomment libraries with comment `#For ADS1115 sensor uncomment following` diff --git a/usermods/ADS1115_v2/usermod_ads1115.h b/usermods/ADS1115_v2/usermod_ads1115.h new file mode 100644 index 00000000..5e2b4b27 --- /dev/null +++ b/usermods/ADS1115_v2/usermod_ads1115.h @@ -0,0 +1,255 @@ +#pragma once + +#include "wled.h" +#include +#include + +#include "ChannelSettings.h" + +using namespace ADS1115; + +class ADS1115Usermod : public Usermod { + public: + void setup() { + ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V + + if (!ads.begin()) { + Serial.println("Failed to initialize ADS"); + return; + } + + if (!initChannel()) { + isInitialized = true; + return; + } + + startReading(); + + isEnabled = true; + isInitialized = true; + } + + void loop() { + if (isEnabled && millis() - lastTime > loopInterval) { + lastTime = millis(); + + // If we don't have new data, skip this iteration. + if (!ads.conversionComplete()) { + return; + } + + updateResult(); + moveToNextChannel(); + startReading(); + } + } + + void addToJsonInfo(JsonObject& root) + { + if (!isEnabled) { + return; + } + + JsonObject user = root[F("u")]; + if (user.isNull()) user = root.createNestedObject(F("u")); + + for (uint8_t i = 0; i < channelsCount; i++) { + ChannelSettings* settingsPtr = &(channelSettings[i]); + + if (!settingsPtr->isEnabled) { + continue; + } + + JsonArray lightArr = user.createNestedArray(settingsPtr->name); //name + float value = round((readings[i] + settingsPtr->offset) * settingsPtr->multiplier, settingsPtr->decimals); + lightArr.add(value); //value + lightArr.add(" " + settingsPtr->units); //unit + } + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(F("ADC ADS1115")); + + for (uint8_t i = 0; i < channelsCount; i++) { + ChannelSettings* settingsPtr = &(channelSettings[i]); + JsonObject channel = top.createNestedObject(settingsPtr->settingName); + channel[F("Enabled")] = settingsPtr->isEnabled; + channel[F("Name")] = settingsPtr->name; + channel[F("Units")] = settingsPtr->units; + channel[F("Multiplier")] = settingsPtr->multiplier; + channel[F("Offset")] = settingsPtr->offset; + channel[F("Decimals")] = settingsPtr->decimals; + } + + top[F("Loop Interval")] = loopInterval; + } + + bool readFromConfig(JsonObject& root) + { + JsonObject top = root[F("ADC ADS1115")]; + + bool configComplete = !top.isNull(); + bool hasEnabledChannels = false; + + for (uint8_t i = 0; i < channelsCount && configComplete; i++) { + ChannelSettings* settingsPtr = &(channelSettings[i]); + JsonObject channel = top[settingsPtr->settingName]; + + configComplete &= !channel.isNull(); + + configComplete &= getJsonValue(channel[F("Enabled")], settingsPtr->isEnabled); + configComplete &= getJsonValue(channel[F("Name")], settingsPtr->name); + configComplete &= getJsonValue(channel[F("Units")], settingsPtr->units); + configComplete &= getJsonValue(channel[F("Multiplier")], settingsPtr->multiplier); + configComplete &= getJsonValue(channel[F("Offset")], settingsPtr->offset); + configComplete &= getJsonValue(channel[F("Decimals")], settingsPtr->decimals); + + hasEnabledChannels |= settingsPtr->isEnabled; + } + + configComplete &= getJsonValue(top[F("Loop Interval")], loopInterval); + + isEnabled = isInitialized && configComplete && hasEnabledChannels; + + return configComplete; + } + + uint16_t getId() + { + return USERMOD_ID_ADS1115; + } + + private: + static const uint8_t channelsCount = 8; + + ChannelSettings channelSettings[channelsCount] = { + { + "Differential reading from AIN0 (P) and AIN1 (N)", + false, + "Differential AIN0 AIN1", + "V", + ADS1X15_REG_CONFIG_MUX_DIFF_0_1, + 1, + 0, + 3 + }, + { + "Differential reading from AIN0 (P) and AIN3 (N)", + false, + "Differential AIN0 AIN3", + "V", + ADS1X15_REG_CONFIG_MUX_DIFF_0_3, + 1, + 0, + 3 + }, + { + "Differential reading from AIN1 (P) and AIN3 (N)", + false, + "Differential AIN1 AIN3", + "V", + ADS1X15_REG_CONFIG_MUX_DIFF_1_3, + 1, + 0, + 3 + }, + { + "Differential reading from AIN2 (P) and AIN3 (N)", + false, + "Differential AIN2 AIN3", + "V", + ADS1X15_REG_CONFIG_MUX_DIFF_2_3, + 1, + 0, + 3 + }, + { + "Single-ended reading from AIN0", + false, + "Single-ended AIN0", + "V", + ADS1X15_REG_CONFIG_MUX_SINGLE_0, + 1, + 0, + 3 + }, + { + "Single-ended reading from AIN1", + false, + "Single-ended AIN1", + "V", + ADS1X15_REG_CONFIG_MUX_SINGLE_1, + 1, + 0, + 3 + }, + { + "Single-ended reading from AIN2", + false, + "Single-ended AIN2", + "V", + ADS1X15_REG_CONFIG_MUX_SINGLE_2, + 1, + 0, + 3 + }, + { + "Single-ended reading from AIN3", + false, + "Single-ended AIN3", + "V", + ADS1X15_REG_CONFIG_MUX_SINGLE_3, + 1, + 0, + 3 + }, + }; + float readings[channelsCount] = {0, 0, 0, 0, 0, 0, 0, 0}; + + unsigned long loopInterval = 1000; + unsigned long lastTime = 0; + + Adafruit_ADS1115 ads; + uint8_t activeChannel; + + bool isEnabled = false; + bool isInitialized = false; + + static float round(float value, uint8_t decimals) { + return roundf(value * powf(10, decimals)) / powf(10, decimals); + } + + bool initChannel() { + for (uint8_t i = 0; i < channelsCount; i++) { + if (channelSettings[i].isEnabled) { + activeChannel = i; + return true; + } + } + + activeChannel = 0; + return false; + } + + void moveToNextChannel() { + uint8_t oldActiveChannel = activeChannel; + + do + { + if (++activeChannel >= channelsCount){ + activeChannel = 0; + } + } + while (!channelSettings[activeChannel].isEnabled && oldActiveChannel != activeChannel); + } + + void startReading() { + ads.startADCReading(channelSettings[activeChannel].mux, /*continuous=*/false); + } + + void updateResult() { + int16_t results = ads.getLastConversionResults(); + readings[activeChannel] = ads.computeVolts(results); + } +}; \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 874776e3..2d4cce43 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -95,6 +95,7 @@ #define USERMOD_ID_AUDIOREACTIVE 32 //Usermod "audioreactive.h" #define USERMOD_ID_ANALOG_CLOCK 33 //Usermod "Analog_Clock.h" #define USERMOD_ID_PING_PONG_CLOCK 34 //Usermod "usermod_v2_ping_pong_clock.h" +#define USERMOD_ID_ADS1115 35 //Usermod "usermod_ads1115.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index ed2a919a..a39bd7aa 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -147,6 +147,10 @@ #include "../usermods/usermod_v2_ping_pong_clock/usermod_v2_ping_pong_clock.h" #endif +#ifdef USERMOD_ADS1115 +#include "../usermods/ADS1115_v2/usermod_ads1115.h" +#endif + void registerUsermods() { /* @@ -282,4 +286,8 @@ void registerUsermods() #ifdef USERMOD_PING_PONG_CLOCK usermods.add(new PingPongClockUsermod()); #endif + + #ifdef USERMOD_ADS1115 + usermods.add(new ADS1115Usermod()); + #endif }