-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGrove_MultichannelGasSensor.cpp
More file actions
382 lines (329 loc) · 9.81 KB
/
Grove_MultichannelGasSensor.cpp
File metadata and controls
382 lines (329 loc) · 9.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
/******************************************************************************
* Arduino-Grove-MultichannelGasSensor-driver *
* -------------------- *
* Arduino driver for Grove Multichannel Gas sensor (CO, NO2, C2H5OH, H2, NH3 *
* CH4, C3H8 and C4H10) *
* Author: Olivier Staquet *
* Last version available on *
* https://github.com/ostaquet/Arduino-Grove-MultichannelGasSensor-driver/ *
******************************************************************************/
#include "Grove_MultichannelGasSensor.h"
/**
* Constructor
* Nothing special, just keep track of the parameters
*/
MiCS6814Class::MiCS6814Class(TwoWire& wire) {
_wire = &wire;
}
/**
* Destructor
* Nothing to do
*/
MiCS6814Class::~MiCS6814Class() {
}
/**
* Start the usage of the driver
* Return :
* - RC 0 if OK
* - RC 1 if problem during the transmission attempt
* - RC 2 if the device attached at the slave address is not the device expected
* - RC 4 if the device has a different version than v2 (only the v2 is supported)
*/
uint8_t MiCS6814Class::begin(uint8_t slaveAddress) {
// Start the Wire protocol
_slaveAddress = slaveAddress;
_wire->begin();
// Check the initialization with the led (10 attempts)
bool isReady = false;
for(uint8_t i = 0; i < 10 && !isReady; i++) {
isReady = ledOn() == 0;
delay(50);
ledOff();
delay(50);
}
if(!isReady) {
return 1;
}
// Check the version (works only with v2 of the sensor)
if(getVersion() != 2) {
return 4;
}
// Turn off heater and LED
ledOff();
heaterOff();
// Get R0 values by channel
r0ByChannel[0] = readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_USER_ADC_CH0);
r0ByChannel[1] = readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_USER_ADC_CH1);
r0ByChannel[2] = readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_USER_ADC_CH2);
return 0;
}
/**
* End the usage of the driver
*/
void MiCS6814Class::end() {
// Cleanup before leaving
ledOff();
heaterOff();
// Close the Wire protocol
_wire->end();
}
/**
* Sample the data on the sensor
*/
void MiCS6814Class::sample() {
// Mark the device as working
ledOn();
delay(10);
// Be sure that the heater is off
heaterOff();
// Let the sensor cold down
delay(1000);
// Switch on the heater
heaterOn();
delay(10);
uint8_t cycleCount = 1;
uint8_t stableCycleCount = 0;
uint8_t prevValueByChannel[3];
// First reading
readRsValues();
prevValueByChannel[0] = rSByChannel[0];
prevValueByChannel[1] = rSByChannel[1];
prevValueByChannel[2] = rSByChannel[2];
delay(1000);
while(stableCycleCount < MICS6814_DEFAULT_STABLE_CYCLE) {
readRsValues();
if((abs(prevValueByChannel[0] - rSByChannel[0]) > 2)
|| (abs(prevValueByChannel[1] - rSByChannel[1]) > 2)
|| (abs(prevValueByChannel[2] - rSByChannel[2]) > 2)) {
prevValueByChannel[0] = rSByChannel[0];
prevValueByChannel[1] = rSByChannel[1];
prevValueByChannel[2] = rSByChannel[2];
} else {
stableCycleCount++;
}
cycleCount++;
delay(1000);
}
heaterOff();
delay(10);
ledOff();
delay(10);
}
void MiCS6814Class::warmup() {
ledOn();
delay(10);
heaterOn();
delay(10);
// For 30 minutes, let the heater on and blink the led
for(uint8_t min = 29; min >= 0; min--) {
for(uint8_t sec = 59; sec >= 0; sec--) {
// Show the user that the warm up is in progress...
if((min * 60 + sec) % 2 == 0) {
ledOn();
} else {
ledOff();
}
delay(1000);
}
}
// Should be good now :-)
heaterOff();
delay(10);
ledOff();
delay(10);
}
void MiCS6814Class::calibrate() {
// Sample at usual
sample();
// Prepare the data for I2C bus
uint8_t tmp[6];
tmp[0] = rSByChannel[0] >> 8;
tmp[1] = rSByChannel[0] & 0xFF;
tmp[2] = rSByChannel[1] >> 8;
tmp[3] = rSByChannel[1] & 0xFF;
tmp[4] = rSByChannel[2] >> 8;
tmp[5] = rSByChannel[2] & 0xFF;
// Store the values as R0 on the EEPROM
Wire.beginTransmission(_slaveAddress);
Wire.write(MICS6814_CMD_SET_R0);
for(uint8_t i = 0; i < 6; i++) {
Wire.write(tmp[i]);
}
Wire.endTransmission();
// Remember the values as R0 in the local instance
r0ByChannel[0] = rSByChannel[0];
r0ByChannel[1] = rSByChannel[1];
r0ByChannel[2] = rSByChannel[2];
}
/**
* Internal helper to read the Rs values
*/
void MiCS6814Class::readRsValues() {
rSByChannel[0] = readOnCommand(MICS6814_CMD_READ_ADC_CH0);
rSByChannel[1] = readOnCommand(MICS6814_CMD_READ_ADC_CH1);
rSByChannel[2] = readOnCommand(MICS6814_CMD_READ_ADC_CH2);
}
/**
* Do the math and return the concentration of a specific gas
* It works only when a sample has been done...
*/
float MiCS6814Class::get(GasType gasType) {
// Compute the ratio based on R0 and Rs
float ratio0 = (float)rSByChannel[0]/(float)r0ByChannel[0]*(1023.0-r0ByChannel[0])/(1023.0-rSByChannel[0]);
float ratio1 = (float)rSByChannel[1]/(float)r0ByChannel[1]*(1023.0-r0ByChannel[1])/(1023.0-rSByChannel[1]);
float ratio2 = (float)rSByChannel[2]/(float)r0ByChannel[2]*(1023.0-r0ByChannel[2])/(1023.0-rSByChannel[2]);
float c = 0;
switch(gasType)
{
case CO:
c = pow(ratio1, -1.179) * 4.385;
break;
case NO2:
c = pow(ratio2, 1.007) / 6.855;
break;
case NH3:
c = pow(ratio0, -1.67) / 1.47;
break;
case C3H8:
c = pow(ratio0, -2.518) * 570.164;
break;
case C4H10:
c = pow(ratio0, -2.138) * 398.107;
break;
case CH4:
c = pow(ratio1, -4.363) * 630.957;
break;
case H2:
c = pow(ratio1, -1.8) * 0.73;
break;
case C2H5OH:
c = pow(ratio1, -1.552)*1.622;
break;
default:
break;
}
return isnan(c)?-3:c;
}
/**
* Change the I2C slave address
*/
void MiCS6814Class::changeSlaveAddress(uint8_t newSlaveAddress) {
command(MICS6814_CMD_CHANGE_I2C_ADDR, newSlaveAddress);
_slaveAddress = newSlaveAddress;
delay(5);
}
/**
* Display configuration on Serial
*/
void MiCS6814Class::displayConfig() {
Serial.print("I2C_ADDRESS = "); Serial.println(readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_I2C_ADDRESS));
Serial.print("FACTORY_ADC_CH0 = "); Serial.println(readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_FACTORY_ADC_CH0));
Serial.print("FACTORY_ADC_CH1 = "); Serial.println(readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_FACTORY_ADC_CH1));
Serial.print("FACTORY_ADC_CH2 = "); Serial.println(readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_FACTORY_ADC_CH2));
Serial.print("USER_ADC_CH0 = "); Serial.println(readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_USER_ADC_CH0));
Serial.print("USER_ADC_CH1 = "); Serial.println(readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_USER_ADC_CH1));
Serial.print("USER_ADC_CH2 = "); Serial.println(readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_USER_ADC_CH2));
}
/**
* Return the version of the Grove Multichannel Gas Sensor
* Return values :
* - 0 = unable to determine the version
* - 1 = version 1
* - 2 = version 2
*/
uint8_t MiCS6814Class::getVersion() {
uint16_t value = readOnCommand(MICS6814_CMD_READ_EEPROM, MICS6814_EEPROM_ADDR_SET);
if(value == 0x466) {
return 2;
} else if(value == 0xFFFF) {
return 0;
} else {
return 1;
}
}
/**
* Switch the heater ON
* Return codes, same as writeRegister()
*/
uint8_t MiCS6814Class::heaterOn() {
return command(MICS6814_CMD_CONTROL_PWR, 0x01);
}
/**
* Switch the heater OFF
* Return codes, same as writeRegister()
*/
uint8_t MiCS6814Class::heaterOff() {
return command(MICS6814_CMD_CONTROL_PWR, 0x00);
}
/**
* Switch the LED ON
* Return codes, same as writeRegister()
*/
uint8_t MiCS6814Class::ledOn() {
return command(MICS6814_CMD_CONTROL_LED, 0x01);
}
/**
* Switch the LED OFF
* Return codes, same as writeRegister()
*/
uint8_t MiCS6814Class::ledOff() {
return command(MICS6814_CMD_CONTROL_LED, 0x00);
}
/**
* Read 2 bytes in return of a command sent (without parameter)
* Return the uint16_t value or 0 by default
*/
uint16_t MiCS6814Class::readOnCommand(uint8_t command) {
_wire->beginTransmission(_slaveAddress);
_wire->write(command);
_wire->endTransmission();
delay(5); // Let the time for the bus to react
_wire->requestFrom(_slaveAddress, (uint8_t) 2); // Add cast to avoid compile warning ;-)
uint8_t raw[2];
for (uint8_t i = 0; i < 2; i++) {
raw[i] = _wire->read();
}
uint16_t data = 0;
data = raw[0];
data <<= 8;
data += raw[1];
return data;
}
/**
* Read 2 bytes in return of a command sent with a parameter
* Return the uint16_t or 0 by default
*/
uint16_t MiCS6814Class::readOnCommand(uint8_t command, uint8_t parameter) {
_wire->beginTransmission(_slaveAddress);
_wire->write(command);
_wire->write(parameter);
_wire->endTransmission();
delay(5); // Let the time for the bus to react
_wire->requestFrom(_slaveAddress, (uint8_t) 2); // Add cast to avoid compile warning ;-)
uint8_t raw[2];
for (uint8_t i = 0; i < 2; i++) {
raw[i] = _wire->read();
}
uint16_t data = 0;
data = raw[0];
data <<= 8;
data += raw[1];
return data;
}
/**
* Write the value in the register defined by address
* Return codes:
* - 0 OK
* - 4 Problem during the transmission
*/
uint8_t MiCS6814Class::command(uint8_t command, uint8_t parameter) {
_wire->beginTransmission(_slaveAddress);
_wire->write(command);
_wire->write(parameter);
if (_wire->endTransmission() != 0) {
return 4;
}
return 0;
}
// Define the default MiCS6814 object
MiCS6814Class MiCS6814(Wire);