Skip to content

Instantly share code, notes, and snippets.

@IOT-123
Last active May 23, 2018 03:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IOT-123/4c501046d365d01a60664c1936de09f5 to your computer and use it in GitHub Desktop.
Save IOT-123/4c501046d365d01a60664c1936de09f5 to your computer and use it in GitHub Desktop.
IOT123 ATTINY85 I2C SLAVE AUTO-JOIN LAYER FOR SENSOR: MQ2
/*
*
* 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