Grove - Multichannel Gas Sensor v2 - abnormal values

I ordered a Grove Multichannel Gas Sensor v2- 4000052511 11/12/20.
It arrived 21 November 2020. While waiting, I viewed the WIKI which stated the sensor needed to “burn-in” for at least 72 hours. I hooked it up to my WIO Terminal (I2C) as soon as it arrived. The blue power light lit up! By the end of 24 November 2020 I was still reporting abnormal results. I downloaded the program listed on the SEEED website:


I assumed the program was ready to go and checked out.
Listed below are snapshots of the results:
First 15-20 minutes of starting the multigas:11/21/2020 (21 November 2020) starting about 530pm pst
VOC: 733 ppm
CO: 337 ppm
NO2: 940 ppm
C2H5CH: 938 ppm
October 22, 2020 PST:
16:23:04.440 -> VOC: 146 ppm
16:23:04.440 -> CO: 388 ppm
16:23:04.674 -> Temperature: 75F
16:23:04.674 -> NO2: 577 ppm
16:23:04.955 -> Humidity: 34.00%
16:23:04.955 -> C2H5CH: 372 ppm

16:29:43.960 -> VOC: 145 ppm
16:29:43.960 -> CO: 386 ppm
16:29:44.240 -> Temperature: 75
F
16:29:44.240 -> NO2: 574 ppm
16:29:44.476 -> Humidity: 34.00%
16:29:44.476 -> C2H5CH: 372 ppm

22:22:36.902 -> NO2: 577 ppm
22:22:37.184 -> Humidity: 34.00%
22:22:37.184 -> C2H5CH: 387 ppm
22:22:38.299 -> VOC: 128 ppm
22:22:38.299 -> CO: 361 ppm
22:22:38.578 -> Temperature: 73F
22:22:38.578 -> NO2: 577 ppm
22:22:38.812 -> Humidity: 35.00%
22:22:38.812 -> C2H5CH: 387 ppm

22:23:33.629 -> VOC: 127 ppm
22:23:33.629 -> CO: 362 ppm
22:23:33.907 -> Temperature: 73
F
22:23:33.907 -> NO2: 576 ppm
22:23:34.188 -> Humidity: 35.00%
22:23:34.188 -> C2H5CH: 388 ppm
22:23:35.262 -> VOC: 128 ppm
23 October 2020:
07:57:34.250 -> Temperature: 69F
07:57:34.250 -> NO2: 549 ppm
07:57:34.528 -> Humidity: 35.00%
07:57:34.528 -> C2H5CH: 348 ppm
07:57:35.603 -> VOC: 102 ppm
07:57:35.650 -> CO: 331 ppm
07:57:35.883 -> Temperature: 69
F
07:57:35.883 -> NO2: 550 ppm
07:57:36.165 -> Humidity: 35.00%
07:57:36.165 -> C2H5CH: 349 ppm

NOTE: after 48 hours and not much change in sensor values, I looked for more information on gitHub and found a reference to “Calibrate and Warmup”:
I then tried another library which had a “warmup” and “calibrate” but I am not sure it was communicating as the 30 minute warmup never said it was done.
Library for “Calibrate and Warmup”: https://github.com/ostaquet/Arduino-Grove-MultichannelGasSensor-driver/tree/master/examples
I do not know if it worked as the Warmup function never said it was done (even though it was supposed to) The “Calibrate” did say finished but I only saw a slight change in values!

27 November 2020
12:47PM
voc: 49; co: 279; no2: 484; eTHYL-C2H5CH: 272
8:42pm
voc: 48; co: 291; no2: 492; eTHYL-C2H5CH: 272

28 November 2020 (moved to a different room in house)
14:26 (24 hour clock)
Temperature: 71*F
NO2: 546 ppm
Humidity: 36.00%
C2H5CH: 324 ppm
VOC: 65 ppm
CO: 364 ppm

I have not tried the Calibrate again - but hopefully someone can help me find a solution to these numbers. If the CO level were truly even 364 ppm I would be in real trouble.
I did notice that the Calibrate and Warmup are not in the same library used by:


Curious why not? Is there a better library? Is my device defective? or?
THank you

SEEED responded 11/30/2020 as follows:

------>The code is not below to us.

You can use our Grove_Multichannel_Gas_Sensor_v2 Library , follow the wiki to do the test.

If you still can’t get the right value, please feel free to contact our professional engineer at our forum

As directed, I used the SEEED Library, which as it turns out looks exactly like the one I was using but with a different fork or author!
Now at day two using the SEEED library the values are still too high (deathly).
12/01/2020. 08:00AM
VOC: 34; CO: 255; NO2: 444; ETHYL: 199; T: 66F; H: 36
12/02/2020 08:00AM PST
VOC: 32; CO: 250; NO2: 433; ETHYL: 185; T: 66F; H: 36 (switched to SEED Library after this reading)

12/03/2020 12:27 PST
VOC: 411; VOC: 25; CO: 255; Ethyl (C2H5CH): 130ppm;

According to the SEED email response, it is time for a professional engineer!!
Thank you, in advance;

How do I contact the professional engineer? Usually, there is a response by now. There must be a solution to this dilemma. I just read another user essentially created a new baseline or “calibration” I would like to hear a recommendation from SEEED, I want to use this device as a useful guideline to fix dangerous conditions.
Thank you

Hello,

Did you found a solution? I have exactly the same problem:

VOC: 523 ppm
CO: 284 ppm
NO2: 499 ppm
C2H5CH: 347 ppm

Thank you!

Here is the “official” response from SEEED:
Hope this helps. My take away is that you cannot trust the numbers output specifically. However, one can track a trend or spike in the values to get an idea the environment is unsafe.
I just purchased and received a Grove - Air quality sensor v1.3 and intend to use it in conjunction with the Multichannel Gas Sensor to help determine if there is danger.

Seeed Customer Support Team [email protected]
Thu 12/17/2020 7:35 PM
Hello gesWho,

Our relevant department told us that
This product can’t be measured quantitatively, only qualitatively.
It can only detect whether there is corresponding gas, but it can not detect the gas concentration.

Please feel free to contact us if you have any questions. Thanks.

Best Regards!

Vicky
Seeed Bazaar Customer Support TeamOn Dec 17, 2020 at 11:47 AM gesWho

Thank you very much for your fast response!

So, it looks that this sensor is useless… I also found, that when I put a test gas to the sensor (for example CO), all four parameters go up to 1000 ppm. So, it doesn’t matter what I give to the sensor - it react for all parameters! My last try was with alcohol. The results are:

VOC: 999 ppm
CO: 999 ppm
NO2: 999 ppm
C2H5CH: 999 ppm

Regards!

Yes, I preheated the sensor for 5 days. The results are the same like at the beginning of burning. Looking at the documentation of the Winsen GM sensors, it looks that the sensors must give a concentration, closer to the real! OK, I agree that they are not super accurate, but for example, the GM102B sensor measures NO2 in the range 0,1-10ppm. In our case, there is nothing real with these reading of hundreds of ppm! It looks that the grove multichannel sensor is not calibrated at all! And I still cannot understand why all sensors on the multichannel sensor react together when there is some gas. Stupid think… I use a lot of different sensors, also different type of Winsen sensor for CO and the results are perfect. It is possible, that these values are not in PPM, but PPB. I will make some tests.

I asked SEEED if one claim that these are ppb and they did not answer, except to say I needed to use their library instead of the hacker.io project!?. I would be interested in your findings. What sensor do you use for Carbon Monoxide?

I tried with all available libraries. The results were always the same - far away from the truth.
The CO sensors that I use are model ZE07-CO, produced by Winsen. I am very happy with that sensors. They are very accurate.
If you have an interest, please send me an e-mail to vcoder(@)abv.bg, and I will explain you what sensors and software I use in my fire and health safety alarm system in my house (I do not want to spam the theme here :slight_smile: )

BR

I had a discussion with SEEED. Unfortunately, in the case of the multichannel sensor they don’t know what they produce. Their sketch means nothing, except the voltage values. These big readings are NOT ppm or ppb. There was absolutely unadequate email conversation between me and the SEEED service engineer (for example he said, that these “ppm” of hundreds are normal LOL! It is not normal! It is deadly! I had a conversation with WINSON, the company which produces the four sensors of the multichannel sensor. Because the WIKI of the SEEED is not full (NO2 calibration curve is missing), and they said nothing about that, I asked WINSON to send me the curve. After that I wrote a sketch which reads the CO and NO2 sensors and I have made the calibration of the sensors. Actually, the sensors are enough accurate (more than I expected before the tests). If someone is interested about how to read REAL ppm from the sensors, please write me an email. My email is already published in upper post. SEEED, take this on mind! You sell not so cheap sensor! Please inform yourself, correct your WIKI and show to your customers how to operate with that sensor!

Hi everyone, I bought 2 Multigas V2 sensors and when I connect them to my esp through I2C I always get the same values.
NO2 = 1023
CO = 1023
C2H5OH = 1023
VOC = 1023.

Then I would like to know what is possible to measure with this sensor in addition to these 4 gases.

did any one have a solutions ?

I contacted vcoder, above. He was very helpful in a solution. The key is to find a way to calibrate or normalize the results. Without normalizing the values are in the “death”, very dangerous range. I am still having trouble normalizing the VOC values, need to find time to do that. I will post the code I am using, with vcoder’s help. It would be nice if SEEED would provide a recommended calibration or normalization algorithm. Hope this helps you.
/* Multichannel sensor Grove V2, by vcoder 2021

  • vcoder email contact: [email protected]
    CO range 0 - 1000 PPM
    Calibrated according calibration curve by Winsen: 0 - 150 PPM
    RS/R0 PPM
    1 0
    0.77 1
    0.6 3
    0.53 5
    0.4 10
    0.29 20
    0.21 50
    0.17 100
    0.15 150

NO2 range 0 - 10 PPM
Calibrated according calibration curve by Winsen: 0 - 10 PPM
RS/R0 PPM
1 0
1.4 1
1.8 2
2.25 3
2.7 4
3.1 5
3.4 6
3.8 7
4.2 8
4.4 9
4.7 10
/
//https://github.com/Seeed-Studio/Seeed_Multichannel_Gas_Sensor
/

*/
#include <TFT_eSPI.h>
#include <Multichannel_Gas_GMXXX.h>
#include “Air_Quality_Sensor.h” //#include"AirQuality.h"
AirQualitySensor sensor(A2); //Attached to Wio Battery expansion (A2D2/A3D3)
#include <Wire.h>
#include “DHT.h”
#include <SPI.h>
#define BUZZER_PIN WIO_BUZZER
GAS_GMXXX gas;

TFT_eSPI tft;
TFT_eSprite spr = TFT_eSprite(&tft); //sprite

unsigned int no2, c2h5ch, voc, co, aqv;
int temp, humid;
int iBeeps;

// Veslin recommendation
static uint8_t recv_cmd[8] = {};
int PPM = 0;
int CO_PPM; //GM_702B CO
int NO2_PPM; //GM_102B NO2
int Eth_PPM; //GM_302B Ethyl
int VOC_PPM; //GM_502B VOC
double lgPPM;

boolean bDisplay = false;
boolean bPrevButtonPress = false;
boolean bAlerts = false; //Start NO alerts
boolean bWio_PressKeyA = false;

#define DHTPIN 0
#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);
int iX = 5;
int iY = 60;
int iWidth = tft.width();
int iHeight = tft.height();
int iHlfw = tft.width() /2;
int iHlfh = tft.height() /2;
int iQw = iHlfw /2;
int iQh = iHlfh /2;
String sAQ = “__”;

//Veslin recommend
uint8_t len = 0;
uint8_t addr = 0;
uint8_t i;
uint32_t val = 0;

float sensor_volt;
float CO_Volt;
float NO2_Volt;
float Eth_Volt;
float VOC_Volt;
float RS_gas;
float R0;
float sensorValue = 0;
float COvalue;
float NO2value;
float EthValue;
float VOCValue;
float ratio;

void setup() {
Wire.begin();
Serial.begin(115200);
tft.begin();
tft.setRotation(3);
tft.fillScreen(TFT_BLACK);

gas.begin(Wire, 0x08);

dht.begin();

pinMode(WIO_KEY_A, INPUT_PULLUP);
pinMode(WIO_KEY_B, INPUT_PULLUP);
pinMode(WIO_KEY_C, INPUT_PULLUP);
pinMode(WIO_5S_UP, INPUT_PULLUP);
pinMode(WIO_5S_DOWN, INPUT_PULLUP);
pinMode(WIO_5S_LEFT, INPUT_PULLUP);
pinMode(WIO_5S_RIGHT, INPUT_PULLUP);
pinMode(WIO_5S_PRESS, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
//displayTemplate();
//Air Quality Sensor REsiding on Pin A2 battery expansion
if (sensor.init()) {
Serial.println(“Sensor ready.”);
} else {
Serial.println(“Sensor ERROR!”);
}
}

void loop() {

CO_PPM = getCO_GasValue(); //GM_702B CO = Call getGasValue function
NO2_PPM = getNO2_GasValue(); //GM_102B NO2 = Call getGasValue function
Eth_PPM = getEth_GasValue(); //GM_302B Ethyl - Call getGasValue function
VOC_PPM = getVOC_GasValue(); //GM_502B VOC - Call getGasValue function
int quality = sensor.slope();
int sValue = sensor.getValue();
aqv = sValue;
//AQ voltage:
//current_V - last_V > 400 OR current_V > 700 : AirQualitySensor::FORCE_SIGNAL
//current_V - last_V > 400 AND current_V < 700 OR current_V - std_V > 150 : AirQualitySensor::HIGH_POLLUTION
//current_V - last_V > 200 AND current_V < 700 OR current_V - std_V > 50 : AirQualitySensor::LOW_POLLUTION
//ELSE : AirQualitySensor::FRESH_AIR
if (quality == AirQualitySensor::FORCE_SIGNAL) {
Serial.println(“High pollution! Force signal active.”);
sAQ = “FS!”;
iBeeps = 6;
} else if (quality == AirQualitySensor::HIGH_POLLUTION) {
Serial.println(“High pollution!”);
sAQ = “Hpol”;
iBeeps = 4;
} else if (quality == AirQualitySensor::LOW_POLLUTION) {
Serial.println(“Low pollution!”);
sAQ = “Lpol”;
iBeeps = 2;
} else if (quality == AirQualitySensor::FRESH_AIR) {
Serial.println(“Fresh air.”);
sAQ = “Frsh”;
iBeeps = 0;
}
// if (digitalRead(WIO_KEY_A) == LOW) {
// playTone(1014, 500);
// }

// VOC
voc = gas.getGM502B();
if (voc > 999) voc = 999;
Serial.print(“VOC: “);
Serial.print(voc);
Serial.println(” ppm”);
//CO
co = gas.getGM702B();
if (co > 999) co = 999;
Serial.print(“CO: “);
Serial.print(co);
Serial.println(” ppm”);

//Temp
float t = dht.readTemperature();
temp = t*1.8 + 32;
//int tem = round(t);
//int tem = t;
Serial.print("Temperature: ");
Serial.print(temp);
Serial.println( “*F”);
//NO2
no2 = gas.getGM102B();
if (no2 > 999) no2 = 999;
Serial.print(“NO2: “);
Serial.print(no2);
Serial.println(” ppm”);

//Humidity
float h = dht.readHumidity();
if (h > 99) h = 99;
humid = h;
Serial.print("Humidity: ");
Serial.print(h);
Serial.println( “%”);
//C2H5CH
c2h5ch = gas.getGM302B();
if (c2h5ch > 999) c2h5ch = 999;
Serial.print(“C2H5CH: “);
Serial.print(c2h5ch);
Serial.println(” ppm”);

// ****************************************
//Button state:
//WIO_5S_PRESS NOT pressed (false) bPrevButtonPress (false) (Do NOT Display GAS data)
//Button just pressed WIO_5S_PRESS is True bPtrue (false)
//Button just pressed WIO_5S_PRESS is True bPtrue {true)

//pinMode(WIO_KEY_A, INPUT_PULLUP);
// boolean bWio_PressKeyA = false;
if ( (digitalRead(WIO_KEY_A) == LOW) && !(bWio_PressKeyA) ) {
bAlerts = true; //Turn ON Alerts
bWio_PressKeyA = true;
playTone(956, 500);
}
if ((digitalRead(WIO_KEY_A) == LOW) && (bWio_PressKeyA)){ //Turn OFF Alerts
bAlerts = false; //Turn OFF Alerts
bWio_PressKeyA = false;
playTone(956, 500);
}

if ( (digitalRead(WIO_5S_PRESS) == LOW) && !(bPrevButtonPress) ) {
bDisplay = true;
bPrevButtonPress = true;
playTone(956, 500);
}
if ((digitalRead(WIO_5S_PRESS) == LOW) && (bPrevButtonPress)){ //Turn OFF Display
bDisplay = false;
bPrevButtonPress = false;
playTone(1700, 500);
}
if (bDisplay) {
displayTemplate();
displayGasData();
}
else {
tft.fillScreen(TFT_BLACK);
}

delay(5000);
}

void displayTemplate() {
//Head
//int iX = 5;
//int iY = 60;
//int iWidth = tft.width();
//int iHeight = tft.height();
//int iHlfw = tft.width() /2;
//int iHlfh = tft.height() /2;
//int iQw = iHlfw /2;
//int iQh = iHlfh /2;
//(3rd rectangle) midpoint is iHlfw
//(4th rectangle) iHlfw +iQw

tft.fillScreen(TFT_BLACK);
tft.setFreeFont(&FreeSansBoldOblique18pt7b);
tft.setTextColor(TFT_WHITE);
tft.drawString(“Air Quality”, 70, 10 , 1);

//Line
for (int8_t line_index = 0; line_index < 5 ; line_index++)
{
tft.drawLine(0, 50 + line_index, tft.width(), 50 + line_index, TFT_GREEN);
}

//New VOC
//tft.drawRoundRect(5 , 60, (tft.width() / 2) / 2 , (tft.height() - 65) /2 , 10, TFT_WHITE); // L1
tft.drawRoundRect(iX, iY, (iHlfw - iQw) + 10, iQh +5, 10, TFT_WHITE);

// AQv
//tft.drawRoundRect((tft.width() / 2) / 2, 60, ((tft.width() / 2) / 2) + (tft.width() / 2) / 2 , (tft.height() - 65) /2 , 10, TFT_WHITE); //
tft.drawRoundRect((iHlfw - iQw) + 17, iY, iHlfw -50, iQh +5, 10, TFT_WHITE);

//New CO
tft.drawRoundRect(iX, iHlfh -10, (iHlfw - iQw) + 10, iHlfh -iQh +3, 10, TFT_WHITE); // s3

//Empty New
tft.drawRoundRect((iHlfw - iQw) + 17, iHlfh -10, iHlfw -50, iHlfh -iQh +3, 10, TFT_WHITE); // s3

//VOC Text
tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED);
tft.drawString(“VOC”, 7 , 65 , 1);
tft.setTextColor(TFT_GREEN);
tft.drawString(“ppm”, 30, 108, 1);

//AQv Text
tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED);
tft.drawString(“AQs”, 100 , 65 , 1);
tft.setTextColor(TFT_GREEN);
tft.drawString(sAQ, 100, 111, 1); //Pollution level!
tft.setTextColor(TFT_BLUE);
tft.drawString(String(iBeeps), 85, 120, 1); //beeps
if (bAlerts) {
tft.setTextColor(TFT_RED);
tft.drawString(String(iBeeps), 85, 120, 1); //beeps
for (int ii = 0; ii < iBeeps; ii++) {
playTone(1900, 25); //(tone, duration)
delay(40);
}
}

//CO Text
tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED);
tft.drawString(“CO”, 7 , 150 , 1);
tft.setTextColor(TFT_GREEN);
tft.drawString(“ppm”, 30, 193, 1);

// Temp rect
tft.drawRoundRect((tft.width() / 2) - 10 , 60, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_BLUE); // s1

tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED) ;
tft.drawString(“Temp”, (tft.width() / 2) - 1 , 70 , 1); // Print the test text in the custom font
tft.setTextColor(TFT_GREEN);
tft.drawString(“o”, (tft.width() / 2) + 30, 95, 1);
tft.drawString(“F”, (tft.width() / 2) + 40, 100, 1);

//No2 rect
tft.drawRoundRect(((tft.width() / 2) + (tft.width() / 2) / 2) - 5 , 60, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_BLUE); // s2

tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED);
tft.drawString(“NO2”, ((tft.width() / 2) + (tft.width() / 2) / 2) , 70 , 1); // Print the test text in the custom font
tft.setTextColor(TFT_GREEN);
tft.drawString(“ppm”, ((tft.width() / 2) + (tft.width() / 2) / 2) + 30 , 120, 1);

//Humi Rect
tft.drawRoundRect((tft.width() / 2) - 10 , (tft.height() / 2) + 30, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_BLUE); // s3

tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED) ;
tft.drawString(“Humi”, (tft.width() / 2) - 1 , (tft.height() / 2) + 40 , 1); // Print the test text in the custom font
tft.setTextColor(TFT_GREEN);
tft.drawString("%", (tft.width() / 2) + 30, (tft.height() / 2) + 70, 1);

//c2h5ch Rect
tft.drawRoundRect(((tft.width() / 2) + (tft.width() / 2) / 2) - 5 , (tft.height() / 2) + 30, (tft.width() / 2) / 2 , (tft.height() - 65) / 2 , 10, TFT_BLUE); // s4

tft.setFreeFont(&FreeSansBoldOblique9pt7b);
tft.setTextColor(TFT_RED) ;
tft.drawString(“Ethyl”, ((tft.width() / 2) + (tft.width() / 2) / 2) , (tft.height() / 2) + 40 , 1); // Print the test text in the custom font
tft.setTextColor(TFT_GREEN);
tft.drawString(“ppm”, ((tft.width() / 2) + (tft.width() / 2) / 2) + 30 , (tft.height() / 2) + 90, 1);

}

void displayGasData() {
// VOC : VOC_PPM = getVOC_GasValue(); //GM_502B VOC
spr.createSprite(40, 30);
spr.fillSprite(TFT_BLACK);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(VOC_PPM, 0, 0, 1); //was voc
spr.pushSprite(15, 85); //was: 15, 100
spr.deleteSprite();

//CO : CO_PPM = getCO_GasValue(); //GM_702B CO
spr.createSprite(40, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(CO_PPM, 0, 0, 1); //Was drawNumber(co, 0, 0, 1);
spr.setTextColor(TFT_GREEN);
spr.pushSprite(15, 170); //Was 15, 185
spr.deleteSprite();

//Air Quality Sensor (aqv)
spr.createSprite(40, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(aqv, 0, 0, 1);
//spr.setTextColor(TFT_GREEN);
spr.pushSprite( ((tft.width() / 2)+12) / 2, 85); //
spr.deleteSprite();

//iHeight iWidth
//spr.createSprite(40, 30);
//spr.setFreeFont(&FreeSansBoldOblique12pt7b);
//spr.setTextColor(TFT_WHITE);
//spr.drawNumber(tft.height(), 0, 0, 1);
//spr.setTextColor(TFT_GREEN);
//spr.pushSprite( ((tft.width() / 2)+10) / 2, 110); //
//spr.deleteSprite();

//Temp
spr.createSprite(30, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(temp, 0, 0, 1);
spr.setTextColor(TFT_GREEN);
spr.pushSprite((tft.width() / 2) - 1, 100);
spr.deleteSprite();

//NO2 : NO2_PPM = getNO2_GasValue(); //GM_102B NO2
spr.createSprite(45, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(NO2_PPM, 0, 0, 1);
spr.pushSprite(((tft.width() / 2) + (tft.width() / 2) / 2), 97);
spr.deleteSprite();

//Humidity
spr.createSprite(30, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(humid, 0, 0, 1);
spr.pushSprite((tft.width() / 2) - 1, (tft.height() / 2) + 67);
spr.deleteSprite();

//C2H5CH : Eth_PPM = getEth_GasValue(); //GM_302B Ethyl
spr.createSprite(45, 30);
spr.setFreeFont(&FreeSansBoldOblique12pt7b);
spr.setTextColor(TFT_WHITE);
spr.drawNumber(Eth_PPM, 0 , 0, 1); //was c2h5ch
spr.pushSprite(((tft.width() / 2) + (tft.width() / 2) / 2), (tft.height() / 2) + 67);
spr.deleteSprite();
}

void playTone(int tone, int duration) {
for (long i = 0; i < duration * 1000L; i += tone * 2) {
digitalWrite(BUZZER_PIN, HIGH);
delayMicroseconds(tone);
digitalWrite(BUZZER_PIN, LOW);
delayMicroseconds(tone);
}
} //End PlayTone

/*
*
Gas Measurements
val = gas.measure_NO2(); //GM_102B NO2
val = gas.measure_C2H5OH(); //GM_302B Ethyl
val = gas.measure_VOC(); //GM_502B VOC
val = gas.measure_CO(); //GM_702B CO
*
*/

int getEth_GasValue()
{
//Average
for(int x = 0 ; x < 100 ; x++)
{
EthValue = gas.measure_C2H5OH(); //GM_302B Ethyl
}

Eth_Volt = (EthValue/1024)*3.3;
RS_gas = (3.3-Eth_Volt)/Eth_Volt;

R0 = 1.0; //measured on ambient air
ratio = RS_gas/R0;
//ratio = 4.7; //for tests of the calibration curve

val = gas.measure_C2H5OH(); Serial.print(“Ethyl: “); Serial.print(val); Serial.print(” eq “);
Serial.print(gas.calcVol(val)); Serial.println(” V”);

lgPPM = (log10(ratio) * + 1.9) - 0.2; //+2 -0.3
PPM = pow(10,lgPPM);
return PPM;
}

int getNO2_GasValue()
{
//Average
for(int x = 0 ; x < 100 ; x++)
{
NO2value = gas.measure_NO2();
}

NO2_Volt = (NO2value/1024)*3.3;
RS_gas = (3.3-NO2_Volt)/NO2_Volt;

R0 = 1.07; //measured on ambient air
ratio = RS_gas/R0;
//ratio = 4.7; //for tests of the calibration curve

val = gas.measure_NO2(); Serial.print(“NO2: “); Serial.print(val); Serial.print(” eq “);
Serial.print(gas.calcVol(val)); Serial.println(” V”);

lgPPM = (log10(ratio) * + 1.9) - 0.2; //+2 -0.3
PPM = pow(10,lgPPM);
return PPM;
}

int getCO_GasValue()
{
//Average
for(int x = 0 ; x < 100 ; x++)
{
COvalue = gas.measure_CO();
}

CO_Volt = (COvalue/1024)*3.3;
RS_gas = (3.3-CO_Volt)/CO_Volt;

R0 = 3.21; //measured on ambient air
ratio = RS_gas/R0;
// ratio = 1; //it is for tests of the calibration curve
// The following “val” is for comparison, I believe
val = gas.measure_CO(); Serial.print(“CO: “); Serial.print(val); Serial.print(” eq “);
Serial.print(gas.calcVol(val)); Serial.println(” V”);
lgPPM = (log10(ratio) * - 2.82) - 0.12; //- 3.82) - 0.66; - default - 2.82) - 0.12; - best for range up to 150 ppm
PPM = pow(10,lgPPM);
return PPM;
}

//GM_502B VOC - Call getGasValue function
int getVOC_GasValue()
{
//Average
for(int x = 0 ; x < 100 ; x++)
{
VOCValue = gas.measure_VOC(); //GM_502B VOC
}

VOC_Volt = (VOCValue/1024)*3.3;
RS_gas = (3.3-VOC_Volt)/VOC_Volt;

R0 = 1; //measured on ambient air
ratio = RS_gas/R0;
// ratio = 1; //it is for tests of the calibration curve
// The following “val” is for comparison, I believe
val = gas.measure_VOC(); Serial.print(“VOC: “); Serial.print(val); Serial.print(” eq “);
Serial.print(gas.calcVol(val)); Serial.println(” V”);
lgPPM = (log10(ratio) * + 1.9) - 0.2; //+2 -0.3 - 2.82) - 0.12; - best for range up to 150 ppm
PPM = pow(10,lgPPM);
return ratio;
}

1 Like

geschmierer and vcoder. Thank you both very much for your work on this. This is the most helpful thing I’ve found on the Seeed Multichannel Gas Sensor so far!

Your code attached above makes a lot of sense until I get to the part where you’re calculating PPM using the Log10 functions. Can you help me understand that part? It looks like you use slightly different values for each of the sensors. But I’m unclear why Logs are required for calculating PPM. Is it just part of converting the voltage to an actual value? If so, where did you get the numbers you used in the equations?

Thank you so much for your help on this.

vcoder researched the specs on the multi-channel gas sensor. His research and testing resulted in the equations you see. Converting voltage to an actual value was my understanding. As I stated above, I still have not calibrated/ normailzed my VOC value - they appear to be higher than “normal”. You might reach out to vcoder for more detail.

Hi geschmierer. Have you been able to get sensical VOC values since ?

If you have code updates I’d be interested in looking at the changes since the code you pasted on Feb’22.

Thanks a lot for sharing your work by the way !

Not really. Once I put "vcoder;s’ values in, I basically use the device to let me know what changes from “normal”. Disappointed in the hype. SEEED should have provided the values or help to calibrate the sensor.

Ok, thanks for the reply. Seeing the many issues wrt to Seeed’s implementation of these sensors I do NOT believe it is possible to use the Winsec curves to get a proper ppm. I posted the explanation in the comment section here: Grove - Gas Sensor V2(Multichannel) | Seeed Studio Wiki

However I think it is possible to develop a rough ppm estimate (in various T / RH conditions) by comparing each sensor’s output to an equivalent sensor from a reliable company (like Sensirion whose sensors are auto-compensated vs T/RH). Shooting for publishing that code in a couple of months. Hoping to get to -50% to +100% precision (so pretty bad but in the ballpark, and leap & bounds better than seeed’s misleading sketches).

Will post when done, as a part of this series (gave you a shoutout :wink:): [DIY] Air Quality Monitor & Datalogger (PM,VOC,NOx,COx,T°,H%) - Part 1 Introduction