0.1.6 ML8511

This commit is contained in:
rob tillaart 2021-06-20 09:13:49 +02:00
parent 1970f32c14
commit a750333d50
9 changed files with 305 additions and 61 deletions

View File

@ -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;
};

View File

@ -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 <Arduino.h>
#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;
};

View File

@ -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();

View File

@ -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 <Arduino.h>
#include <ML8511.h>
#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 --

View File

@ -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)

View File

@ -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": "*"

View File

@ -1,5 +1,5 @@
name=ML8511
version=0.1.5
version=0.1.6
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=ML8511 - UV sensor - library for Arduino

View File

@ -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?)

View File

@ -29,6 +29,7 @@
// assertNAN(arg); // isnan(a)
// assertNotNAN(arg); // !isnan(a)
#include <ArduinoUnitTests.h>
@ -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);
}
}