diff --git a/libraries/ML8511/ML8511.cpp b/libraries/ML8511/ML8511.cpp index d7f6a9c5..39151ccd 100644 --- a/libraries/ML8511/ML8511.cpp +++ b/libraries/ML8511/ML8511.cpp @@ -1,16 +1,19 @@ // // FILE: ML8511.cpp // AUTHOR: Rob.Tillaart@gmail.com -// VERSION: 0.1.5 +// VERSION: 0.1.6 // PURPOSE: ML8511 - UV sensor - library for Arduino // // HISTORY: // 0.1.0 2020-02-03 initial version // 0.1.1 2020-02-17 added _voltPerStep() to support more boards // 0.1.2 2020-06-21 refactor; add estimateDUVindex() -// 0.1.3 2021-01-01 arduino-ci + unit test +// 0.1.3 2021-01-01 Arduino-ci + unit test // 0.1.4 2021-04-23 fix for platformIO -// 0.1.5 2021-05-27 fix arduino-lint +// 0.1.5 2021-05-27 fix Arduino-lint +// 0.1.6 2021-06-19 add get/setDUVfactor(), +// rewrite estimateDUVindex(), +// add reset(); #include "ML8511.h" @@ -22,9 +25,8 @@ // ML8511::ML8511(uint8_t analogPin, uint8_t enablePin) { - _analogPin = analogPin; - _voltsPerStep = 5.0/1023; - _enablePin = enablePin; + _analogPin = analogPin; + _enablePin = enablePin; if (enablePin != 0xFF) { pinMode(_enablePin, OUTPUT); @@ -36,9 +38,17 @@ ML8511::ML8511(uint8_t analogPin, uint8_t enablePin) { _enabled = true; } + reset(); }; +void ML8511::reset() +{ + _voltsPerStep = 5.0/1023; + _DUVfactor = 1.61; // https://github.com/RobTillaart/ML8511/issues/4 +} + + float ML8511::getUV(uint8_t energyMode) { if (!_enabled) @@ -57,7 +67,7 @@ float ML8511::getUV(uint8_t energyMode) // see datasheet - page 4 // mW/cm2 @ 365 nm - // @ 25 Celcius + // @ 25 Celsius // formula estimated on graph if (voltage <= 1.0) { @@ -69,18 +79,23 @@ float ML8511::getUV(uint8_t energyMode) } -// experimental estimate DUV index (not calibrated, USE WITH CARE !!) +// experimental estimate DUV index ( ==> USE WITH CARE !!) +// use setDUVfactor(float w) to calibrate +// // input is power in mW per cm2 -// weight is pretty high float ML8511::estimateDUVindex(float mWcm2) { - float weight = 1.0; // this can be tuned to callibrate + // rewrite in 0.1.6 + // https://github.com/RobTillaart/ML8511/issues/4 + return mWcm2 * _DUVfactor; +}; - // convert to mW per m2 - float mWm2 = mWcm2 * 10000; - // factor to normalize to an 0..10 scale see Wikipedia. - float factor = 0.04; // 1.0/25; - return mWm2 * weight * factor; + +bool ML8511::setDUVfactor(float f) +{ + if (f < 0.01) return false; // enforce positive values + _DUVfactor = f; + return true; }; diff --git a/libraries/ML8511/ML8511.h b/libraries/ML8511/ML8511.h index aee24e4c..c5b9a522 100644 --- a/libraries/ML8511/ML8511.h +++ b/libraries/ML8511/ML8511.h @@ -2,7 +2,7 @@ // // FILE: ML8511.h // AUTHOR: Rob Tillaart -// VERSION: 0.1.5 +// VERSION: 0.1.6 // PURPOSE: ML8511 - UV sensor - library for Arduino // URL: https://github.com/RobTillaart/ML8511 // @@ -18,7 +18,7 @@ #include -#define ML8511_LIB_VERSION (F("0.1.5")) +#define ML8511_LIB_VERSION (F("0.1.6")) class ML8511 @@ -27,14 +27,12 @@ public: // if enablePin is omitted, one must connect EN to 3V3. ML8511(uint8_t analogPin, uint8_t enablePin = 0xFF); + void reset(); // reset internal vars to initial value. + // energyMode = HIGH or LOW; // returns mW per cm2 float getUV(uint8_t energyMode = HIGH); - // experimental estimate DUV index (not calibrated, USE WITH CARE !!) - // input in mW per cm2 - float estimateDUVindex(float mWcm2); - // voltage must be > 0 otherwise it is not set void setVoltsPerStep(float voltage, uint32_t steps); float getVoltsPerStep() { return _voltsPerStep; }; @@ -44,11 +42,32 @@ public: void disable(); bool isEnabled() { return _enabled; }; + + // experimental estimate DUV index + // WARNING: USE WITH CARE + // + // input in mW per cm2 == typical the output of getUV() + float estimateDUVindex(float mWcm2); + // + // https://github.com/RobTillaart/ML8511/issues/4 + // discusses the calibration + // see readme.md how to reverse engineer the factor for + // the estimateDUVindex() conversion function. + // a value of 1.61 was found to be far more accurate + // + // returns false if f < 0.01 (to force positive only) + bool setDUVfactor(float f); + float getDUVfactor() { return _DUVfactor; }; + + private: uint8_t _analogPin; uint8_t _enablePin; float _voltsPerStep; bool _enabled; + + + float _DUVfactor; }; diff --git a/libraries/ML8511/examples/ML8511_DUV_index/ML8511_DUV_index.ino b/libraries/ML8511/examples/ML8511_DUV_index/ML8511_DUV_index.ino index 9fd6370d..aabaf855 100644 --- a/libraries/ML8511/examples/ML8511_DUV_index/ML8511_DUV_index.ino +++ b/libraries/ML8511/examples/ML8511_DUV_index/ML8511_DUV_index.ino @@ -35,6 +35,9 @@ void setup() // manually enable / disable the sensor. light.enable(); + + light.setDUVfactor(1.80); // calibrate your sensor + Serial.print("\tmW cm^2"); Serial.print("\tDUV index"); Serial.println(); diff --git a/libraries/ML8511/examples/ML8511_determine_DUV_factor/ML8511_determine_DUV_factor.ino b/libraries/ML8511/examples/ML8511_determine_DUV_factor/ML8511_determine_DUV_factor.ino new file mode 100644 index 00000000..eae9f133 --- /dev/null +++ b/libraries/ML8511/examples/ML8511_determine_DUV_factor/ML8511_determine_DUV_factor.ino @@ -0,0 +1,75 @@ +// +// FILE: ML8511_determine_DUV_factor.ino +// AUTHOR: Rob Tillaart +// VERSION: 0.1.0 +// PURPOSE: demo ML8511 UV sensor - to determine DUV factor +// DATE: 2021-06-19 +// URL: https://github.com/RobTillaart/ML8511 + +// BREAKOUT +// +-------+--+ +// VIN |o +-+| mounting hole +// 3V3 |o +-+| +// GND |o | +// OUT |o | +// EN |o S | Sensor +// +----------+ +// +// EN = ENABLE + + +#include +#include + +#define ANALOGPIN A0 +#define ENABLEPIN 7 + +ML8511 light(ANALOGPIN, ENABLEPIN); + +// for calculating the average +float sum = 0; +uint32_t count = 0; + + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + Serial.println("UV UltraViolet ML8511"); + + // manually enable / disable the sensor. + light.enable(); + + Serial.print("\tmW cm^2"); + Serial.print("\tDUV index"); + Serial.println(); +} + + +void loop() +{ + Serial.print("enter reference DUV:\t"); + // flush all + while (Serial.available() > 0) Serial.read(); + while (Serial.available() == 0); + float DUV = Serial.parseFloat(); + Serial.println(DUV); + + float UV = light.getUV(); + Serial.print("UV mW cm^2:\t\t"); + Serial.println(UV, 4); + + Serial.print("DUV factor:\t\t"); + float factor = DUV / UV; + Serial.println(factor, 2); + + // calculate the average + count++; + sum += factor; + Serial.print("DUV factor average:\t"); + Serial.println(sum / count); + Serial.println(); + Serial.println(); +} + +// -- END OF FILE -- diff --git a/libraries/ML8511/keywords.txt b/libraries/ML8511/keywords.txt index 3ca229c5..907ed964 100644 --- a/libraries/ML8511/keywords.txt +++ b/libraries/ML8511/keywords.txt @@ -5,13 +5,18 @@ ML8511 KEYWORD1 # Methods and Functions (KEYWORD2) +reset KEYWORD2 getUV KEYWORD2 +setVoltsPerStep KEYWORD2 +getVoltsPerStep KEYWORD2 + enable KEYWORD2 disable KEYWORD2 -setVoltsPerStep KEYWORD2 isEnabled KEYWORD2 -estimateDUVindex KEYWORD2 +estimateDUVindex KEYWORD2 +setDUVfactor KEYWORD2 +getDUVfactor KEYWORD2 # Instances (KEYWORD2) diff --git a/libraries/ML8511/library.json b/libraries/ML8511/library.json index 6cc9a03c..1d45410b 100644 --- a/libraries/ML8511/library.json +++ b/libraries/ML8511/library.json @@ -13,9 +13,9 @@ "repository": { "type": "git", - "url": "https://github.com/RobTillaart/ML8511" + "url": "https://github.com/RobTillaart/ML8511.git" }, - "version": "0.1.5", + "version": "0.1.6", "license": "MIT", "frameworks": "arduino", "platforms": "*" diff --git a/libraries/ML8511/library.properties b/libraries/ML8511/library.properties index 40375709..72fceb4e 100644 --- a/libraries/ML8511/library.properties +++ b/libraries/ML8511/library.properties @@ -1,5 +1,5 @@ name=ML8511 -version=0.1.5 +version=0.1.6 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 bf9c30bd..76ce0b54 100644 --- a/libraries/ML8511/readme.md +++ b/libraries/ML8511/readme.md @@ -48,18 +48,40 @@ can set those with setVoltagePerStep() ``` It is possible to always enable the sensor by connecting the EN to 3V3. -The value of the enablePin in the constructor should then be ommitted +The value of the enablePin in the constructor should then be omitted or set to a negative value; When connecting to an Arduino UNO one can use the 3V3 of the Arduino to power -the sensor. However it is not possible to connect the enablepin directly to the +the sensor. However it is not possible to connect the enable pin directly to the sensor. Use a voltage divider (10K + 20K) to convert the 5 Volts to ~3.3 Volts. +## Interface + +- **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. +- **float getUV(uint8_t energyMode = HIGH)** returns mW per cm2, energyMode = HIGH or LOW +- **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. +- **float getVoltsPerStep()** idem +- **void enable()** manually enable +- **void disable()** manually disable +- **bool isEnabled()** get status. + + +#### experimental + +WARNING: USE WITH CARE + +- **float estimateDUVindex(float mWcm2)** input in mW per cm2, returns a value between 0 and ~15(?) +- **void setDUVfactor(float f)** set the conversion factor +- **float getDUVfactor()** returns the set conversion factor (default 1.61) + +See below how to determine the DUV factor for your sensor. + + ## Sensor sensitivity Indoors there is very little UV light so use a known UV source like -a blacklight or go outside in the sun. +a black-light or go outside in the sun. The formula to convert the ADC reading to mW cm^2 is based upon the graph shown in the datasheet. As I have no reference source to calibrate the library @@ -69,8 +91,26 @@ The sensor has its peak sensitivity ( >80% ) from λ = 300-380 nm with an absolute peak at λ = 365 nm. -## Experimental -(use at own risk) +## Experimental DUVindex + +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 + +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 | + + +#### 0.1.5 and before The formula for the experimental **estimateDUVindex(power)** is based on the following facts / assumptions: @@ -82,19 +122,31 @@ This is the most lethal the sensor can sense > 80%. (Erythemal action spectrum) As we cannot differentiate this is the safest choice. -The DUV index can be used for warning for sunburn etc. -Please note that this library is NOT calibrated so **USE AT OWN RISK** + +#### 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. -Table based upon https://en.wikipedia.org/wiki/Ultraviolet_index, +#### Calibrate estimateDUVindex() -| DUV INDEX | Description | -|:-----:|:----| -| 0 - 2 | LOW | -| 3 - 5 | MODERATE | -| 6 - 7 | HIGH | -| 8 - 10 | VERY HIGH | -| 11+ | EXTREME | +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. You need to make multiple measurements during the +(preferably unclouded) day and calculate the factor. + +``` + DUV from weather station +factor = -------------------------- + getUV(); +``` + +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. ## More about UV @@ -103,7 +155,8 @@ https://en.wikipedia.org/wiki/Ultraviolet_index ## Notes + - 3V3 Sensor so do not connect to 5V directly. - do not forget to connect the EN to either an enablePIN or to 3V3 (constantly enabled). -- library does not work with an external ADC +- library does not work with an external ADC. (todo?) diff --git a/libraries/ML8511/test/unit_test_001.cpp b/libraries/ML8511/test/unit_test_001.cpp index ee1d1417..9151894e 100644 --- a/libraries/ML8511/test/unit_test_001.cpp +++ b/libraries/ML8511/test/unit_test_001.cpp @@ -29,6 +29,7 @@ // assertNAN(arg); // isnan(a) // assertNotNAN(arg); // !isnan(a) + #include @@ -46,22 +47,10 @@ unittest_teardown() { } -/* -unittest(test_new_operator) -{ - assertEqualINF(exp(800)); - assertEqualINF(0.0/0.0); - assertEqualINF(42); - - assertEqualNAN(INFINITY - INFINITY); - assertEqualNAN(0.0/0.0); - assertEqualNAN(42); -} -*/ - #define ANALOGPIN 0 + unittest(test_constructor) { fprintf(stderr, "VERSION: %s\n", ML8511_LIB_VERSION); @@ -72,6 +61,9 @@ unittest(test_constructor) light.setVoltsPerStep(3.3, 4095); assertEqualFloat(3.3/4095, light.getVoltsPerStep(), 0.0001); + light.reset(); + assertEqualFloat(5.0/1023, light.getVoltsPerStep(), 0.0001); + assertTrue(light.isEnabled()); light.disable(); assertFalse(light.isEnabled()); @@ -79,22 +71,104 @@ unittest(test_constructor) assertTrue(light.isEnabled()); } - +/* unittest(test_getUV) { - ML8511 light(ANALOGPIN); + // need god mode to fill the analogRead... + // does not work properly + + GodmodeState* state = GODMODE(); + state->reset(); + int future[6] = {0, 0, 0, 400, 500, 600}; + state->analogPin[0].fromArray(future, 6); + + ML8511 light(ANALOGPIN); // no/default enable pin + + assertEqualFloat(0, light.getUV(), 0.0001); + assertTrue(light.isEnabled()); + assertEqualFloat(0, light.getUV(LOW), 0.0001); + assertFalse(light.isEnabled()); + assertEqualFloat(0, light.getUV(HIGH), 0.0001); + assertTrue(light.isEnabled()); assertEqualFloat(0, light.getUV(), 0.0001); assertEqualFloat(0, light.getUV(LOW), 0.0001); + assertEqualFloat(0, light.getUV(HIGH), 0.0001); +} +*/ + + +/* +unittest(test_getUV_2) +{ + // need god mode to fill the analogRead... + // does not work properly + + GodmodeState* state = GODMODE(); + state->reset(); + int future[6] = {0, 0, 0, 0, 0, 0}; + state->analogPin[0].fromArray(future, 6); + + ML8511 light(ANALOGPIN, 4); // set enable pin + + assertEqualFloat(0, light.getUV(), 0.0001); + assertTrue(light.isEnabled()); + assertEqualFloat(0, light.getUV(LOW), 0.0001); + assertFalse(light.isEnabled()); + assertEqualFloat(0, light.getUV(HIGH), 0.0001); + assertTrue(light.isEnabled()); +} +*/ + + +unittest(test_setDUVfactor) +{ + ML8511 light(ANALOGPIN); light.enable(); - assertEqualFloat(0, light.getUV(HIGH), 0.0001); - for (float uv = 0; uv < 1; uv += 0.1) + for (float factor = 0.10; factor < 2.01; factor += 0.1) { - fprintf(stderr, "%f\t", uv); - assertEqualFloat(0, light.estimateDUVindex(0), 0.0001); + light.setDUVfactor(factor); + assertEqualFloat(factor, light.getDUVfactor(), 0.0001); } + + fprintf(stderr, "\nOUT OF RANGE\n"); + assertTrue(light.setDUVfactor(0.577)); + assertEqualFloat(0.577, light.getDUVfactor(), 0.0001); + + assertFalse(light.setDUVfactor(0)); + assertEqualFloat(0.577, light.getDUVfactor(), 0.0001); + + assertFalse(light.setDUVfactor(-1.0)); + assertEqualFloat(0.577, light.getDUVfactor(), 0.0001); + + light.reset(); + assertEqualFloat(1.61, light.getDUVfactor(), 0.0001); +} + + +unittest(test_estimateDUVindex) +{ + ML8511 light(ANALOGPIN); + + light.enable(); + + for (float mW = 0; mW < 10; mW += 0.1) + { + fprintf(stderr, "%f\t", mW); + fprintf(stderr, "%f\n", light.estimateDUVindex(mW)); + assertEqualFloat(1.61 * mW, light.estimateDUVindex(mW), 0.0001); + } + + light.setDUVfactor(1.0); + for (float mW = 0; mW < 10; mW += 0.1) + { + fprintf(stderr, "%f\t", mW); + fprintf(stderr, "%f\n", light.estimateDUVindex(mW)); + assertEqualFloat(1.0 * mW, light.estimateDUVindex(mW), 0.0001); + } + }