Arduino CO Monitor Using MQ-7 Sensor




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

Intro: Arduino CO Monitor Using MQ-7 Sensor

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!


  • Electronics Tips & Tricks Challenge

    Electronics Tips & Tricks Challenge
  • Audio Contest 2018

    Audio Contest 2018
  • Plastics Contest

    Plastics Contest

125 Discussions


3 days ago

What will i do after 10 hrs? Do i need to fill in the sense value in my arduino code?


Question 2 months ago

I am having trouble with my LED's functioning properly. The red LED stays dimly lit and will blink periodically like its supposed to. However the green LED does no light up at all. My analog readings are on average 607. I've double and triple checked the connections, everything appears to be in order. I've also swapped out the LED's with new ones...same result.

4 more answers

Answer 2 months ago

This looks like red and green leds are mixed. Please put lighter next to the sensor so CO level will rise - if you'll see red led turning off and green one lighting up, then that's it - change places of red and green ones :)


Reply 2 months ago

I did have them switched. Thank you the LED's now illuminate properly. I plan to use this in a car. Secondly as a proof of concept for a mobile setup with a different sensor since the power requirements are quite steep for a wearable setup.


Reply 2 months ago

Should the green LED be connected to D10 & not D9. Secondly while the device is turning on should the green LED be illuminated first?


Reply 2 months ago

They shouldn't be reversed logically, but I can't guarantee it without re-assembling the board :) The most reliable way to tell is to check how it reacts to high CO concentration


Question 3 months ago

Does the blue pot on the breakout board have any effect? If so, how should it be adjusted?

Also, I'm still running the first 10 hour stretch to calibrate, and my ppm is showing about 38 (roughly) and I notice the green led is on faintly, but steady, and the red flashes quite brightly once per second... normal?

Awesome job on this, I put off doing it because it seemed so complicated.

9 more answers

Answer 3 months ago

Blue pot doesn't have any effect if you made modification described in step 2.

But you need to measure and write in the code clean air value after 10 hours run: each sensor is different, value in the code works only for my particular unit. When you will put it there, in clean air it should reliably produce 0-5 ppm. Without that adjustment, 38 ppm looks completely normal.
Thanks :)


Reply 2 months ago

I ripped it all apart, and even made shorter wires so I couldn't get confused :) I have it behaving better today, but I found a discrepancy between the breadboard diagram and the picture of your setup.

On the breadboard diagram, the center leg of the transistor has the resistor, then on to D3, and the right hand leg goes to Aout on the MQ-7 and to A1 on the Arduino.

On your photo, the opposite is true, the center leg goes to Aout and A1, and the middle leg goes through the resistor to D3.

I changed my wiring to match your photo, and it seems to work better. Oh, I also swapped the red and green LEDs, the breadboard diagram appears wrong and your photo shows clear LEDs so can't tell. I swapped them, and now the correct ones light up :)

So, either I discovered a bug that doesn't seem to affect anyone else, or I'm doing something wrong, which may be the case too... lol

Now it's running and when in measure mode, the raw value is about 970, and when heater is on, it drops to about 550.... Is that CHANGE in values normal? I realize I have to calibrate yet and add the clean air value, so I'm not worried about the actual values, just the difference...

If it matters, I'm using a NTE 85 transistor and 100uF electrolytic capacitors, best I could do...

Really appreciate your work on this!


Reply 2 months ago

Clean air value of 970 is completely wrong if you have correct load resistor. But I just checked, your transistor has base on its side - so you should connect it not like on Fritzing diagram.

In any case, you can't use neither photo nor breadboard diagram as a reference: base, collector and emitter are not marked there, and for different transistor models they will be placed in different places. You need to look at schematics and make sure you connect base, collector and emitter to the correct points - and you can find function of each pin of your transistor by looking at its datasheet.


Reply 2 months ago

I feel like such a fool... I blindly followed the breadboard pattern, as I normally do... I ripped it all apart again, and went strictly from the schematics, and NOW I think I've got it! :)

Turns out my MQ-7 board has a different pinout than yours, so using the schematic, I got it right... My breadboard looks much neater too (as in tidier)

Now my "measuring state" reading is 720-730, and my "heating state" reading starts about 170 and climbs to about 270...

I'll leave it run now to calibrate... Fingers crossed!


Reply 2 months ago

Great that you figured that out! :)
720 measuring, ~200 heating looks very good, I'm almost sure you have everything right now


Reply 2 months ago

So I got it working properly on the breadboard, so I soldered everything to one of those DIY PCB's with all the holes, and wired it all up, fired it up, and nothing... No LEDs even on the arduino :( It killed the arduino... FAIL...

So, not wanting to give up, I grabbed another pro-mini, and this time used 2 row female sockets for EVERYTHING, tieing the pin pairs together underneath the board, basically making a custom breadboard, and wired it up again, double checking against the schematic (I'd already torn my original breadboard apart, so couldn't copy it) and fired it up, IT WORKS!

I tried putting a lighter next to it, but the readings didn't change? Do I have to complete the 48 hour burn in first? I have another sensor I could swap them (everything is plug-in on the new board). My MQ7 is VCC, Aout, Dout, GND, different than yours I think, I wonder if I should try a sensor without the cap and resistor removed?

My reason for building this? The day before Christmas, 2013, we had a huge ice storm here (Toronto Canada) and my Mother and Brother were killed by Carbon Monoxide poisoning from a generator running in a supposedly ventilated space (long story) and his wife and son were very sick for quite some time. I bought some good detectors for the house, but since my wife and I camp in our SUV quite a bit, I decided to build this for permanent installation in the car.

I added Bluetooth as well, so I can connect from my phone and see the values, and even a blue LED to show when BT is connected. I'm planning on making a Logger unit that will poll this unit via BT and record the readings at some interval. That same Logger will poll other units as well, including OBD, GPS, etc, and record everything on SD card with proper timestamp...

So, thank you for the fantastic learning opportunity here!


Reply 2 months ago

I'm so sorry for your loss! It's terrible!
I actually want to create a wearable sensor one day, but still hadn't found a low power option for CO measurement.

When you put lighter, readings will update at the end of measuring cycle - so if you put it in the middle of heating phase, you will see the result only in about 2 minutes. If you will put lighter during heating phase, and then will remove it still during heating phase - sensor won't see it at all (heating phase clears sensor surface).
But if you put lighter soon before measuring cycle ends, and won't see any result - then something is definitely wrong.

Burn cycle is required only once in a lifetime for a sensor (maybe once again if it wasn't used for years, but definitely not days).

Sensor without cap and resistor removed will produce wrong results, although there is a way around it with a neat trick in the code - but I hadn't thought about it when created this project, and so hadn't implemented (also, it isn't clear if it will work well with BJT transistors, it could be that it will give accurate results only with mosfets).

Also, for accurate measurements, correction by humidity and temperature must be introduced (humidity/temperature change can move zero point very significantly, for 50-100 ppm, so without them sensor is good only for quite high concentrations, but might not detect less dangerous ones) - I started this work and even made a dedicated PCB for it, but never really completed. Maybe I will soon


Reply 2 months ago

After about 3 hours, the value had gone very high, the beeper was chirping away, so I disconnected the beeper and put it outside, eventually the ppm was about 150, so I unplugged it and decided to wait for another day :) I did make a change to the code to send via bluetooth rather than Serial (actually, just plugged my hc-05 into the tx/rx port on the arduino... Made it simple to check on the values from my phone... I doubt that would cause any issue though. Ok, will go over everything again today, including wiring, I had a few adjustments originally, so perhaps there's still a wire in the wrong place. Thanks again, will update as things happen.


Reply 2 months ago

The value won't change anymore. Put current value as clean air value in the code


Question 4 months ago

Off all the MQ7 projects I have seen, yours is definitely the most interesting. I have even seen projects that were sure to kill the MQ7 which became quite obvious from the comments there.
Anyway, just a few Questions:
You describe a modification of the flying fish board, necessary to get accurate readings, consisting of removing a capacitor and a resistor. As it is hard to find a circuit drawing of the Flying Fish model, I was just wondering where these 2 components connect to that makes it necessary to remove them. As far as I can see they are in parallel and connect between the measuring element and ground the resistor seems a 1k, which might be a bit low for the specified Load resistor, so my guess that is why you want it removed. The capacitor is not described anywhere as necessary in the datasheet as I believe so my guess that is the reason for removing that one.

You describe R3 as a voltage divider, but wouldn't it in fact be the required LOAD resistor (between 4k and 47k)?

I feel a bit awkward about c2 as it is connected between A0 and Vcc, but I guess it is to stabilize the reading?
Anyway, thanks for a great project

2 more answers

Answer 4 months ago

Thanks :)
These resistor and capacitor are connected from AO to sensor's GND pin. The problem is, in this setup sensor's GND is not connected to the schematic's ground, but to transistor which controls heating coil using PWM (the main problem of the module is that heating coil is connected in parallel with measurement sensor - and modification breaks this connection). Resistor would prevent getting any reasonable readings when PWM is on, and capacitor would introduce PWM-induced noise, so both have to go. Recently MakerMaik proposed alternative approach in comments that will work without modifying the module - to turn off PWM and open transistor during (short) measurement phase - but I hadn't thought of this during initial development. Also this would work reliably only with MOSET transistor (because BJT, which I used in this case, will introduce generally unknown voltage shift).

R3 forms voltage divider with sensing element - because sensor is a resistor, which resistance depends on CO concentration. Yes, technically it is a load resistor - but I don't like term "load" when resistor has higher resistance than sensing element, it may lead to a wrong idea about the sensor behavior.

C2 is used to stabilize readings, and is connected to Vcc because at the moment I thought that Vcc might be a bit more stable than GND on a breadboard (since we switch transistor on GND side, not on Vcc side). Although this might be not true depending on particular wiring approach and I believe it won't make any real difference if C2 is connected between AO and GND. But without C2 at all, readings are significantly worse - I showed oscilloscope output with and without it in step 3.


Answer 4 months ago

Thanks for your detailed reply. yes I had seen the proposal for the alternative approach. I may use a FET, and try that.
I guess that understandably, the manufacturer would like to keep the module simple and useable for other MQ sensors as well, but not connecting the heating coil to the Vcc would have made more sense. As it is I am afraid many people will just use the module 'as is' and got totally irrelevant readings. Especially the Dout is totally irrelevant
You seem to be one of the few people who made am MQ 7 project after actually reading the datasheet.

As for the 'load' resistor, was only referring to it as such as that is what the datasheet is calling it.

i'll hae a closer look at step 3 pics.


5 months ago

Hello, great work!

But I could not apply a conversion to plausible ppm, I have two doubts.

1 - In the raw calibration, the value specified in the code for sensor_reading_clean_air is in the 0 - 1023 scale?

2 - I even applied in excel all working from the data to convert to CO_ppm, nothing plausible. Do you have any extra references to do this conversion?