├── extras ├── img │ ├── MQ131_bb.png │ ├── MQ131_schem.png │ └── MQ131_pinout.png ├── MQ131_Arduino_Uno_circuit.fzz └── datasheet │ ├── Sensitivity_curves.xlsx │ ├── MQ131-low-concentration.pdf │ ├── MQ131-high-concentration.pdf │ ├── MQ131-low-concentration-SnO2.pdf │ ├── mq131_high_fit.py │ └── mq131_low_fit.py ├── keywords.txt ├── .gitignore ├── sonar-project.properties ├── library.properties ├── .github └── workflows │ └── build.yml ├── LICENSE ├── examples ├── read_high_concentration │ └── read_high_concentration.ino ├── read_low_concentration │ └── read_low_concentration.ino └── calibrate_and_read │ └── calibrate_and_read.ino ├── src ├── MQ131.h └── MQ131.cpp └── README.md /extras/img/MQ131_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/img/MQ131_bb.png -------------------------------------------------------------------------------- /extras/img/MQ131_schem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/img/MQ131_schem.png -------------------------------------------------------------------------------- /extras/img/MQ131_pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/img/MQ131_pinout.png -------------------------------------------------------------------------------- /extras/MQ131_Arduino_Uno_circuit.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/MQ131_Arduino_Uno_circuit.fzz -------------------------------------------------------------------------------- /extras/datasheet/Sensitivity_curves.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/datasheet/Sensitivity_curves.xlsx -------------------------------------------------------------------------------- /extras/datasheet/MQ131-low-concentration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/datasheet/MQ131-low-concentration.pdf -------------------------------------------------------------------------------- /extras/datasheet/MQ131-high-concentration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/datasheet/MQ131-high-concentration.pdf -------------------------------------------------------------------------------- /extras/datasheet/MQ131-low-concentration-SnO2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ostaquet/Arduino-MQ131-driver/HEAD/extras/datasheet/MQ131-low-concentration-SnO2.pdf -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # Syntax Coloring Map For Arduino-MQ131-driver 2 | 3 | # Datatypes (KEYWORD1) 4 | MQ131 KEYWORD3 5 | 6 | # Methods and Functions (KEYWORD2) 7 | calibrate KEYWORD2 8 | begin KEYWORD2 9 | sample KEYWORD2 10 | 11 | # Instances (KEYWORD2) 12 | 13 | # Constants (LITERAL1) 14 | LOW_CONCENTRATION 15 | HIGH_CONCENTRATION 16 | PPM 17 | PPB 18 | MG_M3 19 | UG_M3 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=ostaquet_Arduino-MQ131-driver 2 | sonar.organization=ostaquet 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=Arduino-MQ131-driver 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | #sonar.sources=. 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | #sonar.sourceEncoding=UTF-8 13 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=MQ131 gas sensor 2 | version=1.5.2 3 | author=Olivier Staquet 4 | maintainer=Olivier Staquet 5 | sentence=Library for measuring ozone (O3) concentration with sensor MQ131 6 | paragraph=This library supports low concentration (WO3 and SnO2 versions) and high concentration sensors and accept variable load resistance. 7 | category=Sensors 8 | url=https://github.com/ostaquet/Arduino-MQ131-driver 9 | architectures=* 10 | repository=https://github.com/ostaquet/Arduino-MQ131-driver 11 | license=MIT 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | sonarcloud: 10 | name: SonarCloud 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 16 | - name: SonarCloud Scan 17 | uses: SonarSource/sonarcloud-github-action@master 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 20 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 21 | -------------------------------------------------------------------------------- /extras/datasheet/mq131_high_fit.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from scipy.optimize import curve_fit 3 | import numpy as np 4 | 5 | def func(x, a, b, c): 6 | return a*(x**b)+c 7 | 8 | x_points = np.array([1, 1.2, 2, 2.7, 4.1, 6, 8]) 9 | y_points = np.array([0, 10, 50, 100, 200, 500, 1000]) 10 | 11 | plt.scatter(x_points, y_points, c='blue', label='real data') 12 | 13 | #give bigger weight to first and last point in real data ("force" function to fit them more precisely) 14 | sigma = np.ones(len(x_points)) 15 | sigma[[0, -1]] = 0.01 16 | 17 | popt, _ = curve_fit(func, x_points, y_points, sigma=sigma) 18 | print(popt) 19 | 20 | x_show = np.arange(x_points[0], x_points[-1], 0.01) 21 | 22 | plt.plot(x_show, func(x_show, *popt), c='red', label='params: a=%5.3f, b=%5.3f, c=%5.3f' % tuple(popt)) 23 | 24 | plt.xlabel('x') 25 | plt.ylabel('y') 26 | plt.legend() 27 | plt.show() 28 | 29 | diffs = y_points - func(x_points, *popt) 30 | ss_tot = np.sum((y_points-np.mean(y_points))**2) 31 | ss_res = np.sum(diffs**2) 32 | r_to_2 = 1 - (ss_res/ss_tot) 33 | print('R^2 score = %.10f' % r_to_2) 34 | -------------------------------------------------------------------------------- /extras/datasheet/mq131_low_fit.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from scipy.optimize import curve_fit 3 | import numpy as np 4 | 5 | def func(x, a, b, c): 6 | return a*(x**b)+c 7 | 8 | x_points = np.array([1, 1.12, 1.9, 2.5, 3.8, 5.6, 7.5]) 9 | y_points = np.array([0, 10, 50, 100, 200, 500, 1000]) 10 | 11 | plt.scatter(x_points, y_points, c='blue', label='real data') 12 | 13 | #give bigger weight to first and last point in real data ("force" function to fit them more precisely) 14 | sigma = np.ones(len(x_points)) 15 | sigma[[0, -1]] = 0.01 16 | 17 | popt, _ = curve_fit(func, x_points, y_points, sigma=sigma) 18 | print(popt) 19 | 20 | x_show = np.arange(x_points[0], x_points[-1], 0.01) 21 | 22 | plt.plot(x_show, func(x_show, *popt), c='red', label='params: a=%5.3f, b=%5.3f, c=%5.3f' % tuple(popt)) 23 | 24 | plt.xlabel('x') 25 | plt.ylabel('y') 26 | plt.legend() 27 | plt.show() 28 | 29 | diffs = y_points - func(x_points, *popt) 30 | ss_tot = np.sum((y_points-np.mean(y_points))**2) 31 | ss_res = np.sum(diffs**2) 32 | r_to_2 = 1 - (ss_res/ss_tot) 33 | print('R^2 score = %.10f' % r_to_2) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Olivier Staquet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/read_high_concentration/read_high_concentration.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Sample the ozone concentration every 60 seconds 3 | * 4 | * Example code base on high concentration sensor (metal) 5 | * and load resistance of 1MOhms 6 | * 7 | * Schematics and details available on https://github.com/ostaquet/Arduino-MQ131-driver 8 | ****************************************************************************** 9 | * MIT License 10 | * 11 | * Copyright (c) 2018 Olivier Staquet 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | *******************************************************************************/ 31 | 32 | #include 33 | 34 | void setup() { 35 | Serial.begin(115200); 36 | 37 | // Init the sensor 38 | // - Heater control on pin 2 39 | // - Sensor analog read on pin A0 40 | // - Model LOW_CONCENTRATION 41 | // - Load resistance RL of 1MOhms (1000000 Ohms) 42 | MQ131.begin(2,A0, HIGH_CONCENTRATION, 1000000); 43 | 44 | Serial.println("Calibration parameters"); 45 | Serial.print("R0 = "); 46 | Serial.print(MQ131.getR0()); 47 | Serial.println(" Ohms"); 48 | Serial.print("Time to heat = "); 49 | Serial.print(MQ131.getTimeToRead()); 50 | Serial.println(" s"); 51 | } 52 | 53 | void loop() { 54 | Serial.println("Sampling..."); 55 | MQ131.sample(); 56 | Serial.print("Concentration O3 : "); 57 | Serial.print(MQ131.getO3(PPM)); 58 | Serial.println(" ppm"); 59 | Serial.print("Concentration O3 : "); 60 | Serial.print(MQ131.getO3(PPB)); 61 | Serial.println(" ppb"); 62 | Serial.print("Concentration O3 : "); 63 | Serial.print(MQ131.getO3(MG_M3)); 64 | Serial.println(" mg/m3"); 65 | Serial.print("Concentration O3 : "); 66 | Serial.print(MQ131.getO3(UG_M3)); 67 | Serial.println(" ug/m3"); 68 | 69 | delay(60000); 70 | } 71 | -------------------------------------------------------------------------------- /examples/read_low_concentration/read_low_concentration.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Sample the ozone concentration every 60 seconds 3 | * 4 | * Example code base on low concentration sensor (black bakelite) 5 | * and load resistance of 1MOhms 6 | * 7 | * Schematics and details available on https://github.com/ostaquet/Arduino-MQ131-driver 8 | ****************************************************************************** 9 | * MIT License 10 | * 11 | * Copyright (c) 2018 Olivier Staquet 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | *******************************************************************************/ 31 | 32 | #include 33 | 34 | void setup() { 35 | Serial.begin(115200); 36 | 37 | // Init the sensor 38 | // - Heater control on pin 2 39 | // - Sensor analog read on pin A0 40 | // - Model LOW_CONCENTRATION 41 | // - Load resistance RL of 1MOhms (1000000 Ohms) 42 | MQ131.begin(2,A0, LOW_CONCENTRATION, 1000000); 43 | 44 | Serial.println("Calibration parameters"); 45 | Serial.print("R0 = "); 46 | Serial.print(MQ131.getR0()); 47 | Serial.println(" Ohms"); 48 | Serial.print("Time to heat = "); 49 | Serial.print(MQ131.getTimeToRead()); 50 | Serial.println(" s"); 51 | } 52 | 53 | void loop() { 54 | Serial.println("Sampling..."); 55 | MQ131.sample(); 56 | Serial.print("Concentration O3 : "); 57 | Serial.print(MQ131.getO3(PPM)); 58 | Serial.println(" ppm"); 59 | Serial.print("Concentration O3 : "); 60 | Serial.print(MQ131.getO3(PPB)); 61 | Serial.println(" ppb"); 62 | Serial.print("Concentration O3 : "); 63 | Serial.print(MQ131.getO3(MG_M3)); 64 | Serial.println(" mg/m3"); 65 | Serial.print("Concentration O3 : "); 66 | Serial.print(MQ131.getO3(UG_M3)); 67 | Serial.println(" ug/m3"); 68 | 69 | delay(60000); 70 | } 71 | -------------------------------------------------------------------------------- /examples/calibrate_and_read/calibrate_and_read.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Calibrate the MQ131 and start sampling every 60 seconds 3 | * 4 | * Example code base on low concentration sensor (black bakelite) 5 | * and load resistance of 1MOhms 6 | * 7 | * Schematics and details available on https://github.com/ostaquet/Arduino-MQ131-driver 8 | ****************************************************************************** 9 | * MIT License 10 | * 11 | * Copyright (c) 2018 Olivier Staquet 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | *******************************************************************************/ 31 | 32 | #include 33 | 34 | void setup() { 35 | Serial.begin(115200); 36 | 37 | // Init the sensor 38 | // - Heater control on pin 2 39 | // - Sensor analog read on pin A0 40 | // - Model LOW_CONCENTRATION 41 | // - Load resistance RL of 1MOhms (1000000 Ohms) 42 | MQ131.begin(2,A0, LOW_CONCENTRATION, 1000000); 43 | 44 | Serial.println("Calibration in progress..."); 45 | 46 | MQ131.calibrate(); 47 | 48 | Serial.println("Calibration done!"); 49 | Serial.print("R0 = "); 50 | Serial.print(MQ131.getR0()); 51 | Serial.println(" Ohms"); 52 | Serial.print("Time to heat = "); 53 | Serial.print(MQ131.getTimeToRead()); 54 | Serial.println(" s"); 55 | } 56 | 57 | void loop() { 58 | Serial.println("Sampling..."); 59 | MQ131.sample(); 60 | Serial.print("Concentration O3 : "); 61 | Serial.print(MQ131.getO3(PPM)); 62 | Serial.println(" ppm"); 63 | Serial.print("Concentration O3 : "); 64 | Serial.print(MQ131.getO3(PPB)); 65 | Serial.println(" ppb"); 66 | Serial.print("Concentration O3 : "); 67 | Serial.print(MQ131.getO3(MG_M3)); 68 | Serial.println(" mg/m3"); 69 | Serial.print("Concentration O3 : "); 70 | Serial.print(MQ131.getO3(UG_M3)); 71 | Serial.println(" ug/m3"); 72 | 73 | delay(60000); 74 | } 75 | -------------------------------------------------------------------------------- /src/MQ131.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Arduino-MQ131-driver * 3 | * -------------------- * 4 | * Arduino driver for gas sensor MQ131 (O3) * 5 | * Author: Olivier Staquet * 6 | * Last version available on https://github.com/ostaquet/Arduino-MQ131-driver * 7 | ****************************************************************************** 8 | * MIT License 9 | * 10 | * Copyright (c) 2018 Olivier Staquet 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | *******************************************************************************/ 30 | 31 | #ifndef _MQ131_H_ 32 | #define _MQ131_H_ 33 | 34 | #include 35 | 36 | // Default values 37 | #define MQ131_DEFAULT_RL 1000000 // Default load resistance of 1MOhms 38 | #define MQ131_DEFAULT_STABLE_CYCLE 15 // Number of cycles with low deviation to consider 39 | // the calibration as stable and reliable 40 | #define MQ131_DEFAULT_TEMPERATURE_CELSIUS 20 // Default temperature to correct environmental drift 41 | #define MQ131_DEFAULT_HUMIDITY_PERCENT 65 // Default humidity to correct environmental drift 42 | #define MQ131_DEFAULT_LO_CONCENTRATION_R0 1917.22 // Default R0 for low concentration MQ131 43 | #define MQ131_DEFAULT_LO_CONCENTRATION_TIME2READ 80 // Default time to read before stable signal for low concentration MQ131 44 | #define MQ131_DEFAULT_HI_CONCENTRATION_R0 235.00 // Default R0 for high concentration MQ131 45 | #define MQ131_DEFAULT_HI_CONCENTRATION_TIME2READ 80 // Default time to read before stable signal for high concentration MQ131 46 | 47 | enum MQ131Model {LOW_CONCENTRATION, HIGH_CONCENTRATION,SN_O2_LOW_CONCENTRATION}; 48 | enum MQ131Unit {PPM, PPB, MG_M3, UG_M3}; 49 | 50 | class MQ131Class { 51 | public: 52 | // Constructor 53 | MQ131Class(uint32_t _RL); 54 | virtual ~MQ131Class(); 55 | 56 | // Initialize the driver 57 | void begin(uint8_t _pinPower, uint8_t _pinSensor, MQ131Model _model, uint32_t _RL, Stream* _debugStream = NULL); 58 | 59 | // Manage a full cycle with delay() without giving the hand back to 60 | // the main loop (delay() function included) 61 | void sample(); 62 | 63 | // Read the concentration of gas 64 | // The environment should be set for accurate results 65 | float getO3(MQ131Unit unit); 66 | 67 | // Define environment 68 | // Define the temperature (in Celsius) and humidity (in %) to adjust the 69 | // output values based on typical characteristics of the MQ131 70 | void setEnv(int8_t tempCels, uint8_t humPc); 71 | 72 | // Setup calibration: Time to read 73 | // Define the time to read after started the heater 74 | // Get function also available to know the value after calibrate() 75 | // (the time to read is calculated automatically after calibration) 76 | void setTimeToRead(uint32_t sec); 77 | long getTimeToRead(); 78 | 79 | // Setup calibration: R0 80 | // Define the R0 for the calibration 81 | // Get function also available to know the value after calibrate() 82 | // (the time to read is calculated automatically after calibration) 83 | void setR0(float _valueR0); 84 | float getR0(); 85 | 86 | // Launch full calibration cycle 87 | // Ideally, 20°C 65% humidity in clean fresh air (can take some minutes) 88 | // For further use of calibration values, please use getTimeToRead() and getR0() 89 | void calibrate(); 90 | 91 | private: 92 | // Internal helpers 93 | // Internal function to manage the heater 94 | void startHeater(); 95 | bool isTimeToRead(); 96 | void stopHeater(); 97 | 98 | // Internal reading function of Rs 99 | float readRs(); 100 | 101 | // Get environmental correction to apply on ration Rs/R0 102 | float getEnvCorrectRatio(); 103 | 104 | // Convert gas unit of gas concentration 105 | float convert(float input, MQ131Unit unitIn, MQ131Unit unitOut); 106 | 107 | // Internal variables 108 | // Model of MQ131 109 | MQ131Model model; 110 | 111 | // Serial console for the debug 112 | Stream* debugStream = NULL; 113 | bool enableDebug = false; 114 | 115 | // Details about the circuit: pins and load resistance value 116 | uint8_t pinPower = -1; 117 | uint8_t pinSensor = -1; 118 | uint32_t valueRL = -1; 119 | 120 | // Timer to keep track of the pre-heating 121 | uint32_t secLastStart = -1; 122 | uint32_t secToRead = -1; 123 | 124 | // Calibration of R0 125 | float valueR0 = -1; 126 | 127 | // Last value for sensor resistance 128 | float lastValueRs = -1; 129 | 130 | // Parameters for environment 131 | int8_t temperatureCelsuis = MQ131_DEFAULT_TEMPERATURE_CELSIUS; 132 | uint8_t humidityPercent = MQ131_DEFAULT_HUMIDITY_PERCENT; 133 | }; 134 | 135 | extern MQ131Class MQ131; 136 | 137 | #endif // _MQ131_H_ 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino-MQ131-driver 2 | Arduino library for ozone gas sensor MQ131 3 | 4 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/LICENSE) 5 | [![GitHub release](https://img.shields.io/github/release/ostaquet/Arduino-MQ131-driver.svg)](#releases) 6 | [![GitHub issues](https://img.shields.io/github/issues/ostaquet/Arduino-MQ131-driver.svg)](https://github.com/ostaquet/Arduino-MQ131-driver/issues) 7 | 8 | This is a comprehensive Arduino library to obtain ozone (O3) concentration in the air with the Winsen MQ131 sensor. The library supports both versions of the sensor (low concentration and high concentration), the calibration, the control of the heater, the environmental adjustments (temperature and humidity) and the output of values in ppm (parts per million), ppb (parts per billion), mg/m3 and µg/m3. 9 | 10 | ## To know before starting... 11 | * The MQ131 is a [semiconductor gas sensor](https://en.wikipedia.org/wiki/Gas_detector#Semiconductor) composed by a heater circuit and a sensor circuit. 12 | * Heater consumes at least 150 mA. So, __don't connect it directly on a pin of the Arduino__. 13 | * It is important to respect the pinout of the sensor. If you put Vcc on the sensor and not on the heater, __you could damage your sensor irreversibly.__ 14 | * Sensor MQ131 requires minimum 48h preheat time before giving consistent results (also called "burn-in" time) 15 | * There are three different MQ131: 16 | * a black bakelite sensor for low concentration of ozone (with WO3 sensitive material) 17 | * a blue bakelite sensor for low concentration of ozone (with SnO2 sensitive material) 18 | * a metal sensor for high concentration of ozone. 19 | * This driver is made to control the "naked" [Winsen](https://www.winsen-sensor.com) MQ131. The driver is able to pilot the [low concentration WO3 version](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/extras/datasheet/MQ131-low-concentration.pdf), the [low concentration Sn02 version](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/extras/datasheet/MQ131-low-concentration-SnO2.pdf) and the [high concentration version](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/extras/datasheet/MQ131-high-concentration.pdf). 20 | * To measure the air quality (e.g. pollution), it's better to use the low concentration MQ131 because the high concentration is not accurate enough for low concentration. 21 | 22 | ## How to install the library? 23 | The easiest way to install the library is to go to the Library manager of the Arduino IDE and install the library. 24 | 1. In the Arduino IDE, go into Menu _Tools_ -> _Manage Libraries..._ 25 | 2. Search for _MQ131_ 26 | 3. Install _MQ131 gas sensor by Olivier Staquet_ 27 | 28 | ## Circuit 29 | * Heater is controlled by MOSFET N-channel via the control pin (on schema pin 2, yellow connector) 30 | * Result of the sensor is read through analog with RL of 1MΩ (on schema pin A0, green connector) 31 | 32 | Remarks: 33 | * The MOSFET is a IRF840 but any N-channel MOSFET that can be controlled by 5V is OK. 34 | * The load resistance (RL) can be different than 1MΩ (tested also with 10kΩ) but don't forget to calibrate the R0 and time to heat. 35 | 36 | ![Breadboard schematics](extras/img/MQ131_bb.png) 37 | 38 | ![MQ131 pinout](extras/img/MQ131_pinout.png) 39 | 40 | ![Schematics](extras/img/MQ131_schem.png) 41 | 42 | ## Basic program to use your MQ131 43 | ``` 44 | #include "MQ131.h" 45 | 46 | void setup() { 47 | Serial.begin(115200); 48 | 49 | // Init the sensor 50 | // - Heater control on pin 2 51 | // - Sensor analog read on pin A0 52 | // - Model LOW_CONCENTRATION 53 | // - Load resistance RL of 1MOhms (1000000 Ohms) 54 | MQ131.begin(2,A0, LOW_CONCENTRATION, 1000000); 55 | 56 | Serial.println("Calibration in progress..."); 57 | 58 | MQ131.calibrate(); 59 | 60 | Serial.println("Calibration done!"); 61 | Serial.print("R0 = "); 62 | Serial.print(MQ131.getR0()); 63 | Serial.println(" Ohms"); 64 | Serial.print("Time to heat = "); 65 | Serial.print(MQ131.getTimeToRead()); 66 | Serial.println(" s"); 67 | } 68 | 69 | void loop() { 70 | Serial.println("Sampling..."); 71 | MQ131.sample(); 72 | Serial.print("Concentration O3 : "); 73 | Serial.print(MQ131.getO3(PPM)); 74 | Serial.println(" ppm"); 75 | Serial.print("Concentration O3 : "); 76 | Serial.print(MQ131.getO3(PPB)); 77 | Serial.println(" ppb"); 78 | Serial.print("Concentration O3 : "); 79 | Serial.print(MQ131.getO3(MG_M3)); 80 | Serial.println(" mg/m3"); 81 | Serial.print("Concentration O3 : "); 82 | Serial.print(MQ131.getO3(UG_M3)); 83 | Serial.println(" ug/m3"); 84 | 85 | delay(60000); 86 | } 87 | ``` 88 | 89 | The result gives us: 90 | ``` 91 | Calibration in progress... 92 | Calibration done! 93 | R0 = 1917.22 Ohms 94 | Time to heat = 80 s 95 | Sampling... 96 | Concentration O3 : 0.01 ppm 97 | Concentration O3 : 7.95 ppb 98 | Concentration O3 : 0.02 mg/m3 99 | Concentration O3 : 16.80 ug/m3 100 | ``` 101 | 102 | ## Usage 103 | The driver has to be initialized with 4 parameters: 104 | * Pin to control the heater power (example: 2) 105 | * Pin to measure the analog output (example: A0) 106 | * Model of sensor `LOW_CONCENTRATION`, `SN_O2_LOW_CONCENTRATION` or `HIGH_CONCENTRATION` (example: `LOW_CONCENTRATION`) 107 | * Value of load resistance in Ohms (example: 1000000 Ohms) 108 | ``` 109 | MQ131.begin(2,A0, LOW_CONCENTRATION, 1000000); 110 | ``` 111 | 112 | Before using the driver, it's better to calibrate it. You can do that through the function `calibrate()`. The best is to calibrate the sensor at 20°C and 65% of humidity in clean fresh air. If you need some log on the console, mention the serial in the function `begin()` (example by using the standard Serial: `MQ131.begin(2,A0, LOW_CONCENTRATION, 1000000, (Stream *)&Serial);`). 113 | 114 | The calibration adjusts 2 parameters: 115 | * The value of the base resistance (R0) 116 | * The time required to heat the sensor and get consistent readings (Time to read) 117 | ``` 118 | MQ131.calibrate(); 119 | ``` 120 | 121 | Those calibration values are used for the usage of the sensor as long as the Arduino is not restarted. Nevertheless, you can get the values for your sensor through the getters: 122 | ``` 123 | MQ131.getR0(); 124 | MQ131.getTimeToRead(); 125 | ``` 126 | 127 | And set up the values in the initialization of your program through the setters: 128 | ``` 129 | MQ131.setR0(value); 130 | MQ131.setTimeToRead(value); 131 | ``` 132 | 133 | In order to get the values from the sensor, you just start the process with the `sample()` function. **Please notice that the function locks the flow.** If you want to do additional processing during the heating/reading process, you should extend the class. The methods are protected and the driver can be extended easily. 134 | ``` 135 | MQ131.sample(); 136 | ``` 137 | 138 | The reading of the values is done through the `getO3()` function. Based on the parameter, you can ask to receive the result in ppm (`PPM`), ppb (`PPB`), mg/m3 (`MG_M3`) or µg/m3 (`UG_M3`). 139 | ``` 140 | MQ131.getO3(PPM); 141 | MQ131.getO3(PPB); 142 | MQ131.getO3(MG_M3); 143 | MQ131.getO3(UG_M3); 144 | ``` 145 | 146 | The sensor is sensible to environmental variation (temperature and humidity). If you want to have correct values, you should set the temperature and the humidity before the call to `getO3()` function with the function `setEnv()`. Temperature are in °C and humidity in %. The values should come from another sensor like the DHT22. 147 | ``` 148 | MQ131.setEnv(23, 70); 149 | ``` 150 | 151 | 152 | ## Links 153 | * [Calculation of sensitivity curves](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/extras/datasheet/Sensitivity_curves.xlsx) 154 | * [Datasheet MQ131 low concentration WO3 (black bakelite version)](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/extras/datasheet/MQ131-low-concentration.pdf) 155 | * [Datasheet MQ131 low concentration SnO2 (blue bakelite version)](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/extras/datasheet/MQ131-low-concentration-SnO2.pdf) 156 | * [Datasheet MQ131 high concentration (metal version)](https://github.com/ostaquet/Arduino-MQ131-driver/blob/master/extras/datasheet/MQ131-high-concentration.pdf) 157 | -------------------------------------------------------------------------------- /src/MQ131.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Arduino-MQ131-driver * 3 | * -------------------- * 4 | * Arduino driver for gas sensor MQ131 (O3) * 5 | * Author: Olivier Staquet * 6 | * Last version available on https://github.com/ostaquet/Arduino-MQ131-driver * 7 | ****************************************************************************** 8 | * MIT License 9 | * 10 | * Copyright (c) 2018 Olivier Staquet 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | *******************************************************************************/ 30 | 31 | #include "MQ131.h" 32 | 33 | /** 34 | * Constructor, nothing special to do 35 | */ 36 | MQ131Class::MQ131Class(uint32_t _RL) { 37 | valueRL = _RL; 38 | } 39 | 40 | /** 41 | * Destructor, nothing special to do 42 | */ 43 | MQ131Class::~MQ131Class() { 44 | } 45 | 46 | /** 47 | * Init core variables 48 | */ 49 | void MQ131Class::begin(uint8_t _pinPower, uint8_t _pinSensor, MQ131Model _model, uint32_t _RL, Stream* _debugStream) { 50 | // Define if debug is requested 51 | enableDebug = _debugStream != NULL; 52 | debugStream = _debugStream; 53 | 54 | // Setup the model 55 | model = _model; 56 | 57 | // Store the circuit info (pin and load resistance) 58 | pinPower = _pinPower; 59 | pinSensor = _pinSensor; 60 | valueRL = _RL; 61 | 62 | // Setup default calibration value 63 | switch(model) { 64 | case LOW_CONCENTRATION : 65 | setR0(MQ131_DEFAULT_LO_CONCENTRATION_R0); 66 | setTimeToRead(MQ131_DEFAULT_LO_CONCENTRATION_TIME2READ); 67 | break; 68 | case HIGH_CONCENTRATION : 69 | setR0(MQ131_DEFAULT_HI_CONCENTRATION_R0); 70 | setTimeToRead(MQ131_DEFAULT_HI_CONCENTRATION_TIME2READ); 71 | break; 72 | case SN_O2_LOW_CONCENTRATION: 73 | // Not tested by @ostaquet (I don't have this type of sensor) 74 | setR0(MQ131_DEFAULT_LO_CONCENTRATION_R0); 75 | setTimeToRead(MQ131_DEFAULT_LO_CONCENTRATION_TIME2READ); 76 | break; 77 | } 78 | 79 | // Setup pin mode 80 | pinMode(pinPower, OUTPUT); 81 | pinMode(pinSensor, INPUT); 82 | 83 | // Switch off the heater as default status 84 | digitalWrite(pinPower, LOW); 85 | } 86 | 87 | /** 88 | * Do a full cycle (heater, reading, stop heater) 89 | * The function gives back the hand only at the end 90 | * of the read cycle! 91 | */ 92 | void MQ131Class::sample() { 93 | startHeater(); 94 | while(!isTimeToRead()) { 95 | delay(1000); 96 | } 97 | lastValueRs = readRs(); 98 | stopHeater(); 99 | } 100 | 101 | /** 102 | * Start the heater 103 | */ 104 | void MQ131Class::startHeater() { 105 | digitalWrite(pinPower, HIGH); 106 | secLastStart = millis()/1000; 107 | } 108 | 109 | /** 110 | * Check if it is the right time to read the Rs value 111 | */ 112 | bool MQ131Class::isTimeToRead() { 113 | // Check if the heater has been started... 114 | if(secLastStart < 0) { 115 | return false; 116 | } 117 | // OK, check if it's the time to read based on calibration parameters 118 | if(millis() / 1000 >= secLastStart + getTimeToRead()) { 119 | return true; 120 | } 121 | return false; 122 | } 123 | 124 | /** 125 | * Stop the heater 126 | */ 127 | void MQ131Class::stopHeater() { 128 | digitalWrite(pinPower, LOW); 129 | secLastStart = -1; 130 | } 131 | 132 | /** 133 | * Get parameter time to read 134 | */ 135 | long MQ131Class::getTimeToRead() { 136 | return secToRead; 137 | } 138 | 139 | /** 140 | * Set parameter time to read (for calibration or to recall 141 | * calibration from previous run) 142 | */ 143 | void MQ131Class::setTimeToRead(uint32_t sec) { 144 | secToRead = sec; 145 | } 146 | 147 | /** 148 | * Read Rs value 149 | */ 150 | float MQ131Class::readRs() { 151 | // Read the value 152 | uint16_t valueSensor = analogRead(pinSensor); 153 | // Compute the voltage on load resistance (for 5V Arduino) 154 | float vRL = ((float)valueSensor) / 1024.0 * 5.0; 155 | // Compute the resistance of the sensor (for 5V Arduino) 156 | if(!vRL) return 0.0f; //division by zero prevention 157 | float rS = (5.0 / vRL - 1.0) * valueRL; 158 | return rS; 159 | } 160 | 161 | /** 162 | * Set environmental values 163 | */ 164 | void MQ131Class::setEnv(int8_t tempCels, uint8_t humPc) { 165 | temperatureCelsuis = tempCels; 166 | humidityPercent = humPc; 167 | } 168 | 169 | /** 170 | * Get correction to apply on Rs depending on environmental 171 | * conditions 172 | */ 173 | float MQ131Class::getEnvCorrectRatio() { 174 | // Select the right equation based on humidity 175 | // If default value, ignore correction ratio 176 | if(humidityPercent == 60 && temperatureCelsuis == 20) { 177 | return 1.0; 178 | } 179 | // For humidity > 75%, use the 85% curve 180 | if(humidityPercent > 75) { 181 | // R^2 = 0.996 182 | return -0.0103 * temperatureCelsuis + 1.1507; 183 | } 184 | // For humidity > 50%, use the 60% curve 185 | if(humidityPercent > 50) { 186 | // R^2 = 0.9976 187 | return -0.0119 * temperatureCelsuis + 1.3261; 188 | } 189 | 190 | // Humidity < 50%, use the 30% curve 191 | // R^2 = 0.9986 192 | return -0.0141 * temperatureCelsuis + 1.5623; 193 | } 194 | 195 | /** 196 | * Get gas concentration for O3 in ppm 197 | */ 198 | float MQ131Class::getO3(MQ131Unit unit) { 199 | // If no value Rs read, return 0.0 200 | if(lastValueRs < 0) { 201 | return 0.0; 202 | } 203 | 204 | float ratio = 0.0; 205 | 206 | switch(model) { 207 | case LOW_CONCENTRATION : 208 | // Use the equation to compute the O3 concentration in ppm 209 | // Compute the ratio Rs/R0 and apply the environmental correction 210 | ratio = lastValueRs / valueR0 * getEnvCorrectRatio(); 211 | // R^2 = 0.9906 212 | // Use this if you are monitoring low concentration of O3 (air quality project) 213 | return convert(9.4783 * pow(ratio, 2.3348), PPB, unit); 214 | 215 | // R^2 = 0.9986 but nearly impossible to have 0ppb 216 | // Use this if you are constantly monitoring high concentration of O3 217 | // return convert((10.66435681 * pow(ratio, 2.25889394) - 10.66435681), PPB, unit); 218 | 219 | case HIGH_CONCENTRATION : 220 | // Use the equation to compute the O3 concentration in ppm 221 | 222 | // Compute the ratio Rs/R0 and apply the environmental correction 223 | ratio = lastValueRs / valueR0 * getEnvCorrectRatio(); 224 | // R^2 = 0.9900 225 | // Use this if you are monitoring low concentration of O3 (air quality project) 226 | return convert(8.1399 * pow(ratio, 2.3297), PPM, unit); 227 | 228 | // R^2 = 0.9985 but nearly impossible to have 0ppm 229 | // Use this if you are constantly monitoring high concentration of O3 230 | // return convert((8.37768358 * pow(ratio, 2.30375446) - 8.37768358), PPM, unit); 231 | 232 | case SN_O2_LOW_CONCENTRATION: 233 | // NOT TESTED BY @ostaquet (I don't have this type of sensor) 234 | ratio = 12.15* lastValueRs / valueR0 * getEnvCorrectRatio(); 235 | // r^2 = 0.9956 236 | return convert(26.941 * pow(ratio,-1.16),PPB,unit); 237 | break; 238 | 239 | default : 240 | return 0.0; 241 | } 242 | } 243 | 244 | /** 245 | * Convert gas unit of gas concentration 246 | */ 247 | float MQ131Class::convert(float input, MQ131Unit unitIn, MQ131Unit unitOut) { 248 | if(unitIn == unitOut) { 249 | return input; 250 | } 251 | 252 | float concentration = 0; 253 | 254 | switch(unitOut) { 255 | case PPM : 256 | // We assume that the unit IN is PPB as the sensor provide only in PPB and PPM 257 | // depending on the type of sensor (METAL or BLACK_BAKELITE) 258 | // So, convert PPB to PPM 259 | return input / 1000.0; 260 | case PPB : 261 | // We assume that the unit IN is PPM as the sensor provide only in PPB and PPM 262 | // depending on the type of sensor (METAL or BLACK_BAKELITE) 263 | // So, convert PPM to PPB 264 | return input * 1000.0; 265 | case MG_M3 : 266 | if(unitIn == PPM) { 267 | concentration = input; 268 | } else { 269 | concentration = input / 1000.0; 270 | } 271 | return concentration * 48.0 / 22.71108; 272 | case UG_M3 : 273 | if(unitIn == PPB) { 274 | concentration = input; 275 | } else { 276 | concentration = input * 1000.0; 277 | } 278 | return concentration * 48.0 / 22.71108; 279 | default : 280 | return input; 281 | } 282 | } 283 | 284 | /** 285 | * Calibrate the basic values (R0 and time to read) 286 | */ 287 | void MQ131Class::calibrate() { 288 | // Take care of the last Rs value read on the sensor 289 | // (forget the decimals) 290 | float lastRsValue = 0; 291 | float lastLastRsValue = 0; 292 | // Count how many time we keep the same Rs value in a row 293 | uint8_t countReadInRow = 0; 294 | // Count how long we have to wait to have consistent value 295 | uint8_t count = 0; 296 | 297 | // Get some info 298 | if(enableDebug) { 299 | debugStream->println(F("MQ131 : Starting calibration...")); 300 | debugStream->println(F("MQ131 : Enable heater")); 301 | debugStream->print(F("MQ131 : Stable cycles required : ")); 302 | debugStream->print(MQ131_DEFAULT_STABLE_CYCLE); 303 | debugStream->println(F(" (compilation parameter MQ131_DEFAULT_STABLE_CYCLE)")); 304 | } 305 | 306 | // Start heater 307 | startHeater(); 308 | 309 | uint8_t timeToReadConsistency = MQ131_DEFAULT_STABLE_CYCLE; 310 | 311 | while(countReadInRow <= timeToReadConsistency) { 312 | float value = readRs(); 313 | 314 | if(enableDebug) { 315 | debugStream->print(F("MQ131 : Rs read = ")); 316 | debugStream->print((uint32_t)value); 317 | debugStream->println(F(" Ohms")); 318 | } 319 | 320 | if((uint32_t)lastRsValue != (uint32_t)value && (uint32_t)lastLastRsValue != (uint32_t)value) { 321 | lastLastRsValue = lastRsValue; 322 | lastRsValue = value; 323 | countReadInRow = 0; 324 | } else { 325 | countReadInRow++; 326 | } 327 | count++; 328 | delay(1000); 329 | } 330 | 331 | if(enableDebug) { 332 | debugStream->print(F("MQ131 : Stabilisation after ")); 333 | debugStream->print(count); 334 | debugStream->println(F(" seconds")); 335 | debugStream->println(F("MQ131 : Stop heater and store calibration parameters")); 336 | } 337 | 338 | // Stop heater 339 | stopHeater(); 340 | 341 | // We have our R0 and our time to read 342 | setR0(lastRsValue); 343 | setTimeToRead(count); 344 | } 345 | 346 | /** 347 | * Store R0 value (come from calibration or set by user) 348 | */ 349 | void MQ131Class::setR0(float _valueR0) { 350 | valueR0 = _valueR0; 351 | } 352 | 353 | /** 354 | * Get R0 value 355 | */ 356 | float MQ131Class::getR0() { 357 | return valueR0; 358 | } 359 | 360 | MQ131Class MQ131(MQ131_DEFAULT_RL); 361 | --------------------------------------------------------------------------------