Używamy plików cookies (tzw. ciasteczka) by spersonalizować treść i ogłoszenia oraz by analizować ruch na stronie.  W sposób automatyczny dzielimy się informacjami o Twoim użyciu tego portalu z dostawcami ogłoszeń, którzy mogą połączyć te informacje z informacjami, które im udzieliłaś/łeś lub, które sami zebrali. Korzystanie z witryny bez zmiany ustawień dotyczących cookies oznacza, że będą one zamieszczane w Państwa urządzeniu końcowym.  Możecie Państwo dokonać w każdym czasie zmiany ustawień dotyczących cookies zmieniając opcje przeglądarki.

Czujnik zapylenia powietrza (Arduino)

Używanie rekuperatora w zimie ma 1 wadę: do domu zasysane są duże ilości świeżego powietrza.  Gdy sąsiad odpala swój węglowy piec, 'świeże powietrze' gryzie w gardło i lepiej byłoby, by zostało sobie na zewnątrz.  Stąd pomysł skonstruowania czujnika jakości powietrza, dzięki któremu można by okresowo wyłączać rekuperator.

Postanowiłem zbudować czujnik bazując na module Adafruit Trinket Pro, laserowy czujnik SHARPa GP2Y1010AU0F oraz czujnik MQ135.  Poruszam się zupełnie po omacku, bazując na tym, co udało mi się znaleźć w sieci. Jako że RPi nie obsługuje w prosty sposób odczytów analogowych, chciałem rozpoznać bojem świat mikrokontrolerów Arduino.  Postanowiłem też, że w możliwie szerokim zakresie bazować będę na komponentach gotowych, by ograniczyć trawienie ścieżek i lutowanie.

Koncepcja jest następująca: Trinekt Pro ma komunikować się z resztą systemu przez sieć 1-wire. Symuluje więc moduły DS2438 i w wartości VAD zwraca odczyty poszczególnych czujników.   Co 5 minut zasilany jest czujnik MQ135, który wymaga podgrzania. Po kolejnej minucie uruchamiany jest wentylator, który przepompowuje powietrze przez obudowę czujnika. Po 10 sekundach wentylator jest wyłączany i pomiarów dokonuje czujnik SHARPA i MQ135.  Po pomiarach rozpoczyna się oczekiwanie na kolejny cykl.

Każdy z elementów czujnika można omawiać i rozpatrywać osobno. Materiałów w sieci jest mnóstwo, ograniczę się więc do skrótowego przedstawienia:

1. Zasilanie

Jako że czujnik znajdować się ma w dużej odległości od zasilacza, a precyzyjne odczyty wartości czujnika GP2Y1010 wymaga stałego napięcia, jest ono stabilizowane przez step-up/regulator napięcia kupiony na alliexpress za 2 USD

dust stab

2. Czujnik pyłów SHARPa GP2Y1010

Czujnik ostatecznie kupiłem na allegro za ok 40 PLN. Z noty aplikacyjnej SHARPa odczytać można, że podłączyć go należy w następujący sposób:

1 (biały) - VCC - do zasilania (5V) przez rezystor 150 Ohm

2 - LED-GND - do GND, między VCC a GND wstawić kondensator 220uF

3 - LED - do wyjścia DO mikrokontrolera, który wysyłany będzie sygnał do rozpoczęcia pomiaru

4 - S-GND - do GND

5 - Vo - do wejścia AI mikrokontrolera, z którego będziemy dokonywać odczytów

6 - Vcc - zasilanie układu (5V)

dust GP2Y1010 2 

3. Czujnik MQ135

Czujnik ten można kupić za 1,5 USD na alliexpress.  Podłączenie jest zupełnie proste: VCC do zasilania (5v), GND to GND i AO to wyjście analogowe, które podłączyć trzeba z AI mikrokontrolera

dust MQ135 connected

4. Wentylator

...kupiony za 10 PLN na allegro.  Podłączony jest przez tranzystor P16NF06: lewa noga tranzystora podłączona jest do DO mikrokontrolera, prawa do GND.  Wentylator podłączony jest bezpośrednio do VCC (przed regulatorem) oraz go GND przez środkową nogę P16NF06.

dust wentylator

5. Testowanie

Oto, jak całość wyglądała po złożeniu na płytce testowej.  Na górze widoczny jest moduł komunikacji NRF24L01, z którego ostatecznie zrezygnowałem (choć działał), jako że implementacja kolejnego sposobu komunikowania się z RPi wydała mi się przerostem i pozostałem przy 1-wire.

dust test board

Jeszcze zdjęcie płytki prototypowej, obudowy przed złożeniem i czujnika na ścianie:

dust prototype dust case dust hanging 2 

6. Programowanie

Czas na kod.  By całość działała, konieczne jest dołączenie biblioteki OneWireHub.h

#include "OneWireHub.h"
#include "DS2438.h"  // Battery Monitor

const uint8_t internal_LED = 13;     // internal led just to signal when busy 
const unsigned long int stepTiming[] = {240000, 50000, 10000, 1000, 100};
char currentStep = 0;

//for SHARP dust sensor
const int dustMeasurePin = 1;           // Analog input 1 -> sensor pin 5
const int dustLedPower = 5;             // Power the measurement led of the sensor -> sensor pin 3
const int samplingTime = 270;           // time after led power-on signal

int samples[5];
int maxDustSample = 0;
int averageDustSample = 0;

//for MQ135
const int gasPower = 6;
const int gasMeasurePin = 0;
int gasVoltage = 0;

//for fan
int fanPower = 8;

//for timing
uint32_t lastAction = 0;         // time of the last action/step in ms from millis()
uint32_t currTime = 0;          // current time om millis()

//for 1-wire
const uint8_t OneWire_PIN   = 9;

auto hub = OneWireHub(OneWire_PIN);
auto sDustAverage = DS2438(DS2438::family_code, 0x0D, 0x01, 0x0D, 0x01, 0x0D, 0x01);  
auto sDustMaximum = DS2438(DS2438::family_code, 0x0D, 0x01, 0x0D, 0x01, 0x0D, 0x02);    
auto sGas = DS2438(DS2438::family_code, 0x0D, 0x01, 0x0D, 0x01, 0x0D, 0x03); 

void setup()
{
  Serial.begin(115200);
  Serial.println("Air quality sensor.  Config starts...");

  pinMode(internal_LED, OUTPUT);
  pinMode(dustLedPower, OUTPUT);
  pinMode(gasPower, OUTPUT);
  pinMode(fanPower, OUTPUT);

  //attach 1-wire simulated elements
  hub.attach(sDustAverage);
  hub.attach(sDustMaximum);
  hub.attach(sGas);

  Serial.println("Config done, beginning the cycle...");
  lastAction = millis() - stepTiming[currentStep];
}

void loop()
{
    // manage 1-wire communication
    hub.poll();
    
    currTime = millis();
    
    //check if it is time to move on to the next step
    if (currTime > (lastAction + stepTiming[currentStep])) {
        
        lastAction = currTime;
        currentStep++;

        switch (currentStep) {
          case 1:  // power up the MQ135 sensor, let it heat!
            digitalWrite(gasPower, HIGH);
            Serial.println("1. MQ135 switched on and heating!");
            break;
          case 2: // power up the fan for the air flow
            digitalWrite(fanPower, HIGH);
            Serial.println("2. Fan is up, the air is flowing");
            break;
          case 3: // power down the fan 
            digitalWrite(fanPower, LOW);
            Serial.println("3. Fan is off");
            break;   
          case 4:  // do the measurements!
            digitalWrite(internal_LED, HIGH);             //show the world we are measurring!
            Serial.println("4. Start the measurements...");
            
            // measure the dust particles
            maxDustSample = 0;
            
            for (int round = 0; round < 3; round++) {
                
              digitalWrite(dustLedPower, LOW);              // power on the LED of dust sensor
              delayMicroseconds(samplingTime);              // wait out the time according to specs
              samples[round] = analogRead(dustMeasurePin);  // read the analog input
              
              digitalWrite(dustLedPower,HIGH);              // turn the LED off
              
              Serial.print("-- raw signal values (0-1023): ");
              Serial.println(samples[round]);
              
              if (samples[round] > maxDustSample) maxDustSample = samples[round]; // find the maximum measurment
              delay(400);   
            }
            averageDustSample = 0;
            for (int i = 0; i < 3; i++) {
              averageDustSample += samples[i];
            }
            averageDustSample = (int)(averageDustSample / 3);
            
            Serial.print("--> the maximum sample from dust sensor: ");
            Serial.println(maxDustSample);
            Serial.print("--> the average sample from dust sensor: ");
            Serial.println(averageDustSample);

            // measure the gases with MQ135
            gasVoltage = analogRead(gasMeasurePin);
            Serial.print("--the reading of MQ135: ");
            Serial.println(gasVoltage);

            //power off the MQ135
            digitalWrite(gasPower, LOW);
            Serial.println("5. MQ135 switched off");
            digitalWrite(internal_LED, LOW);

            //set 1-wire values
            sDustAverage.setVolt(averageDustSample);
            sDustMaximum.setVolt(maxDustSample);
            sGas.setVolt(gasVoltage);

            currentStep = 0;
            //Finish the round, reset the step counter
            Serial.print(F("0. Beginging to wait "));
            Serial.print(stepTiming[currentStep]);
            Serial.println(F("ms."));
            
            break;
        }  // end of switch
    }  //end of if (time checking)
}  // end of loop()

 

W sieci znalazłem kilka opracowań o pomiarze zapylenia za pomocą czujnika SHARPa.  Sam przetestowałem najróżniejsze czasy oczekiwania po włączeniu diody i wielokrotne pomiary.  Ostatecznie jednak ustawienia typowe, tj. ze zwłoką 230-280 ms dają w miarę stałe odczyty.  Postanowiłem też przesyłać dane maksymalne i średnie, by sprawdzić, które będą właściwsze.

Co do skomplikowanych formuł przeliczania wyników - odczytów napięcia - na konkretne jednostki (np. ppm), stwierdziłem, że na potrzeby tego projektu nie ma to sensu.  Nie potrzebuję znać absolutnych wartości zapylenia, a jedynie reagować na nagłe wzrosty odczytów. 

Jako że moje RPi i sieć 1-wire funkcjonuje w świecie OWFS, przykładowy kod PHP do odczytu danych wygenerowanych przez czujnik może wyglądać następująco:

<?php
        $addresses = ["/mnt/1wire/26.0D010D010D01/VAD", 
                      "/mnt/1wire/26.0D010D010D02/VAD",
                      "/mnt/1wire/26.0D010D010D03/VAD"];

        for ($i = 0; $i < sizeof($addresses); $i++) {
                $file = fopen($addresses[$i], "r");
                if ($file) {
                        $sValue = fgets($file);
                        echo ($sValue * 100).";";
                }
                else {
                        echo "ERROR;";
                }
        }

 

7. Pomiary i wnioski

Aby pokazać, jak działają czujniki, najlepiej spojrzeć na wykresy:

dust visu3  mq1351 

Czujnik SHARPa GP2Y1010 zwraca całkiem stabilne odczyty i może być wykorzystany do wyłapywania nagłych wzrostów zapylenia.  Mam jednak wrażenie, że sam pomiar pyłu nie jest wystarczający, by wychwycić momenty zadymiania okolic przez sąsiadów. Nie wiem, jaka substancja powoduje, że powietrze jest kwaśne i ostre.  Czy jest to tlenek siarki?  Jak go mierzyć?

MQ135, natomiast, to czujnik do zupełnie innych zastosowań.  Znawcy tematu mogą powiedzieć, że to sprawa oczywista, ale dla mnie specyfikacje poszczególnych czujników nie są na tyle jednoznaczne, by nie próbować.  Jeśli ktoś ma pomysł, który z czujników z serii MQ lub AQ byłby lepszy, chętnie przygarnę taką poradę.

7. Implementacja po stronie PLC

Dla dopełnienia obrazu, oto przykładowy kod do PLC, który można wykorzystać do reagowania na wzrost zapylenia:

VAR
	rTrigger	: R_TRIG;

	dSensor	        : DWORD;
	dData		: ARRAY[0..59] OF WORD;
	dSize	        : BYTE;
	dDataCounter    : INT;
	dAverage	: REAL;
	dAcceptedDeviation	: BYTE := 50;
	dAlarm		: BOOL := FALSE;
	dFilled		: BOOL := FALSE;  (* flag for first table filling *)

	i : BYTE;

END_VAR

rTrigger(CLK :=  HTTPComm.readSuccess);

IF rTrigger.Q THEN (* data received *)
	dSize := SIZEOF(dData) / 2;  (*calculate array size dynamically out of laziness*)

	IF (dSensor < dAverage + dAcceptedDeviation) OR (NOT dFilled) THEN 
		dAlarm := FALSE;
		dData[dDataCounter] := DWORD_TO_WORD(dSensor);

		(* calculating average*)
		dAverage := dData[0];

		FOR i := 1 TO (dSize - 1) DO
			dAverage := dAverage + dData[i];
		END_FOR;

		dAverage := dAverage / dSize;

		(* increment the array index, INC1 from OSCAT library *)
		dDataCounter := INC1(dDataCounter, dSize);

		IF NOT dFilled THEN
			dFilled :=  (dDataCounter = (dSize - 1));
		END_IF;
	ELSE
		dAlarm := TRUE;
	END_IF;

END_IF;