Skip to content

Commit

Permalink
Adjustments to use the library with the ESP32
Browse files Browse the repository at this point in the history
The original Arduino library for the MQ135 cannot be used with the ESP32 for several reasons. This fork is changed, to work with the ESP32.
  • Loading branch information
MaxQ22 committed May 15, 2022
1 parent 0ed6b93 commit 68b77f6
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 79 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

MQ135_Simulation.raw
MQ135_Simulation.net
MQ135_Simulation.log
7 changes: 5 additions & 2 deletions MQ135.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ v1.0 - First release
@brief Default constructor
@param[in] pin The analog input pin for the readout of the sensor
@param[in] refvpin The analog input pin for the reference voltage measurement
@param[in] rzero Calibration resistance at atmospheric CO2 level
@param[in] rload The load resistance on the board in kOhm
*/
/**************************************************************************/

MQ135::MQ135(uint8_t pin, float rzero, float rload) {
MQ135::MQ135(uint8_t pin, uint8_t refvpin, float rzero, float rload) {
_pin = pin;
_rzero = rzero;
_rload = rload;
_refvpin = refvpin;
}

/**************************************************************************/
Expand Down Expand Up @@ -65,7 +67,8 @@ float MQ135::getCorrectionFactor(float t, float h) {
/**************************************************************************/
float MQ135::getResistance() {
int val = analogRead(_pin);
return ((1023./(float)val) - 1.)*_rload;
int refv = analogRead(_refvpin);
return _rload * ( refv * 0.00080586 * 2 - val * 0.00080586 ) / ( val * 0.00080586 );
}

/**************************************************************************/
Expand Down
3 changes: 2 additions & 1 deletion MQ135.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ v1.0 - First release
class MQ135 {
private:
uint8_t _pin;
uint8_t _refvpin;
float _rload; // The load resistance on the board in kOhm
float _rzero; // Calibration resistance at atmospheric CO2 level

public:
MQ135(uint8_t pin, float rzero=76.63, float rload=10.0);
MQ135(uint8_t pin, uint8_t refvpin, float rzero=130.0, float rload=44.0);
float getCorrectionFactor(float t, float h);
float getResistance();
float getCorrectedResistance(float t, float h);
Expand Down
64 changes: 64 additions & 0 deletions MQ135_Simulation.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Version 4
SHEET 1 992 680
WIRE 0 48 -32 48
WIRE 96 48 80 48
WIRE 224 48 96 48
WIRE 432 48 224 48
WIRE 352 80 64 80
WIRE 96 112 96 48
WIRE 224 112 224 48
WIRE -32 128 -32 48
WIRE 432 128 432 48
WIRE 64 208 64 80
WIRE 352 208 352 80
WIRE 352 208 64 208
WIRE 224 240 224 192
WIRE 304 240 224 240
WIRE 432 240 432 208
WIRE 480 240 432 240
WIRE 224 256 224 240
WIRE 96 272 96 192
WIRE 432 272 432 240
WIRE 16 352 16 272
WIRE 224 352 224 336
FLAG -32 208 0
FLAG 224 352 0
FLAG 432 352 0
FLAG 304 240 VSense
FLAG 480 240 VRef
FLAG 16 352 0
SYMBOL voltage -32 112 R0
SYMATTR InstName V1
SYMATTR Value 5
SYMBOL res 208 96 R0
SYMATTR InstName RSense
SYMATTR Value {Rsense}
SYMBOL res 80 96 R0
SYMATTR InstName RHeat
SYMATTR Value 33
SYMBOL res 96 32 R90
WINDOW 0 0 56 VBottom 2
WINDOW 3 32 56 VTop 2
SYMATTR InstName RPar+
SYMATTR Value 0.5
SYMBOL res 112 256 R90
WINDOW 0 0 56 VBottom 2
WINDOW 3 32 56 VTop 2
SYMATTR InstName Rpar-
SYMATTR Value 0.5
SYMBOL res 208 240 R0
SYMATTR InstName RL
SYMATTR Value 44k
SYMBOL res 416 112 R0
SYMATTR InstName R1
SYMATTR Value 100k
SYMBOL res 416 256 R0
SYMATTR InstName R2
SYMATTR Value 100k
TEXT 584 56 Left 2 !.step param Rsense 24k 90k 1k
TEXT 582 96 Left 2 !.op
TEXT 136 96 Left 2 ;MQ135
TEXT 104 232 Left 2 ;H
TEXT 104 64 Left 2 ;H
TEXT 232 64 Left 2 ;A
TEXT 232 224 Left 2 ;B
76 changes: 62 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
# MQ135 GAS SENSOR

![arduino-library-badge](https://www.ardu-badge.com/badge/MQ135.svg?) ![latest version](https://img.shields.io/github/release/Phoenix1747/MQ135.svg?) ![issues](https://img.shields.io/github/issues/Phoenix1747/MQ135.svg?) ![open pr](https://img.shields.io/github/issues-pr-raw/phoenix1747/MQ135.svg?)

Updated and improved Arduino library for the MQ135 gas/air quality sensor.

This library is also available in the Arduino IDE, see [Arduino Library List](https://www.arduinolibraries.info/libraries/mq135).

## Datasheet

Can be found [here](https://www.olimex.com/Products/Components/Sensors/SNS-MQ135/resources/SNS-MQ135.pdf).
# MQ135 GAS SENSOR ESP32 Fork
This is a fork of the incredible Arduino Library for the MQ135 gas sensor by Phoenix1747, adopted for usage with the ESP32. Despite the inferior performance of the ADC (Analog to Digital Converter) of the ESP32 compared to the Atmel Chips on the Arduinos in terms of linearity and measurement accuracy, i wanted to use the MQ135 with the ESP32s ADC and transmit the values via WIFI. This needed two adjustments to the MQ135 library. First adjustment to be made is the change from a 10 bit ADC (as found on most Arduinos) to a 12 bit ADC, as present on the ESP32. The second adjustment to be made is the voltage level. The MQ135 sensor requires a 5V+-0.1V supply voltage. This means, the output signal needs to be adjusted, so that we get a 0 to 3.3V reading for the ESP32. The concept of this adjustment is to dimension the voltage divider in such a way, that the sense signal voltage swings between 0 and 3.3V.

## Application

Expand All @@ -18,6 +9,8 @@ This type of sensor is used in air quality control equipments for buildings/offi

This library has:
- Corrections for temperature and humidity
- Compatibility with the ESP32 chip, using the Arduino Core
- Measurement of the Reference voltage to enable Kelvin-Sensing
- Measurements:
- getResistance
- getCorrectedResistance
Expand All @@ -26,7 +19,18 @@ This library has:
- getRZero
- getCorrectedRZero

## Calibration
## Library Usage

## Installation
To install the library, just copy the MQ135.cpp and MQ135.h files to your .../arduino/libraries folder.

Then include the MQ135.h file in your project

```cpp
#include <MQ135.h>
```

### Calibration

To get an accurate ppm reading it is important to calibrate the sensor.

Expand All @@ -45,7 +49,51 @@ To finish the calibration process you now only need to pass your `RZERO` value t
MQ135 gasSensor(PIN_MQ135, RZERO);
```
### Sensing concept
The MQ135 sensor itself features 4 pins. Two pins are the 5V supply and ground for the heating element, the two are the connections to the variable resistance, that changes with gas concentration. One of those is connected to a 5V reference voltage and the other one is connected to a reference resistor, called RL in the datasheet. This is the actual measurement signal. The internal sense resistance of the sensor, that changes with gas concentration and the RL resistor form a voltage divider. Via backwards calculation of this voltage divider, the resistance of the sense resistor can be calculated. The MQ135 is wired in this application as shown in the following schematic:
![MQ135 Schematic](/Schematic.PNG)
### Adjusting the sense signal to 3.3V
For usage with the ESP32 The RL reference resistor has to be determined, so that the measurement signal does not exceed 3.3V for the ESP32. As the minimal resistance of the sensor element is 30 kOhms, and the maximum allowed supply voltage is 5.1 V, we can determine the necessary value of the reference resistor. For this calculation, we have to take into account, that the 30 kOhms value is for 100 ppm NH3 gas concentration and it will go down to a factor of 0.8 of that value for higher gas concentrations.
```
RL = ( 3.3V * 30kOhm*0.8 ) / ( 5.1V - 3.3V ) = 44kOhm
```
While this value of reference resistor should be o.k. to be used with the ESP32, of course, to maximize sensitivity and resolution, it is best to measure each individual MQ135s R0 and calculate the corresponding RL. If the R0 of your particular sensor is at the maximum value given in the datasheet of 200 kOhm, the reference resisstor should be 293kOhm, to still get no output voltage above 3.3V and not waist a lot of the resoluation of the ESPs ADC.
On the Arduino, the 5V supply voltage is also used as the reference voltage for the ADC converter. The original library uses this property, so that the reference voltage does not need to be measured. As the MQ135 needs to be supplied with 5V, according to the datasheet, this trick does not work anymore with the ESP32. So another voltage divider (R1 and R2 in the schematic above) is added, to divide the reference voltage by 2 and subsequently measure it with the ESP32 at Node Vref.
This gives another possiblity to enhance the measurement. In the MQ135, the H+ and H- pins are used to supply the heating element, while the A and B pins are used to connect to the sense resistor, that changes with gas concentration. This principle is called a "Kelvin measurement" and prevents an influence of the high current draw of the heating element on the measurement. Unfortunatly, in most cheap MQ135 boards, the H+ and A pins are short circuited and connected via one pin only (VCC). This means, the huge current draw of the heating element (~140mA) leads to a voltage drop over the wires and the connectors. Those parasitic resistances are included in the schematic above as RPar+ and Rpar-. It is recommended to solder the R1/R2 divider directly to the A-pin of the actual MQ135 sensor on the break out board, as otherwise, this voltage drop of several millivolts leads to a measurement error. If the reference resistor is also populated on the break out board, it is recommended to unsolder this resistor, solder it only to the pad on the board, connecting to the B pin of the MQ135 and connect the other resistor terminal via a soldered jumper wire directly to GND of the ESP32.
Using this schematic and measureing the VSense and VRef nodes with the ESP32s ADC, the sense resistor (Rsense) value can be calculated as follows:
```
RSense = RL * ( VRef * 2 - VSense ) / VSense
```
### Adjusting for the 12-bit ADC
Adjusting the library to the 12-bit ADC of the ESP32 is trivial. In a 12bit-ADC of the ESP32, the measured voltage is calculated by the following formula:
```
V = 3.3 V/(4096-1) * Code
V = 0.00080586 * Code
```
So the sense resistance is calculated with:
```
RSense = RRef * ( VRefCode * 0.00080586 * 2 - VSenseCode * 0.00080586 ) / ( VSenseCode * 0.00080586 )
```
This formula is implemented in the MQ135::getResistance() function. Additionally, a new parameter is added to the constructor of the MQ135 class, to pass the pin number, the VRef voltage is measured with. From there on, the original Arduino library is used unchanged, to calculate the actual gas concentration based on the measured sense resistance.
## Datasheet
The datasheet of the MQ135 gas sennsor can be found [here](https://www.olimex.com/Products/Components/Sensors/SNS-MQ135/resources/SNS-MQ135.pdf).
## More Info
The example for the temperature and humidity copensation uses a BME280 to compensate for temperature and humidity effects. The datasheet of the BME280 can be found [here](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf). To read out the values from the BME280, the Adafruit library is used in the example, which can be found [here](https://github.com/adafruit/Adafruit_BME280_Library).
https://hackaday.io/project/3475-sniffing-trinket/log/12363-mq135-arduino-library
For more information on the original Arduino library visit:
https://hackaday.io/project/3475-sniffing-trinket/log/12363-mq135-arduino-library
Binary file added Schematic.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 139 additions & 0 deletions examples/MQ135/MQ135.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**************************************************************************/
/*!
@file MQ135.cpp
@author G.Krocker (Mad Frog Labs)
@license GNU GPLv3
First version of an Arduino Library for the MQ135 gas sensor
TODO: Review the correction factor calculation. This currently relies on
the datasheet but the information there seems to be wrong.
@section HISTORY
v1.0 - First release
*/
/**************************************************************************/

#include "MQ135.h"

/**************************************************************************/
/*!
@brief Default constructor
@param[in] pin The analog input pin for the readout of the sensor
@param[in] refvpin The analog input pin for the reference voltage measurement
@param[in] rzero Calibration resistance at atmospheric CO2 level
@param[in] rload The load resistance on the board in kOhm
*/
/**************************************************************************/

MQ135::MQ135(uint8_t pin, uint8_t refvpin, float rzero, float rload) {
_pin = pin;
_rzero = rzero;
_rload = rload;
_refvpin = refvpin;
}

/**************************************************************************/
/*!
@brief Get the correction factor to correct for temperature and humidity
@param[in] t The ambient air temperature
@param[in] h The relative humidity
@return The calculated correction factor
*/
/**************************************************************************/
float MQ135::getCorrectionFactor(float t, float h) {
// Linearization of the temperature dependency curve under and above 20 degree C
// below 20degC: fact = a * t * t - b * t - (h - 33) * d
// above 20degC: fact = a * t + b * h + c
// this assumes a linear dependency on humidity
if(t < 20){
return CORA * t * t - CORB * t + CORC - (h-33.)*CORD;
} else {
return CORE * t + CORF * h + CORG;
}
}

/**************************************************************************/
/*!
@brief Get the resistance of the sensor, ie. the measurement value
Known issue: If the ADC resolution is not 10-bits, this will give
back garbage values!
@return The sensor resistance in kOhm
*/
/**************************************************************************/
float MQ135::getResistance() {
int val = analogRead(_pin);
int refv = analogRead(_refvpin);
return _rload * ( refv * 0.00080586 * 2 - val * 0.00080586 ) / ( val * 0.00080586 );
}

/**************************************************************************/
/*!
@brief Get the resistance of the sensor, ie. the measurement value corrected
for temp/hum
@param[in] t The ambient air temperature
@param[in] h The relative humidity
@return The corrected sensor resistance kOhm
*/
/**************************************************************************/
float MQ135::getCorrectedResistance(float t, float h) {
return getResistance()/getCorrectionFactor(t, h);
}

/**************************************************************************/
/*!
@brief Get the ppm of CO2 sensed (assuming only CO2 in the air)
@return The ppm of CO2 in the air
*/
/**************************************************************************/
float MQ135::getPPM() {
return PARA * pow((getResistance()/_rzero), -PARB);
}

/**************************************************************************/
/*!
@brief Get the ppm of CO2 sensed (assuming only CO2 in the air), corrected
for temp/hum
@param[in] t The ambient air temperature
@param[in] h The relative humidity
@return The ppm of CO2 in the air
*/
/**************************************************************************/
float MQ135::getCorrectedPPM(float t, float h) {
return PARA * pow((getCorrectedResistance(t, h)/_rzero), -PARB);
}

/**************************************************************************/
/*!
@brief Get the resistance RZero of the sensor for calibration purposes
@return The sensor resistance RZero in kOhm
*/
/**************************************************************************/
float MQ135::getRZero() {
return getResistance() * pow((ATMOCO2/PARA), (1./PARB));
}

/**************************************************************************/
/*!
@brief Get the corrected resistance RZero of the sensor for calibration
purposes
@param[in] t The ambient air temperature
@param[in] h The relative humidity
@return The corrected sensor resistance RZero in kOhm
*/
/**************************************************************************/
float MQ135::getCorrectedRZero(float t, float h) {
return getCorrectedResistance(t, h) * pow((ATMOCO2/PARA), (1./PARB));
}
Loading

0 comments on commit 68b77f6

Please sign in to comment.