Alkomat: LCD auf Prototype Shield und Ausgabe von Gassensor-Messdaten

Ich habe mir auf eBay zwei Gassensoren gekauft. Die kryptischen Bezeichnungen dafür lauten MQ-135 und MQ-3.

Beide Sensoren nutzen Heizelemente, um auf eine Reaktions-Temperatur zu kommen, und wenn sie so 40 bis 45° C erreicht haben, was etwa eine Minute dauert, werden im Inneren irgendwelche chemischen Reaktionen gemessen und der Messwert ermittelt.

Und so ist einiges zu beachten bei der Anwendung. Zum einen brauchen die Sensoren eine "Einbrennzeit" von 24 Stunden, bis sie zuverlässig messen und zum zweiten müssen sie mindestens eine Minute anheizen, bevor man damit messen kann. Und die Heizung läuft im Dauerbetrieb und braucht etwa 200 mA pro Sensor.

MQ-135


Der silberne MQ-135 ist ein Sensor für Luftqualität und erkennt Stickoxide, Kohlendioxid, Ammoniak, Benzin, Alkohol und Rauch, sollte sich also gut für die Messung von Luft-Belastungen an Straßen durch PKW und LKW Verkehr eignen.

Technische Daten:

MQ-3


Der andere, der orange MQ-3 soll hingegen zuverlässig Alkohol erkennen. Daraus will ich später mal einen Atemalkoholtester bauen.

Technische Daten: Es gibt noch weitere Sensoren, z. B. für Erdgas, um bei einem Gasleck Alarm zu geben, aber ich fand diese beiden am nützlichsten für den allgemeinen Gebrauch.


Auf der Rückseite sehen beide Sensor-Module gleich aus und haben ein blaues Potentiometer, mir dem man den Schwellenwert einstellen kann, ab dem der Digitalausgang DO (Digital Out) geschaltet wird, z. B. für einen Alarm. Ansonsten reicht es, den analogen Wert an AO (Analog Out) zu messen und selbst auszuwerten.

Etwas schwierig ist die Kalibrierung bzw. Vergleichsmessdatenerfassung. Auch muss man bei der Erfassung der Werte aufpassen. Denn die Messung ist sehr von der Temperatur abhängig. Die Sensoren halten zwar eine stabile Temperatur, wenn sie nicht beeinflußt werden, aber wenn ich mit einem scharfen Strahl in einen Sensor hineinpuste, dann kühle ich ihn ab und der Messwert sinkt zu sehr. Hauche ich ihn an, entsprechen die ca. 37 °C der Atemluft ungefähr der internen Sensortemperatur, aber die Luftfeuchtigkeit ändert sich. Dies scheint den Messwert allerdings weniger zu verfälschen als scharfen Pusten.

Doch dazu später mehr. Als erstes möchte ich die Sensoren an einen Ort bringen, wo die Luft möglichst sauber ist. Dazu fiel mir natürlich gleich der unweit entfernte Wald ein. Dazu brauche ich aber natürlich eine transportable Lösung, die mir die Messwerte anzeigt.

LCD zur Anzeige der Messwerte


Auf das Protoytpe-Shield-Board passt gerade so ein 1602A-LCD-Modul, mit dem wir ja schon einige Erfahrung gesammelt haben und das ich wie folgt beschaltet habe. LCD Arduino VSS GND VDD +5V V0 über 5 kΩ Poti an GND (für Kontrast) RS D2 E D3 D4 D4 D5 D5 D6 D6 D7 D7 A über 500 Ω Poti an D8 (für Helligkeit) K GND Taster D12
Dabei habe ich mich (fast ein wenig zu sehr) am Raspberry-Anschlussschema orientiert. Ein bisschen zu sehr, weil ich die einstellbare Helligkeit auch über ein PWM-Digital-Ausgang hätte realisieren können und mir so das Poti sparen. Dennoch: so bietet es den Vorteil auch unterwegs ohne einen neuen Sketch hochzuladen, die Helligkeit zu ändern, was vielleicht doch ganz praktisch ist, wenn man man den ganzen Tag unterwegs ist und abends die Helligkeit ein wenig dimmen will.

Den Shield-Taster habe ich wieder an D12 gehängt. Dadurch bleiben noch D9, D10, D11 und D13 frei.

Die rechten 5-fach +5V und GND-Buchsen werden durch das breite LCD allerdings doch überdeckt. Darum habe ich an die Buchsenleiste auf der Unterseite nochmals eine Erweiterung angelötet, der man §.3V, 5V und GND entnehmen kann.




Die sechs Analogeingänge bleiben dabei frei. Für den Fall, dass ich einmal I2C-Sensoren anschließen will, habe ich die Anschlüsse A4 und A5 dafür reserviert.

An A0 bis A3 können noch vier analoge Sensoren angeschlossen werden, deren Werte als Einheiten (0 bis 1023) ausgegeben werden. Man kann diese Werte als annähernde Promille-Werte gut interpretieren und ggf. mit dem Taschenrechner umrechnen. Mir war wichtiger, mehr Sensoren gleichzeitig anzeigen zu können.






Mit dem Taster kann man einen Sensor auswählen (an einem * erkennbar), dessen Werte über die serielle Schnittstelle weitergeleitet werden und so z. B. auf dem seriellen Plotter der Entwicklungsumgebung ausgegeben werden können, wenn das Shield zuhause angeschlossen ist.





Messdatenerfassung

So ausgerüstet bin ich also in den Wald gezogen und habe unterwegs die jeweiligen Messergebnisse notiert: MQ-135 an A0 MQ-3 an A1 Ort 47 74 Zimmer ungelüftet 38 49 Zimmer nach 5 Min. Kipp-Lüften 35 46 Zimmer nach 20 Min. Kipp-Lüften 34 34 vor der Haustür 34 34 am Waldrand 33 29 kleiner Waldweg, ca. 150m im Wald 33 37 Trampelpfad im Wald 31 31 Sitzgruppe ca. 70m im Wald Man kann also sagen, dass für beide Sensoren der Nullwert bei ungefähr 30 liegt, bei dem saubere Luft herrscht.

Als nächstes sollte ich Vergleichs- und Spitzenwerte sammeln gehen, z. B. für den MQ-135 an viel befahreren Straßen oder für den MQ-3 mit unterschiedlichen Alkoholarten und Mengen in der Atemluft. (natürlich alles streng wissenschaftlich ;) ).

MQ-135 an A0 MQ-3 an A1 Substanz 35 35 Normalwert Zimmerluft 35 35 Stabilo Marker 38 (auf Wasserbasis) 35 37 STANGER Textmarker ("German ink") 49 112 Kohlendioxid in Mineralwasserfalsche "Medium" 60 74 Abwesenheit von Frischluft in geschlossenem System 68 66 AL-METAL Polierpaste (stechender Geruch) 120 89 Klarlack Lackstift DUPLI-COLOR 0-40 (enthält Xylol) 130 650 INOXCROM Marker ("auf Alkoholbasis") 130 308 edding 500 (ohne Inhaltshinweise) 150 - 30cm Abstand zum Auspuff Euro5-Diesel im Leerlauf 180 313 edding 500 ("ohne Zusatz von Xylol / Toluofrei") 224 116 langsames Ausatmen von normaler Atemluft über 20 Sek. in geschlossenes System 280 459 edding 3000 (ohne Inhaltshinweise) 300 - 10cm Abstand zum Auspuff Euro5-Diesel im Leerlauf 366 446 Pritt Bastelkleber ("ohne Lösungsmittel") 385 395 WD-40 401 750 zerstäubter Alkohol 85% vol. (Parfum) 596 786 Ethanol 100% im geschlossenen System 650 790 Solvent 14G (Leitlackverdünner mit Cyclohexanol, Ethanol, 1-Ethoxypropan-2-ol, Pflanzenöle) 760 709 zerstäubtes Ethanol 100% (Spiritus) 940 186 Feuerzeuggas (Butan)

Der MQ-3 braucht sehr viel länger als der MQ-135, zum Teil viele Minuten bis gar Stunden, um sich von einer hohen Belastung wieder zu erholen und auf den Normalwert zurückzufallen. Schnelle Messungen hintereinander mit hohen Werten sind deshalb ausgeschlossen. Außerdem hat sich gezeigt, dass die Werte häufig nur zurückfallen, solange der Sensor in Betrieb und beheizt ist.

Umrechnung in Realwerte (Atemalkoholkonzentration)

Interessant sind die Ergebnisse solcher Sensoren eigentlich erst, wenn man sie mit Realwerten vergleichen kann und so z. B. erfahren, ob die Abgasbelastung vor der Haustür die gesetzlichen Grenzwerte überschreitet, oder ob der Atemalkohol anzeigen würde, dass man zuviel getrunken hat, um noch sicher Auto fahren zu können.

Beim "Pustetest" bei einer Verkehrskontrolle wird nichts anderes gemacht als der Atemalkoholgehalt in Milligramm pro Liter Atemluft (mg/l) gemessen. Man macht sich dabei zunutze, dass der im Blut zirkulierende Alkohol auch beim Sauerstoff/Blut-Austausch beim Atmen über die Lunge abgegeben wird und dann in der ausgeatmeten Luft gemessen werden kann.

Dabei ist so einiges zu beachten: Wie man sieht, ist so ein Pustetest nicht in allen Fällen genau und evtl. auch individuell verschieden (Lungenvolumen, Atemtechnik vorher, Pustetechnik etc.). Ein genaueres Ergebnis bringt ein Blutalkoholtest. Dabei wird Blut abgenommen und untersucht. Messfehler sind hierbei so gut wie ausgeschlossen.

1998 hat es sich der Gesetzgeber einfach gemacht und festgelegt, dass auch das Ergebnis eines Atemalkoholtests für Ordnungswidrigkeiten (Strafrecht wird diskutiert) Gültigkeit hat. Er hat dafür einen Grenzwert von 0.25 mg/l Atemluft festgelegt, was ungefähr 0.5 Promille Alkohol im Blut entspricht (Faustregel mg/l mal 2.1 = Promille Blutalkoholkonzentration), bei dem der Fahrer straffrei ausgeht. Ab einem Blutalkoholgehalt von 0.5 Promille liegt eine Ordnungswidrigkeit vor, ab 1.1 Promille bereits eine Straftat (Stand 2018, Deutschland). Wenn man dem Verfahren, dem Gerät oder der Kompetenz hinsichtlich der Gerätebedienung der durchführenden Beamten nicht traut, kann man einem Atemalkoholtest widersprechen; dann muss ein Blutalkoholtest durchgeführt werden (Vorsicht! Dieser wird in vielen Fällen höher ausfallen.). Wenn man sich also gerade den Mund mit Mundwasser eingesprayt hat, aber nichts getrunken hat, Fragen wie "Sie haben doch sicher nichts dagegen, ins Röhrchen zu pusten?" verneinen, weil sonst das Einverständnis vorausgesetzt wird und man bei Ergebnissen bis 0.55 mg/l AAK (gleich 1.1 Promille BAK) keine Option auf eine nachträgliche Blutentnahme mehr hat.

Da ich gerade kein Ammoniak im Haus habe, wohl aber Ethanol in Reinform, bezieht ich Folgendes auf den MQ-3.


Schauen wir uns den Chart des MQ-3 noch einmal genauer an: Wir sehen: Luftalkoholkonzentration in mg/l auf der X-Achse wie auch RS/R0 auf der Y-Achse benutzen logarithmische Skalen.

Außerdem muss noch geklärt werden, was RS/R0 (Y-Achse) bedeutet. R0 ist durch das Datenblatt festgelegt als Widerstand bei 0.4 mg/l Alkohol in sauberer Luft bei 20 °C, 21% Luftsauerstoffgehalt und 65% Luftfeuchtigkeit. RS ist der durch den Sensor gemessener Widerstand, der sich aus dem Messwert und der Spannung (5V) ergibt.

Es gilt also den Messwert in Einheiten für 0.4 mg/l Luftalkohol zu messen und dann die logarithmischen Skalen auf eine Formel abzubilden, mit der man dann ein gemessenes Ergebnis in mg/lg LAK (sollte AAK entsprechen) erhält.





Ich habe mir die Mühe gemacht, die Werte so gut es geht aus der Tabelle im Datenblatt abzulesen und in eine Excel-Tabelle einzutragen.

Daraus habe ich ein Diagramm erstellt, dass die Beziehung der beiden Kurven zueinander und in einem linearen System darstellt. Jetzt kann man gut erkennen, dass sich beide Kurven kreuzen und zwar im Referenzpunkt 1 RS/R0 = 0.4 mg/l. Die Kurven sind annähernd logarithmisch, aber jede mit ihren eigenen Werten für die Basis. Zudem sind doch ein paar "Dellen" in der Kurve.


Jetzt muss ich die beiden Kurven sozusagen gegeneinander aufrechnen und so die Basen für die Logarithmen herausfinden.

Zum Glück gibt es Online-Tools für das "polynomiale Regression" benannte Verfahren, dass mir eine Annäherungsgleichung für die Kurven automatisch errechnet.

Die Summe folgenden 6 Potenzen bildet die Umrechnung von einem Punkt auf der Kurve gegenüber dem korrespondierenden Punkt auf der anderen Kurve recht gut ab: y = 94.14992439 x6 - 583.5255099 x5 + 1353.538731 x4 - 1491.612505 x3 + 830.2311343 x2 - 229.553235 x + 27.17143755 In der Abbildung rechts entspricht die gelbe, aus RS/R0 errechnete Kurve sehr gut der Referenz-Messdaten-Kurven, insbesondere in dem wichtigen Teil von 0.1 bis 2 mg/l, was 0.2 bis 4.0 Promille, was man auch mit "leicht beschwippst" bis "ins Koma gesoffen" beschreiben könnte.

Kalibirierung des MQ-3


Bleibt als letzte Berechnungsgrundlage noch die Frage offen: wieviele Einheiten entsprechen 0.4 mg/l Luftalkohol?

Dazu muss ich eine Atmosphäre herstellen, in der 0.4 mg Alkohol in einem Liter Luft existieren. Und zwar so, dass sich weder Temperatur noch Feuchtigkeit verändern, weil das die Messwerte beeinflussen würde - wobei eine abweichende Luftfeuchtigkeit nicht so schlimm wäre (siehe Chart rechts) wie eine Temperaturänderung durch starken draufpusten (wie schon vorher bemerkt).

Man könnte einen Luftballon mit einem Liter Luft füllen - aber wie 0.4 mg Alkohol, dass sind ja nur 0.0004 ml, abmessen? Das bekomme ich nicht hin. Dann kam mir die Idee, Alkohol in Wasser zu verdünnen, aber nach welcher Bemessung? Nach der AAK / BAK - Umrechnungsformel? Das wären dann 0.84 ml Alkohol auf 1 l Wasser oder - wenn ich meinen größten Behälter (ein 5 l Kanister) nehme 4.2 g. Das könnte man abmessen, müssten den Alkohol dann aber zerstäuben und wer weiß, wie sich der Temperaturabfall und die Luftfeuchtigkeit wieder auswirken würde.

Am sinnvollsten wird es wohl sein, den MQ-3 unter den Bedingungen zu kalibrieren, unten denen er später auch eingesetzt werden soll. Sprich: mir 0.8 Promille antrinken. Dann 20 Minuten warten. Und dann Mund ausspülen und pusten und den Referenzwert für 0.4 mg/l notieren.

Die diversen Promillerechner im Netz meinen, ich müsste mir dazu 0.22 Liter 40%igen Alkohol wie Whiskey innerhalb eines Dreiviertelstunde gönnen und hätte dann in einer Stunde den maximalen Promillewert von 0.81 erreicht. Na, dann: Alles zum Wohle der Wissenschaft! Prost!

Aha! So betrunken ist man also bei 0.8 Promille... Für mich definitiv zu viel, das hat mir keinen Spaß mehr gemacht. Egal, auf jeden Fall habe ich jetzt einen Messwert für 0.81 Promille BAK enstsprechend 0.4 mg/l AAK, nämlich 470. Fünf Minuten später die 2. Messung bei 468. Das ist also mein Eichwert für R0. Umrechnerei in Volt, Ohm oder andere Sperenzchen kann ich mir sparen, denn das verändert das Verhältnis von RS zu R0 nicht.

Weitere Messungen ergaben (der Abbau von Alkohol im Körper ist individuell und zwischen 0.1 und 0.2 Promille pro Stunde, ich rechne hier mit 0.1 ‰/h):

Bau und Test eines Alkomats


Aus einer alten Kontaktlinsenreinigungsflüssigkeitsflasche aus Plastik, die zufällig oben eine Öffnung in richtiger Größe für den Sensor hat und bei mir eh nur rumlag, habe ich auf die Unterseite ein Loch gebohrt und einen Plastikstrohhalm eingesteckt. In diesen wird ungefähr 5 Sekunden hineingepustet und dann der Messwert abgelesen.

Hier ist mir schon einmal etwas aufgefallen. Befindet sich der Sensor in diesem fast geschlossenen System, dem keine Frischluft zugeführt wird, pendelt sich der Wert von sich aus schon bei ca. 85 ein. Diesen Wert, nennen wir ihn AAK_NULL sollten wir später in unsere Berechnungen mit einbeziehen. Die ermittelten 470 für R0 wurden übrigens auch mit dieser "Gerätschaft" ermittelt.

Nun habe ich den Ardunio-Code so abgeändert, dass wenn man den Taster einmal lang drückt, nur noch die Sensordaten von A0 angezeigt werden und diese nach obigen Formel zudem als AAK (Atemalkoholkonzentration in mg/l, kurz A) und BAK (Blutalkoholkonzentration in Promille, kurz B) in der 2. Zeile ausgegeben werden.


Beim Test (ich wieder stocknüchtern) fielen mir dann negative Werte auf, was ja eigentlich gar nicht sein kann. Erst dachte ich, ich hätte die Formel falsch eingetragen, doch als ich die Werte via Excel nachrechnete, bestätigten sich die negativen Werte. Daraufhin habe ich in Excel eine Tabelle erstellt, die alle relevanten Werte von 30 bis 1023 berechnet und mir daraus die ganze Kurve zeichnen lassen (siehe Kurve rechts).

Und wahrhaftig: Auch wenn die Polynominal-Kurve für die angegebenen Referenzmesswerte sehr genau waren, dazwischen lieferte die Formel größtenteils Müll. Also nicht zu gebrauchen. Ich beschloss, mich nicht auf ein Online-Tool zu verlassen, sondern die Kurve selbst nachzubilden per Trial and Error Annäherung.




Mit nur einer Formel bekam ich das nicht exakt genug hin. Darum habe ich zwei Formeln, eine für Werte bis 600 Einheiten (blaue Kurve) und eine für Werte über 600 Einheiten (schwarze Kurve). Im Programm würde ich dann entsprechend je nach Einheitenwert die eine oder andere benutzen.

Die gelben Punkte sind die Referenzpunkte, die jetzt recht gut abgebildet werden sollten und zwar ohne irgendwelche unmöglichen Daten dazwischen. Die schwarze Kurve geht nur bis ca. 900 Einheiten und 5 mg/l AAK oder 10 Promille BAK. Da ein Mensch damit aber eh sicher tot ist, ist der Messbereich darüber hinaus sowieso irrelevant.

Formel bis 600 Einh. (blau) : R2*0.2 - R*0.8 + 1 Formel über 600 Einh. (schwarz): (ABS(LOG(R,1.75)))1.22 wobei R das Verhältnis RS/R0 ist. RS = Messwert in Einheiten, R0 = Messwert für 0.4 mg/l AAK = 470.

Der Source-Code

//////////////////////////////////////////////////////// // (C) 2018 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <time.h> #include <string.h> #include <Math.h> #define PinLcdRS 2 #define PinLcdE 3 #define PinLcdData4 4 #define PinLcdData5 5 #define PinLcdData6 6 #define PinLcdData7 7 #define PinLcdBacklight 8 #define PinTaster 12 // 1602A-LCD #define LcdWidth 16 #define LcdHeight 2 #define LcdAddrLine1 0x00 #define LcdAddrLine2 0x40 #define LcdAddrLine3 0x00 #define LcdAddrLine4 0x40 #define LcdCmdClear 0x01 #define LcdCmdHome 0x02 #define LcdCmdOff 0b1000 #define LcdCmdOn 0b1100 #define LcdCmdCurOn 0b1111 #define LcdCmdCurOff 0b1100 #define LcdPulseDur 500 // microsecs #define LcdDelayDur 500 #define AAK_NULL 85 // Messeinheiten im geschlossenen System ohne Frischluftzufuhr #define AAK_REF 470 // Messeinheiten (0-1023) für 0.4 mg/l AAK char lcdTranslate (char in) { if (in == '°') return 223; if (in == 'ä' || in == 'Ä') return 225; if (in == 'ö' || in == 'Ö') return 239; if (in == 'ü' || in == 'Ü') return 245; if (in == 'ß') return 226; if (in == 'µ') return 228; if (in == '€') return 227; return in; // keine Änderung } void lcdPulseEnable(){ // Einen Pulse auf die Enable-Leitung des LCD schicken delayMicroseconds(LcdDelayDur); digitalWrite(PinLcdE, HIGH); delayMicroseconds(LcdPulseDur); digitalWrite(PinLcdE, LOW); delayMicroseconds(LcdDelayDur); } void lcdByte(uint8_t byte){ // ein Byte im 4-Bit-Mode senden // höherwertiges Halbbyte senden for (uint8_t bnr=4; bnr<8; bnr++) { uint8_t bit=(byte & (1<<bnr)) / (1<<bnr); uint8_t pin=0; if (bnr == 4) pin=PinLcdData4; else if (bnr == 5) pin=PinLcdData5; else if (bnr == 6) pin=PinLcdData6; else if (bnr == 7) pin=PinLcdData7; digitalWrite(pin, bit); } lcdPulseEnable(); // niederwertiges Halbbyte senden for (uint8_t bnr=0; bnr<4; bnr++) { uint8_t bit=(byte & (1<<bnr)) / (1<<bnr); uint8_t pin=0; if (bnr == 0) pin=PinLcdData4; else if (bnr == 1) pin=PinLcdData5; else if (bnr == 2) pin=PinLcdData6; else if (bnr == 3) pin=PinLcdData7; digitalWrite(pin, bit); } lcdPulseEnable(); } void lcdCmdByte(uint8_t byt){ // Kommando als Byte-Wert senden digitalWrite(PinLcdRS, LOW); // 0=command, 1=character lcdByte(byt); } int lcdGetAddrForLine(uint8_t line) { uint8_t addr = 0x00; if (line == 0) addr = LcdAddrLine1; if (line == 1) addr = LcdAddrLine2; if (line == 2) addr = LcdAddrLine3; // für 2004 if (line == 3) addr = LcdAddrLine4; // für 2004 return addr; } void lcdMsg(uint8_t line, uint8_t col, String msg){ // line ist die x. Zeile (gezählt ab 0) lcdCmdByte(lcdGetAddrForLine(line)+0x80+col); // Speicheradresse für Line/Col adressieren digitalWrite(PinLcdRS, HIGH); // 0=command, 1=character uint8_t ch=0; int za=col+1; for (int i=0; i < msg.length(); i++) { ch = lcdTranslate(msg[i]); // uint8_t byte=(ord(char)) # ord gibt ASCII-Code eines Zeichens zurück lcdByte(ch); if (za % LcdWidth == 0) { line+=1; // Zeile zu lang, Zeilenumbruch und in nächster Zeile weiter lcdCmdByte(lcdGetAddrForLine(line)+0x80+col); digitalWrite(PinLcdRS, HIGH); // und wieder zurück in den Zeichenmodus } za+=1; } } void lcdInit(){ // LCD initialisieren lcdCmdByte(0x33); // 110011 Initialize lcdCmdByte(0x32); // 110010 Initialize lcdCmdByte (0b101000); // Function Set Command // ^----- DL: 4-bit-mode (optional 8-bit-Mode) // ^---- N: 2-line-mode (optional 1-line-mode) // ^--- F: 5x8 font (optional 5x11 font) lcdCmdByte (0b1100); // Display on/off Command // ^----- D: Display on // ^---- C: Cursor off // ^--- B: Cursor Pos. off lcdCmdByte (0b110); // Cursor or Display Shift Command // ^----- S/C: 1=Shift, 0=Cursor // ^---- R/L: 1=Right, 0=Left // lcdCmdByte(LcdCmdClear); } long waitTaster(long maxWait) { // gibt zurück, wieviele msecs der Taster gedrückt gehalten war long msecs=0; while (digitalRead(PinTaster) == HIGH) { delay (1); msecs++; if (maxWait != 0 && msecs > maxWait) return 0; } // wielange gehalten ? msecs=0; while (digitalRead(PinTaster) == LOW) { delay (1); msecs++; } return msecs; } void setup() { // put your setup code here, to run once: pinMode(PinLcdRS, OUTPUT); pinMode(PinLcdE, OUTPUT); pinMode(PinLcdData4, OUTPUT); pinMode(PinLcdData5, OUTPUT); pinMode(PinLcdData6, OUTPUT); pinMode(PinLcdData7, OUTPUT); pinMode(PinLcdBacklight, OUTPUT); pinMode(PinTaster, INPUT_PULLUP); Serial.begin(9600); // initialize serial } void loop(void) { // Stromsparen - erst einmal auf Tastendruck warten while (digitalRead(PinTaster) == HIGH) { delay (100); } // und auf Loslassen warten while (digitalRead(PinTaster) == LOW) { delay (10); } // --- Los geht's // Backlight an digitalWrite(PinLcdBacklight, HIGH); lcdInit(); int a[4]={0,0,0,0}; char msg[10]; int za=-1; long w=0; int anzeigeModus=0; while (1){ for (int i=0; i<4;i++) { a[i]=analogRead(i); } if (anzeigeModus ==0) { // alle vier Sensoren A0 bis A3 anzeigen sprintf(msg,"A0%s%4d", za==0?"*":":",a[0]); lcdMsg (0,0,msg); sprintf(msg,"A1%s%4d", za==1?"*":":",a[1]); lcdMsg (0,9,msg); sprintf(msg,"A2%s%4d", za==2?"*":":",a[2]); lcdMsg (1,0,msg); sprintf(msg,"A3%s%4d", za==3?"*":":",a[3]); lcdMsg (1,9,msg); if (za > -1) Serial.println (a[za]); } else if (anzeigeModus == 1) { // nur A0 (MQ-3) anzeigen, aber auch AAK und BAK sprintf(msg,"A0%s%4d", za==0?"*":":",a[0]); lcdMsg (0,0,msg); float w = a[0]; float aak = 0; float r = (1023. - w - AAK_NULL) / (1023. - AAK_REF - AAK_NULL); // RS/R0 if (w <= 600) { aak = pow(r,2)*0.2 - r*0.8 + 1.; // =B13^2*0.2-B13*0.8+1 } else { aak = pow( abs(log(r)/log(1.75)) ,1.22); // =(ABS(LOG(B13;1.75)))^1.22 } // Für niedrige Einheiten werden zu hohe AAK angeziegt wegen AAK_NULL; ausgleichen if (w <= AAK_NULL) { aak = 0; } else if (w <= AAK_NULL*1.1) { aak *= .5; } else if (w <= AAK_NULL*1.2) { aak *= .66; } else if (w <= AAK_NULL*1.3) { aak *= .75; } char strg[6]; dtostrf(aak, 1, 2, strg); sprintf(msg,"A: %s ", strg); lcdMsg (1,0,msg); dtostrf(aak*2.1, 1, 2, strg); sprintf(msg,"B: %s ", strg); lcdMsg (1,8,msg); } // endif anzeigeModus w = waitTaster(500); if (w>0) { if (w > 500) { //lang gedrückt anzeigeModus++; lcdCmdByte(LcdCmdClear); if (anzeigeModus > 1) anzeigeModus=0; } else { za++; if (za>3) za=-1; } } } // end while } // end function Der Code enthält alles, was man für die Direktsteuerung im 4-bit-Mode eines LCD 1602A und die Auswertung eines Tasters braucht. Er enhält zwei Anzeigemodi: einmal, um die Messwerte in Einheiten aller 4 Sensoren (an A0 bis A3) anzuzeigen und ggf. am seriellen Plotter auszugeben (kurz drücken) und zweitens einen Modus, der nur A0 anzeigt, aber mit errechneter Atemalkoholkonzentration (A) und Blutalkoholkonzentration (B). Zwischen den Anzeigemodi kann per langem Tastendruck gewechselt werden.

Der Sensor hat mehrere Probleme: Ich habe mich im Programm für die Umsetzung im geschlossenen System entschieden. Es gibt darum den Definitionswert AAK_NULL (=85) und einen Abschnnitt im Code, der Werte darunter mit 0.0 AAK bewertet und dann langsam steigend nur einen Teil des rechnerisch vorliegenden AAKs nimmt. Ab 130% des gemessenen AAK gibt es dann kein Erbarmen und kein Herunterrechnen mehr (bei AAK_NULL von 85 also ab 110 Einheiten).

Fazit

Nach meinen Tests hab ich so meine Zweifel an der Genauigkeit des Sensors als auch an den Referenzwerten im Datenblatt. Eine eigene Datenerfassung und Erstellung einer Messkurve für jeden einzelnen Sensor durch Vergleich mit einem gutem Alkomaten (z. B. Geräte von Dräger, die auch die Polizei benutzt und die ein paar hundert mal teurer sind) wäre nötig, um die Genauigkeit zu beweisen oder zu widerlegen.

Nichtsdestotrotz war für mich dieser Ausflug in die Welt der Gassensoren lehrreich und hat mir gezeigt, dass diese ganz spezielle Umweltbedingungen brauchen, um halbwegs richtig zu funktionieren. Und dass sie mit ihrer Heizung Stromfresser sind.