Pomocí Arduina a několika externích shieldů, se mi povedlo vytvořit jednoduchý orientační LCD měřič kvality ovzduší v domě, s ovládáním větráku. Jedná se o zapojení desky Arduino Nano, RTC modulu, SD shieldu, LCD displeje, měřiče CO2, čidla teploty DHT11, relé a několika dalších elektronických komponentů. Můžete tak monitorovat kvalitu ovzduší, zároveň logovat data na SD a při zapojení větrání na výstup relé i spouštět větrák. Pro větší bezpečnost však doporučuji pomocí relé ovládat jen stykač na bezpečné napětí 12-24 V.
Jak vidíte na obrázku, naměřené hodnoty se na LCD střídají s hodinami a kalendářem. Na displeji se po spuštění jednou objeví hlášení o případné odpojené/připojené SD kartě, v dalších krocích se pak střídá v intervalu 30s (lze změnit v programu) výpis naměřených hodnot vlhkost, teplota, koncentrace plynů (ppm) a informace o stavu relé větráku s kalendářem a hodinami. Ty se obnovují v pravidelném intervalu 1s. Uložení dat proběhne vždy po výpisu naměřených údajů do souboru MERENI.txt. S těmito daty lze pak pracovat třeba v Excelu.
Jelikož se data ukládají do řádku vždy ve stejném formátu, využil jsem v Excelu pro jejich zpracování do tabulky a grafu funkci text do sloupce. Nezapomeňte si ohlídat formátování sloupců, kde text formátujte jako text a čísla jako čísla. Menší zádrhel nastal, když se mi některé naměřené údaje zpracovaly jako datum v nesmyslném formátu. Prý za to může rozdíl v používání čárky a tečky v desetinném místě, kde některé země používají pro oddělení desetinného místa tečku a jiné čárku. A Excel si desetinou tečku vyhodnotil jako datum.
Popíšu, možná pro někoho nudnou část a kdo chce, přeskočte ji, jak dostat naměřené údaje z textového souboru do tabulky pro další zpracování. Při exportu textu do sloupce jsem postupoval takto: nejprve si označil všechny buňky v tabulkovém editoru, zformátoval jako text. Následně vložil zkopírovaný text ze souboru MERENI.txt a použil záložku TEXT DO SLOUPCE, dále zatrhl pro oddělení jednotlivých sloupečků ODDĚLOVAČ a na DALŠÍ kartě, pak použil možnost MEZERA, na DALŠÍ kartě zvolil možnosti naformátování nově vznikajících sloupečků. Text formátujte jako text a čísla jako OBECNÉ a UPŘESNIT, kde zatrhnete možnost, jak se budou oddělovat desetinná místa ve sloupečku. Tedy zvolte pro ODDĚLOVAČ DESETINNÍCH MÍST namísto čárky TEČKU. Anebo pokud upravíte program v Arduinu a použijete v ukládané zprávě na SD místo teček čárky, můžete nechat v oddělovači desetinných míst předem definovanou možnost čárek.
Pokud jste postupovali správně, objeví se vám v editoru několik sloupečků s textem a naměřenými údaji. Podotýkám, editor stále pracuje s údaji jako s obecným textem. Pro jednotlivé sloupečky s čísly tedy změňte formát z OBECNÉHO TEXTU na ČÍSLA, čas jako ČAS a datum jako DATUM. Dodržíte-li v editoru jednotlivé formáty, je pak tvorba grafu s několika sty naměřenými hodnotami otázkou pár kliků myší.
A teď tedy k samotné konstrukci. Vše je uloženo v plastové krabičče typ Z-5B PS BLACK. Do přední strany je vyříznut obdélník cca 25x75mm a o stejných rozměrech, jsem ze starého obalu na CD vyříznul i plexisklo před LCD. Vše jsem zafixoval vteřinovým lepidlem a pro zakrytí míst se zaschlým lepidlem začistil okraje černou lihovkou. Z boku krabičky byla vyvrtána díra zhruba 30mm pro čidlo CO2, velká tak, aby se hrana čidla nedotýkala okrajů plastové díry, ba naopak aby byl zajištěn i odstup, to kvůli teplotě čidla. Díra je překryta tvarovanou mřížkou ze starého reproduktoru. Jelikož pro mě bylo nepřekonalnou překážkou, porazit svojí lenost, přepájet krátké kabely od čidla teploty, musel jsem čidlo teploty přilepit na teplonevodivém povrchu (kus kartonu) a zespodu pod čidlem vyrobit, opět z plexiskla, průhledný tepelný štít o rozměrech 25x35mm. Aby byly hodnoty co nejméně ovlivňovány teplem z CO2 snímače. I přes tato opatření bylo nutné do programu umístit korekci o 5 stupňů, aby hodnoty seděly s jiným digitálním teploměrem. ( int korekce = 5; ) Poučení pro příště, umístím čidlo z druhé strany krabičky, mimo zdroj tepla.
Zespodu je na chladiči, jediný co mi ležel v šuplíku, připevněn stabilizátor napětí 5V/2A LM7805. Ten je doplňen o přídavný chladič. Místo toho neforemného kusu plechu, zapřemýšlím o něčem elegantnějším ve tvaru krabičky. Vedle chladiče se dále nachází i vypínač a výstup z jednokanálového relé, pro ovládání větráku a USB Arduina pro nahrávání programu. Pravá strana krabičky slouží ke vkládání micro SD karty. Uvnitř jsou uloženy hodiny reálného času DS1307, Arduino Nano, I2C LCD 16x2, snímač plynů MQ135, jednokanálové relé, plošné spoje pro I2C sběrnici, + - 5V sběrnice, destička stabilizátoru napětí se dvema kondenzáty 10uF pro frekvenční stabilitu LM7805 a dioda proti přepólováni s vypínačem.
Jednotlivé moduly drží přilepeny na malých plastových válečcích s předvrtaným závitem pro šroubky. Já zrecykloval staré plastové kryty z elektronického odpadu např. kryty z ovladačů tv, videa apod. Ze kterých jsem vylámal a zkrátil na potřebnou délku ty plastové výstupky, díky kterým drží snad každý plastový kryt rovnou i se šroubky a zakápnul kapkou vteřinového lepidla.
Pro napájení využívám starou nabíječku 12V 2A z nějaké ruční mikrovrtačky. Lze použít však i jiný DC zdroj. Stabilizátor má rozsah od 8V někam ke 40V. Ovšem počítejte i s nárustem odpadního tepla na chladiči a je nutno ho správně dimenzovat. A pro napájení hodin po odpojení použijte NOVOU! kvalitní knoflíkovou baterii CR2032.
Před zavřením krytu nastavte i citlivost čidla plynů, při kterém má zareagovat. Já ten trimr na čidle nastavil docela na nízkou hranici, tak že stačí dýchnout v okolí čidla a ono zareaguje. Operační zesilovač v čidle se tak překlopí, pošle impuls do Arduina, to zareaguje sepnutím relé větráku. A je vypsána hláška na LCD – VĚTRÁNÍ START. Při hranici plynů pod nastavenou mez, vypisuje LCD hlášku VĚTRÁNÍ STOP.
Případnou korekci času provedete nahráním programu po USB do Arduina. V něm smažte dvojité lomítko před tímto řádkem:
// setDS1307time(20,57,15,4,31,5,18);
V závorce pak upravte hodnoty čísel v pořadí:
sekundy, minuty, hodiny, den v týdnu (1 nedele, 7sobota), datum, měsíc, rok
Tedy v tomto případě: 2018 31.5 CT 15:57:20
Po nahrání programu do Arduina, opět vložte před řádek setDS1307time(20,57,15,4,31,5,18);
dvojité lomítko // a znovu nahrajte program do Arduina. Kdyby jste tak neučinili, přepisoval by se pokaždé čas na tato nastavená čísla, po každém přepnutí vypínače.
K samotnému programu. Na začátku jsou deklarovány proměnné, naimplementovány knihovny. Dále několik smyček, ve kterých je postupně zahájena komunikace s LCD a čidly, zkontrolována činnost SD karty, načten a převeden čas z binárního kódu do decimálního, vytvořena zpráva s časem a kalendářem na LCD a v pravidelných sekundových intervalech se čas po vteřinách přepisuje na LCD
delay(1000);
Pomocí funkce vnitřního časovače se v měnitelném intervalu ze začátku programu vytvoří zprávy s naměřenými hodnotami pro LCD a SD kartu.
unsigned long interval = 30000;
if(currentMillis - previousMillis > interval
Pro ochranu proti přetečení časovače slouží tento řádek:
if(currentMillis > 4294967000){
previousMillis = 0; //ochrana proti preteceni
}
Program dále sleduje změnu napětí na vstupu ze snímače CO2 při překročení hladiny CO2 a na to zareaguje sepnutim relé větrání a vypsáním hlášek na LCD o stavu relé. O této změně stavu se i uloží hlášení do textového dokumentu na SD kartu - Prekrocena hranice ppm. (viz. Soubor MERENI.txt)
Pro změnu názvů dnů v týdnu tak učiňte ve funkci switch.
switch(dayOfWeek){
case 1:
lcd.print("NE");
break;
case 2:
lcd.print("PO");
break;
case 3:
lcd.print("UT");
break;
case 4:
lcd.print("ST");
break;
case 5:
lcd.print("ST");
break;
case 6:
lcd.print("PA");
break;
case 7:
lcd.print("SO");
break;
}
Nezapomeňte stáhnout i knihovny pro MQ135, DS1307, DHT11 a LCD knihovnu pro I2C rozhraní.
Pro úplnost dodávám i schéma zapojení.
Celý kód:
unsigned long previousMillis = 0; //cas posledni akce unsigned long interval = 30000; //interval zmeny zápisu na SD byte cteni; byte second, minute, hour, dayOfWeek, dayOfMonth, month, year; byte vstup = 2; int rele = 6; const int sd_CS = 10; int korekce = 5; // korekce teplotniho cidla stupne Celsia // knihovny #include <MQ135.h> #define pinA A0 #define pinD 2 MQ135 senzorMQ = MQ135(pinA); #include "DHT.h" // nastavení čísla pinu s připojeným DHT senzorem #define pinDHT 5 #define typDHT11 DHT11 // inicializace DHT senzoru s nastaveným pinem a typem senzoru #include "Wire.h" #include <SPI.h> #include <SD.h> #define DS1307_I2C_ADDRESS 0x68 #include <LiquidCrystal_I2C.h> // nastavení adresy I2C (0x27 v mém případě), // a dále počtu znaků a řádků LCD, zde 16x2 LiquidCrystal_I2C lcd(0x27, 16, 2); // převod z decimálního na binární číslo DHT mojeDHT(pinDHT, typDHT11); byte decToBcd(byte val){ return( (val/10*16) + (val%10) ); } // převod z binárního na decimální číslo byte bcdToDec(byte val){ return( (val/16*10) + (val%16) ); } void setup(){ digitalWrite(rele, LOW); pinMode(rele, OUTPUT); pinMode(vstup,INPUT); Wire.begin(); Serial.begin(9600); // zapnutí komunikace s teploměrem DHT mojeDHT.begin(); lcd.begin(); // zapnutí podsvícení lcd.backlight(); // DS1307 sekundy, minuty, hodiny, dny (1 nedele, 7sobota), datum, měsíc, rok // cas nastav zde v setDS1307 smazat zavorku // setDS1307time(20,57,15,4,31,5,18); // kontrola připojení SD if (SD.begin(sd_CS)) { lcd.clear(); lcd.setCursor ( 0, 0 ); lcd.print("SD pripojena."); delay(2000); } else { (SD.begin(sd_CS)); lcd.clear(); lcd.setCursor ( 0, 0 ); lcd.print("SD nepripojena, "); lcd.setCursor ( 0, 1 ); lcd.print(" vlozte kartu!"); delay(2000); return; } } void setDS1307time(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year){ // nastaveni data a casu do DS1307 Wire.beginTransmission(DS1307_I2C_ADDRESS); Wire.write(0);// nastav dalsi vstup ke spusteni registru Wire.write(decToBcd(second)); // nastav sekundy Wire.write(decToBcd(minute)); // nastav minuty Wire.write(decToBcd(hour)); // nastav hodiny Wire.write(decToBcd(dayOfWeek)); // nastav den v týdnu (1=Neděle, 7=Sobota) Wire.write(decToBcd(dayOfMonth)); // nastav datum (1 až 31) Wire.write(decToBcd(month)); // nastav měsíc Wire.write(decToBcd(year)); // nastav rok (0 až 99) Wire.endTransmission(); } void readDS1307time(byte *second, byte *minute, byte *hour, byte *dayOfWeek, byte *dayOfMonth, byte *month, byte *year){ Wire.beginTransmission(DS1307_I2C_ADDRESS); Wire.write(0); // nastav registr DC1307 na 00h Wire.endTransmission(); //žádost o sedm bajtů dat z DS1307 od čísla 00h Wire.requestFrom(DS1307_I2C_ADDRESS, 7); *second = bcdToDec(Wire.read() & 0x7f); *minute = bcdToDec(Wire.read()); *hour = bcdToDec(Wire.read() & 0x3f); *dayOfWeek = bcdToDec(Wire.read()); *dayOfMonth = bcdToDec(Wire.read()); *month = bcdToDec(Wire.read()); *year = bcdToDec(Wire.read()); } void displayTime(){ // načti data z DS1307 readDS1307time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year); // odešli na lcd lcd.clear(); lcd.setCursor ( 0, 0 ); // převod hodin na decimální číslo lcd.print(hour, DEC); lcd.print(":"); // pokud je číslo menší než 10 vepiš nulu před číslo if (minute<10){ lcd.print("0"); } // převod minut na decimální číslo lcd.print(minute, DEC); lcd.print(":"); // pokud je číslo menší než 10 vepiš nulu před číslo if (second<10){ lcd.print("0"); } lcd.print(second, DEC); // přesun kurzoru na druhý řádek lcd.setCursor ( 0, 1 ); lcd.print(dayOfMonth, DEC); lcd.print("/"); lcd.print(month, DEC); lcd.print("/"); lcd.print("20"); lcd.print(year, DEC); lcd.print(" "); // dny v týdnu switch(dayOfWeek){ case 1: lcd.print("NE"); break; case 2: lcd.print("PO"); break; case 3: lcd.print("UT"); break; case 4: lcd.print("ST"); break; case 5: lcd.print("ST"); break; case 6: lcd.print("PA"); break; case 7: lcd.print("SO"); break; } } void loop(){ // vytvoření zprávy Datum a cas String datumCas(hour, DEC); datumCas += (":"); if (minute<10){ datumCas += String("0"); } datumCas += String(minute, DEC); datumCas += (":"); if (second<10){ datumCas += String("0"); } datumCas += String(second, DEC); datumCas += (" "); datumCas += String(dayOfMonth, DEC); datumCas += ("/"); datumCas += String(month, DEC); datumCas += ("/"); datumCas += ("20"); datumCas += String(year, DEC); cteni = digitalRead(vstup); float ppm = senzorMQ.getPPM(); float tep = mojeDHT.readTemperature(); float vlh = mojeDHT.readHumidity(); unsigned long currentMillis = millis(); //podivam se na hodiny casovace v Arduinu if(currentMillis > 4294967000){ previousMillis = 0; //ochrana proti preteceni } if(currentMillis - previousMillis > interval) { // uložte, kdy jste naposledy zapsali na SD previousMillis = currentMillis; // odešli na lcd lcd.clear(); lcd.setCursor ( 0, 0 ); lcd.print("Rel. vlhkost:"); lcd.setCursor ( 0, 1 ); String Vlhkost = String(vlh) + " Procent" ; lcd.print(Vlhkost); delay(2000); lcd.clear(); lcd.setCursor ( 0, 0 ); lcd.print("Teplota:"); lcd.setCursor ( 0, 1 ); String Teplota = String(tep-korekce) + " Celsia" ; lcd.print(Teplota); delay(2000); lcd.clear(); lcd.setCursor ( 0, 0 ); lcd.print("Konc. plynu:"); lcd.setCursor ( 0, 1 ); lcd.print(ppm); lcd.print(" ppm"); delay(2000); // vytvorení zprávy pro zápis na SD String dataString(" Datum a Cas: "); dataString += String(datumCas); dataString += (" Teplota: "); dataString += String(tep-korekce) + (" °C /"); dataString += (" Vlhkost: "); dataString += String(vlh) +( " % /"); dataString += (" Hodnota skodlivin: "); dataString += String(ppm) + (" ppm "); if(vstup, !cteni) { dataString += (" Překročení hodnot! "); } File zapisDat = SD.open("mereni.txt", FILE_WRITE); if (zapisDat){ zapisDat.println(dataString); zapisDat.close(); } // pokud vyšle čidlo CO2 impulz na vstup čtení, zapiš na SD hlášku o překročení hranice a sepni relé větráku if(vstup, !cteni) { lcd.clear(); lcd.setCursor ( 0, 0 ); lcd.print("VETRANI START"); lcd.setCursor ( 0, 1 ); lcd.print("Hranice ppm!"); digitalWrite(rele, HIGH); delay(2000); String dataString = " Prekrocena hranice ppm. "; File zapisDat = SD.open("mereni.txt", FILE_WRITE); if (zapisDat){ zapisDat.println(dataString); zapisDat.close(); } } else { digitalWrite(rele, LOW); lcd.clear(); lcd.setCursor ( 0, 0 ); lcd.print("VETRANI STOP"); lcd.setCursor ( 0, 1 ); lcd.print("Hranice ppm OK."); delay (2000); } } else { displayTime(); // kazdou sekundu se vyšle po sériové lince na displej aktualni cas delay(1000); } }