12. CO2 Sensor

The CO2 sensor is used to measure the concentration of carbon dioxide around it. This sensor has a digital and analog output.

The digital output can be used as a way to set limit to when the concentration of CO2 is considered harmful. The setting of this limit is done by fixing the potentiometer on the sensor to the desired calibration. A built-in LED lights up when the CO2 concentration exceeds that limit.

As for the analog pin, the sensor can calculate the value of the concentration of CO2 it detects in ppm (parts per million).

Warning

If you want to perform analog readings using this sensor, you will need to learn about the MCP3008 ADC.

12.1. Connecting the Sensor in Digital Mode

12.1.1. Wiring

To use the digital output of the sensor, connect

  1. + to 5V
  2. The - to a ground pin
  3. The D pin to an RPi GPIO pin

The wiring of the Raspberry Pi to the breadboard and the sensor is shown in the figure below:

../_images/co2sensor-digital.jpg

12.1.2. Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(8,GPIO.IN)

while True:
    if GPIO.input(8):
        print("CO2 concentration is dangerous")
    else:
        print("Safe")
    time.sleep(0.5)

12.2. Connecting the Sensor in Analog Mode

12.2.1. Wiring

The Raspberry Pi doesn’t have an analog input pin. So to read the analogue output of the sensor, we need to use an analog to digital converter (ADC), in this case an MCP3008 ADC.

Connect the sensor as follows:

  1. The VCC to 5V
  2. The GND to a ground pin
  3. The analog pin to CH0 (pin 1) of the MCP3008
../_images/co2sensor-analog.jpg

12.2.2. Code

This code reads the concentration of CO2 in ppm:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
from spidev import SpiDev
import time
import math
import sys

class MCP3008:
    def __init__(self, bus = 0, device = 0):
        self.bus, self.device = bus, device
        self.spi = SpiDev()
        self.open()

    def open(self):
        self.spi.open(self.bus, self.device)
    
    def read(self, channel = 0):
        adc = self.spi.xfer2([1, (8 + channel) << 4, 0])
        data = ((adc[1] & 3) << 8) + adc[2]
        return data
            
    def close(self):
        self.spi.close()


class CO2():

    ############### Hardware Related Macros ###############
    CO2_PIN                       = 0        # define which analog input channel you are going to use (MCP3008)
    ADC_FACTOR                   = 2100     # define the factor which to divide the readings of the MCP3008
 
    ############### Software Related Macros ###############
    READ_SAMPLE_INTERVAL         = 50       # define how many samples you are going to take in measurment operation
    READ_SAMPLE_TIMES            = 5        # define the time interal(in milisecond) between each samples
    
    ############### Application Related Macros ###############
    GAS_CO2                      = 0

    def __init__(self, analogPin=0, adcFactor=ADC_FACTOR):
        self.ADC_FACTOR = adcFactor
        self.CO2_PIN = analogPin
        self.adc = MCP3008()
        
        self.CO2Curve = [2.602,0.324,-0.0422]# two points are taken from the curve. <<CO2>>
    # with these two points, a line is formed which is "approximately equivalent"
    # to the original curve. 
    # data format:{ x, y, slope}; point1: (lg400, 0.324), point2: (lg10000, 0.265)       
    
    def CO2Percentage(self):
        val = {}
        read = self.CO2Read(self.CO2_PIN)/self.ADC_FACTOR
        val["GAS_CO2"]  = self.CO2GetGasPercentage(read, self.GAS_CO2)
        return val           
      
    ###############  CO2Read ###############
    # Input:   CO2_pin - analog channel
    # Output:  Average value of the sensor
    # Remarks: This function reads several samples between intervals defined at the start of the script.
    
    def CO2Read(self, CO2_pin):
        v = 0.0
        for i in range(self.READ_SAMPLE_TIMES):
            v += self.adc.read(CO2_pin)
            time.sleep(self.READ_SAMPLE_INTERVAL/1000.0)

        v = v/self.READ_SAMPLE_TIMES

        return v
     
    ###############  CO2GetGasPercentage ###############
    # Input:   value - Voltage measurement from sensor
    #          gas_id      - target gas type
    # Output:  ppm of the target gas
    # Remarks: This function passes different curves to the CO2GetPercentage function which 
    #          calculates the ppm (parts per million) of the target gas.
    

    def CO2GetGasPercentage(self, value, gas_id):
            if ( gas_id == self.GAS_CO2 ):
                return self.CO2GetPercentage(value, self.CO2Curve)
            return 0
     
    ###############  CO2GetPercentage ###############
    # Input:   value - Voltage measurement from sensor
    #          pcurve      - pointer to the curve of the target gas
    # Output:  ppm of the target gas
    # Remarks: By using the slope and a point of the line. The x(logarithmic value of ppm) 
    #          of the line could be derived if y(voltage value) is provided. As it is a 
    #          logarithmic coordinate, power of 10 is used to convert the result to non-logarithmic 
    #          value.
    

    def CO2GetPercentage(self, value, pcurve):
        return math.pow(10,(value-pcurve[1])/pcurve[2] + pcurve[0])


CO2 = CO2();
while True:
    perc = CO2.CO2Percentage()
    sys.stdout.write("\r")
    sys.stdout.write("\033[K")
    sys.stdout.write("CO2: %g ppm" % perc["GAS_CO2"])
    sys.stdout.flush()
    time.sleep(0.1)