Arduino CO Monitor Using MQ-7 Sensor




About: I love cyberpunk and work as an electronics engineer, not much to add here

A few words why this instructable was created: one day my girlfriend's mother phoned us at the middle of the night because she felt really sick - she had dizziness, tachycardia, nausea, high blood pressure, she even fainted for unknown time (probably ~5 minutes, but there is no way to tell), all without any apparent reason. She lives in a small village far away from hospitals (60 km from our place, 30 km to the closest hospital, 10 km without any normal road in between), so we rushed to her and got there soon after the ambulance. She got hospitalized and in the morning she felt almost well, but doctors weren't able to find the cause of it. The next day we had an idea: it could have been CO poisoning, since she has gas water boiler (on the photo), and was sitting close to it for the whole evening when it happened.
We recently bought MQ-7 CO sensor, but never had time to assemble a schematics for it, so this was the perfect time to do so. After an hour of searching internet for any instructions, I realized that I can't find any guide that at the same time follows sensor manufacturer's instructions provided in its datasheet and explains anything at all (one example seemed to have quite good code, but it wasn't clear how to apply it, others were oversimplified and wouldn't work well). So we spent about 12 hours for developing schematics, making and printing 3d case, testing and calibrating the sensor, and the next day went to the suspicious boiler. It turned out that CO levels there were extremely high, and could be fatal if CO exposure time were longer. So I believe anyone who has similar situation (like gas boiler or other combustion happening inside a living space) should get such sensor to prevent something bad from happening.

All that happened two weeks ago, since then I improved schematics and program quite a lot, and now it seems to be reasonably good and relatively simple (not 3-lines-of-code simple, but still). Although I hope that someone with precise CO meter will provide me some feedback on default calibration that I put in the sketch - I suspect it is far from good.
Here is a complete guide with some experimental data.

Step 1: Bill of Materials

You will need:

0. Arduino board. I prefer Chinese clone of Arduino Nano for its outstanding price of $3, but any 8-bit arduino will work here. Sketch uses some advanced timers operation, and was tested only on atmega328 microcontroller - although probably it will work well on others too.

1. MQ-7 CO sensor. Most commonly available with this Flying Fish sensor module, it has to run through a small modification, details in the next step, or you can use a separaten MQ-7sensor.

2. NPN bipolar transistor. Virtually any NPN transistor that can handle 300 mA or more will work here. PNP transistor won't work with a mentioned Flying Fish module (because it has heater pin soldered to sensor's output), but can be used with a discrete MQ-7 sensor.

3. Resistors: 2 x 1k (from 0.5k to 1.2k will work fine), and 1 x 10k (that one is best kept precise - although if you absolutely must use a different value, adjust reference_resistor_kOhm variable in the sketch accordingly).

4. Capacitors: 2 x 10uF or more. Tantalum or ceramic ones are required, electrolytic won't work well due to high ESR (they won't be able to provide enough current to smooth high-current ripple).

5. Green and red LEDs to indicate current CO level (you can also use a single dual-color LED with 3 terminals, as we used in our yellow box prototype).

6. Piezo buzzer to indicate high CO level.

7. Breadboard and wires (you also can solder everything to Nano pins or squeeze into Uno sockets, but it's easy to make a mistake this way).

Step 2: Module Modification or Discrete Sensor Wiring

For module, you must desolder resistor and capacitor, as shown on the photo. You can desolder basically everything if you want - module electronics is totally useless, we use it only as holder for the sensor itself, but these two components will prevent you from getting correct readings,

If you are using discrete sensor, attach heater pins (H1 and H2) to 5V and transistor's collector correspondingly. Attach one sensing side (any of A pins) to 5V, another sensing side (any of B pins) to 10k resistor, just like the analog pin of the module in schematics.

Step 3: Operation Principle

Why we need all these complications at all, why not to attach 5V, ground, and just get readings?
Well, you won't get anything useful this way, unfortunately.
According to MQ-7 datasheet, sensor has to run through high- and low-heating cycles in order to get proper measurements. During low temperature phase, CO is absorbed on the plate, producing meaningful data. During high temperature phase, absorbed CO and other compounds evaporate from the sensor plate, cleaning it for the next measurement.

So in general operation is simple:

1. Apply 5V for 60 seconds, don't use these readings for CO measurement.

2. Apply 1.4V for 90 seconds, use these readings for CO measurement.

3. Go to step 1.

But here's the problem: Arduino can't provide enough power to run this sensor from its pins - sensor's heater requires 150 mA, while Arduino pin can provide no more than 40 mA, so if attached directly, Arduino pin will burn and sensor still won't work. So we must use some kind of current amplifier that takes small input current to control large output current.
Another problem is getting 1.4V. The only way to reliably get this value without introducing a lot of analog components is to use PWM (Pulse Width Modulation) approach with feedback that will control output voltage.

NPN transistor solves both problems: when it is constantly turned on, voltage across the sensor is 5V and it is heating for high-temperature phase. When we apply PWM to its input, current is pulsing, then it is smoothed by the capacitor, and the average voltage is kept constant. If we use high frequency PWM (in the sketch it has frequency of 62.5KHz) and average a lot of analog readings (in the sketch we average over ~1000 readings), then the result is quite reliable.

It is critical to add capacitors according to schematics. Images here illustrate difference in signal with and without C2 capacitor: without it, PWM ripple is clearly visible and it significantly distorts readings.

Step 4: Schematics and Breadboard

Here is the schematics and breadboard assembly.

WARNING! Modification of a standard breakout module is required! Without modification module is useless. Modification is described in the second step.

It is important to use pins D9 and D10 for LEDs, since there we have outputs of hardware Timer1, it will allow to smoothly change their colors.
Pins D5 and D6 are used for buzzer, because D5 and D6 are outputs of hardware Timer0. We will configure them to be inverse one to another, so they will switch between (5V, 0V) and (0V, 5V) states, thus producing sound on buzzer. Warning: this affects Arduino's main timing interrupt, so all time-dependent functions (like millis() ) won't produce correct results in this sketch (more on this later).
Pin D3 has hardware Timer2 output connected to it (as well as D11 - but it's less convenient to put wire on D11 than on D3) - so we are using it to provide PWM for voltage controlling transistor.
Resistor R1 is used to control brightness of LEDs. It can be anywhere from 300 to 3000 Ohm, 1k is rather optimal in brightness/power consumption.
Resistor R2 is used to limit transistor's base current. It shouldn't be lower than 300 Ohms (to not overload Arduino pin), and not higher than 1500 Ohms. 1k there is a safe choice.

Resistor R3 is used in series with sensor's plate in order to create a voltage divider. Voltage on sensor's output is equal to R3 / (R3 + Rs) * 5V, where Rs is current sensor's resistance. Sensor resistance depends on CO concentration, so voltage changes accordingly.
Capacitor C1 is used to smooth input PWM voltage on MQ-7 sensor, the higher is its capacitance the better, but also it has to have low ESR - so ceramic (or tantalum) capacitor is preferred here, electrolytic one won't perform well.

Capacitor C2 is used to smooth sensor's analog output (output voltage depends on input voltage - and we have quite a high current PWM here, that affects all schematics, so we need C2). The simplest solution is to use the same capacitor as C1.
NPN transistor either conducts current all the time to provide high current on sensor's heater, or works in PWM mode thus reducing heating current.

Step 5: Arduino Program


Calibration is described in the following steps. Rough calibration is very simple, precise is quite complex.

On the general level, program is rather simple:

First we calibrate our PWM in order to produce stable 1.4V required by sensor (proper PWM width depends on a lot of parameters like exact resistor values, this particular sensor's resistance, transistor's VA curve etc etc - so the best way is to try various values and use one that fits best).
Then, we continuously run through cycle of 60 seconds heating and 90 seconds measurement.
In implementation it gets somewhat complicated. We have to use hardware timers because everything that we have here needs high-frequency stable PWM in order to function properly.

The code is attached here and can be downloaded from our github, as well as schematics source in Fritzing.

In the program there are 3 functions that handle timers: setTimer0PWM, setTimer1PWM, setTimer2PWM.
Each of them sets timer in PWM mode with given parameters (commented in the code), and sets pulse width according to input values.
Measurement phases are switched using functions startMeasurementPhase and startHeatingPhase, they handle everything inside. and set proper timer values for switching between 5V and 1.4V heating.
LEDs state is set by function setLEDs which accepts green and red brightness on its input (in linear 1-100 scale) and converts it into corresponding timer setting.

Buzzer state is controlled using functions buzz_on, buzz_off, buzz_beep. On/off functions turn sound on and off, beep function produces specific beeping sequence with period of 1.5 seconds if it is periodically called (this function returns immediately so it doesn't pause the main program - but you have to call it again and again to produce beeping pattern).

Program first runs function pwm_adjust that finds out proper PWM cycle width in order to achieve 1.4V during measurement phase. Then it beeps a few times to indicate that sensor is ready, switches into measurement phase, and starts the main loop.

In the main loop, program checks if we spent enough time in current phase (90 seconds for measurement phase, 60 seconds for heating phase) and if yes, then changes current phase. Also it constantly updates sensor readings using exponential smoothing: new_value = 0.999*old_value + 0.001*new_reading. With such parameters and measuring cycle, it averages signal over approximately last 300 milliseconds.


Step 6: First Run: What to Expect

If you assembled everything properly, after running sketch you will see something like this in Serial monitor:

adjusting PWM w=0, V=4.93


adjusting PWM w=17, V=3.57
PWM result: width 17, voltage 3.57

and then a series of numbers representing current sensor readings.
This part is adjusting PWM width in order to produce sensor's heater voltage as close to 1.4V as possible, measured voltage is deducted from 5V, so our ideal measured value is 3.6V. If this process never ends or ends after a single step (resulting in width equal to 0 or 254) - then something is wrong. Check if your transistor is really NPN and is properly connected (make sure you used base, collector, emitter pins right - base goes to D3, collector to MQ-7 and emitter to ground, don't count on Fritzing breadboard view - it is wrong for some transistors) and make sure that you connected sensor's input to Arduino's A1 input.

If everything is fine, you should see in Serial Plotter from Arduino IDE something similar to the image. Heating and measurement cycles of 60 and 90 seconds length are running one after another, with CO ppm measured and updated at the end of each cycle. You can take some open flame close to the sensor when measurement cycle is almost finished and see how it will affect readings (depending on flame type, it can produce up to 2000 ppm CO concentration in open air - so even though only a small portion of it actually goes into sensor, it still will turn on the alarm, and it won't go off until the end of the next cycle). I showed it on the image, as well as the response to fire from the lighter.

Step 7: Sensor Calibration

According to manufacturer's datasheet, sensor should be running heating-cooling cycles for 48 hours in a row before it can be calibrated. And you should do it if you intend to use it for a long time: in my case, sensor reading in clean air changed for about 30% over 10 hours. If you won't take this into account, you can get 0 ppm result where there is actually 100 ppm of CO. If you don't want to wait for 48 hours, you can monitor sensor output at the end of measurement cycle. When over an hour it won't change for more than 1-2 points - you can stop heating there.

Rough calibration:

After running sketch for at least 10 hours in clean air, take raw sensor value in the end of the measurement cycle, 2-3 seconds before heating phase starts, and write it into sensor_reading_clean_air variable (line 100). That's it. Program will estimate other sensor parameters, they won't be precise, but should be enough to distinguish between 10 and 100 ppm concentration.

Precise calibration:

I highly recommend to find a calibrated CO meter, make 100 ppm CO sample (this can be done by taking some flue gas into syringe - CO concentration there can easily be in the range of several thousands ppm - and slowly putting it into closed jar with calibrated meter and MQ-7 sensor), take raw sensor reading at this concentration and put it into sensor_reading_100_ppm_CO variable. Without this step, your ppm measurement can be wrong several times in either direction (still ok if you need alarm for dangerous CO concentration at home, where normally there should be no CO at all, but not good for any industrial application).

As I didn't have any CO meter, I used a more sophisticated approach. First I prepared high concentration of CO using combustion in isolated volume (first photo). In this paper I found the most useful data, including CO yield for different flame types - it isn't in the photo, but the final experiment used propane gas combustion, with the same setup, resulting in ~5000 ppm CO concentration. Then it was diluted 1:50 in order to achieve 100 ppm, as illustrated in the second photo, and used to determine sensor's reference point.

Step 8: Some Experimental Data

In my case, sensor worked quite well - it is not very sensitive for really low concentrations, but good enough for detecting anything higher than 50ppm. I tried to increase concentration gradually, taking measurements, and built a set of charts. There are two sets of 0ppm lines - pure green before CO exposure and yellow green after. Sensor seems to slightly change its clean air resistance after exposure, but this effect is small. It doesn't seem to be able to clearly distinguish between 8 and 15, 15 and 26, 26 and 45 ppm concentrations - but the trend is very clear, so it can tell whether concentration is in 0-20 or 40-60 ppm range. For higher concentrations dependence is much more distinctive - when exposed to exhaust of an open flame, curve goes up from the start without going down at all, and its dynamics is totally different. So for high concentrations there is no doubt that it works reliably, although I can't confirm its precision since I don't have any rated CO meter.
Also, this set of experiments was done using 20k load resistor - and after that I decided to recommend 10k as the default value, it should be more sensitive this way.

That's it. If you have a reliable CO meter and will have assembled this board, please share some feedback about sensor precision - it would be great to collect statistics over various sensors and improve default sketch assumptions.

5 People Made This Project!


  • Build a Tool Contest

    Build a Tool Contest
  • Tape Contest

    Tape Contest
  • Weaving Challenge

    Weaving Challenge

145 Discussions


10 days ago


my settings are :
sensor_reading_clean_air = 602
sensor_reading_100_ppm_CO = 865

I comment the line :
//if(CO_ppm < 0) CO_ppm = 0;
So I can read the real CO ppm calculation.

In clean air I get -34ppm (for 602 sensor reading) and ppm becomes positive only at 746.
Why does not ppm start to raise form zero when sensor reading > 602 (sensor reading in clean air) ?
Thank you.


5 replies

Reply 10 days ago

Wow... I've made a serious mistake there: I've misinterpreted datasheet, thought there was exponential relation of PPM on relative resistance, but actually it is linear. Not sure how I got quite reasonable results when I tested it, the only explanation is that I tested it only in 0-40 PPM area and there difference between linear and exponential wasn't that big. For everything higher than 50 PPM my results were much higher than real concentration.

I've fixed it and updated code in the instructable, please download and test it. Also if you can test it in high PPM area, that would be awesome.


Reply 10 days ago

Thank you very much for such a fast answer !

I changed only the lines, because I had personalized the code yet. :
sensor_100ppm_CO_resistance_kOhm = sensor_base_resistance_kOhm * 0.25; (instead of 0.50)
float CO_ppm = 134 * R_relation - 35;

I did not see any other change.

The readings seems now credible.
Il will test later with a reliable CO meter to see if it's accurate and will tell you.



Reply 8 hours ago

I measured a concentration of 54ppm with a smoke analyser but unhappily the Arduino/MQ7 gave me 2.19ppm...
Don't see where the trouble is in calculation...


Reply 8 hours ago

In datasheet, scales of Resistance ratio and ppm are exponentials...


Reply 8 hours ago

Seems to be :
ppm = 100 * pow(log10(40)/log10(0,09), ratio)
where ratio is RS_gas/R0


18 days ago

if(sensor_reading_100_ppm_CO > 0) ????
sensor_reading_100_ppm_CO = -1 ( forever)

2 replies

Reply 17 days ago

Yes, what's the problem? If you have set it using calibrated CO-meter - then one formula is applied. If you don't have one, it remains -1 and another formula is applied


Reply 17 days ago

okay. Thank you!
very good work. Modify it a bit, add a humidity meter. In order to send infrared commands to the air conditioner, combine the pwm for the transistor because the IR library uses the pin 3. Also modify the pwm of the LEDs and I did it by varying the ignition frequency. The formulas was the only thing that does not change them jajajaja.Saludos!!!


21 days ago

I can't get some PWM from D3 so the sensor is not turning on and I can't get width of 17 at 3.57V. I'm using arduino mega-2560 and it's new.

1 reply

Reply 17 days ago

Arduino Mega-2560 has totally different PWM pins. I'm using hardware timers so pins are linked to particular mcu. When you switch from atmega328 to atmega2560, following changes must be applied:
Arduino Uno pin -> Arduino Mega pin:
3 -> 9
5 -> 4
6 -> 13
9 -> 11
10 -> 12
11 -> 10


6 weeks ago

Thanks for the best how-to for mq-7. I have setup the circuit. When I check current drawn by M-7 during heating cycle I see ¨80mA's, not close to 140mA . Should I see a value close to 140 mA or does it also change from sensor to sensor ? Sİnce I'm not sure about the current I haven't done calibration yet and I see following CO readings.

CO test.png
6 replies

Reply 6 weeks ago

80mA might be ok, it varies a lot from sensor to sensor. But it's worth checking if you are powering it properly - what power source you are using?

As for your chart - not sure how you got it, if it is CO value from my program then probably something went wrong ))


Reply 5 weeks ago

I was using nano's 5V source when I collected the results. I have setup the circuit using BC140 (*BC140 Transistör BJT NPN TO-39) and I'm using your code with modifications (removed led indicators, added and SD card for recording the results).

I am using modified flying fish card but got the same results when I use a card I built from scratch with MQ-7 without any additional circuitry (just power and analog out)

According to MQ-7 datasheet it consumes 350mW. More or less 80mA seems ok with 5V. but I don't know why I keep receiving same results what ever I try.


Reply 5 weeks ago

What powers Nano? Is that source good enough? If you are powering it via USB from a PC port, it might not be able to send more than 100 mA (due to usb configuration specifics - depending on exact Nano model that might or might not be the case). You can check that by powering it via USB that is connected to some phone charger.
*if you are powering Nano with some kind of 9V battery - then it's most likely the case, 9V ones normally can't output enough current.
Also have you checked that base/emitter/collector of transistor are connected in the right way? You never can rely on how a package looks, always need to check datasheet for this exact part.

If all of it is not the case - and if you are storing not calculated CO values but raw analog output from the sensor - then I need to see a longer sample, maybe I'll have a guess what else could be wrong here


Reply 5 weeks ago

I was using a usb port from a laptop. Seeing your comment I've changed power source to a 12V adapter and power nano through Vin pin (pin 30). This realy change total current drawn. I've done two tests one with modified MQ-7 and one with MQ7 soldered onto an empty folder pcb.

With the pcb version, during reading phase setup draw 84 mA and during heating cycle current was 171mA (nano was also connected to laptop through usb port for serial monitoring). Still very low raw reading. When I measured voltage between Vin and Ground on the pcb it was 1.4V during reading cycle and between 3.8V & 4V during heating cycle. Raw output was very low like between 60 to 200 with 0 CO ppm.

With the modified flyingfish modul current was 73mA and 158mA and voltage was 1.34V to 3.7V. Raw reading was very high in this case 850+ and CO ppm was 1000+.

I've added code . Since I have removed buzzer as well I have changed timescale to 1.


Reply 5 weeks ago

Hmm. these sensors really have high variation in resistance of sensitive coil - but it looks a bit too much (one with low readings implies sensing resistance of ~40k instead of 10k, one with high readings implies sensing resistance of ~3k instead of nominal 10k). Still not impossible, especially if sensors are from different manufacturers.

Have you added capacitors C1 and C2? From your data it looks like in modified_modul file you are missing C2: values are very noisy.

As for CO measurements - in the second case (sensor_with_pcb_only), data look +- fine, so you can replace R3 with something like 30k and then calibrate the sensor (fill sensor_reading_clean_air variable at line 95 with your actual reading and fill reference_resistor_kOhm variable with actual R3 value in kOhms)


Reply 5 weeks ago

*actually, if you modified my code - please upload your project here. I'm heavily using hardware timers, if you added library that uses them too, then that might be the answer ))


Question 3 months ago

Hi, great project! I'll try to assemble your setup in the next days to test it. I'm also doing some research on the MQ-135 sensor, which works like the MQ-7 sensor. I wonder if I have to do the same type of setup for it to have correct measurements. I tried different setups for that sensor and never got anything reliable...

1 answer

Answer 3 months ago

I'm not 100% sure but it's very likely that mq-135 would require the same procedure of heating-cooling cycles and taking sample at the end of cooling cycle