Last active
May 23, 2018 03:07
-
-
Save IOT-123/4c501046d365d01a60664c1936de09f5 to your computer and use it in GitHub Desktop.
IOT123 ATTINY85 I2C SLAVE AUTO-JOIN LAYER FOR SENSOR: MQ2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* | |
* IOT123 ATTINY85 I2C SLAVE AUTO-JOIN LAYER FOR SENSOR: MQ2 | |
* | |
* Take readings on MQ2 and send across wire on request via I2C in 3 segment 16byte packets | |
* ID of PROPERTY (set in _properties) | |
* VALUE of PROPERTY (set in getProperties) | |
* MORE TO COME (0/1 0 = last property) | |
* | |
* Pins on ATTINY85 | |
* SDA PB0 | |
* SCL PB2 | |
* MQ2 A3 | |
*/ | |
#include <Wire.h> //SDA pin5/PB0, SCL pin7/PB2 | |
#define arraySize(x) (sizeof(x) / sizeof(x[0])) | |
#define NVC_NUM_STAGES 3 | |
//------------------------------SENSOR SPECIFIC DECLARATIONS | |
#define ADDRESS_SLAVE 12 | |
#define PIN_SENSOR A3 | |
#define MILLIS_WARMUP 120000 | |
#define CALIBARAION_SAMPLE_TIMES 50 | |
#define CALIBRATION_SAMPLE_INTERVAL 500 | |
#define READ_SAMPLE_TIMES 5 | |
#define READ_SAMPLE_INTERVAL 50 | |
#define RL_VALUE 5 | |
#define RO_CLEAN_AIR_FACTOR 9.83 | |
#define GAS_LPG (0) | |
#define GAS_CO (1) | |
#define GAS_SMOKE (2) | |
float LPGCurve[3] = {2.3,0.21,-0.47}; //two points are taken from the curve. | |
//with these two points, a line is formed which is "approximately equivalent" | |
//to the original curve. | |
//data format:{ x, y, slope}; point1: (lg200, 0.21), point2: (lg10000, -0.59) | |
float COCurve[3] = {2.3,0.72,-0.34}; //two points are taken from the curve. | |
//with these two points, a line is formed which is "approximately equivalent" | |
//to the original curve. | |
//data format:{ x, y, slope}; point1: (lg200, 0.72), point2: (lg10000, 0.15) | |
float SmokeCurve[3] ={2.3,0.53,-0.44}; //two points are taken from the curve. | |
//with these two points, a line is formed which is "approximately equivalent" | |
//to the original curve. | |
//data format:{ x, y, slope}; point1: (lg200, 0.53), point2: (lg10000, -0.22) | |
float RO = 10; | |
volatile bool _preparing = true; | |
volatile bool _calibrating = false; | |
volatile bool _metasStarted = false; | |
volatile bool _isReadingSensor = false; | |
volatile int _metaIndex = 0; | |
//--------------------------END SENSOR SPECIFIC DECLARATIONS | |
#define TIME_RESPONSE_MS 0 // will be last value sent to master + padding | |
#if (TIME_RESPONSE_MS) | |
unsigned long startMillis; | |
#endif | |
struct nvc | |
{ | |
char Name[16]; | |
char Value[16]; | |
bool Continue; | |
}; | |
//--------------------------END SENSOR SPECIFIC METADATA | |
//------------------------------SENSOR SPECIFIC PROPERTIES | |
nvc _props[3] ={ | |
// {"PREPARE (MQ2)", "Ready to read", true}, | |
{"LPG (PPM)", "", true}, | |
{"CO (PPM)", "", true}, | |
{"SMOKE (PPM", "", false} | |
}; | |
const static char p1[] PROGMEM = "PREPARE (MQ2)"; | |
const static char p2[] PROGMEM = "Heating"; | |
const static char p3[] PROGMEM = "1"; | |
const char* const _prepareMsgs[] PROGMEM = { p1, p2, p3 }; | |
const static char c1[] PROGMEM = "PREPARE (MQ2)"; | |
const static char c2[] PROGMEM = "Calibrating"; | |
const static char c3[] PROGMEM = "1"; | |
const char* const _calibrateMsgs[] PROGMEM = { c1, c2, c3 }; | |
#define META_COUNT 8 | |
const static char m1[] PROGMEM = "ASSIM_NAME"; | |
const static char m2[] PROGMEM = "MQ2"; | |
const static char m3[] PROGMEM = "1"; | |
const static char m4[] PROGMEM = "ASSIM_VERSION"; | |
const static char m5[] PROGMEM = "1"; | |
const static char m6[] PROGMEM = "1"; | |
const static char m7[] PROGMEM = "ASSIM_ROLE"; | |
const static char m8[] PROGMEM = "SENSOR"; | |
const static char m9[] PROGMEM = "1"; | |
const static char m10[] PROGMEM = "POWER_DOWN"; | |
const static char m11[] PROGMEM = "0"; | |
const static char m12[] PROGMEM = "1"; | |
const static char m13[] PROGMEM = "PREPARE_MS"; | |
const static char m14[] PROGMEM = "140000"; | |
const static char m15[] PROGMEM = "1"; | |
const static char m16[] PROGMEM = "RESPONSE_MS"; | |
const static char m17[] PROGMEM = "50"; | |
const static char m18[] PROGMEM = "1"; | |
const static char m19[] PROGMEM = "MQTT_TOPIC"; | |
const static char m20[] PROGMEM = "LPG_CO_SMOKE"; | |
const static char m21[] PROGMEM = "1"; | |
const static char m22[] PROGMEM = "VCC_MV"; | |
const static char m23[] PROGMEM = ""; | |
const static char m24[] PROGMEM = "0"; | |
const char* const _metas[] PROGMEM = { m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20, m21, m22, m23, m24 }; | |
//--------------------------END SENSOR SPECIFIC PROPERTIES | |
volatile byte _packetStage = 0; | |
volatile int _propertyIndex = 0; | |
volatile bool _metasConfirmed = false; | |
uint16_t _vcc; | |
void setup() | |
{ | |
static long startMillis = millis(); | |
_vcc = getVcc(); | |
Wire.begin(ADDRESS_SLAVE); | |
Wire.onReceive(receiveEvent); | |
Wire.onRequest(requestEvent); | |
while (true){ | |
if (millis() - startMillis >= MILLIS_WARMUP) | |
{ | |
break; | |
} | |
} | |
_calibrating = true; | |
RO = MQCalibration(PIN_SENSOR); | |
_calibrating = false; | |
_preparing = false; | |
} | |
void loop() | |
{ | |
} | |
void receiveEvent (int howMany) | |
{ | |
byte buf[10]; | |
int i; | |
for (i=0; i<howMany; i++) | |
{ | |
buf[i] = Wire.read(); // receive byte as a character | |
} | |
if((buf[0] == 1) && (howMany == 1)){ | |
_metasConfirmed = true; | |
_packetStage = 0; | |
_propertyIndex = 0; | |
// because of async the _packetStage may need to finish | |
} | |
} | |
void requestEvent() { | |
char currentPacket[16]; | |
int propCount = 1000; // arbitary high value - after prepare it matters | |
// METADATA | |
if (!_metasConfirmed){ | |
propCount = META_COUNT; | |
if (_metaIndex == ((META_COUNT * 3) - 2)){// if last metadata (VCC), only runtime entry | |
itoa(_vcc, currentPacket, 10); | |
}else{ // just a normal metadata item | |
//itoa(_metaIndex, currentPacket, 10); | |
strcpy_P(currentPacket, (char*)pgm_read_word(&(_metas[_metaIndex]))); | |
} | |
_metaIndex++; | |
} | |
// CALIBRATING | |
else if (_calibrating){ // keep processing the same on master; MORE TO COME = 1 so it keeps polling | |
strcpy_P(currentPacket, (char*)pgm_read_word(&(_calibrateMsgs[_packetStage]))); | |
} | |
// HEATING | |
else if (_preparing){ // keep processing the same on master; MORE TO COME = 1 so it keeps polling | |
strcpy_P(currentPacket, (char*)pgm_read_word(&(_prepareMsgs[_packetStage]))); | |
} | |
else if (_metasConfirmed){ | |
if (_propertyIndex == 0){ | |
// SENSOR PROPERTIES | |
getProperties(); | |
_isReadingSensor = true; | |
} | |
strcpy(currentPacket, nvcAsCharArray(_props[_propertyIndex], _packetStage)); | |
propCount = arraySize(_props); | |
} | |
Wire.write(currentPacket); // send metadate or sensor property | |
_packetStage++; | |
// go to next property if at last stage of current property | |
if (_packetStage == NVC_NUM_STAGES){ | |
_packetStage = 0; | |
if (!_preparing && _isReadingSensor){ | |
_propertyIndex++; | |
} | |
} | |
// all properties processed? | |
if (_propertyIndex == propCount){ | |
_propertyIndex = 0; | |
// "0" should terminate requests to this slave | |
} | |
} | |
//------------------------------SENSOR SPECIFIC PROPERTY READING | |
void getProperties(){ | |
#if (TIME_RESPONSE_MS) | |
startMillis = millis(); | |
#endif | |
// read sensor | |
float value = MQRead(PIN_SENSOR); | |
// put values in table | |
dtostrf(MQGetGasPercentage(value/RO,GAS_LPG),3,0,_props[0].Value); | |
dtostrf(MQGetGasPercentage(value/RO,GAS_CO),3,0,_props[1].Value); | |
dtostrf(MQGetGasPercentage(value/RO,GAS_SMOKE),3,0,_props[2].Value); | |
} | |
//--------------------------END SENSOR SPECIFIC PROPERTY READING | |
char* nvcAsCharArray(nvc nvc, int packetStage){ | |
switch (packetStage){ | |
case 0: | |
return nvc.Name; | |
break; | |
case 1: | |
#if (TIME_RESPONSE_MS) | |
unsigned long currentMillis; | |
currentMillis = millis(); | |
char millis[16]; | |
itoa(currentMillis - startMillis, millis, 10); | |
return millis; | |
#endif | |
return nvc.Value; | |
break; | |
case 2: | |
return nvc.Continue ? "1" : "0"; | |
break; | |
default: | |
char result[16]; | |
itoa(packetStage, result, 10); | |
return result; | |
} | |
} | |
// https://www.avrfreaks.net/forum/attiny85-vcc-measurement-skews-higher-vcc-voltages | |
//5v = 6393, 6504 | |
//3.3V 3430 | |
uint16_t getVcc() { | |
// Read 1.1V reference against AVcc | |
// set the reference to Vcc and the measurement to the internal 1.1V reference | |
ADMUX = _BV(MUX3) | _BV(MUX2); | |
delay(2); // Wait for Vref to settle | |
uint16_t result = 0; | |
for (int x = 0; x < 32; x++){ | |
ADCSRA |= _BV(ADSC); // Start conversion | |
while (bit_is_set(ADCSRA,ADSC)); // measuring | |
if (x > 15){ | |
result += (int16_t)((int16_t)(ADC - result) / 2); | |
} | |
else{ | |
result = ADC; | |
} | |
} | |
uint16_t voltage = 1125300 / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 | |
return voltage; | |
} | |
/***************************** MQGetGasPercentage ********************************** | |
Input: rs_ro_ratio - Rs divided by Ro | |
gas_id - target gas type | |
Output: ppm of the target gas | |
Remarks: This function passes different curves to the MQGetPercentage function which | |
calculates the ppm (parts per million) of the target gas. | |
************************************************************************************/ | |
int 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; | |
} | |
/***************************** MQGetPercentage ********************************** | |
Input: rs_ro_ratio - Rs divided by Ro | |
pcurve - pointer to the curve of the target gas | |
Output: ppm of the target gas | |
Remarks: By using the slope and a point of the line. The x(logarithmic value of ppm) | |
of the line could be derived if y(rs_ro_ratio) is provided. As it is a | |
logarithmic coordinate, power of 10 is used to convert the result to non-logarithmic | |
value. | |
************************************************************************************/ | |
int MQGetPercentage(float rs_ro_ratio, float *pcurve){ | |
return (pow(10, (((log(rs_ro_ratio)-pcurve[1])/pcurve[2]) + pcurve[0]))); | |
} | |
/***************************** MQRead ********************************************* | |
Input: mq_pin - analog channel | |
Output: Rs of the sensor | |
Remarks: This function use MQResistanceCalculation to caculate the sensor resistenc (Rs). | |
The Rs changes as the sensor is in the different consentration of the target | |
gas. The sample times and the time interval between samples could be configured | |
by changing the definition of the macros. | |
************************************************************************************/ | |
float MQRead(int mq_pin) | |
{ | |
int i; | |
float rs=0; | |
for (i=0;i<READ_SAMPLE_TIMES;i++) { | |
rs += MQResistanceCalculation(analogRead(mq_pin)); | |
//delay(READ_SAMPLE_INTERVAL); | |
} | |
rs = rs/READ_SAMPLE_TIMES; | |
return rs; | |
} | |
/****************** MQResistanceCalculation **************************************** | |
Input: raw_adc - raw value read from adc, which represents the voltage | |
Output: the calculated sensor resistance | |
Remarks: The sensor and the load resistor forms a voltage divider. Given the voltage | |
across the load resistor and its resistance, the resistance of the sensor | |
could be derived. | |
************************************************************************************/ | |
float MQResistanceCalculation(int raw_adc) | |
{ | |
return ( ((float)RL_VALUE*(1023-raw_adc)/raw_adc)); | |
} | |
/***************************** MQCalibration **************************************** | |
Input: mq_pin - analog channel | |
Output: Ro of the sensor | |
Remarks: This function assumes that the sensor is in clean air. It use | |
MQResistanceCalculation to calculates the sensor resistance in clean air | |
and then divides it with RO_CLEAN_AIR_FACTOR. RO_CLEAN_AIR_FACTOR is about | |
10, which differs slightly between different sensors. | |
************************************************************************************/ | |
float MQCalibration(int mq_pin) | |
{ | |
int i; | |
float val=0; | |
for (i=0;i<CALIBARAION_SAMPLE_TIMES;i++) { //take multiple samples | |
val += MQResistanceCalculation(analogRead(mq_pin)); | |
delay(CALIBRATION_SAMPLE_INTERVAL); | |
} | |
val = val/CALIBARAION_SAMPLE_TIMES; //calculate the average value | |
val = val/RO_CLEAN_AIR_FACTOR; //divided by RO_CLEAN_AIR_FACTOR yields the Ro | |
return val; //according to the chart in the datasheet | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment