Skip to content
Snippets Groups Projects
Commit 1ac1b5f0 authored by user2684's avatar user2684
Browse files

Add support for ML8511 UV sensor #37

parent 17f53c08
Branches
Tags
No related merge requests found
......@@ -4,6 +4,30 @@
#include "NodeManager.h"
/***************************************
Global functions
*/
// return vcc in V
float getVcc() {
// Measure Vcc against 1.1V Vref
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = (_BV(MUX5) | _BV(MUX0));
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = (_BV(MUX3) | _BV(MUX2));
#else
ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#endif
// Vref settle
delay(70);
// Do conversion
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA, ADSC)) {};
// return Vcc in mV
return (float)((1125300UL) / ADC) / 1000;
}
/***************************************
PowerManager
......@@ -58,6 +82,7 @@ void PowerManager::powerOff() {
digitalWrite(_vcc_pin, LOW);
}
/******************************************
Sensors
*/
......@@ -429,6 +454,205 @@ void SensorThermistor::onReceive(const MyMessage & message) {
onLoop();
}
/*
SensorML8511
*/
// contructor
SensorML8511::SensorML8511(int child_id, int pin): Sensor(child_id, pin) {
// set presentation, type and value type
setPresentation(S_UV);
setType(V_UV);
setValueType(TYPE_FLOAT);
}
// what do to during before
void SensorML8511::onBefore() {
// set the pin as input
pinMode(_pin, INPUT);
}
// what do to during setup
void SensorML8511::onSetup() {
onLoop();
}
// what do to during loop
void SensorML8511::onLoop() {
// read the voltage
int uvLevel = analogRead(_pin);
int refLevel = getVcc()*1024/3.3;
//Use the 3.3V power pin as a reference to get a very accurate output value from sensor
float outputVoltage = 3.3 / refLevel * uvLevel;
//Convert the voltage to a UV intensity level
float uvIntensity = _mapfloat(outputVoltage, 0.99, 2.8, 0.0, 15.0);
#if DEBUG == 1
Serial.print(F("UV I="));
Serial.print(_child_id);
Serial.print(F(" V="));
Serial.print(outputVoltage);
Serial.print(F(" I="));
Serial.println(uvIntensity);
#endif
// store the value
_value_float = uvIntensity;
}
// what do to as the main task when receiving a message
void SensorML8511::onReceive(const MyMessage & message) {
onLoop();
}
// The Arduino Map function but for floats
float SensorML8511::_mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/*
* SensorMQ
*/
SensorMQ::SensorMQ(int child_id, int pin): Sensor(child_id,pin) {
setPresentation(S_AIR_QUALITY);
setType(V_LEVEL);
}
//setter/getter
void SensorMQ::setRlValue(float value) {
_rl_value = value;
}
void SensorMQ::setRoValue(float value) {
_ro = value;
}
void SensorMQ::setCleanAirFactor(float value) {
_ro_clean_air_factor = value;
}
void SensorMQ::setCalibrationSampleTimes(int value) {
_calibration_sample_times = value;
}
void SensorMQ::setCalibrationSampleInterval(int value){
_calibration_sample_interval = value;
}
void SensorMQ::setReadSampleTimes(int value) {
_read_sample_times = value;
}
void SensorMQ::setReadSampleInterval(int value) {
_read_sample_interval = value;
}
void SensorMQ::setLPGCurve(float *value) {
_LPGCurve[0] = value[0];
_LPGCurve[2] = value[1];
_LPGCurve[2] = value[2];
}
void SensorMQ::setCOCurve(float *value) {
_COCurve[0] = value[0];
_COCurve[2] = value[1];
_COCurve[2] = value[2];
}
void SensorMQ::setSmokeCurve(float *value) {
_SmokeCurve[0] = value[0];
_SmokeCurve[2] = value[1];
_SmokeCurve[2] = value[2];
}
// what do to during before
void SensorMQ::onBefore() {
// prepare the pin for input
pinMode(_pin, INPUT);
}
// what do to during setup
void SensorMQ::onSetup() {
_ro = _MQCalibration();
}
// what do to during loop
void SensorMQ::onLoop() {
if (_pin == -1) return;
// calculate rs/ro
float mq = _MQRead()/_ro;
// calculate the ppm
float lpg = _MQGetGasPercentage(mq,_gas_lpg);
float co = _MQGetGasPercentage(mq,_gas_co);
float smoke = _MQGetGasPercentage(mq,_gas_smoke);
// assign to the value the requested gas
uint16_t value;
if (_target_gas == _gas_lpg) value = lpg;
if (_target_gas == _gas_co) value = co;
if (_target_gas == _gas_smoke) value = smoke;
#if DEBUG == 1
Serial.print(F("MQ I="));
Serial.print(_child_id);
Serial.print(F(" V="));
Serial.print(value);
Serial.print(F(" LPG="));
Serial.print(lpg);
Serial.print(F(" CO="));
Serial.print(co);
Serial.print(F(" SMOKE="));
Serial.println(smoke);
#endif
// store the value
_value_int = (int16_t)ceil(value);
}
// what do to as the main task when receiving a message
void SensorMQ::onReceive(const MyMessage & message) {
onLoop();
}
// returns the calculated sensor resistance
float SensorMQ::_MQResistanceCalculation(int raw_adc) {
return ( ((float)_rl_value*(1023-raw_adc)/raw_adc));
}
// This function assumes that the sensor is in clean air
float SensorMQ::_MQCalibration() {
int i;
float val=0;
//take multiple samples
for (i=0; i< _calibration_sample_times; i++) {
val += _MQResistanceCalculation(analogRead(_pin));
delay(_calibration_sample_interval);
}
//calculate the average value
val = val/_calibration_sample_times;
//divided by RO_CLEAN_AIR_FACTOR yields the Ro
val = val/_ro_clean_air_factor;
//according to the chart in the datasheet
return val;
}
// This function use MQResistanceCalculation to caculate the sensor resistenc (Rs).
float SensorMQ::_MQRead() {
int i;
float rs=0;
for (i=0; i<_read_sample_times; i++) {
rs += _MQResistanceCalculation(analogRead(_pin));
delay(_read_sample_interval);
}
rs = rs/_read_sample_times;
return rs;
}
// This function passes different curves to the MQGetPercentage function which calculates the ppm (parts per million) of the target gas.
int SensorMQ::_MQGetGasPercentage(float rs_ro_ratio, int gas_id) {
if ( gas_id == _gas_lpg ) {
return _MQGetPercentage(rs_ro_ratio,_LPGCurve);
} else if ( gas_id == _gas_co) {
return _MQGetPercentage(rs_ro_ratio,_COCurve);
} else if ( gas_id == _gas_smoke) {
return _MQGetPercentage(rs_ro_ratio,_SmokeCurve);
}
return 0;
}
// returns ppm of the target gas
int SensorMQ::_MQGetPercentage(float rs_ro_ratio, float *pcurve) {
return (pow(10,( ((log10(rs_ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0])));
}
/*
SensorDigitalInput
*/
......@@ -1015,150 +1239,6 @@ void SensorBME280::onReceive(const MyMessage & message) {
}
#endif
/*
* SensorMQ
*/
SensorMQ::SensorMQ(int child_id, int pin): Sensor(child_id,pin) {
setPresentation(S_AIR_QUALITY);
setType(V_LEVEL);
}
//setter/getter
void SensorMQ::setRlValue(float value) {
_rl_value = value;
}
void SensorMQ::setRoValue(float value) {
_ro = value;
}
void SensorMQ::setCleanAirFactor(float value) {
_ro_clean_air_factor = value;
}
void SensorMQ::setCalibrationSampleTimes(int value) {
_calibration_sample_times = value;
}
void SensorMQ::setCalibrationSampleInterval(int value){
_calibration_sample_interval = value;
}
void SensorMQ::setReadSampleTimes(int value) {
_read_sample_times = value;
}
void SensorMQ::setReadSampleInterval(int value) {
_read_sample_interval = value;
}
void SensorMQ::setLPGCurve(float *value) {
_LPGCurve[0] = value[0];
_LPGCurve[2] = value[1];
_LPGCurve[2] = value[2];
}
void SensorMQ::setCOCurve(float *value) {
_COCurve[0] = value[0];
_COCurve[2] = value[1];
_COCurve[2] = value[2];
}
void SensorMQ::setSmokeCurve(float *value) {
_SmokeCurve[0] = value[0];
_SmokeCurve[2] = value[1];
_SmokeCurve[2] = value[2];
}
// what do to during before
void SensorMQ::onBefore() {
// prepare the pin for input
pinMode(_pin, INPUT);
}
// what do to during setup
void SensorMQ::onSetup() {
_ro = _MQCalibration();
onLoop();
}
// what do to during loop
void SensorMQ::onLoop() {
if (_pin == -1) return;
// calculate rs/ro
float mq = _MQRead()/_ro;
// calculate the ppm
float lpg = _MQGetGasPercentage(mq,_gas_lpg);
float co = _MQGetGasPercentage(mq,_gas_co);
float smoke = _MQGetGasPercentage(mq,_gas_smoke);
// assign to the value the requested gas
uint16_t value;
if (_target_gas == _gas_lpg) value = lpg;
if (_target_gas == _gas_co) value = co;
if (_target_gas == _gas_smoke) value = smoke;
#if DEBUG == 1
Serial.print(F("MQ I="));
Serial.print(_child_id);
Serial.print(F(" V="));
Serial.print(value);
Serial.print(F(" LPG="));
Serial.print(lpg);
Serial.print(F(" CO="));
Serial.print(co);
Serial.print(F(" SMOKE="));
Serial.println(smoke);
#endif
// store the value
_value_int = (int16_t)ceil(value);
}
// what do to as the main task when receiving a message
void SensorMQ::onReceive(const MyMessage & message) {
onLoop();
}
// returns the calculated sensor resistance
float SensorMQ::_MQResistanceCalculation(int raw_adc) {
return ( ((float)_rl_value*(1023-raw_adc)/raw_adc));
}
// This function assumes that the sensor is in clean air
float SensorMQ::_MQCalibration() {
int i;
float val=0;
//take multiple samples
for (i=0; i< _calibration_sample_times; i++) {
val += _MQResistanceCalculation(analogRead(_pin));
delay(_calibration_sample_interval);
}
//calculate the average value
val = val/_calibration_sample_times;
//divided by RO_CLEAN_AIR_FACTOR yields the Ro
val = val/_ro_clean_air_factor;
//according to the chart in the datasheet
return val;
}
// This function use MQResistanceCalculation to caculate the sensor resistenc (Rs).
float SensorMQ::_MQRead() {
int i;
float rs=0;
for (i=0; i<_read_sample_times; i++) {
rs += _MQResistanceCalculation(analogRead(_pin));
delay(_read_sample_interval);
}
rs = rs/_read_sample_times;
return rs;
}
// This function passes different curves to the MQGetPercentage function which calculates the ppm (parts per million) of the target gas.
int SensorMQ::_MQGetGasPercentage(float rs_ro_ratio, int gas_id) {
if ( gas_id == _gas_lpg ) {
return _MQGetPercentage(rs_ro_ratio,_LPGCurve);
} else if ( gas_id == _gas_co) {
return _MQGetPercentage(rs_ro_ratio,_COCurve);
} else if ( gas_id == _gas_smoke) {
return _MQGetPercentage(rs_ro_ratio,_SmokeCurve);
}
return 0;
}
// returns ppm of the target gas
int SensorMQ::_MQGetPercentage(float rs_ro_ratio, float *pcurve) {
return (pow(10,( ((log10(rs_ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0])));
}
/*******************************************
......@@ -1256,6 +1336,7 @@ int NodeManager::registerSensor(int sensor_type, int pin, int child_id) {
else if (sensor_type == SENSOR_LDR) return registerSensor(new SensorLDR(child_id, pin));
else if (sensor_type == SENSOR_THERMISTOR) return registerSensor(new SensorThermistor(child_id, pin));
else if (sensor_type == SENSOR_MQ) return registerSensor(new SensorMQ(child_id, pin));
else if (sensor_type == SENSOR_ML8511) return registerSensor(new SensorML8511(child_id, pin));
#endif
#if MODULE_DIGITAL_INPUT == 1
else if (sensor_type == SENSOR_DIGITAL_INPUT) return registerSensor(new SensorDigitalInput(child_id, pin));
......@@ -1607,7 +1688,7 @@ void NodeManager::_process(const char * message) {
else if (strcmp(message, "BATTERY") == 0) {
// measure the board vcc
float volt = 0;
if (_battery_internal_vcc || _battery_pin == -1) volt = _getVcc();
if (_battery_internal_vcc || _battery_pin == -1) volt = getVcc();
else volt = analogRead(_battery_pin) * _battery_volts_per_bit;
// calculate the percentage
int percentage = ((volt - _battery_min) / (_battery_max - _battery_min)) * 100;
......@@ -1834,29 +1915,6 @@ int NodeManager::_getAvailableChildId() {
}
}
#if BATTERY_MANAGER == 1
// return vcc in V
float NodeManager::_getVcc() {
// Measure Vcc against 1.1V Vref
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = (_BV(MUX5) | _BV(MUX0));
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = (_BV(MUX3) | _BV(MUX2));
#else
ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
#endif
// Vref settle
delay(70);
// Do conversion
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA, ADSC)) {};
// return Vcc in mV
return (float)((1125300UL) / ADC) / 1000;
}
#endif
// guess the initial value of a digital output based on the configured interrupt mode
int NodeManager::_getInterruptInitialValue(int mode) {
if (mode == RISING) return LOW;
......@@ -1864,3 +1922,4 @@ int NodeManager::_getInterruptInitialValue(int mode) {
return -1;
}
......@@ -149,6 +149,8 @@
#define SENSOR_THERMISTOR 3
// MQ2 air quality sensor
#define SENSOR_MQ 19
// ML8511 UV sensor
#define SENSOR_ML8511 20
#endif
#if MODULE_DIGITAL_INPUT == 1
// Generic digital sensor, return a pin's digital value
......@@ -196,7 +198,7 @@
// MLX90614 sensor, contactless temperature sensor
#define SENSOR_BME280 18
#endif
// last Id: 19
// last Id: 20
/***********************************
Libraries
*/
......@@ -248,6 +250,7 @@ class PowerManager {
void setPowerPins(int ground_pin, int vcc_pin, long wait = 50);
void powerOn();
void powerOff();
float getVcc();
private:
int _vcc_pin = -1;
int _ground_pin = -1;
......@@ -407,6 +410,78 @@ class SensorThermistor: public Sensor {
float _offset = 0;
};
/*
SensorMQ
*/
class SensorMQ: public Sensor {
public:
SensorMQ(int child_id, int pin);
// define the target gas whose ppm has to be returned. 0: LPG, 1: CO, 2: Smoke (default: 1);
void setTargetGas(int value);
// define the load resistance on the board, in kilo ohms (default: 1);
void setRlValue(float value);
// define the Ro resistance on the board (default: 10000);
void setRoValue(float value);
// Sensor resistance in clean air (default: 9.83);
void setCleanAirFactor(float value);
// define how many samples you are going to take in the calibration phase (default: 50);
void setCalibrationSampleTimes(int value);
// define the time interal(in milisecond) between each samples in the cablibration phase (default: 500);
void setCalibrationSampleInterval(int value);
// define how many samples you are going to take in normal operation (default: 50);
void setReadSampleTimes(int value);
// define the time interal(in milisecond) between each samples in the normal operations (default: 5);
void setReadSampleInterval(int value);
// set the LPGCurve array (default: {2.3,0.21,-0.47})
void setLPGCurve(float *value);
// set the COCurve array (default: {2.3,0.72,-0.34})
void setCOCurve(float *value);
// set the SmokeCurve array (default: {2.3,0.53,-0.44})
void setSmokeCurve(float *value);
// define what to do at each stage of the sketch
void onBefore();
void onSetup();
void onLoop();
void onReceive(const MyMessage & message);
protected:
float _rl_value = 1.0;
float _ro_clean_air_factor = 9.83;
int _calibration_sample_times = 50;
int _calibration_sample_interval = 500;
int _read_sample_interval = 50;
int _read_sample_times = 5;
float _ro = 10000.0;
float _LPGCurve[3] = {2.3,0.21,-0.47};
float _COCurve[3] = {2.3,0.72,-0.34};
float _SmokeCurve[3] = {2.3,0.53,-0.44};
float _MQResistanceCalculation(int raw_adc);
float _MQCalibration();
float _MQRead();
int _MQGetGasPercentage(float rs_ro_ratio, int gas_id);
int _MQGetPercentage(float rs_ro_ratio, float *pcurve);
int _gas_lpg = 0;
int _gas_co = 1;
int _gas_smoke = 2;
int _target_gas = _gas_co;
};
/*
SensorML8511
*/
class SensorML8511: public Sensor {
public:
SensorML8511(int child_id, int pin);
// define what to do at each stage of the sketch
void onBefore();
void onSetup();
void onLoop();
void onReceive(const MyMessage & message);
protected:
float _mapfloat(float x, float in_min, float in_max, float out_min, float out_max);
};
/*
SensorDigitalInput: read the digital input of the configured pin
*/
......@@ -622,60 +697,7 @@ class SensorBME280: public Sensor {
};
#endif
/*
SensorMQ
*/
class SensorMQ: public Sensor {
public:
SensorMQ(int child_id, int pin);
// define the target gas whose ppm has to be returned. 0: LPG, 1: CO, 2: Smoke (default: 1);
void setTargetGas(int value);
// define the load resistance on the board, in kilo ohms (default: 1);
void setRlValue(float value);
// define the Ro resistance on the board (default: 10000);
void setRoValue(float value);
// Sensor resistance in clean air (default: 9.83);
void setCleanAirFactor(float value);
// define how many samples you are going to take in the calibration phase (default: 50);
void setCalibrationSampleTimes(int value);
// define the time interal(in milisecond) between each samples in the cablibration phase (default: 500);
void setCalibrationSampleInterval(int value);
// define how many samples you are going to take in normal operation (default: 50);
void setReadSampleTimes(int value);
// define the time interal(in milisecond) between each samples in the normal operations (default: 5);
void setReadSampleInterval(int value);
// set the LPGCurve array (default: {2.3,0.21,-0.47})
void setLPGCurve(float *value);
// set the COCurve array (default: {2.3,0.72,-0.34})
void setCOCurve(float *value);
// set the SmokeCurve array (default: {2.3,0.53,-0.44})
void setSmokeCurve(float *value);
// define what to do at each stage of the sketch
void onBefore();
void onSetup();
void onLoop();
void onReceive(const MyMessage & message);
protected:
float _rl_value = 1.0;
float _ro_clean_air_factor = 9.83;
int _calibration_sample_times = 50;
int _calibration_sample_interval = 500;
int _read_sample_interval = 50;
int _read_sample_times = 5;
float _ro = 10000.0;
float _LPGCurve[3] = {2.3,0.21,-0.47};
float _COCurve[3] = {2.3,0.72,-0.34};
float _SmokeCurve[3] = {2.3,0.53,-0.44};
float _MQResistanceCalculation(int raw_adc);
float _MQCalibration();
float _MQRead();
int _MQGetGasPercentage(float rs_ro_ratio, int gas_id);
int _MQGetPercentage(float rs_ro_ratio, float *pcurve);
int _gas_lpg = 0;
int _gas_co = 1;
int _gas_smoke = 2;
int _target_gas = _gas_co;
};
/***************************************
NodeManager: manages all the aspects of the node
......
......@@ -29,7 +29,7 @@ void before() {
/*
* Register below your sensors
*/
nodeManager.registerSensor(SENSOR_MQ,A1);
nodeManager.registerSensor(SENSOR_ML8511,A1);
/*
* Register above your sensors
......
......@@ -38,8 +38,9 @@ NodeManager configuration includes compile-time configuration directives (which
## Setup MySensors
Since NodeManager has to communicate with the MySensors gateway on your behalf, it has to know how to do it. Place on top of the `config.h` file all the MySensors typical directives you are used to set on top of your sketch so both your sketch AND NodeManager will be able to share the same configuration. For example:
~~~c
#define MY_BAUD_RATE 9600
//#define MY_DEBUG
#define MY_NODE_ID 100
//#define MY_NODE_ID 100
#define MY_RADIO_NRF24
//#define MY_RF24_ENABLE_ENCRYPTION
......@@ -76,7 +77,7 @@ Those NodeManager's directives in the `config.h` file control which module/libra
// if enabled, a battery sensor will be created at BATTERY_CHILD_ID and will report vcc voltage together with the battery level percentage
#define BATTERY_SENSOR 1
// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR, SENSOR_MQ
// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR, SENSOR_MQ, SENSOR_ML8511
#define MODULE_ANALOG_INPUT 1
// Enable this module to use one of the following sensors: SENSOR_DIGITAL_INPUT
#define MODULE_DIGITAL_INPUT 1
......@@ -197,6 +198,7 @@ SENSOR_BH1750 | BH1750 sensor, return light level in lux
SENSOR_MLX90614 | MLX90614 contactless temperature sensor, return ambient and object temperature
SENSOR_BME280 | BME280 sensor, return temperature/humidity/pressure based on the attached BME280 sensor
SENSOR_MQ | MQ sensor, return ppm of the target gas
SENSOR_ML8511 | ML8511 sensor, return UV intensity
To register a sensor simply call the NodeManager instance with the sensory type and the pin the sensor is conncted to. For example:
~~~c
......@@ -427,6 +429,13 @@ A NodeManager object must be created and called from within your sketch during `
### Sensor::before()
* Call sensor-specific implementation of before by invoking `onBefore()` to initialize the sensor
## NodeManager::setup()
* Send a custom message with a STARTED payload to the controller
* Call `setup()` of each registered sensor
### Sensor::setup()
* Call sensor-specific implementation of setup by invoking `onSetup()` to initialize the sensor
## NodeManager::loop()
* If all the sensors are powered by an arduino pin, this is set to HIGH
* Call `loop()` of each registered sensor
......
......@@ -48,7 +48,7 @@
// if enabled, a battery sensor will be created at BATTERY_CHILD_ID and will report vcc voltage together with the battery level percentage
#define BATTERY_SENSOR 1
// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR, SENSOR_MQ
// Enable this module to use one of the following sensors: SENSOR_ANALOG_INPUT, SENSOR_LDR, SENSOR_THERMISTOR, SENSOR_MQ, SENSOR_ML8511
#define MODULE_ANALOG_INPUT 1
// Enable this module to use one of the following sensors: SENSOR_DIGITAL_INPUT
#define MODULE_DIGITAL_INPUT 1
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment