diff --git a/src/sensors/TurnerTurbidityPlus.cpp b/src/sensors/TurnerTurbidityPlus.cpp new file mode 100644 index 000000000..4229993b7 --- /dev/null +++ b/src/sensors/TurnerTurbidityPlus.cpp @@ -0,0 +1,160 @@ +/** + * @file TurnerTurbidityPlus.cpp + * @copyright 2020 Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino + * @author Sara Geleskie Damiano + * Adapted from TurnerCyclops by Matt Barney + * @brief Implements the TurnerTurbidityPlus class. + */ + + +#include "TurnerTurbidityPlus.h" +#include + + +// The constructor - need the power pin, the data pin, and the calibration info +TurnerTurbidityPlus::TurnerTurbidityPlus(int8_t powerPin, int8_t wiperTriggerPin, adsDiffMux_t adsDiffMux, + float conc_std, float volt_std, float volt_blank, + uint8_t i2cAddress, adsGain_t PGA_gain, + uint8_t measurementsToAverage, float voltageDividerFactor) + : Sensor("TurnerTurbidityPlus", TURBIDITY_PLUS_NUM_VARIABLES, TURBIDITY_PLUS_WARM_UP_TIME_MS, + TURBIDITY_PLUS_STABILIZATION_TIME_MS, TURBIDITY_PLUS_MEASUREMENT_TIME_MS, + powerPin, -1, measurementsToAverage) { + _wiperTriggerPin = wiperTriggerPin; + _adsDiffMux = adsDiffMux; + _conc_std = conc_std; + _volt_std = volt_std; + _volt_blank = volt_blank; + _i2cAddress = i2cAddress; + _PGA_gain = PGA_gain; + _voltageDividerFactor = voltageDividerFactor; +} +// Destructor +TurnerTurbidityPlus::~TurnerTurbidityPlus() {} + + +String TurnerTurbidityPlus::getSensorLocation(void) { +#ifndef MS_USE_ADS1015 + String sensorLocation = F("ADS1115_0x"); +#else + String sensorLocation = F("ADS1015_0x"); +#endif + sensorLocation += String(_i2cAddress, HEX); + sensorLocation += F("_adsDiffMux"); + sensorLocation += String(_adsDiffMux); + return sensorLocation; +} + +void TurnerTurbidityPlus::runWiper() { + // Turner Tubidity Plus wiper requires a 50ms LOW signal pulse to trigger one wiper rotation. + // Also note: I was unable to trigger multiple rotations without pausing for ~540ms between them. + MS_DBG(F("Initate TurbidityPlus wiper on"), getSensorLocation()); + digitalWrite(_wiperTriggerPin, LOW); + delay(50); + digitalWrite(_wiperTriggerPin, HIGH); + // It takes ~7.5 sec for a rotation to complete. Wait for that to finish before continuing, + // otherwise the sensor will get powered off before wipe completes, and any reading taken + // during wiper cycle is invalid. + delay(8000); + MS_DBG(F("TurbidityPlus wiper cycle should be finished")); +} + +bool TurnerTurbidityPlus::setup(void) { + // Set up the wiper trigger pin, which is active-LOW. + pinMode(_wiperTriggerPin, OUTPUT); + return Sensor::setup(); +} + +bool TurnerTurbidityPlus::wake(void) { + // Run the wiper before taking a reading + runWiper(); + + return Sensor::wake(); +} + +void TurnerTurbidityPlus::powerDown(void) { + // Set the wiper trigger pin LOW to avoid power drain. + digitalWrite(_wiperTriggerPin, LOW); + return Sensor::powerDown(); +} + +void TurnerTurbidityPlus::powerUp(void) { + // Set the wiper trigger pin HIGH to prepare for wiping. + digitalWrite(_wiperTriggerPin, HIGH); + return Sensor::powerUp(); +} + +bool TurnerTurbidityPlus::addSingleMeasurementResult(void) { + // Variables to store the results in + float adcVoltage = -9999; + float calibResult = -9999; + + // Check a measurement was *successfully* started (status bit 6 set) + // Only go on to get a result if it was + if (bitRead(_sensorStatus, 6)) { + MS_DBG(getSensorNameAndLocation(), F("is reporting:")); + +// Create an Auxillary ADD object +// We create and set up the ADC object here so that each sensor using +// the ADC may set the gain appropriately without effecting others. +#ifndef MS_USE_ADS1015 + Adafruit_ADS1115 ads(_i2cAddress); // Use this for the 16-bit version +#else + Adafruit_ADS1015 ads(_i2cAddress); // Use this for the 12-bit version +#endif + // ADS Library default settings: + // - TI1115 (16 bit) + // - single-shot mode (powers down between conversions) + // - 128 samples per second (8ms conversion time) + // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) + // - TI1015 (12 bit) + // - single-shot mode (powers down between conversions) + // - 1600 samples per second (625µs conversion time) + // - 2/3 gain +/- 6.144V range (limited to VDD +0.3V max) + + + ads.setGain(_PGA_gain); + // Begin ADC + ads.begin(); + + // Print out the calibration curve + MS_DBG(F(" Input calibration Curve:"), _volt_std, F("V at"), _conc_std, + F(". "), _volt_blank, F("V blank.")); + + // Read Analog to Digital Converter (ADC) + // Taking this reading includes the 8ms conversion delay. + // We're using the ADS1115 library's voltsPerBit() to do the bit-to-volts conversion + // for us + adcVoltage = + ads.readADC_Differential(_adsDiffMux) * ads.voltsPerBit() * _voltageDividerFactor; // Getting the reading + MS_DBG(F(" ads.readADC_Differential("), _adsDiffMux, F("):"), + String(adcVoltage, 3)); + + // The ADS1X15 outputs a max value corresponding to Vcc + 0.3V + if (adcVoltage < 5.3 && adcVoltage > -0.3) { + // Skip results out of range + // Apply the unique calibration curve for the given sensor + calibResult = (_conc_std / (_volt_std - _volt_blank)) * + (adcVoltage - _volt_blank); + MS_DBG(F(" calibResult:"), String(calibResult, 3)); + } else { // set invalid voltages back to -9999 + adcVoltage = -9999; + } + } else { + MS_DBG(getSensorNameAndLocation(), F("is not currently measuring!")); + } + + verifyAndAddMeasurementResult(TURBIDITY_PLUS_VAR_NUM, calibResult); + verifyAndAddMeasurementResult(TURBIDITY_PLUS_VOLTAGE_VAR_NUM, adcVoltage); + + // Unset the time stamp for the beginning of this measurement + _millisMeasurementRequested = 0; + // Unset the status bits for a measurement request (bits 5 & 6) + _sensorStatus &= 0b10011111; + + if (adcVoltage < 5.3 && adcVoltage > -0.3) { + return true; + } else { + return false; + } +} diff --git a/src/sensors/TurnerTurbidityPlus.h b/src/sensors/TurnerTurbidityPlus.h new file mode 100644 index 000000000..e60998442 --- /dev/null +++ b/src/sensors/TurnerTurbidityPlus.h @@ -0,0 +1,324 @@ +/** + * @file TurnerTurbidityPlus.h + * @copyright 2020 Stroud Water Research Center + * Part of the EnviroDIY ModularSensors library for Arduino + * @author Sara Geleskie Damiano + * Adapted from TurnerCyclops by Matt Barney + * + * @brief Contains the TurnerTurbidityPlus sensor subclass and the variable subclasses + * TurnerTurbidityPlus_Turbidity and TurnerTurbidityPlus_Voltage. + * + * These are used for the Turner Turbidity Plus. + * + * This depends on the soligen2010 fork of the Adafruit ADS1015 library. + */ + +// Header Guards +#ifndef SRC_SENSORS_TURNERTURBIDITYPLUS_H_ +#define SRC_SENSORS_TURNERTURBIDITYPLUS_H_ + +// Debugging Statement +// #define MS_TURNERTURBIDITYPLUS_DEBUG + +#ifdef MS_TURNERTURBIDITYPLUS_DEBUG +#define MS_DEBUGGING_STD "TurnerTurbidityPlus" +#endif + +// Included Dependencies +#include "ModSensorDebugger.h" +#undef MS_DEBUGGING_STD +#include "VariableBase.h" +#include "SensorBase.h" +#include + +// Sensor Specific Defines +/** @ingroup sensor_turbidity_plus */ +/**@{*/ +/** + * @brief Sensor::_numReturnedValues; the Turbidity Plus can report 2 values. + * + */ +#define TURBIDITY_PLUS_NUM_VARIABLES 2 + +/** + * @anchor sensor_turbidity_plus_timing + * @name Sensor Timing + * The sensor timing for an Turbidity Plus + */ +/**@{*/ +/// @brief Sensor::_warmUpTime_ms; the ADS1115 warms up in 2ms. +#define TURBIDITY_PLUS_WARM_UP_TIME_MS 2 +/** + * @brief Sensor::_stabilizationTime_ms; Turner states that it takes 3 sec settling time from power-on. + */ +#define TURBIDITY_PLUS_STABILIZATION_TIME_MS 3000 +/** + * @brief Sensor::_measurementTime_ms; the ADS1115 completes 860 conversions per + * second, but the wait for the conversion to complete is built into the + * underlying library, so we do not need to wait further here. +*/ +#define TURBIDITY_PLUS_MEASUREMENT_TIME_MS 0 +/**@}*/ + +/* clang-format off */ +/** + * @anchor sensor_turbidity_plus_output + * @name Calibrated Parameter Output + * The primary output variable from an Turbidity Plus + */ +/**@{*/ +/* clang-format on */ +/// Variable number; the primary variable is stored in sensorValues[0]. +#define TURBIDITY_PLUS_VAR_NUM 0 +#ifdef MS_USE_ADS1015 +/// @brief Decimals places in string representation; 1. +#define TURBIDITY_PLUS_RESOLUTION 1 +#else +/// @brief Decimals places in string representation; 5. +#define TURBIDITY_PLUS_RESOLUTION 5 +#endif +/**@}*/ + +/** + * @anchor sensor_turbidity_plus_voltage + * @name Voltage + * The voltage variable from an Turbidity Plus + * - Range is 0 to 3.6V when using an ADS1x15 powered at 3.3V + * - Full sensor range is 0-5V + * - Accuracy: + * - 16-bit ADC (ADS1115): < 0.25% (gain error), <0.25 LSB (offset errror) + * - @m_span{m-dim}(@ref #TURBIDITY_PLUS_VOLTAGE_RESOLUTION = 4)@m_endspan + * - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): < 0.15% + * (gain error), <3 LSB (offset errror) + * - @m_span{m-dim}(@ref #TURBIDITY_PLUS_VOLTAGE_RESOLUTION = 1)@m_endspan + * + * {{ @ref TurnerTurbidityPlus_Voltage::TurnerTurbidityPlus_Voltage }} + */ +/**@{*/ +/// Variable number; voltage is stored in sensorValues[1]. +#define TURBIDITY_PLUS_VOLTAGE_VAR_NUM 1 +/// @brief Variable name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/variablename/); +/// "voltage" +#define TURBIDITY_PLUS_VOLTAGE_VAR_NAME "voltage" +/// @brief Variable unit name in +/// [ODM2 controlled vocabulary](http://vocabulary.odm2.org/units/); "volt" +#define TURBIDITY_PLUS_VOLTAGE_UNIT_NAME "volt" +/// @brief Default variable short code; "TurbidityPlusVoltage" +#define TURBIDITY_PLUS_VOLTAGE_DEFAULT_CODE "TurbidityPlusVoltage" + +#ifdef MS_USE_ADS1015 +/// @brief Decimals places in string representation; voltage should have 1. +/// - Resolution: +/// - 16-bit ADC (ADS1115): 0.125 mV +#define TURBIDITY_PLUS_VOLTAGE_RESOLUTION 1 +#else +/// @brief Decimals places in string representation; voltage should have 4. +/// - Resolution: +/// - 12-bit ADC (ADS1015, using build flag ```MS_USE_ADS1015```): 2 mV +#define TURBIDITY_PLUS_VOLTAGE_RESOLUTION 4 +#endif +/**@}*/ + +/// @brief The assumed address of the ADS1115, 1001 000 (ADDR = GND) +#define ADS1115_ADDRESS 0x48 + +/* clang-format off */ +/** + * @brief The Sensor sub-class for the + * [Turner Turbidity Plus turbidity sensor](@ref sensor_turbidity_plus). + * + * @ingroup sensor_turbidity_plus + */ +/* clang-format on */ +class TurnerTurbidityPlus : public Sensor { + public: + // The constructor - need the power pin, the ADS1X15 data channel, and the + // calibration info + /* clang-format off */ + /** + * @brief Construct a new Turner Turbidity Plus object - need the power pin, the + * ADS1X15 data channel, and the calibration info. + * + * @note ModularSensors only supports connecting the ADS1x15 to the primary + * hardware I2C instance defined in the Arduino core. Connecting the ADS to + * a secondary hardware or software I2C instance is *not* supported! + * + * @param powerPin The pin on the mcu controlling power to the Turbidity Plus + * Use -1 if it is continuously powered. + * - The ADS1x15 requires an input voltage of 2.0-5.5V + * - The Turbidity Plus itself requires a 3-15V power supply, which can be + * turned off between measurements. + * @param wiperTriggerPin The pin on the mcu that triggers the sensor's wiper. + * + * @param adsDiffMux Which two pins _on the TI ADS1115_ that will measure + * differential voltage from the Turbidity Plus. See Adafruit_ADS1015.h. + * @param conc_std The concentration of the standard used for a 1-point + * sensor calibration. The concentration units should be the same as the + * final measuring units. + * @param volt_std The voltage (in volts) measured for the conc_std. This + * voltage should be the final voltage *after* accounting for any voltage + * dividers or gain settings. + * @param volt_blank The voltage (in volts) measured for a blank. This + * voltage should be the final voltage *after* accounting for any voltage + * dividers or gain settings. + * @param i2cAddress The I2C address of the ADS 1x15, default is 0x48 (ADDR + * = GND) + * @param PGA_gain The programmable gain amplification to set on the + * ADS 1x15, default is GAIN_DEFAULT (0). + * @param measurementsToAverage The number of measurements to take and + * average before giving a "final" result from the sensor; optional with a + * default value of 1. + * @param voltageDividerFactor For 3.3V processors like the Mayfly, The + * Turner's 0-5V output signal must be shifted down to a maximum of 3.3V. + * This can be done either either with a level-shifting chip (e.g. Adafruit BSS38), + * OR by connecting the Turner's output signal via a voltage divider. This + * voltageDividerFactor is used for the latter case: e.g., a divider that + * uses 2 matched resistors will halve the voltage reading and requires a + * voltageDividerFactor of 2. The default value is 1. + */ + /* clang-format on */ + TurnerTurbidityPlus(int8_t powerPin, int8_t wiperTriggerPin, adsDiffMux_t adsDiffMux, float conc_std, + float volt_std, float volt_blank, + uint8_t i2cAddress = ADS1115_ADDRESS, + adsGain_t PGA_gain = GAIN_DEFAULT, + uint8_t measurementsToAverage = 1, + float voltageDividerFactor = 1); + /** + * @brief Destroy the Turner Turbidity Plus object + */ + ~TurnerTurbidityPlus(); + + /** + * @copydoc Sensor::getSensorLocation() + */ + String getSensorLocation(void) override; + + /** + * @brief Run one wiper cycle + */ + void runWiper(void); + + /** + * @brief Do any one-time preparations needed before the sensor will be able + * to take readings. + * + * This sets pin mode on the wiper trigger pin + * + * @return **bool** True if the setup was successful. + */ + bool setup(void) override; + + bool wake(void) override; + + void powerUp(void) override; + + void powerDown(void) override; + + /** + * @copydoc Sensor::addSingleMeasurementResult() + */ + bool addSingleMeasurementResult(void) override; + + private: + int8_t _wiperTriggerPin; + adsDiffMux_t _adsDiffMux; + float _conc_std, _volt_std, _volt_blank; + uint8_t _i2cAddress; + adsGain_t _PGA_gain; + float _voltageDividerFactor; +}; + + +// Also returning raw voltage +/** + * @brief The Variable sub-class used for the + * [raw voltage output](@ref sensor_turbidity_plus_voltage) from a + * [Turner Turbidity Plus](@ref sensor_turbidity_plus). + * + * This could be helpful if the calibration equation was typed incorrectly or if + * it is suspected to have changed over time. + * + * @ingroup sensor_turbidity_plus + */ +class TurnerTurbidityPlus_Voltage : public Variable { + public: + /** + * @brief Construct a new TurnerTurbidityPlus_Voltage object. + * + * @param parentSense The parent TurnerTurbidityPlus providing the result + * values. + * @param uuid A universally unique identifier (UUID or GUID) for the + * variable; optional with the default value of an empty string. + * @param varCode A short code to help identify the variable in files; + * optional with a default value of "TurnerVoltage". + */ + explicit TurnerTurbidityPlus_Voltage( + TurnerTurbidityPlus* parentSense, const char* uuid = "", + const char* varCode = TURBIDITY_PLUS_VOLTAGE_DEFAULT_CODE) + : Variable(parentSense, (const uint8_t)TURBIDITY_PLUS_VOLTAGE_VAR_NUM, + (uint8_t)TURBIDITY_PLUS_VOLTAGE_RESOLUTION, + TURBIDITY_PLUS_VOLTAGE_VAR_NAME, TURBIDITY_PLUS_VOLTAGE_UNIT_NAME, varCode, + uuid) {} + /** + * @brief Construct a new TurnerTurbidityPlus_Voltage object. + * + * @note This must be tied with a parent TurnerTurbidityPlus before it can be + * used. + */ + TurnerTurbidityPlus_Voltage() + : Variable((const uint8_t)TURBIDITY_PLUS_VOLTAGE_VAR_NUM, + (uint8_t)TURBIDITY_PLUS_VOLTAGE_RESOLUTION, + TURBIDITY_PLUS_VOLTAGE_VAR_NAME, TURBIDITY_PLUS_VOLTAGE_UNIT_NAME, + TURBIDITY_PLUS_VOLTAGE_DEFAULT_CODE) {} + /** + * @brief Destroy the TurnerTurbidityPlus_Voltage object - no action needed. + */ + ~TurnerTurbidityPlus_Voltage() {} +}; + + +/** + * @brief The Variable sub-class used for the + * [turbidity output](@ref sensor_turbidity_plus_output) from a + * [Turner Turbidity Plus](@ref sensor_turbidity_plus). + * + * Turbidity is measured (and should be calibrated) in nephelometric turbidity + * units (NTU). + * + * + * @ingroup sensor_turbidity_plus + */ +class TurnerTurbidityPlus_Turbidity : public Variable { + public: + /** + * @brief Construct a new TurnerTurbidityPlus_Turbidity object. + * + * @param parentSense The parent TurnerTurbidityPlus providing the result + * values. + * @param uuid A universally unique identifier (UUID or GUID) for the + * variable; optional with the default value of an empty string. + * @param varCode A short code to help identify the variable in files; + * optional with a default value of "TurnerTurbidity". + */ + explicit TurnerTurbidityPlus_Turbidity(TurnerTurbidityPlus* parentSense, + const char* uuid = "", + const char* varCode = "TurnerTurbidity") + : Variable(parentSense, (const uint8_t)TURBIDITY_PLUS_VAR_NUM, + (uint8_t)TURBIDITY_PLUS_RESOLUTION, "Turbidity", + "nephelometricTurbidityUnit", varCode, uuid) {} + /** + * @brief Construct a new TurnerTurbidityPlus_Turbidity object. + * + * @note This must be tied with a parent TurnerTurbidityPlus before it can be + * used. + */ + TurnerTurbidityPlus_Turbidity() + : Variable((const uint8_t)TURBIDITY_PLUS_VAR_NUM, (uint8_t)TURBIDITY_PLUS_RESOLUTION, + "Turbidity", "nephelometricTurbidityUnit", + "TurnerTurbidity") {} + ~TurnerTurbidityPlus_Turbidity() {} +}; + +/**@}*/ +#endif // SRC_SENSORS_TURNERTURBIDITYPLUS_H_