Csapadékmérés (ATtiny85-el is…)

Tartalom:

  • Billenőtartályos csapadékmérő működése, csapadékmennyiség számítási algoritmusa
  • Megvalósítás Arduino nano-val és 16×2 karakteres LCD kijelzővel
  • Megvalósítás ATtiny85 alaplappal
  • Megvalósítás ATtiny85-el és OLED kijelzővel

Az automata locsolórendszerem egy ideje már tökéletesen működik, mindössze az okoz kényelmetlenséget, hogy minden nap figyelni kell, mert ha esik az eső, akkor is locsol. Eleve tervbe volt véve, hogy összekötöm egy csapadékmérővel, és ezzel akadályozom meg a víz pocsékolást! Most elérkezett az idő.

Törni kezdtem a fejem, hogyan is használjam fel a csapadékmérőt az automata locsoló vezérlésében. Nézzük meg először, hogyan is működik a csapadékmérő. Aliexpress-en vettem kb. 5000 Ft-ért. Még abban az időben, amikor nem kellett a postai vámkezelési díjat és ÁFA-t fizetni, ma már legalább 7-8000Ft-ba kerülne. Pofon egyszerű szerkezet:

Két darabra szedhető szét. Belül egy billenő szerkezet található. Amikor felűről befolyik a víz, folyamatosan telik meg vízzel egy kis tartály, és egy adott víz mennyiségnél átbillen. Ekkor a másik oldali tartály telik, és újra átbillen. A víz alul kifolyik! Az átbillenések során egy mágnes halad el egy reed kontaktus előtt, ami egy rövid időre kontaktus jelet ad. Ezt lehet érzékelni. Sajnos a leírásban semmit nem találtam a mért csapadék mennyiségről, ezért magamnak kellett megfejteni.

Rögvest írtam egy egyszerű programot, ami megszámolta a kontaktus jelek számát. Aztán vettem egy 1 literes mérőedényt, beletöltöttem pontosan 1 liter vizet, és egy kis csövön keresztül kivezettem a vizet a csapadékmérőbe. Úgy csináltam, mint a benzinlopóval, megszívtam a cső végét, és szép lassan folyni kezdett a víz:

A mérésre a mosogatónkban került sor. A víz a vékony csövön kb. 20 perc alatt folyt le. Ez elég tempós az esőhöz képest, de azért elég lassú ahhoz, hogy a billenési idők ne legyenek nagyon mások mint az a szokásos csendes esőzés esetén várható. Összesen 586 kontaktus jelet számoltam meg, amíg lefolyt az összes víz. Természetesen a mérőedény aljáról is kicsurgattam minden csepp vizet, mert a cső nem szívta ki teljesen. Abban nem lehetek biztos, hogy a mérőedény teljesen pontos lenne. Olcsó műanyag mérőpohár, de számomra elfogadható egy kb. 5%-os pontosság, azt pedig biztosan tudta teljesíteni. Az 586 billenés szerintem nem véletlenül van ennyire közel a 600-hoz, így később fel is kerekítettem!

Természetesen a csapadék mennyiséget nem literben, hanem mm-ben szokás megadni. Nem véletlenül, hiszen a felület kiesik a képletből, így nekem is át kell számolnom az 1 litert mm-re. Ehhez ismerni kell a csapadékmérő alapterületét. Ehhez szerencsére csak vonalzó kell, 5cm x 11 cm. Tehát a felület 55 cm2. 1 liter víz ekkora alapterületen 1000cm3/55cm2=18,18cm magas vízoszlopot jelent, tehát az egy liter víz 181,8mm csapadéknak felelt meg. Innen már egyszerű, mert ha a 181,8-at elosztom a billenésem számával, akkor megkapom, hogy mennyi csapadéktól billen egyet a szerkezet. 181,8/600=0,3mm. Kerekítettem kicsit, de majdnem teljesen pontosan ennyi. 20-30mm csapadék egy óra alatt már elönti a városokat, így sejteni lehet, hogy nem fog gyakran átbillenni a mérőedény a szerkezetben.

Most jön a következő probléma! Hogyan lehetne értelmesen kijelezni a lehullott csapadékmennyiséget, hogy az használható adat legyen? Általában az eső vagy „csendesen”, de sokáig esik, vagy hirtelen zúdul le az égből zivatar formájában. Mindkét esetben a lehullott mennyiségtől függ, hogy milyen mélyen nedvesíti át a talajt. Számomra ez a fontos, hiszen a növények gyökeréhez kell elérni a víznek. Tapasztalatból tudom, hogy egy kiadós eső után a föld felszíne napsütésben szinte azonnal felszárad, de ha egy ásóval felfordítom a talajt, lenn a gyökereknél még bőven van víz. Ezért várhatóan engem az fog érdekelni, hogy mennyi csapadék esett összesen az elmúlt néhány napban. Ha napokig nem esik semmi, akkor locsolni kell. Ha tegnap jó sok esett, akkor még nem kell locsolni. Immár mérőeszközöm is van rá, hogy kísérletezzek, mert pontos receptet sehol nem találtam arra a kérdésre, hogy hány milliméter csapadék kell pl. a káposztának vagy a paprikának naponta. Szerencsére a locsolás által kijuttatott víz mennyiséget át tudom számolni ekvivalens csapadákmennyiségbe, hiszen ehhez csak a szivattyú által kiemelt víz mennyiséget el kell osztani a meglocsolt terület nagyságával. Így azért némi tudományt is belevihetünk a dologba, de azért leginkább a tapasztalatban bízok. Viszont megosztanék egy konkrét tapasztalati adatot ezzel kapcsolatban. Nálunk a kút vize kb. 120cm-t csökken egy locsoláskor. ekkor már nem is tudok tovább locsolni, míg újra meg nem telik, de ennyi víz elég is egy teljes locsoláshoz nagy szárazságban is. A kútgyűrű átmérője 80cm. Tehát a locsolásra felhasznált víz 40x40x3,14×120=602880, ami kb. 600 liter víznek felel meg. Az ágyások teljes felülete nálunk kb. 20m*5m=100nm. Vagyis az a kérdés, hány mm csapadéknak felel meg 600l víz 100nm felületen. Azonos dimenzióba kell hozni a mértékegységeket! 100nm = 1.000.000cm2, 600l pedig 600000cm3! Így már el lehet osztani a térfogatot az alapterülettel, azaz 600.000/1.000.000=0,6cm, ami viszont 6mm. Tehát a napi egy locsolásom kb. 6mm esőnek felel meg. Sajnos ez csak nagyon irányadó szám, mert vannak területek, amiket sokkal intenzívebben locsolok, pl. paprika és paradicsom nagy víz igényű, míg a krumplit csak néhány naponta locsolom meg. De már ez az adat is valami, lehet elképzelésünk, hogy mit is kell figyelni majd a jövőben a csapadékmérővel.

Azért azt már sejtem, hogy nem az fog érdekelni, éppen mennyi csapadék esett, hanem az, hogy mennyi csapadék esett egy adott időn belül. Tehát e mérőműszeremnek is valamilyen időtényezővel kell rendelkeznie. Mivel nem tudom, hogy mi lesz az igazán jól használható adat, három mértékegységet is kiválasztottam:

  • Mennyi csapadék hullott az elmúlt 24 órában
  • Mennyi csapadék hullott az elmúlt 3 napban
  • Mennyi csapadék hullott az elmúlt 2 hétben.

Ebből a középső nem teljesen hasra ütésre keletkezett! Azt tapasztalatuk, hogy egy kiadós eső után 2-3 napig nem kell locsolni. Tehát várhatóan az lesz a vezérlő elv, hogy ha az elmúlt egy napban jó sok eső esik (hogy ez mennyi azt még nem tudom, de kb. 3-6mm), akkor leállítom az automata locsolást, és ha három napja nem esett csapadék akkor pedig újra indítom. A két hét leginkább érdeklődésből került be a rendszerbe, mert azt hallottam, hogy Magyarországon nyáron átlagosan 12 naponta esik eső. Állítólag a globális felmelegedés hatásai miatt ez az időszak egyre növekszik, és a számítások szerint 2050-re már 15 nap fog eltelni szárazságban átlagosan. Nem hinném, hogy megérem, de elkezdek figyelni! A csapadékmérőt gyerekeim fogják örökölni (ha még működik akkor), és ezzel lesz lehetőségük ellenőrizni a hírek valódiságát!

Ha a fenti elvek alapján kijelzett adatokra van szükségem, akkor nem érdekes a műszerben a pillanatnyi pontos idő, de azt figyelnie kell, hogy mennyi idő telt el. Utóbbit az Arduino pontosan el tudja látni, hiszen kvarc kristály adja az órajelet. Ezen kívül valamilyen tároló elemre lesz még szükségem, ami nem felejti el a tárolt adatot akkor sem, ha áramszünet van a lakásban. Nagy viharokban nálunk gyakran van rövid, néhány perces áramszünet, így pont akkor felejtené el a mért adatokat, amikor kellenének. Tárolhatnám az adatokat F-RAM-ban is, vagy elemmel védett SRAM-ban, de érdemes megvizsgálni, hogy alkalmas lehet-e erre az ATmega328 beépített belső EEPROM-ja. Erről azt tudjuk, hogy kb. 100.000 újraírást bír ki, kiolvasni tetszőleges számban lehet. A 100.000 írási ciklus egy adott cellára vonatkozik (remélem), így azt kell megnéznem, ha bármelyik konkrét cellát sűrűn írogatom, akkor az mennyi idő alatt éri el a százezer ciklust. Ha ez több év, netán 10-20 év, akkor a belső eeprom alkalmas lehet adattárolásra. Tegyük fel, hogy csak átbillenésenként tárolok! Hány átbillenésre kell számítanom? Szereztem egy táblázatot az országos meteorológiai központ weboldaláról:

Ebből a táblázatból az utolsó előtti sor lényeges számunkra, ami azt mondja meg, hogy Budapesten (Pest közelében lakom), egy év alatt átlagosan 593mm csapadék esik. Mivel egy átbillenés 0,3mm csapadéknak felel meg, várhatóan évente 593/0,3=1976 átbillenés fog történni. Ez átlagosan havi 167, és napi 5-6 billenés. Én ezt nagyvonalúan felkerekítettem évi 5000 billenésre (hátha jövőre már trópusi éghajlat lesz és nem elsivatagosodik Magyarország). Ha az átbillenéseket mindig ugyanabba az egy cellába írom, akkor is ez 20 év működési idő. Eldőlt, eeprom-ot fogok használni.

De hogyan határozzam meg az elmúlt utolsó 24 óra, 3 nap és két hét csapadékmennyiségét? Nem triviális a feladat, mert nem konkrét időszakokat kell figyelni. Ha éjféltől éjfélig kellene meghatározni, az egyszerű, hiszen éjfélkor lenullázom a számlálót, és egész nap összegzek. De én nem ezt szeretném, hanem bármikor ránézek a kijelzőre, az utolsó 24 órát szeretném látni, legyen ez a pillanat reggel, vagy éppen este. Ez sem túl bonyolult! Azt találtam ki, hogy mindig egy óra ideig gyűjtöm az adatokat, és az eredményt tárolom. Egy teljes naphoz ezért 24 memória cellára lesz szükségem. Egy óra átbillenéseinek száma még irdatlan nagy viharban sem lehet 256-nál nagyobb, így elegendő egy byte! Egy adott cella tartalma 24 óra múlva már nem lényeges, elveszíthető, felülírható. Legegyszerűbb úgy elképzelni, hogy a 24 memória cella egy cső, aminek az egyik végén beléptetem óránként az adatokat, és eközben a már bent lévő adatok lépnek egyet a tároló sor vége felé, majd a végén kiesnek a csőből. Így az utolsó 24 óra csapadékmennyisége a 24 tárolócella mindenkori összege. Természetesen nekem nem 24 óra kell, hanem 2 hét, vagyis 14*24=336 db tárolócella. Ez ezt jelenti, hogy óránként léptetnem kell az összes cella tartalmát egy-el hátrafelé, vagyis óránként újra kell írnom az összes cellát. Számoljunk csak megint: egy adott cellát egy nap 24-szer írom, az egy év alatt  8760 írás, ami 100000/8760=11 év működési idő. Ráadásul  még évente 2-5000 billenéskor is írok ezekbe a cellákba, és ez a kettő együtt már akár kevesebb mint 7-8 év működési idő is lehet. Persze a rendszer üzemeltetési költségébe elvileg belefér, hogy 7-8 évente veszek egy új Arduino nano áramkört, de mi lesz, ha akkor már nem is létezik Arduino nano?! Így átgondoltam, és egy eeprom kímélőbb megoldás született. Mivel a teljes 1kbyte eeprom-ból csak 336 byte-ot használok fel, van még bőven hely. Legyen egy óra tárolásához 2 byte fenntartva. Ettől kezdve a 2byte memória területet adatrekordnak fogom hívni, mert itt már két összetartozó adatról lesz szó. Az egyik byte egy jelző byte. A másik pedig a billenések száma. A jelző byte-ba 1-et írok, ha az adott adatrekord éppen  aktív, vagyis abba kell gyűjteni a billenések számát. Ha letelt az egy óra, akkor az adott aktív adatrekord jelző byte-ját 0-ra írom, és veszem a következő adatrekordot, és annak a jelzőjét állítom 1—re és abba gyűjtök tovább. Nem írom újra az összes byte-ot minden órában, mert nem léptetem az adatokat, mindössze a jelzés vándorol 0-tól a 335-ös rekordig. Így viszont egy konkrét cellát kétszer írok 14 naponta. Az adatrekordok billenésszámláló byte-ját meg csak akkor, ha átbillenés történik. A jelző byte írása okozza a nagyobb terhelést, 14 naponta két írás, egy év alatt az 52, életem végéig működni fog!

És íme a forrás:

/***********************************************************************************************************************
 * Ez a program egy csapadékmérő átbillenéseit gyűjti óránkénti bontásban, azaz minden órában egy másik tárolóba
 * gyűjti az abban az órában történt átbillenéseket. A kijelző mindíg az utolsó 24 óra, 3nap és 14nap adatait 
 * jelzi ki. Nem kell óra, csak óránként kell váltani tárolót, és a tárolók 14 naponta felülírhatók. Így összesen
 * 14*24=336 tároló rekeszre van szükség.
 * Az utolsó két hét adatait gyűjtöm az eeprom-ban két byte-on óránkénti bontásban. Ez összesen 14*24=336 adatrekord.
 * Minden adatrekord két byte-ból áll, egyik jelzi, hogy melyik az aktuálisan gyűjtésre használt adatrekord.
 * A másik byte-ban gyűjtöm az átbillenések számát. A jelző byte-ra csak azért van szükség, hogy minél kevesebbet 
 * kelljen írni az eeprom-ba. Lehetne úgy is, hogy az adatot mindig lejjebb léptetem egy byte-al, de akkor óránként
 * végig kellene írni az összes byte-ot, és hamarabb használódna el az eeprom. Így mindig csak óránként egy 
 * adott byte-ot írok. A jelző byte mögötti byte-ba csak akkor írok, ha átbillen a csapadék gyűjtő. 
 * Magyarországon , az átlagos csapadék mennyiség kb 600mm, vagyis kb. 2000 billenés, azaz 2000 írás. 
 * Az eeprom-ot az óránkénti írás terheli meg. Egy konkrét cellát 14 naponta írok, azaz 100.000*14 nap alatt
 * fog elhasználódni egy adott cella. Ez kb. 1400000/365=~3800 év.
 * 
 * EEPROM használat:
 * Egy rekord egy jelző byte-ból és egy szám adatból áll
 * Jelző byte: jelzi, hogy melyik az aktuális rekord, egy órát ebbe írom a váltásokat. 
 *             Ha eltelt az egy óra, akkor kinullázom, és a következő rekord jelzőjébe írok 1-et
 * Szám adat:  gyűjti az adott 24 órában történt átbillenések számát, amikor a hozzá tartozó jelző byte-ba
 *             1-et írok, akkor törlöm az adatrekord értékét, ekkor lesz felülírva az előző adat (felejti
 *             a 14 nappal régebbi értékeket
 * 
 * Az áramszünet kicsi problémát okozhat, mert esetleg nem sikerül beírnia  következő rekordba az 1-et, ekkor előfordulhat,
 * hogy a régi rekordot folytatja újraindítás után, mert a régi marad 1. Az is lehet, hogy áramszünetkor sikerül beírni
 * az 1-et az új rekordba, de nem sikerül törölni az előzőt. Ekkor két rekord előtt lesz 1, mindig az elsőt találja meg,
 * tehát ekkor  is a régi rekordba gyűjt egy újabb órát. 
 *  ***********************************************************************************************************************/
#include <EEPROM.h>
#include <Wire.h>    //I2C library 
#include <LiquidCrystal_I2C.h>                                  //I2C LCD kezelő könyvtár
//LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  //LCD paraméterek megadása, ez most 2x16 karakteres kijelző
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  //LCD paraméterek megadása, a 4 soros LCD-m címe 3F

bool prell_tmp;            //segéd változó az első kontaktus megtörténtének jelzésére
long prell_time;           //segéd változó az első kontaktus időpontjának rögzítésére
bool allapot;              //ebbe a változóba beolvassuk a digitális bemenet állapotát 
bool elozoallapot;         //minden loop ciklus végén ebbe áttöltjük a ciklus elején beolvasott bemeneti állapotot, ez segít észre venni, ha a következő ciklusban változott a bemenet állapota
int szamlalo;              //minden átbillenéskor növeljük az értékét
long ido;                  //ebben tároljuk az utolsó idoszámlálo állást, hogy 1 óránként múlva újra lefuthasson a szükséges programrész
byte ertek;                //ebbe a változóba olvasom ki az aktuális értékét az adatrekordnak, mielőtt növelem 1-el
int cim;                   //az a cim, amiben 1-et találok a jelölő byte-on
int elozo_cim;             //az előző aktuális rekord címét tárolja amikor váltom a rekordot óránként
int csapadek24;            //az elmúlt 24 óra billenésszáma
int csapadek72;            //az elmúlt 3 nap billenésszáma
int csapadek14;            //az elmúlt 14 nap billenésszáma
float csapadek_mm;         //a csapadék mennyiséget tizedes értékben akarom kiírni, ezért kell egy float típusú változó a számoláskor
int akt_cim;               //annak a rekordnak a sorszáma, amibe éppen írok 0-717 közötti szám, ami kettesével növekvő értékeket vehet fel: 0,3,5...
int index;                 //segédváltozó az adatok összegyűjtéséhez. Az éppen kiolvasott rekord kezdőcíme
int adat;                  //segédváltozó, hogy ne kelljen többször olvasni ugyanazt az eeprom cellát
String szoveg;             //segéd változó a formázott kiíráshoz

/*******************************************************************************
 * Amennyiben a kontaktust (PIN3) a bekapcsoláskor nyomvatartjuk, a program    *
 * Kitörli az eepromban átbillenésre fentartott területet, és beállítja        *
 * a 0. címet 1-re, ezzel jelzi, hogy ez az első rekord, ami éppen aktív       *
 *******************************************************************************/
void setup() {
  Serial.begin(9600);               //csak fejlesztéshez használva, ha kell soros port
  lcd.begin(16,2);                  //LCD inicializálása
  lcd.clear();                      //LCD törlés
  lcd.backlight();                  //háttérvilágítás bekapcsolása
  Serial.println("Indul...");
  pinMode(3,INPUT);                 //2-es kivezetésre kötöttem a kontaktus jelét             
  digitalWrite(3,HIGH);             //felhúzó ellenállás bekapcsolása   
  if (digitalRead(3)==0) {          //alaphelyzetbe állítás, mert nyomtam indításkor a gombot, teljes eeprom törlés
    for (int i=0;i<672;i=i+2) {
      EEPROM.write(i,0);
      EEPROM.write(i+1,0);
      //EEPROM.write(i+1,4);        //amikor teszteltem a működést, beállítottam minden rekordot 4-re, és figyeltem, ahogy halad előre
                                    //úgy felejti el az elmút időszak adatait, kivéve, ha közben nyomkodtam a pin3-ra kötött
                                    //nyomógombot. A nagyobb számok kijelzését 14 beírásával teszteltem. 14+336=4704, de ez még csak
                                    //kontaktus szám, ebből a kijelzett csapadék: 4704*0,3=1411mm. A felejtés helyes működését 1 beírásával
                                    //teszteltem, mert ekkor a kijelzett érték az utolsó 1 napnál 7,2, az utolsó 3 napnál 21,6 és az 
                                    //utolsó 2 hétnél 100. Elindítottam az óránkénti rekordváltással, és figyeltem, hogy valóban
                                    //1 nap alatt csökken az utolsó nap 7,2-ről 0-ra. 
    }
    EEPROM.write(0,1);              //az első rekordot megjelöljük, az lesz az aktuális.
  }

  //meg kell állapítani az index váltoó értékét, ami megagadja, honna kell kezdeni az adatok kiírásakor 
  //az adatok kinyerését
  for (int i=0;i<672;i=i+2) {      //végig megyünk az összes adatrekordon 2 byte-onként
    if (EEPROM.read(i)==1) {
        akt_cim=cim;               //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
        break;                     //megtaláltuk az 1-et, nem kell befejezni a for ciklust
    }
  }
  osszegzes();
}


/*************************************************************************************************** 
 * A loop végzi a kontaktusjelek figyelését, prellmentesítést is végez, nehogy több átbillenést    *
 * is érzékelje. Amikor kontaktus jelet érzékel (átbillenés), akkor az éppen aktívan jelölt        *
 * rekord adatbyte-ját egy-el megnöveli. Egy óránként törli az éppen aktív rekord jelölő           *
 * byte-ját, és a soron következőt állítja 1-re, valamint azt az adatrekordot törli is,            *
 * ezzel felejti a több mint 14 napos adatokat. Ha a soron következő adatrekor címe nagyobb        *
 * mint 670, akkor újra a 0. címen kezdődő rekord lesz az aktív, vagyis a jelölés körbe jár        *
 * a kijelölt memória területen.                                                                   *
 * Minden rekord váltáskor és átbillenéskor meghívja a kijelzőt frissítő "osszesít()"              *
 * függvényt, ami kiszámolja az adatokat és felfrissíti a kijelzőn.                                *
 ***************************************************************************************************/
void loop() {
  /*************************************************************************************************************
  * Ez a programrész prellmentesíti a csapdékgyüjtő kontaktusát, nehogy egy billenésre kettőt számoljunk       *
  * reed kontaktus van benne, ami nem prellezik elméletileg, de már tapasztaltam egy másik eszköznél prell-t   *
  **************************************************************************************************************/
  if (digitalRead(3)==LOW and prell_tmp==0)                             //első lenyomás érzékelése
    {prell_tmp=1;prell_time=millis();}                                  //prell_tmp=1 jelzi, hogy már volt egy kontaktus
  if (digitalRead(3)==LOW and prell_tmp==1 and millis()>prell_time+50)  // már 50msecv óta nyomva van, most már biztos, hogy lenyomták és nem prellezik
    {allapot=1;digitalWrite(11,HIGH);prell_tmp=0;}                      //allapot=1 jelzi a nyomógomb lenyomást és ez már nem prell-es, 
                                                                        //prell_tmp=0-val várjuk a következő eseményt
  if (digitalRead(3)==HIGH and prell_tmp==0)                            //első elengedés érzékelése
    {prell_tmp=1;prell_time=millis();}                                  //prell_tmp=1 jelzi, hogy megszakadt a kontaktus      
  if (digitalRead(3)==HIGH and prell_tmp==1 and millis()>prell_time+50) //már 50msecv óta elengedve, most már biztos, hogy elengedték és nem prellezik
    {allapot=0;prell_tmp=0;}                                            //allapot=0 jelzi a nyomógomb elengedést s ez már nem prell-es, 
                                                                        //prell_tmp=0-val várjuk a következő eseményt

  /**********************************************************************************************
  * Billenés esetén növeljük az aktuális rekord tartalmát 1-el.                                 *
  ***********************************************************************************************/
  if (allapot==0 and elozoallapot==1)   //azt érzékeljük, amikor az előző ciklusban még nem volt megnyomva 
                                        //a nyomógomb, most pedig igen. Ez a bemeneten egy "lefutó él" érzékelést jelent
  {
    //történt egy átbillenés, ezért növelni kell az éppen aktuális gyűjtő rekord tartalmát 1-el
    //elősször megkeresem az aktuális rekordot, aztán az ahhoz tartozó adatot növelem.
    for (int i=0;i<672;i=i+2) {      //végig megyünk a rekordokon, amíg meg nem találjuk az 1-el jelölt rekordot
      if (EEPROM.read(i)==1) {       //ha az aktuális éppen 1, akkor megtaláltuk azt, amit növelni kell
        akt_cim=i;                   //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
        ertek=EEPROM.read(i+1);      //kiolvassuk a tárolt billenésszámot
        EEPROM.write(i+1,ertek+1);   //...és visszaírunk egy-el nagyobbat
        break;                       //nem kell tovább folytatni a for ciklust
      }
    }
   osszegzes();                      //rögvest kiírjuk a képernyőre az adatokat
  }
  elozoallapot=allapot;              //tároljuk a ciklus elején beolvasott bemeneti állapotot

  /*********************************************************************************
  * Ha 1 óra telt el, megkeressük az aktuális adatrekordot, töröljük a jelzőjét    *
  * a következő rekord jelzőjét 1-re állítjuk, és az adat mezőjét 0-ra töröljük    *
  **********************************************************************************/
  if (ido+3600000<millis())         //ha 1 óra telt el, megkeressük az aktuális adatrekordot, jelzőt töröljük a számlálót növeljük.
                                 //teszteléskor ezt 2 másodpercre állítottam a "felejtés" kipróbálásához és 10 másodpercre
                                 //a kontaktus számlálás teszteléséhez.
  {
   for (int i=0;i<672;i=i+2) {
      if (EEPROM.read(i)==1) {
        cim=i;
        break;
      }
    }
    elozo_cim=cim;
    cim=cim+2;                    //ez lesz a következő rekord címe
    if (cim>670) {cim=0;}         //ha az utolsó rekord volt megjelölve, akkor legelőről fogjuk folytatni
    akt_cim=cim;                  //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
    EEPROM.write(cim,1);
    EEPROM.write(cim+1,0);
    //csak akkor törlöm az előző adatrekord jelölőjét, ha már beírtam a következőbe az 1-et. Ha áramszünet miatt nem tudnám
    //beírni az 1-et a következő rekordba, akkor legfeljebb egy órával többet ugyanebbe a rekordba írok tovább, de működik a rendszer
    //legalább egy rekordnak mindíg lesz 1-es jelölője. Ha előbb törölném az 1-et, akkor előfordulhatna, hogy áramszünet miatt
    //nem tudom beírnia  jelölő 1-et a következő rekordba, és nem működne a gyűjtés újra indulás után.
    EEPROM.write(elozo_cim,0);
    ido=millis();                 //tároljuk a millis() értékét, így az if-ben vizsgálhatjuk, hogy eltelt-e 1 másodperc
    osszegzes();                  //rögvest kiírjuk a képernyőre az adatokat, tehát legalább óránként frissítjük a kijelző tartalmát
                                  //Ez kell is, mert óránként felejtődnek a régi adatok, és változik a kijelzett csapadék mennyiség
  }
}

/***********************************************************************************************
 * Ennek a függvénynek a feladata, hogy összeszámolja az elmúlt egy nap, három nap, és 14 nap  *
 * billenési adatait, és ezeket átszámolja csapadék milliméterbe és megjelenítse a képernyőn.  *
 * A meghívás pillanatában az aktiv_cim változóba be kell állítani az éppen aktív rekord       *
 * kezdőcímét. Innen visszafelé halad az összegzéssel, mert visszafelé található az elmúlt     *
 * időszak. Bár egy for ciklussal nézi végig a 336 rekordot, az éppen összegzett rekord címét  *
 * az "index" nevű változó tartalmazza. Ha ennek értéke 0ra csökken, akkor beállítja 672-re    *
 * ami az adatterület utolsó rekordjának címe.                                                 *
 * A kijelzőn rövidítve mindhárom adat elfér egyszerre!                                        *
 ***********************************************************************************************/
void osszegzes() {
  index=akt_cim;                            //akt_cim minden esetben az a cím, ahool éppen számoljuk a billenéseket
    csapadek24=0;                             //az utolsó 24 óra billenéseinek számát fogjuk gyüjteni  
    csapadek72=0;                             //az utolsó 72 óra billenéseinek számát fogjuk gyüjteni  
    csapadek14=0;                             //az utolsó 14 nap billenéseinek számát fogjuk gyüjteni  
    for (int i=0;i<336;i++) {                 //336 rekordot fogunk körbejárni 0 és 335 között
      adat=EEPROM.read(index+1);
      if (i<24) {                             //az utolsó 24 órához az első 24 rekord eredménye kell
        csapadek24=csapadek24+adat;
      }
      if (i<72) {                             //az utolsó 72 órához az első 24 rekord eredménye kell
        csapadek72=csapadek72+adat;
      }
      csapadek14=csapadek14+adat;
      if (index==0) {index=672;}              //a legelső rekordon állunk, mivel visszafelé haladunk a soron következő a 717-es cím (359-es rekord kezdőcíme)
      index=index-2;
    }
    lcd.setCursor(0,0);lcd.print(" 24o:  72o: 2ht:");
    //Így fognak kinézni a számok:999.9 999.9 9999 
    //az utolsó 14 nap adatait már csak milliméter pontossággal írom ki
    
    //egy billenés 0,3mm esőt jelent
    csapadek_mm=(float)csapadek24*0.3;              //24 óra alatt hullott csapadék mm-ben
    lcd.setCursor(0,1);                             //kezdő pozíció beállítása a kiírás előtt
    if (csapadek_mm<10) {lcd.print("  ");}          //két szóköz, ha csak egyjegyű + egy tizedes a szám
    else {if (csapadek_mm<100) {lcd.print(" ");}}   //egy szóköz ha kétjegyű + egy tizedes a szám
    lcd.print(String(csapadek_mm,1));
    
    csapadek_mm=(float)csapadek72*0.3;              //72 óra alatt hullott csapadék mm-ben
    lcd.setCursor(6,1);                             //kezdő pozíció beállítása a kiírás előtt
    if (csapadek_mm<10) {lcd.print("  ");}          //két szóköz, ha csak egyjegyű + egy tizedes a szám
    else {if (csapadek_mm<100) {lcd.print(" ");}}   //egy szóköz ha kétjegyű + egy tizedes a szám
    lcd.print(String(csapadek_mm,1));
    
    csapadek14=csapadek14*0.3;                      //14 nap alatt hullott csapadék mm-ben egész értékre kerekítve
    lcd.setCursor(12,1);                            //kezdő pozíció beállítása a kiírás előtt
    if (csapadek14<10) {lcd.print("   ");}          //három szóköz ha csak egyjegyű a szám
    else {
      if (csapadek14<100) {lcd.print("  ");}        //két szóköz ha kétjegyű a szám
      else {if (csapadek14<1000){lcd.print(" ");}}  //egy szóköz ha háromjegyű a szám
    } 
    lcd.print(String(csapadek14));
}

Ez a program nagyon egyszerű és rövid, csak a kommentektől hosszú! Alig csinál valamit. Óránként vándoroltatja a jelzést az eepromban, ha billenés történik akkor az éppen megjelölt adatrekord tartalmát egy-el növeli, és ha bármi esemény történik (jelzést írunk, vagy billenés van), akkor frissíti a kijelző tartalmát. A végső megoldás az én konkrét felhasználásomban (automata locsoló rendszer) nem igényel majd külön kijelzőt, hiszen csak egy jelzést kell adnia, ha nem kell locsolni. Így a kijelzőt most csak azért tettem rá, hogy önálló műszer legyen belőle, amíg kísérletezgetek és megfigyelek! Eszembe jutott az is, hogy az időjárás állomásomba is beintegrálom idővel az új adatokat. Az időjárás állomásomnak már van kijelzője, ami pár másodperces ciklusban írogatja az adatokat ugyanarra a 7 szegmenses led kijelzőre. Ebbe a körbejáró ciklusba fogom betenni az új adatokat. Valószínűleg az utolsó 24 óra és az utolsó három nap adatait fogom csak kiírni. Az időjárás állomásomban jelenleg is 2db nano működik, egyik a master, ami lekérdezi a másikat, illetve adatokat ad át! Logikusnak tűnik, hogy ezt a nano-t beillesszem 3.-nak I2C buszon keresztül. Elméletben az SD kártya írását végző nano, bírná a csapadék számlálást is, így annak programjába is beilleszthetem, de még ezzel nem foglalkoztam. Erre a nano spórolós megoldásra csak akkor van lehetőség, ha az időjárás állomásom slave programja nem csinál olyan műveletet, ami hosszabb ideig tart, mint egy átbillenés ideje. Pedig az lehetséges, hogy van ilyen, hiszen sd kártyát ír és kommunikál is a masterrel. Ráadásul az I2C busz lehető legkisebb sebességgel működik, mert hosszúak a vezetékek, így ott hosszú idők telhetnek el. Idővel kiderül majd, melyik lesz az üdvözítő megoldás.

Csapadékmérő ATtiny85 alaplapon

Nem volt egyszerű ezt a rendkívül buta és egyszerű programot feltölteni az ATTiny85 alaplapra. Több probléma is adódott. Az első probléma az a belső EEPROM használatával adódott. Nem volt hajlandó lefordítani a programot az Arduino IDE, több hibát írt, mint amannyi sora volt a programnak. Miközben az EEPROM.h kezelés és az I2C-hez szükséges TinyWireM.h is külön-külön működött, együtt már nem. A megoldást a TinyWireM ismert hibájának a javítása jelentette, itt olvashatod a megoldást (a legvégén).

A következő probléma a mérettel adódott. 6Kbyte méretet meghaladta a program néhány byte-al. Elkezdtem rövidíteni. Elsőként az LCD kijelzőre kiírt adatok formázását szüntettem meg. Jóval egyszerűbb lett a program, és úgy tűnik feleslegesen küzdöttem az első verzióban a számok igazításával, mert így sem rossz. Aztán rájöttem, hogy felesleges tizedes pontossággal kiírni a csapadékot. Egyébként is csak 0,3mm pontossággal tudom kiírni az adatot, és zavaró, hogy pl. 3,0mm után a 3,3mm következik. További méret csökkenést jelentett az a felismerés, hogy nem kell a kontaktus jelet prellmentesíteni. Mivel a korlátozott flash memória miatt ez a program már semmi mással nem fog foglalkozni, csak ezzel az egy feladattal, nyugodtan használhatok delay()-t a loop()-ban. Be is raktam egy fél másodperces időzítést, ami gondoskodik arról, hogy ne érzékeljen a program két kontaktus jelet. Az átbillenés ideje ennél sokkal kisebb, és biztosan lezajlik. Az esőmérő átbillenése még viharban sem lesz kisebb mint 1-2 perc! Amint megoldódott a méret probléma (immár 4892 byte), leesett, hogy a belső eeprom az ATtiny85-nél csak 512 byte. Így aztán nem lehet a jelenleg rendelkezésre álló rekord struktúrával 14 napot regisztrálni. Ezt lazán lecsökkentettem 10 napra, így már csak 480byte eeprom kell. Lehetne változtatni a rekord struktúrán, hiszen óránként biztosan nem billen a csapadékmérő 127-szer, így lehetne ugyanazzal az egy byte-el jelezni, hogy melyik az aktuális rekor, és ugyanabban a byte-ben tárolni a billenések számát (az akt rekord 128-el kezdődik, amikor új aktuális rekordot jelölök ki, levonok a tartalmából 128-at). De ezzel sem akartam sokat küzdeni, így maradt a 10 nap. Aztán kiderült, hogy az ATtiny85 modul 1-es kivezetése (P1) nem használható bemenetnek, mert erre kötötték a LED-et, ami folyamatosan lehúzza a bemenetet 0-ra, így a nyomógombomat nem érzékelte. Mondjuk ez sem megoldhatatlan probléma, mert van még szabad kivezetés dögivel (összesen 3 db). Így lett a csapadékmérő kontaktusának érzékelője a P3 kivezetés. Aztán kiderült, hogy az USB-ről nem lehet táplálni a modult, mert akkor is számol, ha nem nyomom meg a nyomógombot. Ezt nem értem hogyan történik, elvileg az USB a P0 és P2 kivezetésen van. Ha azonban külső tápról kapott 5V-ot, akkor rendben működött. Végleges helyén egyébként is külső tápról fog működni, így ez rendben is van. Csak fejlesztéskor kell erre is gondolni. Feltöltés után le kell húzni az USB-t, és külső tápot adni a kütyünek. Nálam most a külső táp egy Arduino UNO, amit bedugok ugyanabban a gépbe az USB portján keresztül, és felhasználom az 5V-os kivezetését. Kicsit röhejes, de jól működik. Macerás a program feltöltés: 1. Uno kihúz, 2. ATtiny85 bedug, 3. feltöltés után ATtiny85 kihúz, 4. Uno bedug!

Ha LCD kijelzővel fog készülni az esőmérő, akkor még tervezek egy nyomógombot a kijelző háttérvilágításának bekapcsolására, ami pár másodpercre felkapcsolja a villanyt, aztán magától kikapcsol. A maradék egy kivezetés (ez lesz az egyes láb) megmarad jelző kimenetnek, ez fogja a locsoló rendszeremnek jelezni, ha nem kell locsolni mert nemrég esett az eső! Ehhez persze be kell még építeni azt az algoritmust, ami az eső mennyisége alapján előállítja ezt a jelzést. Ezt még ki kell találni, de a maradék 1120 byte elégnek tűnik a néhány if() beépítésére. A programot már leteszteltem, érzékeli a nyomógombot, és kiírja a kijelzőre az adatokat, óránként felejti a regisztrált adatokat stb..

Íme a működő forrás:

/***********************************************************************************************************************
 * Ez a program egy csapadékmérő átbillenéseit gyűjti óránkénti bontásban, azaz minden órában egy másik tárolóba
 * gyűjti az abban az órában történt átbillenéseket. A kijelző mindíg az utolsó 24 óra, 3nap és 10nap adatait 
 * jelzi ki. Nem kell óra, csak óránként kell váltani tárolót, és a tárolók 14 naponta felülírhatók. Így összesen
 * 10*24=240 tároló rekeszre van szükség.
 * Az utolsó 10 nap adatait gyűjtöm az eeprom-ban két byte-on óránkénti bontásban. Ez összesen 10*24=240 adatrekord.
 * Minden adatrekord két byte-ból áll, egyik jelzi, hogy melyik az aktuálisan gyűjtésre használt adatrekord.
 * A másik byte-ban gyűjtöm az átbillenések számát. A jelző byte-ra csak azért van szükség, hogy minél kevesebbet 
 * kelljen írni az eeprom-ba. Lehetne úgy is, hogy az adatot mindig lejjebb léptetem egy byte-al, de akkor óránként
 * végig kellene írni az összes byte-ot, és hamarabb használódna el az eeprom. Így mindig csak óránként egy 
 * adott byte-ot írok. A jelző byte mögötti byte-ba csak akkor írok, ha átbillen a csapadék gyűjtő. 
 * Magyarországon , az átlagos csapadék mennyiség kb 600mm, vagyis kb. 2000 billenés, azaz 2000 írás. 
 * Az eeprom-ot az óránkénti írás terheli meg. Egy konkrét cellát 10 naponta írok, azaz 100.000*10 nap alatt
 * fog elhasználódni egy adott cella. Ez kb. 1000000/365=~2700 év.
 * 
 * EEPROM használat:
 * Egy rekord egy jelző byte-ból és egy szám adatból áll
 * Jelző byte: jelzi, hogy melyik az aktuális rekord, egy órát ebbe írom a váltásokat. 
 *             Ha eltelt az egy óra, akkor kinullázom, és a következő rekord jelzőjébe írok 1-et
 * Szám adat:  gyűjti az adott 24 órában történt átbillenések számát, amikor a hozzá tartozó jelző byte-ba
 *             1-et írok, akkor törlöm az adatrekord értékét, ekkor lesz felülírva az előző adat (felejti
 *             a 10 nappal régebbi értékeket
 * 
 * Az áramszünet kicsi problémát okozhat, mert esetleg nem sikerül beírnia  következő rekordba az 1-et, ekkor előfordulhat,
 * hogy a régi rekordot folytatja újraindítás után, mert a régi marad 1. Az is lehet, hogy áramszünetkor sikerül beírni
 * az 1-et az új rekordba, de nem sikerül törölni az előzőt. Ekkor két rekord előtt lesz 1, mindig az elsőt találja meg,
 * tehát ekkor  is a régi rekordba gyűjt egy újabb órát. 
 *  ***********************************************************************************************************************/
#include <EEPROM.h>                     // A TinyWireM.h ismert hibája miatt ehhez előbb át kell másolni az Arduino alap könvtár EEPROM.h állományát
                                        // és kijavítani benne az 53. sor tartalmát. Lásd leírások
#include <TinyWireM.h>                  // I2C könyvtár ATtiny85-höz
#include <LiquidCrystal_I2C.h>          // LCD könyvtár ATtiny85-höz
LiquidCrystal_I2C lcd(0x3F,20,4);       // kijelző I2C címének és méretének beállítása

int szamlalo;              //minden átbillenéskor növeljük az értékét
long ido;                  //ebben tároljuk az utolsó idoszámlálo állást, hogy 1 óránként múlva újra lefuthasson a szükséges programrész
byte ertek;                //ebbe a változóba olvasom ki az aktuális értékét az adatrekordnak, mielőtt növelem 1-el
int cim;                   //az a cim, amiben 1-et találok a jelölő byte-on
int elozo_cim;             //az előző aktuális rekord címét tárolja amikor váltom a rekordot óránként
int csapadek24;            //az elmúlt 24 óra billenésszáma
int csapadek72;            //az elmúlt 3 nap billenésszáma
int csapadek10;            //az elmúlt 14 nap billenésszáma
int akt_cim;               //annak a rekordnak a sorszáma, amibe éppen írok 0-717 közötti szám, ami kettesével növekvő értékeket vehet fel: 0,3,5...
int index;                 //segédváltozó az adatok összegyűjtéséhez. Az éppen kiolvasott rekord kezdőcíme
int adat;                  //segédváltozó, hogy ne kelljen többször olvasni ugyanazt az eeprom cellát

/*******************************************************************************
 * Amennyiben a kontaktust (PIN3) a bekapcsoláskor nyomvatartjuk, a program    *
 * Kitörli az eepromban átbillenésre fentartott területet, és beállítja        *
 * a 0. címet 1-re, ezzel jelzi, hogy ez az első rekord, ami éppen aktív       *
 *******************************************************************************/
void setup() {
  TinyWireM.begin();                // I2C inicializálása
  lcd.init();                       //LCD inicializálása
  lcd.clear();                      //LCD törlés
  lcd.backlight();                  //háttérvilágítás bekapcsolása
  pinMode(3,INPUT);                 //2-es kivezetésre kötöttem a kontaktus jelét             
  digitalWrite(3,HIGH);             //felhúzó ellenállás bekapcsolása   
  if (digitalRead(3)==0) {          //alaphelyzetbe állítás, mert nyomtam indításkor a gombot, teljes eeprom törlés
    lcd.setCursor(0,0);lcd.print("TORLES!");
    for (int i=0;i<480;i=i+2) {
      EEPROM.write(i,0);
      EEPROM.write(i+1,0);
      //EEPROM.write(i+1,4);        //amikor teszteltem a működést, beállítottam minden rekordot 4-re, és figyeltem, ahogy halad előre
                                    //úgy felejti el az elmút időszak adatait, kivéve, ha közben nyomkodtam a pin3-ra kötött
                                    //nyomógombot. A nagyobb számok kijelzését 14 beírásával teszteltem. 14+336=4704, de ez még csak
                                    //kontaktus szám, ebből a kijelzett csapadék: 4704*0,3=1411mm. A felejtés helyes működését 1 beírásával
                                    //teszteltem, mert ekkor a kijelzett érték az utolsó 1 napnál 7,2, az utolsó 3 napnál 21,6 és az 
                                    //utolsó 2 hétnél 100. Elindítottam az óránkénti rekordváltással, és figyeltem, hogy valóban
                                    //1 nap alatt csökken az utolsó nap 7,2-ről 0-ra. 
    }
    EEPROM.write(0,1);              //az első rekordot megjelöljük, az lesz az aktuális.
  }

  //meg kell állapítani az index váltoó értékét, ami megagadja, honna kell kezdeni az adatok kiírásakor 
  //az adatok kinyerését
  for (int i=0;i<480;i=i+2) {      //végig megyünk az összes adatrekordon 2 byte-onként
    if (EEPROM.read(i)==1) {
        akt_cim=cim;               //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
        break;                     //megtaláltuk az 1-et, nem kell befejezni a for ciklust
    }
  }
  osszegzes();
}


/*************************************************************************************************** 
 * A loop végzi a kontaktusjelek figyelését, prellmentesítést is végez, nehogy több átbillenést    *
 * is érzékelje. Amikor kontaktus jelet érzékel (átbillenés), akkor az éppen aktívan jelölt        *
 * rekord adatbyte-ját egy-el megnöveli. Egy óránként törli az éppen aktív rekord jelölő           *
 * byte-ját, és a soron következőt állítja 1-re, valamint azt az adatrekordot törli is,            *
 * ezzel felejti a több mint 14 napos adatokat. Ha a soron következő adatrekor címe nagyobb        *
 * mint 670, akkor újra a 0. címen kezdődő rekord lesz az aktív, vagyis a jelölés körbe jár        *
 * a kijelölt memória területen.                                                                   *
 * Minden rekord váltáskor és átbillenéskor meghívja a kijelzőt frissítő "osszesít()"              *
 * függvényt, ami kiszámolja az adatokat és felfrissíti a kijelzőn.                                *
 ***************************************************************************************************/
void loop() {
  if (digitalRead(3)==LOW)   // Atbillenés történt, növelni kell az aktuális rekord számlálóját 
  {
    delay(500);              // várunk fél másodpercet, hogy biztosan megtörténjen az átbillenés, nehogy kettőt számoljunk. Az átbillenés kevesebb
                             // mint egy tized másodperc alatt lezajlik, és addig zár a kontaktus, de nincs miért sietni, most mással nem kell foglalkoznia a programnak!
                             
    //történt egy átbillenés, ezért növelni kell az éppen aktuális gyűjtő rekord tartalmát 1-el
    //elősször megkeresem az aktuális rekordot, aztán az ahhoz tartozó adatot növelem.
    for (int i=0;i<480;i=i+2) {      //végig megyünk a rekordokon, amíg meg nem találjuk az 1-el jelölt rekordot
      if (EEPROM.read(i)==1) {       //ha az aktuális éppen 1, akkor megtaláltuk azt, amit növelni kell
        akt_cim=i;                   //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
        ertek=EEPROM.read(i+1);      //kiolvassuk a tárolt billenésszámot
        EEPROM.write(i+1,ertek+1);   //...és visszaírunk egy-el nagyobbat
        break;                       //nem kell tovább folytatni a for ciklust
      }
    }
   osszegzes();                      //rögvest kiírjuk a képernyőre az adatokat
  }

  /*********************************************************************************
  * Ha 1 óra telt el, megkeressük az aktuális adatrekordot, töröljük a jelzőjét    *
  * a következő rekord jelzőjét 1-re állítjuk, és az adat mezőjét 0-ra töröljük    *
  **********************************************************************************/
  if (ido+3600000<millis())      //ha 1 óra telt el, megkeressük az aktuális adatrekordot, jelzőt töröljük a számlálót növeljük.
                                 //teszteléskor ezt 2 másodpercre állítottam a "felejtés" kipróbálásához, és 10 másodpercre
                                 //a kontaktus számlálás teszteléséhez.
  {
    for (int i=0;i<480;i=i+2) {
      if (EEPROM.read(i)==1) {
        cim=i;
        break;
      }
    }
    elozo_cim=cim;
    cim=cim+2;                    //ez lesz a következő rekord címe
    if (cim>480) {cim=0;}         //ha az utolsó rekord volt megjelölve, akkor legelőről fogjuk folytatni
    akt_cim=cim;                  //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
    EEPROM.write(cim,1);
    EEPROM.write(cim+1,0);
    //csak akkor törlöm az előző adatrekord jelölőjét, ha már beírtam a következőbe az 1-et. Ha áramszünet miatt nem tudnám
    //beírni az 1-et a következő rekordba, akkor legfeljebb egy órával többet ugyanebbe a rekordba írok tovább, de működik a rendszer
    //legalább egy rekordnak mindíg lesz 1-es jelölője. Ha előbb törölném az 1-et, akkor előfordulhatna, hogy áramszünet miatt
    //nem tudom beírnia  jelölő 1-et a következő rekordba, és nem működne a gyűjtés újra indulás után.
    EEPROM.write(elozo_cim,0);
    ido=millis();                 //tároljuk a millis() értékét, így az if-ben vizsgálhatjuk, hogy eltelt-e 1 másodperc
    osszegzes();                  //rögvest kiírjuk a képernyőre az adatokat, tehát legalább óránként frissítjük a kijelző tartalmát
                                  //Ez kell is, mert óránként felejtődnek a régi adatok, és változik a kijelzett csapadék mennyiség
  }
}

/***********************************************************************************************
 * Ennek a függvénynek a feladata, hogy összeszámolja az elmúlt egy nap, három nap, és 14 nap  *
 * billenési adatait, és ezeket átszámolja csapadék milliméterbe és megjelenítse a képernyőn.  *
 * A meghívás pillanatában az aktiv_cim változóba be kell állítani az éppen aktív rekord       *
 * kezdőcímét. Innen visszafelé halad az összegzéssel, mert visszafelé található az elmúlt     *
 * időszak. Bár egy for ciklussal nézi végig a 336 rekordot, az éppen összegzett rekord címét  *
 * az "index" nevű változó tartalmazza. Ha ennek értéke 0ra csökken, akkor beállítja 672-re    *
 * ami az adatterület utolsó rekordjának címe.                                                 *
 * A kijelzőn rövidítve mindhárom adat elfér egyszerre!                                        *
 ***********************************************************************************************/
void osszegzes() {
  index=akt_cim;                            //akt_cim minden esetben az a cím, ahool éppen számoljuk a billenéseket
  csapadek24=0;                             //az utolsó 24 óra billenéseinek számát fogjuk gyüjteni  
  csapadek72=0;                             //az utolsó 72 óra billenéseinek számát fogjuk gyüjteni  
  csapadek10=0;                             //az utolsó 10 nap billenéseinek számát fogjuk gyüjteni  
  for (int i=0;i<240;i++) {                 //240 rekordot fogunk körbejárni 0 és 239 között
    adat=EEPROM.read(index+1);
    if (i<24) {                             //az utolsó 24 órához az első 24 rekord eredménye kell
      csapadek24=csapadek24+adat;
    }
    if (i<72) {                             //az utolsó 72 órához az első 72 rekord eredménye kell
      csapadek72=csapadek72+adat;
    }
    csapadek10=csapadek10+adat;
    if (index==0) {index=480;}              //a legelső rekordon állunk, mivel visszafelé haladunk a soron következő a 478-as cím (240-es rekord kezdőcíme)
    index=index-2;
  }
  lcd.clear();                      //LCD törlés
  lcd.setCursor(0,0);lcd.print("24ora 3nap  10np");
  //Így fognak kinézni a számok: 999   999   999  
  //az utolsó 14 nap adatait már csak milliméter pontossággal írom ki
    
  //egy billenés 0,3mm esőt jelent
  lcd.setCursor(1,1);                             //kezdő pozíció beállítása a kiírás előtt
  lcd.print(csapadek24*0.3,0);                    //24 óra alatt hullott csapadék mm-ben egész értékre kerekítve
  
  lcd.setCursor(7,1);                             //kezdő pozíció beállítása a kiírás előtt
  lcd.print(csapadek72*0.3,0);                    //72 óra alatt hullott csapadék mm-ben egész értékre kerekítve
    
  lcd.setCursor(13,1);                            //kezdő pozíció beállítása a kiírás előtt
  lcd.print(csapadek10*0.3,0);                    //10 nap alatt hullott csapadék mm-ben egész értékre kerekítve
}

Csapadékmérő ATtiny85 alaplappal és OLED kijelzővel

Biztatóan hangzik a cím, és nagyon jó hírem van, mert végül is sikerült.

Első próbálkozásban kicseréltem az LCD kezelését végző programsorokat (és természetesen az #include sort) az OLED kezeléshez kapott könyvtárakra, és a program hibátlanul lefordult. Azonban a mérete 7,2 Kbyte. Nincs reményem arra, hogy ezen lényegesen csökkentsek, hiszen a program már alig csinál valamit. Először feladtam, aztán mégis elővettem a problémát és elkezdtem törni a fejem, hogyan is lehetne lecsökkenteni a program mértét. Az eeprom írásán, csapadék mérő szenzor kezelését végző programon aligha tudok lényegesen csökkenteni, marad a kijelző. Eldobtam hát a kijelző kezelését végző kész programkönyvtárakat, és írtam egy saját függvényt. Sokkal kevesebbet tud, vannak benne jelentős megszorítások, de használható lett, sőt, jó nagy karaktereket is tud írni, így az eredmény igazán kielégítő lett. A karakter megjelenítő függvény készítéséről ennek a leírásnak a végén olvashatsz. Ezt használtam fel a továbbiakban.

Lényeges különbség még, hogy az OLED kijelző nem képes egyszerre három adatot megjeleníteni. Lehetne sorban egymás után kijelezni az adatokat, és ezt végtelen ciklusban ismételni, de sajnos a kijelző élettartama korlátozott, és ekkor csak kb. 5 év működésre számíthatnék, ezért beépítettem egy nyomógombot. Erre azért is szükség van, mert valahol azt is be kell állítani, hogy milyen csapadék mennyiségnél állítsa le a locsolást. Különben minden alkalommal újra kell tölteni a programot, amikor a tapasztalatok alapján változtatnom kell az értéken. Tehát a program kicsit bonyolultabb lett, mert egy nyomógombot is kezelni kell. Az ATtiny85 használható kivezetéseinek száma nagyon kievés, szinte mindet felhasználtam:

Az 1-es kimeneten van egy led, ez lett a kimenet a locsolórendszer felé, ami HIGH szinttel jelzi, ha nem kell locsolni. Mindaddig 1 a kimenet, amíg az utolsó egy napban több csapadék esett, mint egy beállított érték, és akkor lesz nulla, ha az elmúlt három napban kevesebb csapadék esett, mint 1mm. Tehát egy kiadós eső után kb. 3 napig nem locsolunk. A kiadós eső kb. 6 mm előzetes számításaim szerint, de majd a tapasztalatokból kiderül.

A 3-es kivezetés lett a csapadékmérő kontaktusjelének bemenete. Ezt a bemenetet az átbillenés földre húzza le. Ez volt az egyetlen kivezetés, ami bemenetként problémamentesen működött. Ez ugyanis az USB kivezetés egyik bemenete, és van rajta egy felhúzó ellenállás és egy 3,6V-os zener, így folyamatosan HIGH szinten, van, de le lehet húzni földre. A 4-es kivezetésre kötöttem a nyomógombot. Ez viszont alapból nem működött, mert itt a zener lehúzza 0-ra a bemenetet, tehát a nyomógombot úgy kell bekötni, hogy +5V-ra húzza fel a bemenetet, és a programot is ehhez kell igazítani. Itt a kapcsolási rajz, hogy érthető legyen a probléma:

A 0, és 1-es kivezetések kellettek az I2C-hez, az 5-ös kivezetés pedig a RESET láb, ha bemenetnek állítom. Kimenetként még használható lett volna. Mivel pont az USB kivezetéseit használtam fel, arra számítottam, hogy nem fog menni a program feltöltés, csak ha leszedem a nyomógombokat. De nem volt gond, lehet közvetlenül szétszedés nélkül programot rátölteni (ha nem nyomom le a nyomógombot közben). Viszont az áramkör csak akkor működik rendesen, ha külső tápfeszt kap, mert az USB port a számítógépből hamis jeleket küld a 3-as kivezetésre, amiket az billenéskét érzékel.

Egy kicsit átalakítottam a programot azért is, mert a nyomógomb kezelését úgy kellett megoldani, nehogy elvesszenek átbillenés adatok. Ezért nem használhattam a gombnyomást követően delay()-t. Az időt figyelem és 1,5 másodperc után növelek egy változót, ami a következő kijelzési fázissal meghívja a kijelző írását végző függvényt. Van egy negyedik fázis is, amikor a locsolás tiltásának csapadék értékét jelzi ki. Ha ekkor a nyomógombot nyomva tartom, akkor itt a program megáll, és egy számlálót pörget 0-tól 20-ig. Amikor elérte a megfelelő adatot, elengedem a nyomógombot, és ezzel be-setup-oltam a locsolás tiltási értéket. Ezt az eeprom-ban tárolom. Itt használok delay()-t, tehát setup-olni csak esőmentes napokon szabad, különben átbillenést veszíthetünk. Néhány kép a kijelzés fázisairól:

Külön rutinba került az adatok összegzése is, amit csak óránként egyszer, illetve átbillenéskor hívok meg. Az utolsó 1, 3 és 10 nap adatait immár globális változókban tartom, és ha valami változik akkor azonnal frissítek, hogy a kijelző működésekor ne kelljen összegzést futtatni, csak megjeleníteni. Ezt már az első verzióban is csinálhattam volna így, csak akkor még nem volt kritikus az idő, illetve nem is foglalkoztam ezzel, hiszen kijelzőt csak óránként egyszer kellett frissíteni, hiszen folyamatosan látszik az adat.

És végül íme a forrás, ami 4,8K memóriát használ, valamint 157byte ram-ot. Igazán elégedett vagyok, a maradék 1,2K-ba beleférne még az Apolló űrhajó irányítása is.

#include <Wire.h>
#include <EEPROM.h>                     // Ismert hiba miatt ehhez előbb át kell másolni az Arduino alap könvtár EEPROM.h állományát

// Karakter bit térkép definíció
const byte font_5x8[] = {
 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
 0x00, 0x42, 0x7F, 0x40, 0x00, // 1
 0x42, 0x61, 0x51, 0x49, 0x46, // 2
 0x21, 0x41, 0x45, 0x4B, 0x31, // 3
 0x18, 0x14, 0x12, 0x7F, 0x10, // 4
 0x27, 0x45, 0x45, 0x45, 0x39, // 5
 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
 0x01, 0x71, 0x09, 0x05, 0x03, // 7
 0x36, 0x49, 0x49, 0x49, 0x36, // 8
 0x06, 0x49, 0x49, 0x29, 0x1E, // 9
 0x00, 0x60, 0x60, 0x00, 0x00, // .      -10
 0x08, 0x08, 0x08, 0x08, 0x08, // -      -11
 0x00, 0x00, 0x00, 0x00, 0x00, // sp     -12
 0x7F, 0x04, 0x08, 0x10, 0x7F, // N      -13
 0x7C, 0x12, 0x11, 0x12, 0x7C, // A      -14
 0x7F, 0x09, 0x09, 0x09, 0x06, // P      -15
 0x7C, 0x04, 0x18, 0x04, 0x78, // m      -16
 0x01, 0x01, 0x7F, 0x01, 0x01, // T      -17
 0x00, 0x41, 0x7F, 0x41, 0x00, // I      -18
 0x7F, 0x40, 0x40, 0x40, 0x40, // L      -19
 // Példa karakter (0): 
 // .xxx.  00111110
 // x...x  01010001
 // x..xx  01001001
 // x.x.x  01000101
 // xx..x  01111110
 // x...x
 // .xxx.
 // .....
};


int szamlalo;               //minden átbillenéskor növeljük az értékét
long ido;                   //ebben tároljuk az utolsó idő számláló állást, hogy 1 óránként múlva újra lefuthasson a szükséges programrész
byte ertek;                 //ebbe a változóba olvasom ki az aktuális értékét az adatrekordnak, mielőtt növelem 1-el
int cim;                    //az a cím, amiben 1-et találok a jelölő byte-on
int elozo_cim;              //az előző aktuális rekord címét tárolja amikor váltom a rekordot óránként
int csapadek24;             //az elmúlt 24 óra billenésszáma
int csapadek72;             //az elmúlt 3 nap billenésszáma
int csapadek10;             //az elmúlt 14 nap billenésszáma
int akt_cim;                //annak a rekordnak a sorszáma, amibe éppen írok 0-717 közötti szám, ami kettesével növekvő értékeket vehet fel: 0,3,5...
int index;                  //segédváltozó az adatok összegyűjtéséhez. Az éppen kiolvasott rekord kezdőcíme
int adat;                   //segédváltozó, hogy ne kelljen többször olvasni ugyanazt az eeprom cellát
byte kijelzes_fazis=1;      //a kijelző három lépésben jelzi ki az értékeket. 0=kijelző sötét, 1=utolsó 24h, 2=utolsó 3 nap, 4=utolsó 10 nap
                            //Azért egy az induló érték, hogy a bekapcsoláskor is megjelenjen egyszer az adat gombnyomás nélkül
long ido_tmp=millis()-1500; //Azért kap induló értéket, hogy a bekapcsoláskori első megjelenítésnek is jó legyen az időzítése

void setup()
{
   //kivezetés konfigurálás és eeprom törlés
  pinMode(1,OUTPUT);                 //4-es kivezetésre kötöttem a kontaktus jelét             

  pinMode(4,INPUT);                 //4-es kivezetésre kötöttem a kontaktus jelét             
  digitalWrite(4,HIGH);             //felhúzó ellenállás bekapcsolása   

  pinMode(3,INPUT);                 //2-es kivezetésre kötöttem a kontaktus jelét             
  digitalWrite(3,HIGH);             //felhúzó ellenállás bekapcsolása  
   
  // OLED SSD1306 meghajtó chip inicializálása
	Wire.begin();
  Wire.beginTransmission(0x3C); // Az SSD136 chip kijelölése az I2C buszon
  Wire.write(0x00);             // ezzel jelezzük az SSD1306 chip-nek, hogy parancsot fogunk küldeni
  Wire.write(0xAE);             // A kijelzőt sleep módba kapcsoljuk (lekapcsolja a pixeleket, parancsokat, adatokat fogad)
  Wire.write(0x20);             // Memória címzési mód beállítása következik
  Wire.write(0x00);             // Horizontális címzési mód bekapcsolása (00=Horizontal, 01=Vertical, 10=Page (RESET) mód)
  Wire.write(0xB0);             // Horizontális módban a start page címe (0-7 lehet, mert 8 page-re van osztva a memória)
  Wire.write(0x00);             // Oszlop cím alsó értéke
  Wire.write(0x10);             // Oszlop cím magas értéke
  Wire.write(0x40);             // --set start line address
  Wire.write(0xC8);             // Set COM Output Scan Direction
  Wire.write(0x81);             // A fényerő beállítása következik 0x00 - 0xFF
  Wire.write(0xFF);             // A fényerőt a maximumra állítjuk
  Wire.write(0xA1);             // Set Segment Re-map. A0=address mapped; A1=address 127 mapped. 
  Wire.write(0xA6);             // Set display mode. A6=Normal; A7=Inverse
  Wire.write(0xA8);             // Set multiplex ratio(1 to 64) 63
  Wire.write(0x3F);
  Wire.write(0xA4);             // Output RAM to Display, 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
  Wire.endTransmission();       //I2C küldés vége

  // Az inicializáló parancsok Arduino UNO esetében egyben kiküldhetők voltak (nincs összesen 32 byte), azonban ATtiny85 esetében
  // egyszer le kellett zárni a kommunikációt, és újra nyitni, mert különben a kijelző nem kapcsolt be. Érdekes, mert adatból 
  // lehet egyszerre 32 byte-ot küldeni, lásd képernyő törlés. Az I2C buszon egy menetben csak 32 byte-ot lehet küldeni, de ebben az esetben
  // ez nem volt igaz.
  Wire.beginTransmission(0x3C); //az SSD136 chip kijelölése az I2C buszon
  Wire.write(0x00);             // ezzel jelezzük az SSD1306 chip-nek, hogy parancsot fogunk küldeni
  Wire.write(0xD3);             // Set display offset. 
  Wire.write(0x00);             //00 = no offset
  Wire.write(0xD5);             // --set display clock divide ratio/oscillator frequency
  Wire.write(0xF0);             // --set divide ratio
  Wire.write(0xD9);             // Set pre-charge period
  Wire.write(0x22);
  Wire.write(0xDA);             // Set com pins hardware configuration 
  Wire.write(0x12);  
  Wire.write(0xDB);             // --set vcomh
  Wire.write(0x20);             // 0x20,0.77xVcc
  Wire.write(0x8D);             // Set DC-DC enable
  Wire.write(0x14);
  Wire.write(0xAF);             // kijelző bekapcsolása (pixelek működni kezdenek)
  Wire.endTransmission();//I2C küldés vége

	// Képernyő törlés, végig írjuk a kijelző memóriát 0-val
  // A memória 8 lapra van osztva, ezt kell beállítani először
  // egy lapon belül 128 byte-ot kell írni, de az I2C egyszerre csak 32 byte-ot tud kiírni.
	for (byte s=0;s<8;s++)
  {
    Wire.beginTransmission(0x3C);
    Wire.write(0x00);              // Azt jelezzük ezzel az SSD1306-nak, hogy parancsot fogunk küldeni
    Wire.write(0xB0 + s);          // Kijelöljük a memória lapot (0xB0 - 0xB7)
    Wire.write(0x00);              // Oszlop cím alsó része
    Wire.write(0x10);              // Oszlop cím felső része (azért van két részre bontva, mert a parancsok cím tartományokra vannak osztva
                                   // és az első 4 bit fenn van tartva a parancs értelmezésére, ezért két részletben lehet kiküldeni a 8 bites címet,
    Wire.endTransmission();
		for (byte i=0;i<8;i++) { 
      Wire.beginTransmission(0x3C);
      Wire.write(0x40); //write data
			for (byte o=0;o<16;o++) {	Wire.write(0);}
      Wire.endTransmission();
		}
	}

  // EEPROM törlést hajtunk végre, ha a setup alatt nyomva van a kijelzőt aktiváló nyomógomb. Kb 5 másodperc alatt indul
  // el az ATtiny85 miután tápfeszt kapott, ez alatt végig nyomni kell. Ha a törlés elindul, azt a LED villogása jelzi.
  if (digitalRead(4)==1) {              //alaphelyzetbe állítás, mert nyomtam indításkor a gombot, teljes eeprom törlés
   for (byte i=0;i<5;i++) {             //Villogtatjuk a LED-et 4-szer   
    digitalWrite(1,HIGH);delay(300);
    digitalWrite(1,LOW);delay(300);
   } 

   for (int i=0;i<480;i=i+2) {      // Ezzel a ciklussal töröljük végig az eeprom-ot (csak amit használunk, 32 byte még maradt másra, ha kell)
      EEPROM.write(i,0);
      EEPROM.write(i+1,0);
      //EEPROM.write(i+1,4);        //amikor teszteltem a működést, beállítottam minden rekordot 4-re, és figyeltem, ahogy halad előre
                                    //úgy felejti el az elmúlt időszak adatait, kivéve, ha közben nyomkodtam a pin3-ra kötött
                                    //nyomógombot. A nagyobb számok kijelzését 14 beírásával teszteltem. 14+336=4704, de ez még csak
                                    //kontaktus szám, ebből a kijelzett csapadék: 4704*0,3=1411mm. A felejtés helyes működését 1 beírásával
                                    //teszteltem, mert ekkor a kijelzett érték az utolsó 1 napnál 7,2, az utolsó 3 napnál 21,6 és az 
                                    //utolsó 2 hétnél 100. Elindítottam az óránkénti rekordváltással, és figyeltem, hogy valóban
                                    //1 nap alatt csökken az utolsó nap 7,2-ről 0-ra. 
    }
    EEPROM.write(0,1);              //az első rekordot megjelöljük, az lesz az aktuális.
    EEPROM.write(500,6);            //Az 500-as címen tároljuk azt az értéket, aminél a locsolórendszert le kell tiltani. 
                                    //Az itt tárolt érték mm-ben értendő, ha az utolsó 24 órában ennél több eső esett, akkor 
                                    //az 1-es kimenetet 1-re állítjuk, és folyamatosan így is marad. Ebből tudja a locsoló 
                                    //rendszer, hogy nem kell elindítani az időzített locsolást. Ezt a jelzést akkor töröljük, ha 
                                    //az utolsó 3 nap értéke 1 alá csökken, miközben az utolsó 24 órában semmi nem esett.
                                    //Alepértelmezetten 6mm-t állitok be, ezt meg lehet változtatni a nyomógombbal.
  }
  
  //meg kell állapítani az index változó értékét, ami megadja, honnan kell kezdeni az adatok kiírásakor 
  //az adatok kinyerését
  for (int i=0;i<480;i=i+2) {      //végig megyünk az összes adatrekordon 2 byte-onként
    if (EEPROM.read(i)==1) {
        akt_cim=cim;               //akt_cim-re az összegzés során van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
        break;                     //megtaláltuk az 1-et, nem kell befejezni a for ciklust
    }
  }
  osszegzes();                     // feltölti a csapadek24, csapadek72,csapadek10 változókat az aktuális értékkel (billenések száma az adott időszakban)
}

void loop()
{
 /**********************************************************************************************************************
  * kijelző adatmegjelenítésének vezérlése                                                                             *
  * gombnyomás után 2 másodperc várakozással sorban egymás után jelennek meg az utolsó 24 óra, 72 óra és 10 nap adatai *
  * ********************************************************************************************************************/
  if (digitalRead(4)==HIGH and kijelzes_fazis==0)  //ha megnyomtuk a nyomógombot és épp nincs adat kijelzés, akkor elindítjuk 
  {
    kijelzes_fazis=1;                             //ezzel a következő sorokban az utolsó 24 óra értékeinek kijelzése fog megtörténni
    ido_tmp=millis()-1500;                        //ezért, hogy a gombnyomás után azonnal megjelenjen a kijelzőn az adat
  }
  if (kijelzes_fazis>0 and millis()>ido_tmp+1500) //ha ki kell jelezni valamit, és eltelt 2 másodperc, akkor vesszük a következő adat kijelzést
  {
    kijelzes(kijelzes_fazis);                     //kijelezzük a kijelzes_fazis változó által meghatározott adatot (értékek lehetnek: 1,2,3,4)
    kijelzes_fazis=kijelzes_fazis+1;              //növeljük az értéket, hogy 2 másodperc múlva a következőt jelenítsük meg
    ido_tmp=millis();                             //innen indul a 2 másodperc időzítése
    if (kijelzes_fazis==6) {kijelzes_fazis=0;}    //a negyedik fázis a törlés, ezt követően 0-ra állunk, hogy várjuk a következő nyomógomb lenyomást
  }

 /**********************************************************************************************************
  * a csapadék szenzor átbillenéskor egy rövid idejű kontaktus jelet ad. Ezt érzékeljük                    *
  * a kontaktus jel elvileg nem prellezik, de ha mégis, akkor sem érzékeny rá a program, mert              *
  * fél másodperces időzítés van a végrehajtásban. Ez alatt a kijelző vezérlő nyomógombot sem érzékeljük   *
  * ********************************************************************************************************/
  if (digitalRead(3)==LOW)   // Átbillenés történt, növelni kell az aktuális rekord számlálóját       
  {
    delay(200);              // várunk fél másodpercet, hogy biztosan megtörténjen az átbillenés, nehogy kettőt számoljunk. Az átbillenés kevesebb
                             // mint egy tized másodperc alatt lezajlik, és addig zár a kontaktus, de nincs miért sietni, most mással nem kell foglalkoznia a programnak!
                             
    //történt egy átbillenés, ezért növelni kell az éppen aktuális gyűjtő rekord tartalmát 1-el
    //elősször megkeresem az aktuális rekordot, aztán az ahhoz tartozó adatot növelem.
    for (int i=0;i<480;i=i+2) {      //végig megyünk a rekordokon, amíg meg nem találjuk az 1-el jelölt rekordot
      if (EEPROM.read(i)==1) {       //ha az aktuális éppen 1, akkor megtaláltuk azt, amit növelni kell
        akt_cim=i;                   //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
        ertek=EEPROM.read(i+1);      //kiolvassuk a tárolt billenésszámot
        EEPROM.write(i+1,ertek+1);   //...és visszaírunk egy-el nagyobbat
        break;                       //nem kell tovább folytatni a for ciklust
      }
    }
   osszegzes();                      //Befrissítjük a csapadek24 csapadek72 csapadek10 változók értékét, mert átbillenéssel megváltoznak az adatok
  }

  /*********************************************************************************
  * Ha 1 óra telt el, megkeressük az aktuális adatrekordot, töröljük a jelzőjét    *
  * a következő rekord jelzőjét 1-re állítjuk, és az adat mezőjét 0-ra töröljük    *
  **********************************************************************************/
  if (ido+3600000<millis())      //ha 1 óra telt el, megkeressük az aktuális adatrekordot, jelzőt töröljük a számlálót növeljük.
                                 //teszteléskor ezt 2 másodpercre állítottam a "felejtés" kipróbálásához, és 10 másodpercre
                                 //a kontaktus számlálás teszteléséhez.
  {
    for (int i=0;i<480;i=i+2) {
      if (EEPROM.read(i)==1) {
        cim=i;
        break;
      }
    }
    elozo_cim=cim;
    cim=cim+2;                    //ez lesz a következő rekord címe
    if (cim>480) {cim=0;}         //ha az utolsó rekord volt megjelölve, akkor legelőről fogjuk folytatni
    akt_cim=cim;                  //akt_cim-re a kijelzőre íráskor van szükség, ez jelzi mi az aktuális rekord, ahonnan el kell kezdeni az összegzést
    EEPROM.write(cim,1);
    EEPROM.write(cim+1,0);
    //csak akkor törlöm az előző adatrekord jelölőjét, ha már beírtam a következőbe az 1-et. Ha áramszünet miatt nem tudnám
    //beírni az 1-et a következő rekordba, akkor legfeljebb egy órával többet ugyanebbe a rekordba írok tovább, de működik a rendszer
    //legalább egy rekordnak mindig lesz 1-es jelölője. Ha előbb törölném az 1-et, akkor előfordulhatna, hogy áramszünet miatt
    //nem tudom beírnia  jelölő 1-et a következő rekordba, és nem működne a gyűjtés újra indulás után.
    EEPROM.write(elozo_cim,0);
    ido=millis();                 //tároljuk a millis() értékét, így az if-ben vizsgálhatjuk, hogy eltelt-e 1 másodperc
    osszegzes();                  ////befrissítjük a csapadek24 csapadek72 csapadek10 változók értékét, mert eltelt egy óra és változhattak az adatok
  }
}

/*********************************************************************************************
 * Amikor megnyomjuk a nyomógombot, a kijelző sorban jeleníti meg az utolsó 1,3 és 10 nap    *
 * adatait. Ez a függvény paraméterként mindig megkapja, hogy éppen melyik fázisban jár      *
 * a kijelzés, és az annak megfelelő adatot jeleníti meg. A negyedik fázisban kijelzi a      *
 * locsolás kikapcsolásához szükséges csapadék mennyiséget. Ez be lehet állítani, ha eközben *
 * nyomva tartjuk a nyomógombot. 0-tól 20mm-ig folyamatosan számlál, és ott kell elengedni   *
 * a nyomógombot, amely értéket szeretnénk használni. A megjegyzett értéket az eeprom-ban    *
 * tárolja, és felhasználja futás közben az 1-es kimenet vezéréléére.                        *
 *********************************************************************************************/
void kijelzes(byte adat) {
  switch (adat) {
    case 1:
      //bekapcsoljuk a kijelzőt
      Wire.beginTransmission(0x3C);
      Wire.write(0x00); //write command
      Wire.write(0xAF);      // Display ON in normal mode
      Wire.endTransmission();
      //"mm" felirat a csapadékmennyiséggel azonos alap magasságban kisebb betűmérettel
      karakter_write(16,2,6,100);     //m
      karakter_write(16,2,6,112);     //m
      //1 NAP felirat
      karakter_write(12,3,0,0);       //sp  ez az előző kijelzés utolsó fázisának egyesét törli ("10 nap" egyese)
      karakter_write(1,3,0,15);       //1
      karakter_write(13,3,0,43);      //N
      karakter_write(14,3,0,61);      //A
      karakter_write(15,3,0,79);      //P
      szam_kiir((int)csapadek24*0.3*10); //csapadékmennyiség kiírása csapadek24 változó értéke alapján (utolsó 24 óra)
                                         //A 10-szeres értéket adjuk át, hogy a tizedes értéket is megjelenítsük, de kiíráskor
                                         //teszünk egy tizedespontot az utolsó számjegy elé
       //Serial.println(csapadek24*0.3*10);                                  
      break;
    case 2:
      //3 NAP felirat
      karakter_write(3,3,0,15);       //3
      karakter_write(13,3,0,43);      //N
      karakter_write(14,3,0,61);      //A
      karakter_write(15,3,0,79);      //P
      szam_kiir((int)csapadek72*0.3*10); //csapadékmennyiség kiírása csapadek72 változó értéke alapján (utolsó 72 óra)
                                         //A 10-szeres értéket adjuk át, hogy a tizedes értéket is megjelenítsük, de kiíráskor
                                         //teszünk egy tizedespontot az utolsó számjegy elé
      break;
    case 3:
      //10 NAP felirat
      karakter_write(1,3,0,0);        //1
      karakter_write(0,3,0,15);       //0
      karakter_write(13,3,0,43);      //N
      karakter_write(14,3,0,61);      //A
      karakter_write(15,3,0,79);      //P
      szam_kiir((int)csapadek10*0.3*10); //csapadékmennyiség kiírása csapadek24 változó értéke alapján (utolsó 24 óra)
                                         //A 10-szeres értéket adjuk át, hogy a tizedes értéket is megjelenítsük, de kiíráskor
                                         //teszünk egy tizedespontot az utolsó számjegy elé
      break;
    case 4:
      //TILT felirat
      karakter_write(17,3,0,0);       //T
      karakter_write(18,3,0,15);      //I
      karakter_write(19,3,0,43);      //L
      karakter_write(17,3,0,61);      //T
      karakter_write(12,3,0,79);      //sp
      szam_kiir((int)EEPROM.read(500)*10); //AZ eeprom-ban tárolt locsolórendszer kikapcsolási szintet jelezzük ki.
                                           //Ha ezen a ponton megnyomjuk a nyomógombot, és nyomva tartjuk, akkor folyamatosan 
                                           //számláltatni fogjuk az értékez, és amikor elengedjük a gombot, azt a pillanatnyi értéket 
                                           //tároljuk.
      break;
    case 5:
      if (digitalRead(4)==HIGH) {          //A setup érték kijelzése közben megnyomtuk a gombot, és nyomva tartjuk
        byte i=0;
        do {
        szam_kiir((int)i*10);
        delay(600);
        i=i+1;
        if (i>20) {i=0;}
        } while (digitalRead(4)==HIGH);
        EEPROM.write(500,i);
      }
      //kikapcsoljuk a kijelzőt
      Wire.beginTransmission(0x3C);
      Wire.write(0x00);       //write command
      Wire.write(0xAE);       // Display OFF (sleep mode)
      Wire.endTransmission(); //I2C küldés vége
  }
}

/**********************************************************************************************
 * A csapadék mennyiség kiírásakor a kijelző mindig egyszerre csak egy karaktert tud          *
 * megjeleníteni, ezért ez a függvény a paraméterben megkapott számot számjegyekre bontja     *
 * helyiértékenként, és kiírja a megfelelő helyre. Ha a százas vagy tízes helyiérték 0,       *
 * akkor oda nem ír semmit                                                                    *
 **********************************************************************************************/
void szam_kiir(int szam) {
  byte h_szam;
  bool nullakijelzes=0;
  karakter_write(10,3,5,64);         //előre kitesszük a tizedes pontot, mert annyira összezsúfoljuk a számokat, hogy jobbról is és balról is
                                     //bele fog törölni az előtte illetve utána álló számjegy, de ez nem baj, csak a sorrend fontos.
                                     //kisebb méretben raktam ki, mert nagyon bumfordi volt a 4-es méret. Így viszont egy sorral lejjebb kellet rakni.
  if (szam>999) {
    h_szam=szam/1000;          //ezres helyiértéken lévő számjegy meghatározása
    karakter_write(h_szam,4,4,0);    // számjegy kiírása
    szam=szam-h_szam*1000;           //maradék képzése
    nullakijelzes=1;                 //ha volt ezres, akkor a százashoz akkor is számot kell írni, ha az éppen nulla
  }
  else {karakter_write(12,4,4,0);}   //nem volt ezres számjegy, szóközt írunk
  
  if (szam>99 or nullakijelzes) {    //ha ez a szám kisebb mint száz, nem kell kiírni a nullát, kivéve, ha volt ezres számjegy
    h_szam=szam/100;           //százas helyiértéken lévő szám meghatározása
    karakter_write(h_szam,4,4,21);   //számjegy kiírása
    szam=szam-h_szam*100;            //maradék képzése
    
  }
  else {karakter_write(12,4,4,21);}  //sem ezres, sem százas számjegy nem volt így a százas helyén is szóközt írunk

  h_szam=(byte)szam/10;              //a tízes helyiértéket mindenképpen kiírjuk, ha nulla akkor is, mert tizedes pont van utána
  karakter_write(h_szam,4,4,43);     //számjegy kiírása
  szam=szam-h_szam*10;               //maradék képzése
  
  karakter_write(szam,4,4,76);       //egyes helyiérték kiírása (ez már tizedes érték, csak 10-el szoroztunk, hogy egyszerűbb legyen)
}

/************************************************************************************************
 * Ez a függvény végig olvassa az eeprom adat területét az aktuális rekordtól visszafelé        *
 * az utolsó 10 nap minden rekordjáig, és összegzi az adatokat. A csapadek24 az utolsó egy nap, *
 * a csapadek72 az utolsó 3 nap, a csapadek10 pedig az utolsó tiz nap átbillenéseinek számát    *
 * tartalmazza. Mivel a függvényt minden órában, és minen átbillenéskor meghívjuk, az a három   *
 * változó minden pillanatban az aktuális értéket tartalmazza. Miközben a kijelző sorban        *
 * egymás után megjeleníti az adatokat, közben is érkezhet átbillenés, de ez nem baj, mert      *
 * a belső eeprom olvasása elegendően gyors, befejeződik a kontaktus jel közben, ha netán pont  *
 * egyszerre történnének az események                                                           *
 ************************************************************************************************/
void osszegzes() {
  index=akt_cim;                            //akt_cim minden esetben az a cím, ahol éppen számoljuk a billenéseket
  csapadek24=0;                             //az utolsó 24 óra billenéseinek számát fogjuk gyűjteni  
  csapadek72=0;                             //az utolsó 72 óra billenéseinek számát fogjuk gyűjteni  
  csapadek10=0;                             //az utolsó 10 nap billenéseinek számát fogjuk gyűjteni  
  for (int i=0;i<240;i++) {                 //240 rekordot fogunk körbejárni 0 és 239 között
    adat=EEPROM.read(index+1);
    if (i<24) {                             //az utolsó 24 órához az első 24 rekord eredménye kell
      csapadek24=csapadek24+adat;
    }
    if (i<72) {                             //az utolsó 72 órához az első 72 rekord eredménye kell
      csapadek72=csapadek72+adat;
    }
    csapadek10=csapadek10+adat;
    if (index==0) {index=480;}              //a legelső rekordon állunk, mivel visszafelé haladunk a soron következő a 478-as cím (240-es rekord kezdőcíme)
    index=index-2;
  }
  int tilt=(EEPROM.read(500)/0.3);          //kiolvassuk az EEPROM-ban tárolt tiltási szintet (ha ennyi mm csapadék esik, akkor az 1-es kivezetés 1-be megy)
  if (csapadek24>tilt or csapadek72>tilt or (csapadek24==0 and csapadek72>4)) {digitalWrite(1,HIGH);} //Mind addig HIGH az 1-es kivezetés, amíg az utolsó 24 órában
                                                                                                      //(és az utolsó 3 napban) több csapadék esett, mint a beállított 
                                                                                                      //tiltási érték.
  else {digitalWrite(1,LOW);}               //már nem esett 3 napja eső (illetve csak nagyon kevés, összesen kb 1 mm, ezért az 1-es kivezetés LOW.
}

/***************************************************************************************************
 * Ez a függvény felrajzol egy karaktert a képernyőre. Mivel nincs teljes ASCII kódtábla egyéni    *
 * karakterkódokat használok. A függvény egyszerre csak egy karaktert rajzol fel, tehát szöveg     *
 * esetén betűnkén kell meghívni.                                                                  *
 * Paraméterek:                                                                                    *
 *   - kod, ez a karakter egyedi általam önkényesen kialakított kódja, a font_5x8[] tömb határozza *
 *          meg azzal, hogy milyen sorrendben töltöm fel az 5db karakterképet tartalmazó byte-al.  *
 *   - nagyitas, ez a paraméter határozza meg a felrajzolt karakter méretét, lehet 2,3,4 szeres.   *
 *          A maximális nagyitás 4, mert long változót használok, és abban csak négy byte fér el   *
 *          (a long mérete 32 bit azaz 4 byte)                                                     *
 *   - kezdo_lap, ez a paraméter határozza meg, hogy a karakter bal felső sarka melyik memória     *
 *          lapon fog kezdődni. A 128x64-es kijelző 8 lapra van bontva, így értéke 0-7-ig. Mivel   *
 *          a legkisebb karakter a 2-es nagyítás, legfeljebb 6 lehet a maximális mérete, de pl.    *
 *          4-es nagyításnál már csak a 4. lapon kezdhetjük a karakter megjelenítését.             *
 *   - kezdo_oszlop, ez a paraméter mondja meg, hogy hol kezdődik a karakter bal oldali legelső    *
 *          oszlopa. Egy karakter bárhová helyezhető egy soron belül, így ennek értéke 0-127-ig    *
 *          bármi lehet, de pl. egy 2 szeres nagyítású betű legfeljebb a 117-ban kezdhető,         *
 *          mert a karakter mérete 10 oszlop.                                                      *
 *                                                                                                 *
 * Működése:                                                                                       *
 *   Először a kijelzőt átkapcsolja vertikális módba, azaz a fogadott adatokat a kijelző           *
 *   föntről lefelé jeleníti meg, majd amikor elérte a kijelölt rajzolási terület alját,           *
 *   arrébb meg egy oszloppal, és ismét föntről lefelé jeleníti meg a fogadott byte-okat.          *
 *   A megadott lap, és oszlop alapján kiszámolja a karakter bel felső pontját, és a jobb          *
 *   alsó pontját, és ezt megadja a kijelzőnek.                                                    *
 *   A nagyítás paraméter alapján veszi a karakter egy byte-ját, és belépteti egy long változóba   *
 *   úgy, hogy annyi bitet léptet be egy karakter bit alapján, amennyi a nagyítás. Pl. négyes      *
 *   nagyítás esetén 4 azonos bitet léptet egy db bit helyett. Kétszeres nagyításnál csak kettőt   *
 *   stb. Ha elkészült a long változó, akkor kiküld annyi bájtot a kijelzőre, amennyi a nagyítás   *
 *   mértéke, tehát pl. 3-szoros nagyításnál 3 byte-ot. Aztán veszi a karakterkép következő        *
 *   byte-ját, azt is felnagyítja egy long változóba és kiküldi. Ezt a műveletsort 5-ször végzi    *
 *   el, mert egy karakterkép 5 byte-ból áll                                                       *
 ***************************************************************************************************/
void karakter_write(byte kod, byte nagyitas,byte kezdo_lap,byte kezdo_oszlop)
{
	byte karakterbyte;
	unsigned long nagy_karakter=0;
	unsigned long nagy_karakter2=0;
	bool k;
	unsigned long leptetbyte;  //ebbe fogjuk a 32 bites karakterképet byte-onként kiléptetni
	unsigned long maskbyte=255; //ezzel maszkoljuk ki a 32 bites karakteroszlopból az egy byte-ot
	byte maskbit=128;
	Wire.beginTransmission(0x3C);              //az SSD136 chip kijelölése az I2C buszon
	Wire.write(0x00);                          //Ezzel jelezzük, parancsokat fogunk küldeni
	Wire.write(0x20);                          //Írási mód beállítása következik
	Wire.write(0x01);                          //Függőleges írási mód
	Wire.write(0x21);                          //első és utolsó oszlop megadása
	Wire.write(kezdo_oszlop);                  //Kezdő oszlop cím
	Wire.write(kezdo_oszlop+(nagyitas+1)*5);   //záró oszlop cím
	Wire.write(0x22);                          //első és utolsó lap cím
	Wire.write(kezdo_lap);                     //Az a lap, ahol a karakter kezdődik
	Wire.write(kezdo_lap+nagyitas-1);          //az a lap, ahol a karakter befelyeződik
	Wire.endTransmission();                    //kommunikáció vége
	for (byte i=0;i<5;i++)
	{
    //Serial.println(i);
		maskbit=128;       //maszkbit 1 bitjét fogjuk léptetni jobbra
		karakterbyte=font_5x8[kod*5+i]; //ez a karakter soron következő karakterbyte-ja
		nagy_karakter=0;   //ebbe léptetjük be a biteket többszörözve
		for (byte j=0;j<8;j++)
		{
			k=karakterbyte & maskbit;   //kimaszkoljuk a karakterbyte bitjét
			nagy_karakter=nagy_karakter << nagyitas; //léptetjük a 32 bites karaktert 
			if (k>0)
			{
				switch (nagyitas)
				{
					case 2:
						nagy_karakter |= 3;
						break;
					case 3:
						nagy_karakter |= 7;
						break;
					case 4:
						nagy_karakter |= 15;
						break;
				}
			}
			maskbit=maskbit >> 1;
		}
		//itt kell a ki jelzőre küldeni a 32 bitet
    Wire.beginTransmission(0x3C);              //az SSD136 chip kijelölése az I2C buszon
    Wire.write(0x40);                          //Ezzel jelezzük, hogy adatokat fogunk küldeni
		for (byte m=0;m<nagyitas;m++)              //a nagyitás miatt többször ugyanazt írjuk ki de mindig eggyel jobbra a következő oszlopba
		{
			nagy_karakter2=nagy_karakter;            //többször fog kelleni az oszlop tartalom
			for (byte l=0;l<nagyitas;l++)   
			{
				leptetbyte=nagy_karakter2 & maskbyte;  //ebben van az adott oszlop aktuális byte-ja
				Wire.write(leptetbyte);                //most írjuk ki az oszlop tartalmat (ha van nagyítás, akkor egymás alá több byte, mind ugyanabba az oszlopba
				nagy_karakter2=nagy_karakter2 >> 8;   
			}
		}
    Wire.endTransmission();
	}
}

Mennyire volt hasznos amit olvastál? Értékelés után szövegesen is leírhatod megjegyzéseidet és véleményedet!

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet! Ha kérdésed van, ne felejtsd el megadni az email címedet!