From 7fb98199e610999eb971075764906da31995fe9a Mon Sep 17 00:00:00 2001 From: Rob Tillaart Date: Thu, 12 Sep 2024 17:24:13 +0200 Subject: [PATCH] 0.2.0 ML8511 --- libraries/ML8511/CHANGELOG.md | 14 +- libraries/ML8511/ML8511.cpp | 86 ++++++------ libraries/ML8511/ML8511.h | 50 +++---- .../ML8511_cumulative_joule.ino | 77 +++++++++++ libraries/ML8511/keywords.txt | 1 + libraries/ML8511/library.json | 2 +- libraries/ML8511/library.properties | 2 +- libraries/ML8511/readme.md | 126 ++++++++++++------ libraries/ML8511/test/unit_test_001.cpp | 19 ++- 9 files changed, 267 insertions(+), 110 deletions(-) create mode 100644 libraries/ML8511/examples/ML8511_cumulative_joule/ML8511_cumulative_joule.ino diff --git a/libraries/ML8511/CHANGELOG.md b/libraries/ML8511/CHANGELOG.md index c4e957cf..c7896720 100644 --- a/libraries/ML8511/CHANGELOG.md +++ b/libraries/ML8511/CHANGELOG.md @@ -6,10 +6,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.2.0] - 2024-09-12 +- add parameter check in **estimateDUVindex()** +- improve behaviour enable functions +- change return type **setVoltsPerStep()** +- change return type **enable()** +- change return type **disable()** +- reorder code in .cpp file to match .h file. +- add experimental example - ML8511_cumulative_joule.ino +- update readme.md +- minor edits + +---- + ## [0.1.11] - 2023-11-13 - update readme.md - ## [0.1.10] - 2023-02-18 - update readme.md - update GitHub actions diff --git a/libraries/ML8511/ML8511.cpp b/libraries/ML8511/ML8511.cpp index 31a3beb2..7e3283b2 100644 --- a/libraries/ML8511/ML8511.cpp +++ b/libraries/ML8511/ML8511.cpp @@ -1,7 +1,7 @@ // // FILE: ML8511.cpp // AUTHOR: Rob Tillaart -// VERSION: 0.1.11 +// VERSION: 0.2.0 // DATE: 2020-02-03 // PURPOSE: ML8511 - UV sensor - library for Arduino // URL: https://github.com/RobTillaart/ML8511 @@ -35,7 +35,7 @@ ML8511::ML8511(uint8_t analogPin, uint8_t enablePin) void ML8511::reset() { - _voltsPerStep = 5.0/1023; + _voltsPerStep = 5.0 / 1023.0; _DUVfactor = 1.61; // https://github.com/RobTillaart/ML8511/issues/4 } @@ -61,13 +61,13 @@ float ML8511::getUV(uint8_t energyMode) } -// to be used by external ADC +// can be used by external ADC float ML8511::voltage2mW(float voltage) { // see datasheet - page 4 // mW/cm2 @ 365 nm // @ 25 Celsius - // formula estimated on graph + // formula estimated from graph if (voltage <= 1.0) { return 0.0; @@ -78,6 +78,46 @@ float ML8511::voltage2mW(float voltage) } +bool ML8511::setVoltsPerStep(float voltage, uint32_t steps) +{ + if (steps == 0) return false; + if (voltage <= 0.0) return false; + _voltsPerStep = voltage / steps; + return true; +} + + +float ML8511::getVoltsPerStep() +{ + return _voltsPerStep; +} + + +bool ML8511::enable() +{ + if (_enablePin == 0xFF) return false; + digitalWrite(_enablePin, HIGH); + _enabled = true; + return true; +} + + +bool ML8511::disable() +{ + if (_enablePin == 0xFF) return false; + digitalWrite(_enablePin, LOW); + _enabled = false; + return true; +} + + +bool ML8511::isEnabled() +{ + return _enabled; +} + + + // experimental estimate DUV index ( ==> USE WITH CARE !!) // use setDUVfactor(float w) to calibrate // @@ -86,8 +126,9 @@ float ML8511::estimateDUVindex(float mWcm2) { // rewrite in 0.1.6 // https://github.com/RobTillaart/ML8511/issues/4 + if (mWcm2 <= 0.0) return 0.0; return mWcm2 * _DUVfactor; -}; +} bool ML8511::setDUVfactor(float factor) @@ -95,47 +136,14 @@ bool ML8511::setDUVfactor(float factor) if (factor < 0.01) return false; // enforce positive values _DUVfactor = factor; return true; -}; +} float ML8511::getDUVfactor() { return _DUVfactor; -}; - - -void ML8511::setVoltsPerStep(float voltage, uint32_t steps) -{ - if (steps == 0) return; - if (voltage > 0.0) _voltsPerStep = voltage / steps; } -float ML8511::getVoltsPerStep() -{ - return _voltsPerStep; -} - - -void ML8511::enable() -{ - if (_enablePin != 0xFF) digitalWrite(_enablePin, HIGH); - _enabled = true; -}; - - -void ML8511::disable() -{ - if (_enablePin != 0xFF) digitalWrite(_enablePin, LOW); - _enabled = false; -}; - - -bool ML8511::isEnabled() -{ - return _enabled; -}; - - // -- END OF FILE -- diff --git a/libraries/ML8511/ML8511.h b/libraries/ML8511/ML8511.h index d8d8fb39..dbacc24e 100644 --- a/libraries/ML8511/ML8511.h +++ b/libraries/ML8511/ML8511.h @@ -2,7 +2,7 @@ // // FILE: ML8511.h // AUTHOR: Rob Tillaart -// VERSION: 0.1.11 +// VERSION: 0.2.0 // DATE: 2020-02-03 // PURPOSE: ML8511 - UV sensor - library for Arduino // URL: https://github.com/RobTillaart/ML8511 @@ -23,7 +23,7 @@ #include -#define ML8511_LIB_VERSION (F("0.1.11")) +#define ML8511_LIB_VERSION (F("0.2.0")) class ML8511 @@ -32,30 +32,34 @@ public: // if enablePin is omitted, one must connect EN to 3V3. ML8511(uint8_t analogPin, uint8_t enablePin = 0xFF); - void reset(); // reset internal variables to initial value. + void reset(); // reset internal variables to initial value. // energyMode = HIGH or LOW; // returns mW per cm2 - float getUV(uint8_t energyMode = HIGH); + float getUV(uint8_t energyMode = HIGH); + // for external ADC - float voltage2mW(float voltage); + // voltage must be >= 1.0 otherwise 0 is returned. + // returns mW per cm2 + float voltage2mW(float voltage); - // voltage must be > 0 otherwise it is not set - void setVoltsPerStep(float voltage, uint32_t steps); - float getVoltsPerStep(); + // voltage and steps must both be > 0 otherwise voltagePerStep + // is not set and the function returns false. + bool setVoltsPerStep(float voltage, uint32_t steps); + float getVoltsPerStep(); - // manually enable / disable - void enable(); - void disable(); - bool isEnabled(); + // manually enable / disable the sensor + // if enable pin is not set, device is always enabled. + bool enable(); // returns false if enable pin not set. + bool disable(); // returns false if enable pin not set. + bool isEnabled(); - // experimental estimate DUV index + // EXPERIMENTAL: estimate DUV index // WARNING: USE WITH CARE // - // input in mW per cm2 == typical the output of getUV() - float estimateDUVindex(float mWcm2); - + // input in mW per cm2 == typical the output of getUV() + float estimateDUVindex(float mWcm2); // https://github.com/RobTillaart/ML8511/issues/4 // discusses the calibration @@ -64,17 +68,17 @@ public: // a value of 1.61 was found to be far more accurate // // returns false if f < 0.01 (to force positive factor only) - bool setDUVfactor(float factor); - float getDUVfactor(); + bool setDUVfactor(float factor); + float getDUVfactor(); private: - uint8_t _analogPin; - uint8_t _enablePin; - float _voltsPerStep; - bool _enabled; + uint8_t _analogPin; + uint8_t _enablePin; + float _voltsPerStep; + bool _enabled; - float _DUVfactor; + float _DUVfactor; }; diff --git a/libraries/ML8511/examples/ML8511_cumulative_joule/ML8511_cumulative_joule.ino b/libraries/ML8511/examples/ML8511_cumulative_joule/ML8511_cumulative_joule.ino new file mode 100644 index 00000000..121b6e02 --- /dev/null +++ b/libraries/ML8511/examples/ML8511_cumulative_joule/ML8511_cumulative_joule.ino @@ -0,0 +1,77 @@ +// +// FILE: ML8511_cumulative_joule.ino +// AUTHOR: Rob Tillaart +// PURPOSE: demo UV sensor - EXPERIMENTAL +// URL: https://github.com/RobTillaart/ML8511 +// +// BREAKOUT +// +-------+--+ +// VIN |o +-+| mounting hole +// 3V3 |o +-+| +// GND |o | +// OUT |o | +// EN |o S | Sensor +// +----------+ +// +// EN = ENABLE + + +#include "ML8511.h" + +#define ANALOGPIN A0 +#define ENABLEPIN 7 + +ML8511 light(ANALOGPIN, ENABLEPIN); + + +float joulePerCM = 0; +float interval = 2.5; // in seconds. + +uint32_t lastTime = 0; + + +void setup() +{ + Serial.begin(115200); + Serial.println(); + Serial.println(__FILE__); + Serial.println(); + Serial.println("UV ML8511 - cumulative joule/cm2"); + + // manually enable / disable the sensor. + light.enable(); + + light.setDUVfactor(1.80); // calibrate your sensor + + Serial.println(); + Serial.print("time"); + Serial.print("\tmW/cm^2"); + Serial.print("\tDUV idx"); + Serial.print("\tcum j/cm^2"); + Serial.println(); +} + + +void loop() +{ + // make a measurement and a print every interval. + if (millis() - lastTime >= (interval * 1000)) + { + lastTime += (interval * 1000); + float UV = light.getUV(); + float DUV = light.estimateDUVindex(UV); + joulePerCM += UV * interval; + + Serial.print(lastTime * 0.001, 1); + Serial.print("\t"); + Serial.print(UV, 3); + Serial.print("\t"); + Serial.print(DUV, 1); + Serial.print("\t"); + Serial.print(joulePerCM, 1); + Serial.println(); + } +} + + +// -- END OF FILE -- diff --git a/libraries/ML8511/keywords.txt b/libraries/ML8511/keywords.txt index b119cf34..0b8370b9 100644 --- a/libraries/ML8511/keywords.txt +++ b/libraries/ML8511/keywords.txt @@ -9,6 +9,7 @@ reset KEYWORD2 getUV KEYWORD2 voltage2mW KEYWORD2 + setVoltsPerStep KEYWORD2 getVoltsPerStep KEYWORD2 diff --git a/libraries/ML8511/library.json b/libraries/ML8511/library.json index b803e9e9..ec0a7988 100644 --- a/libraries/ML8511/library.json +++ b/libraries/ML8511/library.json @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/ML8511.git" }, - "version": "0.1.11", + "version": "0.2.0", "license": "MIT", "frameworks": "*", "platforms": "*", diff --git a/libraries/ML8511/library.properties b/libraries/ML8511/library.properties index 5797f1cd..81581f63 100644 --- a/libraries/ML8511/library.properties +++ b/libraries/ML8511/library.properties @@ -1,5 +1,5 @@ name=ML8511 -version=0.1.11 +version=0.2.0 author=Rob Tillaart maintainer=Rob Tillaart sentence=ML8511 - UV sensor - library for Arduino diff --git a/libraries/ML8511/readme.md b/libraries/ML8511/readme.md index 01fcbfe7..fa9f3f61 100644 --- a/libraries/ML8511/readme.md +++ b/libraries/ML8511/readme.md @@ -16,7 +16,7 @@ Arduino library for the ML8511 UV sensor. ## Warning -**Always take precautions as UV radiation can cause sunburn, eye damage and other problems**. +**Always take precautions as UV radiation can cause sunburn, eye damage and other severe problems**. Do not expose yourself to the sun as UV source too long. @@ -31,15 +31,22 @@ ML8511 - UV sensor - library for Arduino UNO. - do not forget to connect the EN to either an enablePIN or to 3V3 (constantly enabled). -#### Related +Use of an external ADC see below. -- https://github.com/RobTillaart/TSL235R pulse based irradiance variant. -- https://github.com/RobTillaart/TSL260R analog IR irradiance variant. + +### Related + +- https://github.com/RobTillaart/TSL235R pulse based irradiance variant. +- https://github.com/RobTillaart/TSL260R analog IR irradiance variant. - https://github.com/RobTillaart/AnalogUVSensor +- https://github.com/RobTillaart/LTR390_DFR UV sensor (DF Robotics edition) +- https://github.com/RobTillaart/LTR390_RT UV sensor - https://github.com/RobTillaart/ML8511 UV sensor +- https://learn.sparkfun.com/tutorials/ml8511-uv-sensor-hookup-guide +- https://en.wikipedia.org/wiki/Ultraviolet_index -#### Breakout +### Breakout ``` // +-------+--+ @@ -81,32 +88,50 @@ Use a voltage divider (10K + 20K) to convert the 5 Volts to ~3.3 Volts. #include "ML8511.h" ``` -#### Constructor +### Constructor - **ML8511(uint8_t analogPin, uint8_t enablePin = 0xFF)** Constructor, if enable is connected to 3V3 constantly one does not need to set the enablePin parameter. -#### Core + +### Core - **float getUV(uint8_t energyMode = HIGH)** returns mW per cm2, energyMode = HIGH or LOW. -LOW will disable the sensor after each read. +energyMode = LOW will disable the sensor after each read. +Function blocks for (at least) 1 millisecond if sensor is disabled. - **float voltage2mW(float voltage)** returns mW per cm2 from voltage. -To be used when one uses an external ADC e.g. ADS1115 -- **void setVoltsPerStep(float voltage, uint32_t steps)** to calibrate the **internal** ADC used. -Voltage must be > 0 otherwise it is not set and the default of 5 volts 1023 steps is used. +Can be used when one uses an external ADC e.g. ADS1115 +Formula is based upon the graph in the datasheet page 4 (at 25 Celsius) +- **bool setVoltsPerStep(float voltage, uint32_t steps)** to calibrate the **internal** ADC used. +If one of the parameters voltage or steps is <= 0 the function returns false. +Then the previous set value or the default of 5 volts 1023 steps is used. This function has no meaning for an external ADC. - **float getVoltsPerStep()** idem. -- **void enable()** manually enable. -- **void disable()** manually disable. -- **bool isEnabled()** get enabled status. -#### Experimental +### Enable / disable + +These functions only work if the enable pin is set in the constructor. + +- **bool enable()** manually enable the device. +Returns false if enable pin not set. +- **bool disable()** manually disable the device. +Returns false if enable pin not set. +- **bool isEnabled()** returns the enabled status. +Returns true if enable pin not set as it cannot be disabled. + + +### Experimental WARNING: USE WITH CARE -- **float estimateDUVindex(float mWcm2)** input in mW per cm2, returns a value between 0 and ~15(?) -- **void setDUVfactor(float factor)** set the conversion factor +- **float estimateDUVindex(float mWcm2)** input in mW per cm2. +Typically returns a value between 0 and ~15(?) +Returns zero if the parameter mWcm2 <= 0.0. +- **bool setDUVfactor(float factor)** set the conversion factor. +Returns false if the factor < 0.01 (hard coded minimum). +This factor can be used to sort of calibrate a system if a medium +(e.g. glass) is between sensor and UV source - **float getDUVfactor()** returns the set conversion factor (default 1.61) See below (Experimental DUVindex) how to determine the DUV factor for your sensor. @@ -114,8 +139,10 @@ See below (Experimental DUVindex) how to determine the DUV factor for your senso _Note: The UV index can be very high, in La Paz, Bolivia, one of the highest cities in the world the DUV index can go above 20. See link below. -This is really extreme and it is unknown how the ML8511 sensor (and this library) behaves under such conditions, and how long the sensor would survive. -Datasheet goes up to 15 mW per cm2, with a default DUVfactor of ~1.61 the measurements could handle DUV of ~24 in theory._ +This is really extreme and it is unknown how the ML8511 sensor (and this library) +behaves under such conditions, and how long the sensor would survive. +Datasheet goes up to 15 mW per cm2, with a default DUVfactor of ~1.61 +the measurements could handle DUV of ~24 in theory._ https://edition.cnn.com/2021/11/03/americas/bolivia-heatwave-highlands-intl/index.html @@ -140,21 +167,26 @@ Note: this library is **NOT** calibrated so **USE AT OWN RISK** The DUV index can be used for warning for sunburn etc. -#### DUV index table +### DUV index table -Based upon https://en.wikipedia.org/wiki/Ultraviolet_index, - -| DUV INDEX | Description | -|:-----------:|:--------------| -| 0 - 2 | LOW | -| 3 - 5 | MODERATE | -| 6 - 7 | HIGH | -| 8 - 10 | VERY HIGH | -| 11+ | EXTREME | +Based upon https://en.wikipedia.org/wiki/Ultraviolet_index, +| DUV INDEX | Description | Colour | +|:-----------:|:--------------|:---------| +| 0 - 2 | LOW | GREEN | +| 3 - 5 | MODERATE | YELLOW | +| 6 - 7 | HIGH | ORANGE | +| 8 - 10 | VERY HIGH | RED | +| 11+ | EXTREME | PURPLE | -#### Calibrate estimateDUVindex() + +Colour codes are indicative to be used in a user interface. +A more elaborated colour scheme may be made with map2colour. +- https://github.com/RobTillaart/map2colour + + +### Calibrate estimateDUVindex() To calibrate the **estimateDUVindex()** function one needs to determine the DUVfactor. To do this you need an external reference e.g. a local or nearby weather station. @@ -162,21 +194,25 @@ You need to make multiple measurements during the (preferably unclouded) day and calculate the factor. ``` - DUV from weather station -factor = -------------------------- - getUV(); + DUV from weather station +DUVfactor = -------------------------- + getUV(); ``` -you do this e.g. once per hour, so you get multiple values. +You do this e.g. once per hour, so you get multiple values. You can then average them to have a single factor. Hardcode this found value in the library (in the constructor) or better use the **setDUVfactor(factor)** call in **setup()** to calibrate your sensor. +It might be useful to calibrate the DUV factor on different moments of the +day to correct for the angle of inclination. One might need to adjust this +during measurements by using e.g. an RTC = real time clock. + ## Version info -#### 0.1.5 and before +### 0.1.5 and before The formula for the experimental **estimateDUVindex(mWcm2)** is based on the following facts / assumptions: @@ -188,7 +224,7 @@ This is the most lethal the sensor can sense > 80%. (Erythemal action spectrum) As we cannot differentiate wavelengths, this is the safest choice. -#### 0.1.6 +### 0.1.6 The formula is simplified to a single factor that the user needs to determine. Below is described how to do the calibration. @@ -216,18 +252,24 @@ https://en.wikipedia.org/wiki/Ultraviolet_index #### Should -- test more -- get unit tests up and running -- investigate in calibration -- check performance +- test more platforms +- investigate in calibration #### Could -- investigate serial UV communication with UV led -- voltage2mW -> handle negative voltages by taking abs value? +- get unit tests up and running +- math for duration of exposure + - Converting from mW/cm2 ==> Joule / s / cm2 + - integrate sum of multiple measurements. + - experimental example? #### Wont +- investigate serial communication with UV led and UV sensor + - however fun experiment. +- check performance (mainly ADC dependent) +- add base class without enable code? + ## Support diff --git a/libraries/ML8511/test/unit_test_001.cpp b/libraries/ML8511/test/unit_test_001.cpp index d8b31fd2..269d0311 100644 --- a/libraries/ML8511/test/unit_test_001.cpp +++ b/libraries/ML8511/test/unit_test_001.cpp @@ -49,7 +49,7 @@ unittest_teardown() #define ANALOGPIN 0 - +#define ENABLEPIN 10 unittest(test_constructor) { @@ -62,6 +62,18 @@ unittest(test_constructor) light.reset(); assertEqualFloat(5.0/1023, light.getVoltsPerStep(), 0.0001); + assertTrue(light.isEnabled()); + light.disable(); + assertTrue(light.isEnabled()); // always true without ENABLEPIN set + light.enable(); + assertTrue(light.isEnabled()); +} + + +unittest(test_constructor_2) +{ + ML8511 light(ANALOGPIN, ENABLEPIN); // explicit ENABLEPIN + assertTrue(light.isEnabled()); light.disable(); assertFalse(light.isEnabled()); @@ -69,6 +81,7 @@ unittest(test_constructor) assertTrue(light.isEnabled()); } + /* unittest(test_getUV) { @@ -171,8 +184,8 @@ unittest(test_estimateDUVindex) light.enable(); // output a table - fprintf(stderr, "mW\tDUV\n"); - for (float mW = 0; mW < 10; mW += 0.5) + fprintf(stderr, "mW/cm2\tDUV\n"); + for (float mW = 0; mW < 10.5 ; mW += 0.5) { fprintf(stderr, "%f\t", mW); fprintf(stderr, "%f\n", light.estimateDUVindex(mW));