MQ-131 Gas Sensor

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

Hi Leute,

ich habe mir vor ein paar Wochen den Ozon Sensor MQ-131 gekauft und versuche jetzt schon länger diesem Daten zu entlocken.

Bisher bin ich diesem Tutorial gefolgt:
https://tutorials-raspberrypi.de/raspbe ... -auslesen/

Hardware: Raspberry Pi 3 ModelB; MCP3008; Spannungswandler; MQ-131
Verkabelung wie im Tutorial beschrieben.

Wenn man nach diesem Tutorial vorgeht werden einem Codes zur Verfügung gestellt.

example.py

Code: Alles auswählen

from mq import *
import sys, time

    
mq = MQ();
while True:
     perc = mq.MQPercentage()
     sys.stdout.write("\r")
     sys.stdout.write("\033[K")
     sys.stdout.write("LPG: %g ppm, CO: %g ppm, Smoke: %g ppm" % (perc["GAS_LPG"], perc["CO"], perc["SMOKE"]))
     sys.stdout.flush()
     time.sleep(0.1)
Dieser basiert offensichtlich auf diesen Beiden:

MCP3008.py

Code: Alles auswählen

from spidev import SpiDev

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()
und:

mq.py

Code: Alles auswählen

# adapted from sandboxelectronics.com/?p=165

import time
import math
from MCP3008 import MCP3008

class MQ():

    ######################### Hardware Related Macros #########################
    MQ_PIN                       = 0        # define which analog input channel you are going to use (MCP3008)
    RL_VALUE                     = 5        # define the load resistance on the board, in kilo ohms
    RO_CLEAN_AIR_FACTOR          = 9.83     # RO_CLEAR_AIR_FACTOR=(Sensor resistance in clean air)/RO,
                                            # which is derived from the chart in datasheet
 
    ######################### Software Related Macros #########################
    CALIBARAION_SAMPLE_TIMES     = 50       # define how many samples you are going to take in the calibration phase
    CALIBRATION_SAMPLE_INTERVAL  = 500      # define the time interal(in milisecond) between each samples in the
                                            # cablibration phase
    READ_SAMPLE_INTERVAL         = 50       # define how many samples you are going to take in normal operation
    READ_SAMPLE_TIMES            = 5        # define the time interal(in milisecond) between each samples in 
                                            # normal operation
 
    ######################### Application Related Macros ######################
    GAS_LPG                      = 0
    GAS_CO                       = 1
    GAS_SMOKE                    = 2

    def __init__(self, Ro=10, analogPin=0):
        self.Ro = Ro
        self.MQ_PIN = analogPin
        self.adc = MCP3008()
        
        self.LPGCurve = [2.3,0.21,-0.47]    # two points are taken from the curve. 
                                            # with these two points, a line is formed which is "approximately equivalent"
                                            # to the original curve. 
                                            # data format:{ x, y, slope}; point1: (lg200, 0.21), point2: (lg10000, -0.59) 
        self.COCurve = [2.3,0.72,-0.34]     # two points are taken from the curve. 
                                            # with these two points, a line is formed which is "approximately equivalent" 
                                            # to the original curve.
                                            # data format:[ x, y, slope]; point1: (lg200, 0.72), point2: (lg10000,  0.15)
        self.SmokeCurve =[2.3,0.53,-0.44]   # two points are taken from the curve. 
                                            # with these two points, a line is formed which is "approximately equivalent" 
                                            # to the original curve.
                                            # data format:[ x, y, slope]; point1: (lg200, 0.53), point2: (lg10000,  -0.22)  
                
        print("Calibrating...")
        self.Ro = self.MQCalibration(self.MQ_PIN)
        print("Calibration is done...\n")
        print("Ro=%f kohm" % self.Ro)
    
    
    def MQPercentage(self):
        val = {}
        read = self.MQRead(self.MQ_PIN)
        val["GAS_LPG"]  = self.MQGetGasPercentage(read/self.Ro, self.GAS_LPG)
        val["CO"]       = self.MQGetGasPercentage(read/self.Ro, self.GAS_CO)
        val["SMOKE"]    = self.MQGetGasPercentage(read/self.Ro, self.GAS_SMOKE)
        return val
        
    ######################### MQResistanceCalculation #########################
    # Input:   raw_adc - raw value read from adc, which represents the voltage
    # Output:  the calculated sensor resistance
    # Remarks: The sensor and the load resistor forms a voltage divider. Given the voltage
    #          across the load resistor and its resistance, the resistance of the sensor
    #          could be derived.
    ############################################################################ 
    def MQResistanceCalculation(self, raw_adc):
        return float(self.RL_VALUE*(1023.0-raw_adc)/float(raw_adc));
     
     
    ######################### MQCalibration ####################################
    # Input:   mq_pin - analog channel
    # Output:  Ro of the sensor
    # Remarks: This function assumes that the sensor is in clean air. It use  
    #          MQResistanceCalculation to calculates the sensor resistance in clean air 
    #          and then divides it with RO_CLEAN_AIR_FACTOR. RO_CLEAN_AIR_FACTOR is about 
    #          10, which differs slightly between different sensors.
    ############################################################################ 
    def MQCalibration(self, mq_pin):
        val = 0.0
        for i in range(self.CALIBARAION_SAMPLE_TIMES):          # take multiple samples
            val += self.MQResistanceCalculation(self.adc.read(mq_pin))
            time.sleep(self.CALIBRATION_SAMPLE_INTERVAL/1000.0)
            
        val = val/self.CALIBARAION_SAMPLE_TIMES                 # calculate the average value

        val = val/self.RO_CLEAN_AIR_FACTOR                      # divided by RO_CLEAN_AIR_FACTOR yields the Ro 
                                                                # according to the chart in the datasheet 

        return val;
      
      
    #########################  MQRead ##########################################
    # Input:   mq_pin - analog channel
    # Output:  Rs of the sensor
    # Remarks: This function use MQResistanceCalculation to caculate the sensor resistenc (Rs).
    #          The Rs changes as the sensor is in the different consentration of the target
    #          gas. The sample times and the time interval between samples could be configured
    #          by changing the definition of the macros.
    ############################################################################ 
    def MQRead(self, mq_pin):
        rs = 0.0

        for i in range(self.READ_SAMPLE_TIMES):
            rs += self.MQResistanceCalculation(self.adc.read(mq_pin))
            time.sleep(self.READ_SAMPLE_INTERVAL/1000.0)

        rs = rs/self.READ_SAMPLE_TIMES

        return rs
     
    #########################  MQGetGasPercentage ##############################
    # Input:   rs_ro_ratio - Rs divided by Ro
    #          gas_id      - target gas type
    # Output:  ppm of the target gas
    # Remarks: This function passes different curves to the MQGetPercentage function which 
    #          calculates the ppm (parts per million) of the target gas.
    ############################################################################ 
    def MQGetGasPercentage(self, rs_ro_ratio, gas_id):
        if ( gas_id == self.GAS_LPG ):
            return self.MQGetPercentage(rs_ro_ratio, self.LPGCurve)
        elif ( gas_id == self.GAS_CO ):
            return self.MQGetPercentage(rs_ro_ratio, self.COCurve)
        elif ( gas_id == self.GAS_SMOKE ):
            return self.MQGetPercentage(rs_ro_ratio, self.SmokeCurve)
        return 0
     
    #########################  MQGetPercentage #################################
    # Input:   rs_ro_ratio - Rs divided by Ro
    #          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(rs_ro_ratio) is provided. As it is a 
    #          logarithmic coordinate, power of 10 is used to convert the result to non-logarithmic 
    #          value.
    ############################################################################ 
    def MQGetPercentage(self, rs_ro_ratio, pcurve):
        return (math.pow(10,( ((math.log(rs_ro_ratio)-pcurve[1])/ pcurve[2]) + pcurve[0])))
Wenn ich den oberen Code ausführe stoße ich aber auf eine umfangreiche Fehlermeldung.

Bild

ICH BITTE DRINGEND UM HILFE!
vielleicht könnt ihr das Problem ja lösen.

danke euch im Voraus :)

mfG. Morris Messing - MoinxD
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da kommt halt 0 vom AD-Wandler. Ob das so soll oder nicht haengt vom Datenblatt ab, und wenn das so sein soll, dann muss du halt auf 0 pruefen, und den Wert dann speziell behandeln. ZB auch 0 zurueckgeben, oder -1 oder was auch immer ein geeigneter Wert sein sollte.
BlackJack

@MoinXD: Bilder von Text sind unpraktisch. Besser ist es wenn man den Text kopiert:
[codebox=text file=Unbenannt.txt]Traceback (most recent call last):
File "/home/pi/Raspberry-Pi-Gas-Sensor-MQ/example.py", line 6, in <module>
mq = MQ():
File "/home/pi/Raspberry-Pi-Gas-Sensor-MQ/mq.py", line 48, in __init__
self.Ro = self.MQCalibration(self.MQ_PIN)
File "/home/pi/Raspberry-Pi-Gas-Sensor-MQ/mq.py", line 83, in MQCalibration
val += self.MQResistanceCalculation(self.adc.read(mq_pin))
File "/home/pi/Raspberry-Pi-Gas-Sensor-MQ/mq.py", line 69, in MQResistanceCalculation
return float(self.RL_VALUE*(1023.0-raw_adc)/float(raw_adc)):
ZeroDivisionError: float division by zero[/code]
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

__deets__ hat geschrieben:Da kommt halt 0 vom AD-Wandler. Ob das so soll oder nicht haengt vom Datenblatt ab, und wenn das so sein soll, dann muss du halt auf 0 pruefen, und den Wert dann speziell behandeln. ZB auch 0 zurueckgeben, oder -1 oder was auch immer ein geeigneter Wert sein sollte.
Du meinst bei MQ_Pin? Ich hab aus dem Datenblatt entnommen, dass da für den MQ-131 "2" stehen muss aber die Fehlermeldung ist die Gleiche.
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, ich meine, dass der Wert, der gelesen wird, 0 ist. Auch du wirst in der Schule gelernt haben, dass man nicht durch 0 teilen kann, oder? Und das heisst nunmal ZeroDivisionError.

Ob der Wert 0 sein *darf* oder nicht, haengt vom Sensor ab. Dann kann es natuerlich noch sein, dass der Sensor nicht richtig verkabelt ist, so dass der AD halt immer 0 liefert. Oder der AD ist falsch verkabelt, und dann liefert er immer 0. Das kann bei SPI passieren.

Zum debuggen bietet es sich an, per Spannungsteiler zb eine definierte Spannung an den gewuenschten Kanal anzulegen. Da muss dann irgendwas zwischen 0 und 1023 rauskommen. Solange das nicht passiert, stimmt die Verkabelung nicht.
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

Also an der Verkabelung liegt es wahrscheinlich nicht.
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

Hier ist der datasheet des Sensors. Vielleicht kann da ja jemand von euch etwas rauslesen.

http://www.sensorsportal.com/DOWNLOADS/MQ131.pdf
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich denke schon das es an der Verkabelung liegt. Das macht das Datenblatt deutlich. Es kann natürlich sein, dass der Sensor oder der AD wandler defekt sind. Darum muss man Komponentenweise testen, wie ich das schon vorgeschlagen habe.
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

Also ich habe jetzt den MCP3008 getestet.

Code: Alles auswählen

from MCP3008 import MCP3008

while True:
	adc = MCP3008()
	value = adc.read( channel = 1 )
	print(value)
Bei jedem Kanal wurden Werte im Bereich von 0 bis 1023 ausgegeben (sprich oftmals auch 0).

:? :?:
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@MoinXD: Dann addiere doch einfach 1.
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

An dem Kanal an dem der Sensor hängt sollten die Werte nicht auf 0 gehen, und nur unbedeutend schwanken. Oder hast du komplett ohne angeschlossenen Bauteile getestet? Dann ist das zu erwarten, weil dein Eingang "floatet". Du solltest den Eingang mal auf definierte Pegel legen. 0V, VCC (i 3.3 V, nicht 5!) und wie schon gesagt per Spannungsteiler auch mal auf etwa dazwischen. Ein Spannungsteiler sind übrigens einfach zwei Widerstände.
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

Hey Leute,

ich habe jetzt einfach 1 addiert wie @kbr geschrieben hatte und zumindest liefert der sensor jetzt Daten.
Die Abweichung durch die addierte 1 dürften nicht all zu hoch sein da immer der Durchschnitt von 50 Werten gebildet wird.

Die ausgegebenen Werte sehen relativ vielversprechend aus, allerdings sind da immer ein paar Ausreißer dabei.

Seht selbst:

Code: Alles auswählen

03: 8998.99 ppm
03: 0.0146293 ppm
03: 0.0427497 ppm
03: 1950.72 ppm
03: 0.0455045 ppm
03: 999.221 ppm
03: 0.0426006 ppm
03: 0.0146296 ppm
03: 9120.24 ppm
03: 0.0454971 ppm
03: 0.045507 ppm
03: 5908.61 ppm
03: 0.014629 ppm
03: 0.0429934 ppm
03: 186.466 ppm
03: 0.0455049 ppm
03: 10843 ppm
03: 0.0146289 ppm
03: 0.0146293 ppm
03: 6183.86 ppm
03: 0.0455024 ppm
03: 0.0455049 ppm
03: 8712.79 ppm
03: 0.0430926 ppm
03: 0.0146288 ppm
03: 0.0407158 ppm
03: 0.0146293 ppm
03: 0.0146293 ppm
03: 0.0454915 ppm
03: 21.0113 ppm
03: 11249.6 ppm
03: 0.0455045 ppm
03: 650.313 ppm
03: 194.581 ppm
03: 0.0455054 ppm
03: 0.0146292 ppm
03: 209.682 ppm
03: 10464 ppm
03: 0.302975 ppm
03: 7322.81 ppm
03: 0.0454796 ppm
03: 0.0455054 ppm
03: 0.0455015 ppm
03: 9852.6 ppm
03: 0.205942 ppm
03: 6745.35 ppm
03: 0.0146291 ppm
03: 0.0146293 ppm
03: 0.0146289 ppm
03: 4262.26 ppm
03: 0.0454584 ppm
03: 0.0455049 ppm
03: 0.0455054 ppm
03: 0.242765 ppm
03: 35.6088 ppm
03: 0.0146291 ppm
03: 0.0146289 ppm
03: 8761.31 ppm
03: 0.0449277 ppm
03: 0.0146294 ppm
03: 0.0364744 ppm
03: 4223.83 ppm
03: 0.0454629 ppm
03: 0.0455049 ppm
03: 0.218168 ppm
03: 4237.62 ppm
03: 0.27685 ppm
03: 0.0146294 ppm
03: 0.0146296 ppm
03: 0.0146289 ppm
03: 0.277451 ppm
03: 5477.06 ppm
03: 3008.28 ppm
03: 0.0454988 ppm
03: 0.0455062 ppm
03: 0.0455024 ppm
03: 246.178 ppm
03: 9968.98 ppm
03: 0.0146292 ppm
03: 0.0146292 ppm
03: 0.25701 ppm
03: 9633.76 ppm
03: 0.0455041 ppm
03: 0.0455045 ppm
03: 0.228002 ppm
03: 11681.7 ppm
03: 0.0447141 ppm
03: 0.0146298 ppm
03: 0.0146289 ppm
03: 270.023 ppm
03: 0.0454912 ppm
03: 0.0455054 ppm
03: 0.045492 ppm
03: 10615.3 ppm
03: 0.044939 ppm
03: 0.0146295 ppm
03: 0.0146291 ppm
03: 32.7965 ppm
03: 0.29353 ppm
03: 0.0455049 ppm
03: 0.0455019 ppm
03: 7355.37 ppm
03: 0.0448345 ppm
03: 0.0146293 ppm
03: 0.0146289 ppm
03: 3049.27 ppm
03: 0.0146294 ppm
03: 0.0146296 ppm
03: 0.225772 ppm
03: 2306.74 ppm
03: 0.0455049 ppm
03: 0.0296239 ppm
03: 9894.78 ppm
03: 0.0455041 ppm
03: 0.0146292 ppm
03: 8717.11 ppm
03: 0.298307 ppm
03: 0.0146294 ppm
03: 0.0146293 ppm
03: 0.195551 ppm
03: 0.302654 ppm
03: 0.0146288 ppm
03: 8913.29 ppm
03: 0.0146296 ppm
03: 0.141702 ppm
03: 2969.41 ppm
03: 0.0146295 ppm
03: 0.04549 ppm
03: 3361.9 ppm
03: 0.0146294 ppm
03: 0.041881 ppm
03: 4992.69 ppm
03: 0.218183 ppm
03: 2298.24 ppm
03: 0.0146296 ppm
03: 0.0403057 ppm
03: 5713.91 ppm
03: 0.0455041 ppm
03: 0.0455032 ppm
03: 5565.65 ppm
03: 0.0455045 ppm
03: 0.0146288 ppm
03: 0.045026 ppm
03: 0.0455049 ppm
03: 0.300715 ppm
03: 6060.06 ppm
03: 0.0146295 ppm
03: 21.9776 ppm
03: 0.0146292 ppm
03: 0.0146288 ppm
03: 10573.9 ppm
03: 0.0146295 ppm
03: 0.267234 ppm
03: 8216.6 ppm
03: 0.0455037 ppm
03: 0.0454961 ppm
03: 8264.89 ppm
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was findest du denn daran vielversprechend ? Das ist doch quasi Zufall was du du bekommst. Und das Prinzip des Sensors gibt ein solches rauschen nicht her.

Alles was du getan hast ist deine schon bestehende Beobachtung von Wild schwankenden werten durch die Umrechnung zu pressen, weil du den 0 Fehlerfall ignorierst.

Wie man dem auf die Spur kommen könnte schrieb ich ja schon mehrfach.
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Bild
Das rauscht für mich ohne erkennbares Muster. Bzw wenn, dann erkennt man da sowas wie Netzfrequenz Einstreuung. Mangels zeitbasis ist das aber nur geraten.
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

Die meisten werte liegen zwischen 0.01 und 0.05ppm. Das hört sich für mich ziemlich realistisch an.
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich scheitere gerade daran hier ein Bild hochzuladen. Vielleicht klappt das als Link: https://www.dropbox.com/s/0l0zbunkl1b9t ... 9.png?dl=0

Das ist nutzlos. Wirklich. Der Sensor schwankt so dolle nicht, und damit stimmt da halt etwas nicht.

Edit: 42% der Werte sind größer als 0.05. Wenn das 5% wären, dann könnte man die Filtern.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@MoinXD: Die Werte sehen leider noch nicht vielversprechend aus. Zwar taucht nun der ZeroDivisionError nicht mehr auf, aber die Addition von 1 war nur ein pragmatischer Ansatz dies zu vermeiden in der Annahme, dass der Sensor nach einer Kalibrierung den gültigen Messbereich mit Daten von 0-1023 (resp. 1-1024) beschreibt. Das kann man dann später korrigieren, falls die Abweichung signifikant ist, oder eben die Messdaten mit 0 als Resultat anders behandeln. Die Ergebnisse, die Du erhältst, sind aber erratisch. D.h. mit dem Sensor oder dessen Anschluß ist scheinbar etwas nicht in Ordnung.
MoinXD
User
Beiträge: 11
Registriert: Sonntag 25. Dezember 2016, 17:52

Alles klar ich werde über die nächsten Tage mal ein paar Tests durchführen ich meld mich dann wieder.

Danke für eure Hilfe und Lösungsansätze :)
Antworten