MQ135 and Spark Core



Does anyone know any libraries suitable for MQ135 gas sensor please?
I have been struggling to find anything in Web IDE.

Many thanks

MQ135 CO2 PPM Calculation

Any specific link for MQ135?

It looks like a simple analogRead() device.



This is a datasheet -
I got one of these -


I did a quick online search and found this:

Don’t think there’s a need for the library for a simple quick test using analogRead() :smiley:


@alexsh1 and @kennethlimcp, without a schematic, it is hard to tell what the load resistor is on the output of the the sensing element. This resistor creates a voltage divider with the sensing element and controls the output voltage of the board. Since the board is powered by 5V, I would recommend against connecting it directly to the Photon analog input. The output of the sensor will most likely be higher than 3.3v when starting up and while detecting significant quantities of gas.

Putting more than 3.3v on a Photon analog input may/will damage that input. You could use another resistive voltage divider or a buffering op-amp with a gain of less than 1 to reduce the voltage to the correct range.

On the code side, make sure to adjust the input scaling since Arduino uses a 10bit ADC vs the Photon’s 12-bit ADC. :smile:


@peekay123 Thanks for your answer. Any recommendation for a gas sensor for Core? I tried to search the forum, but was not able to come up with a suitable sensor.


@alexsh1, the MQ135 is excellent and can be used with 3.3v for the sensing element and 5v for its heater but that is not how most boards are designed. I noticed that he sainsmart board has what seems to be an adjustable load resistor (the blue potentiometer). If you have a digital multimeter, you can measure the output of the board (NOT connected to the Core/Photon) and adjust the pot to see if the voltage drops below 3.3v. Let me know your findings :smile:

The MQ-135 does require about 1W at 5V for its heater and it requires a burn-in of at least 24hrs and a warm-up of a minute or more. This is not a low power device!



I’ve put some concepts about gases sensors here:

Here is my sketch for the core, it pushes the values to a domoticz server on the local lan.

The best to start is to have a MQ135 with a borard, and for the voltage I guess @peekay123 already said everything :wink:

The gasses detected by these gas sensors can be deadly in high concentrations. Always be careful to perform gas tests in well ventilated areas.

#include <math.h>
/************************Hardware Related Macros************************************/
#define         MQ131_SENSOR                 (0)  //define which analog input channel you are going to use
#define         MQ6_SENSOR                   (1)
#define         MQ2_SENSOR                   (2) 
#define         TGS2600_SENSOR               (3)
#define         MQ135_SENSOR                 (4)
#define         S2SH12_SENSOR                (5)
#define         MQ_DEFAULTPPM 399 //default ppm of CO2 for calibration
#define         MQ_DEFAULTRO 68550 //default Ro for MQ135_DEFAULTPPM ppm of CO2
#define         RL_VALUE                     (22000) //define the load resistance on the board, in ohms
/***********************Software Related Macros************************************/
#define         CALIBRATION_SAMPLE_TIMES     (50)    //define how many samples you are going to take in the calibration phase
#define         CALIBRATION_SAMPLE_INTERVAL  (500)   //define the time interal(in milisecond) between each samples in the
                                                     //cablibration phase
#define         READ_SAMPLE_INTERVAL         (50)    //define how many samples you are going to take in normal operation
#define         READ_SAMPLE_TIMES            (5)     //define the time interal(in milisecond) between each samples 
/**********************Application Related Macros**********************************/
#define         GAS_CL2                      (0)
#define         GAS_O3                       (1)  
#define         GAS_CO2                      (2)
#define         GAS_CO                       (3)  
#define         GAS_NH4                      (4)
#define         GAS_CO2H50H                  (5)
#define         GAS_CH3                      (6)
#define         GAS_CH3_2CO                  (7)
#define         GAS_H2                       (8)
#define         GAS_C2H5OH                   (9)
#define         GAS_C4H10                   (10)
#define         GAS_LPG                     (11)
#define         GAS_Smoke                   (12)
#define         GAS_CO_sec                  (13)  
#define         GAS_LPG_sec                 (14)
#define         GAS_CH4                     (15)
#define         GAS_NO2                     (16)  
#define         GAS_SO2                     (17)  
float           COCurve[2]      =  {37793.94418, -3.24294658};  //MQ2
float           H2Curve[2]      =  {957.1355042, -2.07442628};  //MQ2
float           LPGCurve[2]     =  {591.6128784, -1.679699732}; //MQ2
float           SmokeCurve[2]   =  {3426.376355, -2.225037973}; //MQ2
float           LPG_secCurve[2] =  {1051.200149, -2.434978052}; //MQ6
float           CH4Curve[2]     =  {1081.498208, -1.443059209}; //MQ6
float           H2_secCurve[2]  =  {137997.7173, -3.76632598};  //MQ6
float           CL2Curve[2]     =  {56.01727602, -1.359048399}; //MQ131
float           O3Curve[2]      =  {42.84561841, -1.043297135}; //MQ131
float           CO2Curve[2]     =  {113.7105289, -3.019713765}; //MQ135
float           CO_secCurve[2]  =  {726.7809737, -4.040111669}; //MQ135
float           NH4Curve[2]     =  {84.07117895, -4.41107687};  //MQ135
float           CO2H50HCurve[2] =  {74.77989144, 3.010328075};  //MQ135
float           CH3Curve[2]     =  {47.01770503, -3.281901967}; //MQ135
float           CH3_2COCurve[2] =  {7.010800878, -2.122018939}; //MQ135
float           C2H5OHCurve[2]  =  {0.2995093465, -3.148170562}; //TGS2600
float           C4H10Curve[2]   =  {0.3555567714, -3.337882361}; //TGS2600
float           H2_terCurve[2]  =  {0.3417050674, -2.887154835}; //TGS2600
unsigned long SLEEP_TIME = 30000; // Sleep time between reads (in seconds)

float mq_ro = 10000.0;  // this has to be tuned 10K Ohm
int val = 0;               // variable to store the value coming from the sensor
int valr = 0;               // variable to store the value coming from the sensor
float valAIQ =0.0;
float valAIQro =0.0;
float lastAIQ =0.0;
int  value =0;
TCPClient client;
byte server[] = { 192, 168, 0, 28 }; // local

void setup() {
  // Register a Spark variable here
  Spark.variable("valAIQ", &valAIQ, DOUBLE);
  Spark.variable("val", &val, INT);
  Spark.variable("mq_ro", &mq_ro, INT);

  Serial.begin(115200);   // open serial over USB
  // On Windows it will be necessary to implement the following line:
  // Make sure your Serial Terminal app is closed before powering your Core
  // Now open your Serial Terminal, and hit any key to continue!
  while(!Serial.available()) SPARK_WLAN_Loop();
  //Serial1.begin(115200);  // open serial over TX and RX pins
  //pinMode(A0, INPUT);
  Serial.println("Ro value calibration: ");
  Serial.println(" ohms");
  Serial.println("Ro default: ");

void loop() {

  //convert to ppm (using default ro)
  valAIQ = MQGetGasPercentage(MQRead(MQ135_SENSOR),mq_ro,GAS_CO2,MQ135_SENSOR);

  Serial.print ( "Vrl / Rs / ratio def / ratio dyn:");
  Serial.print ( val);
  Serial.print ( " / ");
  Serial.print ( mq_ro);
  Serial.print ( " / ");
  Serial.print ( valAIQ);
    if (client.connect(server, 8080))
    client.println("GET /json.htm?type=command&param=udevice&idx=124&nvalue="+String(value)+" HTTP/1.0");
    client.println("Content-Length: 0");
    Serial.println("connection failed");


/***************************** 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, double ppm, float *pcurve )
  int i;
  float val=0;

  for (i=0;i<CALIBRATION_SAMPLE_TIMES;i++) {            //take multiple samples
    val += MQResistanceCalculation(analogRead(mq_pin));
  val = val/CALIBRATION_SAMPLE_TIMES;                   //calculate the average value
  //Ro = Rs * sqrt(a/ppm, b) = Rs * exp( ln(a/ppm) / b )

  return  (long)val*exp((log(pcurve[0]/ppm)/pcurve[1]));


/****************** 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*(4095-raw_adc)/raw_adc));

/*****************************  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));
  return rs;  

/*****************************  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 
int  MQGetPercentage(float rs_ro_ratio, float ro, float *pcurve)
  return (double)(pcurve[0] * pow(((double)rs_ro_ratio/ro), pcurve[1]));

/*****************************  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, float ro, int gas_id, int sensor_id)
  if (sensor_id == MQ2_SENSOR ) {
    if ( gas_id == GAS_CO ) {
     return MQGetPercentage(rs_ro_ratio,ro,COCurve);      //MQ2
    } else if ( gas_id == GAS_H2 ) {
     return MQGetPercentage(rs_ro_ratio,ro,H2Curve);      //MQ2
    } else if ( gas_id == GAS_LPG ) {
     return MQGetPercentage(rs_ro_ratio,ro,LPGCurve);     //MQ2
    } else if ( gas_id == GAS_Smoke ) {
     return MQGetPercentage(rs_ro_ratio,ro,SmokeCurve);   //MQ2
  } else if (sensor_id == MQ6_SENSOR ){
    if ( gas_id == GAS_LPG_sec ) {
      return MQGetPercentage(rs_ro_ratio,ro,LPG_secCurve);  //MQ6
    } else if ( gas_id == GAS_CH4 ) {
      return MQGetPercentage(rs_ro_ratio,ro,CH4Curve);      //MQ6
    } else if ( gas_id == GAS_H2 ) {
      return MQGetPercentage(rs_ro_ratio,ro,H2_secCurve);   //MQ6
  } else if (sensor_id == MQ131_SENSOR ){
    if ( gas_id == GAS_CL2 ) {
       return MQGetPercentage(rs_ro_ratio,ro,CL2Curve);     //MQ131
    } else if ( gas_id == GAS_O3 ) {
       return MQGetPercentage(rs_ro_ratio,ro,O3Curve);      //MQ131
  } else if (sensor_id == MQ135_SENSOR ){
    if ( gas_id == GAS_CO2 ) {
     return MQGetPercentage(rs_ro_ratio,ro,CO2Curve);     //MQ135
    } else if ( gas_id == GAS_NH4 ) {
     return MQGetPercentage(rs_ro_ratio,ro,NH4Curve);     //MQ135
    } else if ( gas_id == GAS_CO2H50H ) {
     return MQGetPercentage(rs_ro_ratio,ro,CO2H50HCurve); //MQ135
    } else if ( gas_id == GAS_CH3 ) {
     return MQGetPercentage(rs_ro_ratio,ro,CH3Curve);     //MQ135
    } else if ( gas_id == GAS_CH3_2CO ) {
     return MQGetPercentage(rs_ro_ratio,ro,CH3_2COCurve); //MQ135
    } else if ( gas_id == GAS_CO_sec ) {
     return MQGetPercentage(rs_ro_ratio,ro,CO_secCurve);  //MQ135
  } else if (sensor_id == TGS2600_SENSOR ){
    if ( gas_id == GAS_C2H5OH ) {
      return MQGetPercentage(rs_ro_ratio,ro,C2H5OHCurve);  //TGS2600
    } else if ( gas_id == GAS_C4H10 ) {
       return MQGetPercentage(rs_ro_ratio,ro,C4H10Curve);   //TGS2600
    } else if ( gas_id == GAS_H2 ) {
       return MQGetPercentage(rs_ro_ratio,ro,H2_terCurve);  //TGS2600
  } else if (sensor_id == S2SH12_SENSOR) {
    if ( gas_id == GAS_SO2 ) {
      //return MQGetPercentage(rs_ro_ratio,ro,C2H5OHCurve);  //TGS2600
      return rs_ro_ratio;
  return 0;


@epierre and @peekay123 - many thanks guys. Really appreciate your help.
My sensor is off ebay -
This is exactly the one I purchased.

I have a multimeter and I’ll share you my findings shortly with respect of voltage. It says in the description that this is a 5V sensor (working voltage).


If you need graphing, my library may save you some time.
The sensor code is from @epierre. Code for the MQ-131 is there
although I never used that sensor in the end. The other sensors
work and graph however (DHT22 / TGS2602 / WSP2110 / Dust)


@Rockvole Thanks for sharing your library. I’ll take a look at it as this would be stage 2 for me.

For now I do the following.
I have water sensor, DHT11, DS18B20 and now MQ-135 as well as four relays connected to Spark Core. The idea is to remotely control water heating (2 immersion heaters 3kw each controlled by 50A SSRs) as well as control water leak, temperature as well as smoke and fire. I am using Twilio to receive an SMS if there is an alert.


@alexsh1 beware of having the MQ sensor too much into vapor, I guess this would alter its behavior. I guess an optical smoke sensor would be best in your case but I don’t know any one except in smoke dectors…


@epierre There is around 32C at around 32% humidity in the boiler room - so not much vapor environment, is it? I was not able to find an optical smoke detector (at least in the UK).


How did you end up reading the analog value of 0-5V?


Hi All,

I’m struggling to calibrate my MQ135.

How do you measure the RL value of the board? I have one I ordered off ebay thats attached onto a pcb with a variable resister. The readings off the variable resister doesn’t come up anywhere near 20k. Looking from the data sheet I measured the resistance between the left bottom pin of the sensor to the A0 off the board but only reading 1k. I also tried to read resistance between the H pins but I didn’t manage to get a reading. Could you take a photo of exactly which pins to measure?

The same with the mq_Ro, how do you tune it to 10K?

I saw that you’ve used for CO2
float CO2Curve[2] = {113.7105289, -3.019713765}; //MQ135

I’ve found uses the following.
ppm = 116.6020682 (Rs/Ro)^-2.769034857
Just wondering how others have set up and how stable and accurate their results have been.

It might be that I have a bad copied version of it… but I’d love to check how others are doing it to make sure of that.



on the Formula it is the same through power regression, I discuss a lot with Davide so I managed to get something I could use. The values I have are from the datasheet, maybe I have taken more point that him, or the no so precise datasheed I use have led to omehow different values but they are not so far !

Regarding resistance, I prefer the “clean air resistance” meaning you should get with the curve a value on the outside air of 399 ppm, and so you deduce the Rl from this (e.g. Netatmo never goes below 399 so they have the same calibration ;-( but it is not a precise one as if you break a perfume bottle next to a MQxx it would jump to the infinite juste because air is saturated with particles…


I have been struggling to get a reliable readings from MQ-135. @epierre did a lot of work, not sure what’s your experience with this sensor, but my experience is that reading are only reliable in the enclosed premises (like storage room). If there is human presence, readings are going up and down - I have not been able to understand why. Additionally, I’d like to have a low powered sensor to run on a battery. MQ-135 is 5V (I’d like to have 3.3V) and consumes a lot of power to heat up.

I am now moving towards NDIR sensor and possibly other more reliable sensors (TGS 2600 and MiCS-4512, which I had difficulty sourcing)


@alexsh1 it reads air, so if air moves it changes. there is no constant concentration in an open volume.


@epierre What would you suggest? Putting the sensor in the box with a vent to present any air movement (like a draft or air conditioner?)


On my MQ135, I read…

  1. .288 volts between Analog Out and Ground, when connected to Arduino’s 5V (Without incense stick smoke)
  2. .253 volts between Analog Out and Ground, when connected to Photon’s Vin (Without incense stick smoke)

analogRead on Arduino returns
RAW value: 38 (Without incense stick smoke)
RAW value: 150 (With incense stick smoke)

analogRead on Photon returns
RAW value: 750 (Without incense stick smoke)
RAW value: 1735 (With incense stick smoke)

With current analogRead PPM value becomes
Arduino: 0.22
Photon: 150
which is completely unusable…

While the analog voltage output remains almost the same, the analog read’s raw value is so very different and making calculation of PPM more complex…

The MQ135 library for Arduino on GitHub here:
And the MQ135 library on Particle’s Web IDE are almost similar, ecept for the fact that in the example code the PPM calculated gets divided by Zero.

The code suggested here by epierre returns this (Without incense stick smoke):
Vrl / Rs / ratio def / ratio dyn:0 / 474629.72 / 38381.00
which also doesn’t match in anyway with any of the other calculations

Any suggestions on getting matched readings between Arduino, Particle and a commercial CO2 Meter?