PCF8563 RTC óra

Kezdetben a DS3231 RTC óra chip-et illetve modult kezdtem el használni. Minden webáruházban ebbe akad bele először, aki óra IC-t keres megának. Nincs is vele semmi baj, nagyon pontos, hőmérsékelt kompenzált, két időpontot is lehet riasztásra használni stb. Egy aprócska problémám adódott. Az IC INT kimenete, amit egy ATmega chip felélesztésére szerettem volna használni, csak bekapcsolt tápfesz esetén működik. Ha elemről működik az óra IC, akkor az INT kimenete nem küld megszakítást. Viszont, ha tápfesszel látom el folyamatosan a modult (mert én nem csupasz IC-t használtam), már elég tekintélyes áramfelvétel adódott, ami elemes áramköröknél nagy hátrány. Hát szétnéztem, hogy mi van még a piacon. Találtam is egy őskövületet az 1995 környékéről. Ez a PCF8563 IC-re épülő modul. Áramfelvétele ha nem használom az I2C kommunikációt, akkor szinte elenyésző, adatlap szerint 250-300nA. Az INT kimenete mindig működik, mert ez az IC annyira korszerűtlen, hogy nincs külön elemes táplálásra szolgáló tápfesz bemenete, nem is tud arról, hogy elemről működik. Szóval az én céljaimra tökéletes. Bónusz tulajdonság, hogy van beépített timer, ami beállítható időnként ad megszakítás jelet az INT kimeneten, vagyis nem kell azzal vacakolnom, hogy egy megszakítást követően kiszámolgassam a következő riasztási időpontot és beállítsam a riasztási idő regiszterekben. Hátrány, hogy ez a timer nem egy rugalmasan beállítható darab, de esetemben néhány perces, esetleg néhány órás időintervallumokat szerettem volna beállítani, így számomra ez is tökéletes.

Lássunk néhány műszaki adatot:

  • tápfeszültség:1,8V – 5,5V
  • áramfelvétel ha nem használom az I2C kommunikációt: 250nA
  • timer intervallumok: ~2mikrosec-től 4 óra 15 percig. (hosszú időzítéseknél 1 perces felbontással)

Az óra IC beállítása nagyon hasonló a DS3231-nél használatos megoldásokhoz. Van egy csomó regisztere, amiket lehet írni és olvasni. Az I2C buszon az IC a Hex 0x51 címen (decimális 81) érhető el. A regisztereinek összefoglaló táblázata:

A 0-val jelölt biteket mindig 0-val kell írni, olvasáskor a kiolvasott érték 0 és 1 is lehet.
Az „x”-el jelölt bitek nincsenek használva. Regiszter beállításakor tetszőleges értéket lehet írni az adott bit pozícióba, amikor ezeket visszaolvassák, a visszaolvasott értékek eltérhetnek beírt értéktől.

Néhány tulajdonság azoknak, akik használni szeretnék:

  • Az idő beállításához az I2C buszon keresztül az 0x02-től az 0x08 regiszterig kell beírni az adatokat nagyrészt BCD kódolással. Kiolvasni ugyanezeket a regisztereket kell.
  • A riasztási időpont beállítása a 0x09-tól a 0x0C regiszterekkel lehetséges. Csak az óra és perceket állíthatjuk be, azonban lehetőség van óránként azonos percben, naponta, hónap adott napján, vagy hét adott napján történő ismétlésre. Ezt úgy lehet elérni, hogy a regiszterek balról első bitjébe 1-et kell írni annál a paraméterben, aminek az egyezőségét nem szeretnénk figyelni. Pl. ha csak a perc regiszterben adunk meg ezen a biten 0 értéket, a többi raisztás regiszterben itt 1-et írunk, akkor minden órában ugyanabban a percben fog riasztani. Ha pl. beállítjuk a percet, órát és a hét egy adott napját, akkor csak a hétnek az adott napján fog a megadott időben risztani. A példa programban csináltam egy setAlarm() nevű függvényt négy bemenő paraméterrel (sorrendben: perc, óra, hónap napja, hét napja). Néhány példa ezzel a függvénnyel:
    – riasztás minden óra minden adott (15.) percében -> setAlarm(15,99,99,99)
    – riasztás minden nap 15 óra 14 perckor -> setAlarm(14,15,99,99)
    – riasztás minden hónap 5. napján 15 óra 14 perckor -> setAlarm(14,15,5,99)
    – riasztás minden hétfőn (1 nap) 15 óra 14 perckor -> setAlarm(14,15,99,1)
  • Létezik egy időintervallum beállítási lehetőség, aminek a segítségével ciklikusan a beállított időintervallumban keletkezik riasztás. Az intervallum megadáshoz egy byte áll rendelkezésre, tehát 1-255 közötti értéket állíthatunk be. Erről az értékről mindig visszaszámol 0-ra, és ekkor ad megszakítás jelet az INT kimeneten. Persze a visszaszámlálás sebessége beállítható. Az órajel ami ezt végzi 4096Hz, 64Hz, 1Hz, és 1/60Hz lehet. Ezzel gyakorlatilag kb. 2miliSec és 4 óra 15 perc között állíthatunk be riasztási (időzítési) időintervallumot. Azonban a beállítható idő felbontása is elég korlátozott, pl. a legnagyobb időzítésnél már csak 1 perc. De ugyan kit érdekel 4 órás időzítésnél, hogy nem adhatja meg másodperc pontosan az időintervallumot!
    További hátrány, hogy a visszaszámlálás órajelét a folyamatosan működő órajel osztóból kapja a visszaszámoló regiszter, így az első számlálási időzítés meglehetősen pontatlan is lehet. Amikor elindítjuk a visszaszámlálást, szerencsétlen esetben szinte azonnal megkapjuk ez első visszaszámoló impulzust, miközben a következőre már fix és pontos időt kell várni. Pl. ha az időalap 1/60Hz, és 2-et állítunk be a timer regiszterbe, akkor lehet hogy az első időzítésünk 1 perc lesz, míg ettől kezdve a többi már stabilan és pontosan az elvárt 2 perc. Ezzel meg kell békélni! Ez kőkorszaki masina, lehet, hogy még ma is kőbaltával faragják a szilicium lapkát.
  • Az időpont és a visszaszámláló timer riasztás szabadon keverhető, akár mindkettő működhet egyszerre. Azt, hogy az INT kimenet melyik riasztásra reagáljon 0 szinttel (folyamatosan magas a kimenet, de riasztáskor lehúzza az IC 0-ra), az a STATUS2 regiszterben beállítható. Jobbról a második AIE (alarm interrupt enable) bittel az időpont riasztás, míg jobbról az első TIE (timer interrupt enable) bittel a timer riasztás megszakítást engedélyezhetjük. Ha bármelyik 1, akkor az INT kimenet 0 lesz riasztáskor. A riasztás bekövetkeztét pedig jobbról a 4. AF (alarm flag) és jobbról a 3. TF (timer flag) bitek jelzik. Ezek 1-re billennek be, ha elérte az óra a beállított időpontot, vagy a timer leszámolt 0-ra. Azt nagyon fontos tudni, hogy a riasztás bebillenti a megfelelő bitet (AF vagy TF), de semmi nem billenti vissza 0-ba. Márpedig az INT kimenet mindaddig 0, amíg ezen bitek valamelyike 1. Tehát ha egy riasztás megtörtént, akkor szoftverből gondoskodni kell ezen bitek törléséről. Ha a megfelelő bitet töröltük, az INT azonnal újra 1 lesz, és várhatjuk is a következő riasztás.
  • A timer riasztás használatában van egy picike könnyítés. Ha a STATUS2 regiszter jobbról 5. bitjét a TI/TP bitet 1-re állítjuk, akkor nem kell a TF bittel vacakolni. Minden egyes visszaszámlálás okozta riasztás után az INT kimenet vissza áll 1-re. A visszaállási idő késleltetése a visszaszámlálási frekvenciától függ, de maximális értéke is 1/64 másodperc. Végül is ez közömbös, hiszen itt a lefutó élet fogjuk figyelni.  

Ehhez az RTC óra chip-hez nem találtam számomra megfelelő könyvtárat. Van könyvtár hozzá, de túl sok függvényt valósítottak meg benn, külön függvény van a dátum beállításra, az idő beállításra, külön lehet lekérdezni az órát, percet stb. Írtam hát egy saját függvény gyűjteményt, amit a végletekig egyszerűsítettem, vagyis minimalizáltam a meghívható függvények számát. Ha valaki hasznát veszi, annak alább a minta program, és részletes kommentekkel az összes függvény. Sok sikert a használatához!

#include <Wire.h>
int PCF8563_I2C_cim = 81;

// globális változók az időpont tárolásához. A getDateTime() függvény 
// ezen változók érétkét tölti az aktuális dátummal és időponttal
byte ora;
byte perc;
byte sec;
byte nap;
byte het_napja;
byte honap;
int ev;
// globális változók a riasztási időpontok és flag-ek tárolásához. A getAlarm() függvény
// ezen változók értékét tölti fel az aktuális riasztási időpont adatokkal
byte alarm_ora;
byte alarm_perc;
byte alarm_nap;
byte alarm_het_napja;
bool alarm_flag; //a getAlarm() geletölti az STATUS2 regiszter AF flag állapotát (0 esetén még nem volt riasztás)
bool timer_flag; //a getAlarm() geletölti az STATUS2 regiszter TF flag állapotát (0 esetén még nem volt riasztás)
bool int_alarm; //a getAlarm() geletölti az STATUS2 regiszter AIE flag állapotát (0 esetén INT tiltva, 1 engedélyezve)
bool int_timer; //a getAlarm() geletölti az STATUS2 regiszter TIE flag állapotát (0 esetén INT tiltva, 1 engedélyezve)
byte timer; //a getAlarm() geletölti az timer számláló regiszter (0x0F) tertalmát, ha 0-ra ér, akkor riaszt (TF=1)

//byte status2;
long ido_tmp=millis();  //segéd változó a másodpercenkénti idő kiíráshoz

void setup()
{
  pinMode(13, OUTPUT);           // A beépített LED fogj jelezni a PCF8563 INT kivezetésének állapotát
  pinMode(3, INPUT);           // Ezen a bemeneten fogjuk figyelni PCF8563 INT kivezetésének állapotát
  digitalWrite(3, HIGH);       // felhúzó ellenállás bekapcsolása a 3-as bemeneten
  //megvillogtatjuk a beépített ledet, hogy lássuk az alaplapon melyik az
  delay(500);
  digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);delay(100);digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);delay(100);
  digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);delay(100);digitalWrite(13,HIGH);delay(100);digitalWrite(13,LOW);
  delay(500);
  Wire.begin();
  Serial.begin(9600);
  
  //Egyszer be kell állítani a dátumot és az órát
  //Paraméterek sorrendje: év, hó, nap, hét napja, óra, perc
  //setDateTime(2020,9,8,2,7,43);

  //a riasztási üzemmódok beállítása
  // Paraméterek sorrendje: időpont riasztás, timer riasztás
  // Nem szükséges kiadni, mert setAlarm() és a setTimer() is beállítja magának
  // a szükséges biteket a STATUS2 regiszterben. Azonban flag-ek törlésére és 
  // a megszakítás (INT) kimenet tiltására ezt lehet felhasználni
  // Az időpont és a timer riasztás szabadon keverhető, egyszerre mindkettőt
  // be lehet kapcsolni
  setAlarmTimerMode(0,0);
  
  // Ha az időpont riasztást teszteljük, akkor setAlarm() fügvénnyel állíthatjuk be az időpontot
  // Paraméterek sorrendje: perc, óra, nap, hét napja
  //   Ha periódikus riasztást akarsz, akkor 99-et kell megadni annál a paraméternél, 
  //   amit nem kell vizsgálnia a chip-nek
  //   pl. minden óra minden adott (15.) percében -> 15,99,99,99
  //   pl. minden nap 15 óra 14 perckor -> 14,15,99,99
  //   pl. minden hónap 5. napján 15 óra 14 perckor -> 14,15,5,99
  //   pl. minden hétfőn (1 nap) 15 óra 14 perckor -> 14,15,99,1
  setAlarm(46,99,99,99);
  
  //Riasztás beállított időintervallumonként (ismétlődő).
  //Paraméterek sorrendje: időzítési idő (másodperc)
  //Az időzítési idő csak 1-tól 15300 másodpercig (aza 255 perc, ami 4óra 15 perc) terjedhet
  //255 másodperc után már csak perc pontosságú azaz, egész percre kerekíti a beállított értéket (lefele kerekít egészre)
  setTimer(12); //10 másodperc


  getAlarm();  //lekérdezi a riasztással kapcsolatos paramétereket és biteket
  Serial.println("Ido riasztás status:"); 
  Serial.println("===================="); 
  Serial.print("           AF flag:"); Serial.println(alarm_flag);
  Serial.print(" Megszakítás (INT):"); Serial.println(int_alarm);
  Serial.print("              Perc:"); if (alarm_perc==99) {Serial.println("-");} else {Serial.println(alarm_perc);} 
  Serial.print("               Ora:"); if (alarm_ora==99) {Serial.println("-");} else {Serial.println(alarm_ora);} 
  Serial.print("       Honap napja:"); if (alarm_nap==99) {Serial.println("-");} else {Serial.println(alarm_nap);}
  Serial.print("         Het napja:"); if (alarm_het_napja==99) {Serial.println("-");} else {Serial.println(alarm_het_napja);} 
  Serial.println("Timer riasztás status:"); 
  Serial.println("======================"); 
  Serial.print("           TF flag:"); Serial.println(alarm_flag);
  Serial.print(" Megszakítás (INT):"); Serial.println(int_timer);
  Serial.print("          Szamlalo:"); Serial.println(timer);
}

void loop()
{
  if (ido_tmp<millis()){      //másodpercenként kiirjuk az időt a soros portra
    getDateTime();
    Serial.print("Datum:"); Serial.print(ev); Serial.print(":"); Serial.print(honap); Serial.print(":"); Serial.print(nap);
    Serial.print("  Het napja:"); Serial.print(het_napja);
    Serial.print("  Ido:"); Serial.print(ora); Serial.print(":"); Serial.print(perc); Serial.print(":"); Serial.println(sec);
    ido_tmp=millis()+1000;
  }
  if (digitalRead(3))          //AZ INT kimenet állapotát beolvassuk 
    {digitalWrite(13,HIGH);}   //INT kimenet HIGH, tehát nincs megszakítás
  else {
    digitalWrite(13,LOW);      //INT kimenet LOW, tehát megszakítás érkezett, törölni kell a TF bitet
    Serial.println("Riasztas");
    delay(200);                //ha ezt nem tesszük be, nem látható, hogy a led egy pillanatra elalszik amikor riasztás jött. 
    clearAlarm();              //Törli az AF és a TF bitet a status2 regiszterben, így jöhet egy újabb riasztás ami majd bebillenti ezek valamelyikét
  }
}

void clearAlarm()
/********************************************************************************************************
 * STATUS2 (0x01) regiszter AF (BIT3) és TF (BIT2) törlése                                              *
 * Amikor az időpont riasztás megtörténik az AF flag bebillen 1-re. Ha a timer riasztást történik meg   *
 * a TF flag billen be 1-re. Egy újabb riasztást csak akkor lehet észre venni, ha ezen biteket 0-ra     *
 * billentjük vissza. Mivel az INT kimenet akkor generál megszakítást, ha az AF vagy a TF 1, ezért      *
 * Ezen bitek törlése az INT kimeneten is megszünteti a LOW szintet. Az INT LOW szintű megszakításához  *
 * még engedélyezni kell a megszakítás láb (INT) működését is a AIE illetve a TIE bitekkel.             *
 ********************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);     //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2 = Wire.read();                    //beolvassuk a STATUS2 regiszter tartalmát
  status2=status2 & 0b11110011;
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.write(status2);                      //Írjuk a STATUS2 regisztert az új értékével
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void setTimer(int masodperc)
/***************************************************************************************************************
 * Beállítja a timert, ami ciklikusan megszakítást generál az INT kimeneten, amikor a számláló 0-ra számol     *
 * vissza a beállított értékről (max 255). Ha a STATUS2 regiszterben a TI_TP bit értéke 0, akkor a TI bitet    *
 * a STATUS2 regiszterben minden megszakítás után törölni kell, különben INT folyamatosan alacsony marad       *
 * az első generált timer riasztás után. Ha TI_TP bit értéke 1, akkor az INT kimenet egy órajel ciklus után    *
 * automatikusan HIGH lesz, vagyis nem kell törölni a TF bitet. Ez a függvény nem használja ki ezt a           *
 * lehetőséget, mert így egységesen ugyanúgy kell lekezelni a timer riasztást, mint ha időpont riasztás        *
 * történt volna.                                                                                              *
 * Bemenő paramétere a visszaszámlálási ciklus időtartama másodpercben megadva. Ennek lehetséges intervalluma  *
 * 1-15300 sec. Amíg a bemenő paraméter értéke 1-255, addig a timer számláló visszaszámoló időalapja           *
 * 1hz-ra állítódik be, ha az érték 255-nél nagyobb, akkor 1/60Hz-re. Utóbbi esetben már csak egy perces       *
 * felbontásban lehet a riasztást beállítani 1-255 perc között. Ha ennél pontosabb beállítás szükséges         *
 * az időpont riasztást lehet felhasználni a következő riasztási időpont kiszámolásával és beállításával       *
 * Az első timer riasztás időintervalluma nem pontos. Az 1/60Hz előállításakor az órajel az óra egész          *
 * perceikor keletkezik, így szerencsétlen esetben akár 1 perccel kevesebb is lehet az eltelt idő.             *
 * Pl 4. perc beállításakor (bemenő paraméter 256), és ezt pont az adott perc 59. másodpercében állítjuk       *
 * be, a következő másodpercben már számol is egyet vissza a timer, így 3 perc múlva jön a riasztás. A további *
 * időintervallumok már pontosak. Ez a működés minden bizonnyal igaz a többi visszaszámolási frekvenciára      *
 * is, tehát pl. az 1Hz frekvencia is egész másodperc értékeknél keletkezik.                                   *
 * Időalap lehetséges értékei:                                                                                 *
 *  1 - 0,000244140625 sec (0,244 milisec, 0,24 msec - 62,2 msec) ==> BIT1=0,BIT0=0                            *
 *  2 - 0,015625 sec (15,6 milisec 15 msec - 3,98 sec)==> BIT1=0,BIT0=1                                        *
 *  3 - 1 sec (1 sec - 4,25 perc)==> BIT1=1,BIT0=0                                                             *
 *  4 - 1/60 sec (60 sec 1 perc - 4,25 óra)==> BIT1=1,BIT0=1                                                   *
 *   (ezekből mi csak a 3-as és 4-es paramétert fogjuk használni)                                              *
 ***************************************************************************************************************/
{
  byte ciklus;                              //ebben a változóban adjuk meg a "timer" regiszter értékét 
  byte timercontrol;                        //ebben a változóban képezzük a "timer control" regiszter értékét
  if (masodperc == 0) {ciklus = 1;}         //a ciklus minimuma 1
  if (masodperc<256){timercontrol = 0b10000010;ciklus=masodperc;} //BIT7=1 engedélyezi a timer működését és 1Hz-es visszaszámolást állít be
  else {timercontrol = 0b10000011;ciklus=masodperc/60;} //BIT7=1 engedélyezi a timer működését és 1/60Hz-es visszaszámolást állít be
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);     //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2 = Wire.read();                    //beolvassuk a STATUS2 regiszter tartalmát
  status2 = status2 & 0b11101111;           //A BIT4 TI/TP=0, TI-t mindig törölni kell riasztás után
  status2 = status2 & 0b11111011;           //A BIT2 TF=0, a következő timer riasztás bebillentheti 1-re
  status2 = status2 | 0b00000001;           //A BIT0 TIE=1, engedélyezett az interrupt kimenet (INT) amikor TF bebillen 1-re
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.write(status2);                      //írjuk a STATUS2 regisztert az új értékével
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x0E);                         //"timer control" regiszter címzése
  Wire.write(timercontrol);                 //írjuk a "timer control" regisztert az új értékével
  Wire.write(ciklus);                       //visszaszámlálási ciklusszám beállítása
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}



void setAlarm(byte perc, byte ora, byte nap, byte het_napja)
/*******************************************************************************************************************
 * Ez a függvény beállítja a riasztási időpontot. A lehetséges parméterek a perc, óra nap és hét napja             *
 * A beállított időpont ciklikusan ismétlődő riztást fog eredményezni amikor a beállított paraméter megegyezik     *
 * az aktuális időponttal. Pl. ha csak a percet adjuk meg, akkor minden órában időpont riasztás fog generálódni.   *
 * Ha egy paramétert nem akarunk megadni, akkor annak értéke legyen 99. Ekkor az adott paramétert Pl. hét napját   *
 * Nem fogja figyelni, és csak a többi paraméter egyezőségét figyeli.                                              *
 * Pl.  riasztás minden óra minden adott (15.) percében -> setAlarm(15,99,99,99)                                   *
 *      riasztás minden nap 15 óra 14 perckor -> setAlarm(14,15,99,99)                                             *
 *      riasztás minden hónap 5. napján 15 óra 14 perckor -> setAlarm(14,15,5,99)                                  *
 *      riasztás minden hétfőn (1 nap) 15 óra 14 perckor -> setAlarm(14,15,99,1)                                   *
 * A STATUS2 regiszterben az AF bitet minden egyes riasztás után törölni kell, különben a következő ciklikus       *
 * riasztás nem jut érvényre hiszen már 1 az AF értéke. Ez a függvény a riasztási időpont beállításakor            *
 * beállítja megának a STATUS2 regiszterben az AF bitet 0-ra, valamint beállítja 1-re az AIE bitet, ami            *
 * engedélyezi az INT kimenet működését. INT kimenet LOW szinttel jelzi a riasztás bekövetkeztét. INT akkor        *
 * lesz újra HIGH, ha az AF bitet töröljük, egyébként folyamatosan LOW marad a riasztás bekövetkezte után.         *
 *******************************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //megadjuk a STATUS2 regiszter címét
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);     //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2 = Wire.read();                    //beolvassuk a status regiszter tartalmát
  if (perc < 99) {                          //Ha a perc nem 99, akkor értékét figyelni fogja a chip riasztáskor
    perc=constrain(perc, 0, 59);
    perc=decToBcd(perc);
    perc=perc & 0b01111111;                 //ha 0x09 regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {perc = 0b10000000;}                 //ha 0x09 regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  if (ora < 99) {                           //Ha az óra nem 99, akkor értékét figyelni fogja a chip riasztáskor
    ora=constrain(ora, 0, 23);
    ora=decToBcd(ora);
    ora=ora & 0b01111111;                   //ha 0x0A regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {ora = 0b10000000;}                  //ha 0x0A regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  if (nap < 99) {                           //Ha a nap nem 99, akkor értékét figyelni fogja a chip riasztáskor
    nap=constrain(nap, 1, 31);
    nap=decToBcd(nap);
    nap=nap & 0b01111111;                   //ha 0x0B regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {nap = 0b10000000;}                  //ha 0x0B regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  if (het_napja < 99) {                     //Ha a hét napja nem 99, akkor értékét figyelni fogja a chip riasztáskor
    het_napja=constrain(het_napja, 0, 6);
    het_napja=decToBcd(het_napja);
    het_napja=het_napja & 0b01111111;       //ha 0x0C regiszter 7. bitje 0, ekkor a regiszter értékét is figyeli riasztáskor
  } 
  else {het_napja = 0b10000000;}            //ha 0x0C regiszter 7. bitje 1, ekkor a regiszter értékét nem figyeli riasztáskor
  status2 = status2 & 0b11110111;           //A BIT3 AF=0, a következő riasztás bebillentheti 1-re
  status2 = status2 | 0b00000010;           //A BIT1 AIE=1, engedélyezett az interrupt kimenet
  Wire.beginTransmission(PCF8563_I2C_cim);  // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                         //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.write(status2);                      //kiírjuk az I2C buszon a STATUS2 regiszter új tartalmát a slave-re
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.beginTransmission(PCF8563_I2C_cim);  //megszóllítjuk a I2C buszon a slave-et
  Wire.write(0x09);                         //alarm perc regiszter címét állítjuk be
  Wire.write(perc);                         //beírjuk a riasztás perc értékét a 0x09 regiszterbe
  Wire.write(ora);                          //beírjuk a a riasztás óra értékét a 0X0A regiszterbe (soron következő cím a slave-en)
  Wire.write(nap);                          //beírjuk a a riasztás nap értékét a 0X0B regiszterbe (soron következő cím a slave-en)
  Wire.write(het_napja);                    //beírjuk a a riasztás hét napja értékét a 0X0C regiszterbe (soron következő cím a slave-en)
  Wire.endTransmission();                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void setAlarmTimerMode(bool alarm, bool timer) 
/******************************************************************************************************************
 * Ez a függvény beállítja vagy törli az időpont illetve timer riasztás megfelelő bitjeit a STATUS2 regiszterben  *
 * Az AF és TF riasztás flegeket törli, a következő riasztás bebillentheti ezeket újra.                           *
 * bemenő paraméterek:                                                                                            *
 * alarm=1 AIE=1 időpont megszakítás engedélyezve az INT kimeneten                                                *
 * alarm=0 AIE=0 időpont megszakítás tiltva az INT kimeneten                                                      *
 * timer=1 TIE=1 timer megszakítás engedélyezve az INT kimeneten                                                  *
 * timer=0 TIE=0 timer megszakítás tiltva az INT kimeneten                                                        *
 ******************************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);   // megszólítjuk a slave egységet a címével
  Wire.write(0x01);                          //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.endTransmission();                    //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);      //1 byte-ot kérünk az I2C buszon a slave-től
  byte status2=Wire.read();                  //beolvassuk a STATUS2 regiszter tartalmát
  status2=status2 & 0b11110011;              //A BIT3 AF, és BIT2 TF 0, a következő riasztás bebillentheti 1-re
  if (alarm) {status2=status2 | 0b00000010;} //A BIT1 AIE=1, engedélyezett az interrupt kimenet időpont riasztás esetén
  else {status2 = status2 & 0b11111101;}     //nem kell időpont riasztás, ezért AIE=0 (INT kimenet tiltva)
  if (timer) {status2=status2 | 0b00000001;} //A BIT0 TIE=1, engedélyezett az interrupt kimenet timer riasztás esetén
  else {status2 = status2 & 0b11111110;}     //nem kell timer riasztás, ezért TIE=0 (INT kimenet tiltva)
  Wire.beginTransmission(PCF8563_I2C_cim);   //vége az I2C tranzakciónak
  Wire.write(0x01);                          //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.write(status2);                       //kiírjuk az I2C buszon a STATUS2 regiszter új tartalmát a slave-re
  Wire.endTransmission();                    //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void setDateTime(int ev, byte honap, byte nap, byte het_napja, byte ora, byte perc)
/*******************************************************************************************************************
 * Beállítja az óra dátumát és időpontját. A másodperceket nem lehet állítani, az alapértelmezetten 0 beállításkor *
 *******************************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);               // megszólítjuk a slave egységet a címével
  Wire.write(0x02);                                      //megmondjuk a "second" regiszter címét
  Wire.write(decToBcd(0));                               //kiírjuk az I2C-ra a slave nek a 0x02 regiszterbe a másodperc értékét (0 a beállításkor)
  Wire.write(decToBcd(perc));                            ///kiírjuk az I2C-ra a slave nek a 0x03 regiszterbe a perc értékét
  Wire.write(decToBcd(ora));                             //kiírjuk az I2C-ra a slave nek a 0x04 regiszterbe az óra értékét
  Wire.write(decToBcd(nap));                             //kiírjuk az I2C-ra a slave nek a 0x05 regiszterbe a nap értékét
  Wire.write(decToBcd(het_napja));                       //kiírjuk az I2C-ra a slave nek a 0x06 regiszterbe a hét napjának értékét
  if (ev > 1999) {honap=decToBcd(honap);ev=ev-2000;}     //Ha az év 2000, akkor a 0x07 regiszter balról első bitje 0, vagyis a hónap értékét 
                                                         //változatlanul tároljuk, az év értékéből 2000-et kivonunk tárolás előtt 
  else {honap=decToBcd(honap) | 0b10000000;ev=ev-1900;}  //Ha az év 2000 előtti, akkor a hónap regiszter balról első bitje 1, és az 
                                                         //év értékéből 1900-at kell kivonni tárolás előtt
  Wire.write(honap);                                     //kiírjuk az I2C-ra a slave nek a 0x07 regiszterbe a hónap értékét
  Wire.write(decToBcd((byte)ev));                        //kiírjuk az I2C-ra a slave nek a 0x08 regiszterbe az év értékét
  Wire.endTransmission();                                //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
}

void getDateTime()
/*****************************************************************************************
 * Ez a függvény beolvassa a dátum és időpont értékeket a megfelelő globális változókba  *
 *****************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);                  // megszólítjuk a slave egységet a címével
  Wire.write(0x02);                                         //megadjuk a second regiszter címét
  Wire.endTransmission();                                   //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 7);                     //megadjuk a slave-nak, hogy küldjön 7 byte-ot, a dátum és idő registereket
  sec=bcdToDec(Wire.read() & 0b01111111);                   //másodperc kiolvasás 0x02 "second" regiszterből, az elso bit nem kell a regiszterből, 
                                                            //mert az az óra integritásról tartalmaz információt
  perc=bcdToDec(Wire.read() & 0b01111111);                  //perc kiolvasás 0x03 "minute" regiszterből, az első bit nem kell a regiszterből
  ora=bcdToDec(Wire.read() & 0b00111111);                   //őra kiolvasás 0x04 "hour" regiszterből, az első bit nem kell a regiszterből
  nap=bcdToDec(Wire.read() & 0b00111111);                   //őra kiolvasás 0x05 "day" regiszterből, az első bit nem kell a regiszterből
  het_napja=bcdToDec(Wire.read() & 0b01111111);             //őra kiolvasás 0x06 "weekday" regiszterből, az első bit nem kell a regiszterből
  honap=Wire.read();                                        //őra kiolvasás 0x07 "mount" regiszterből, az első bit nem kell a regiszterből
  if (honap & 0b10000000) {ev=1900+bcdToDec(Wire.read());}  //a 0x07 "mount" regiszter balról első bitje 1, év kiolvasás 0x08 "year" regiszterből év=+1900 
  else {ev=2000+bcdToDec(Wire.read());}                     //a 0x07 "mount" regiszter balról első bitje 0, év kiolvasás 0x08 "year" regiszterből év=+2000 
  honap=honap & 0b00011111;honap=bcdToDec(honap);           //Az első három bit nem kell a hónaphoz
}


void getAlarm()
/******************************************************************************************************
 * Ez a függvény kiolvassa az időpont riasztási értékeket. Az egyes riasztási értékek byte-jainak     *
 * balról első bitje mindig azt adja meg, hogy figyeli-e a chip a rsiztás létrehozásakor az adott     *
 * értéket. Ha ez a bit 0, akkor az adotot figyeli, ha 1 akkor nem figyeli. Utóbbi esetben a függvény *
 * riasztási adatnak 99-et ad vissza, ezzel jelzi, hogy az adott adat nem vesz részt a riasztás       *
 * létrehozásában                                                                                     *
 ******************************************************************************************************/
{
  Wire.beginTransmission(PCF8563_I2C_cim);                 //megszólítjuk a slave egységet a címével
  Wire.write(0x09);                                        //megadjuk a "minute alarm" regiszter címét
  Wire.endTransmission();                                  //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 4);                    //megadjuk a slave-nak, hogy küldjön 4 byte-ot, a riasztási időpont registereket
  alarm_perc=Wire.read();                                  //riasztás perc kiolvasás 0x09 "minute alarm" regiszterből
  if(0b10000000 & alarm_perc){alarm_perc=99;}              //ha 0x09 regiszter balról első bitje 1, akkor a perc nem vesz részt a riasztásban (visszadott érték 99)
  else {alarm_perc=bcdToDec(alarm_perc);}                  //0x09 regiszter balról első bitje 0, visszaadjuk a beállított perc értéket
  alarm_ora=Wire.read();                                   //riasztás óra kiolvasás 0x0A "hour alarm" regiszterből
  if(0b10000000 & alarm_ora){alarm_ora=99;}                //ha 0x0A regiszter balról első bitje 1, akkor az óra nem vesz részt a riasztásban (visszadott érték 99)
  else {alarm_ora=bcdToDec(alarm_ora);}                    //0x0A regiszter balról első bitje 0, visszaadjuk a beállított óra értéket
  alarm_nap=Wire.read();                                   //riasztás perc kiolvasás 0x0B "day alarm" regiszterből
  if(0b10000000 & alarm_nap){alarm_nap=99;}                //ha 0x0B regiszter balról első bitje 1, akkor a hónap napja nem vesz részt a riasztásban (visszadott érték 99)
  else {alarm_nap=bcdToDec(alarm_nap);}                    //0x0B regiszter balról első bitje 0, visszaadjuk a beállított hónap napja értéket
  alarm_het_napja=Wire.read();                             //riasztás perc kiolvasás 0x0C "weekday alarm" regiszterből
  if(0b10000000 & alarm_het_napja){alarm_het_napja=99;}    //ha 0x0C regiszter balról első bitje 1, akkor a hét napja nem vesz részt a riasztásban (visszadott érték 99) 
  else {alarm_het_napja=bcdToDec(alarm_het_napja);}        //0x0C regiszter balról első bitje 0, visszaadjuk a beállított hét napja értéket
  Wire.beginTransmission(PCF8563_I2C_cim);                 //megszólítjuk a slave egységet a címével
  Wire.write(0x01);                                        //STATUS2 regiszter címét állítjuk be a slave-en
  Wire.endTransmission();                                  //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);                    //megadjuk a slave-nak, hogy küldjön 1 byte-ot, a STATUS2 registert
  byte status2=Wire.read();                                     //beolvassuk a STATUS2 regiszter tartalmát
  if(0b00001000 & status2){alarm_flag=1;}                  //kimaszkoljuk az AF bitet
  else {alarm_flag=0;}
  if(0b00000100 & status2){timer_flag=1;}                  //kimaszkoljuk a TF bitet
  else {timer_flag=0;}
  if(0b00000010 & status2){int_alarm=1;}                   //kimaszkoljuk a AIE bitet
  else {int_alarm=0;}
  if(0b00000001 & status2){int_timer=1;}                   //kimaszkoljuk a TIE bitet
  else {int_timer=0;}
  Wire.beginTransmission(PCF8563_I2C_cim);                 //megszólítjuk a slave egységet a címével
  Wire.write(0x0F);                                        //megadjuk a "timer" regiszter címét
  Wire.endTransmission();                                  //vége az I2C tranzakciónak (íráskor nem mondjuk meg előre, hogy hány byte-ot akarunk írni)
  Wire.requestFrom(PCF8563_I2C_cim, 1);                    //megadjuk a slave-nak, hogy küldjön 1 byte-ot, a timer registert
  timer=Wire.read();                                       //timer regiszter értékének kiolvasása (pillanatnyi érték, közben lehet, hogy épp visszaszámol)
}


byte decToBcd(byte val)  //decimálisból BCD-be alakít
{return ((val/10*16)+(val%10));}

byte bcdToDec(byte val)  //BCD-ből decimálisba alakít
{return ((val/16*10)+(val%16));}



Az I2C kommunikáció

Az I2C kommunikációhoz szükséges két vezetéket I2C busz-nak is szokták becézni. Azt hiszem valami akkor “busz” ha sokan vannak rajta. Ezen a két vezetéken bizony sokan is lehetnek egyszerre, tehát busz.
A buszrendszer konkrét működését nem is kell megismerni a használatához. Természetesen nem árt, de néhány apróság is elég. Ha részletesebb infóra van szükséged, kattints ide!
Az I2C buszrendszert azért találták ki, hogy sok-sok eszközt lehessen kevés kivezetés felhasználásával összekötni. Ehhez elegendő két kivezetés, melyeknek a neve SDA és SCL. Ezeket a jelöléseket minden eszközön megtaláljuk. Az Arduino Uno-on erre a az analóg bemenetekből kettőt kell feláldoznunk, az A5 és A4 bemeneteket, sőt még külön is kivezették a fenti jelöléssel:


Sok-sok eszközt (összesen 127db-ot) párhuzamosan lehet kötni. Természetesen minél több eszköz lóg a buszon, annál lassúbb lesz a kommunikáció, hiszen egyszerre a párhuzamos bekötés miatt mindig csak egy-egy eszköz kommunikálhat egymással. Az egyszerűbb áramköröknél (LCD kijelző, hőmérő chip, memória bővítő chip stb.) ez általában elegendő sebességet fog eredményezni. Műholdat amúgy sem akarunk irányítani. Legalább is mi kezdők!
A sok párhuzamosan kötött eszköz közül mindig egy határozza meg, hogy ki adhat vagy fogadhat adatokat. Ezt hívjuk master-nak. A master állítja elő az órajelet az SCL vezetékre. A többi kiszolgáló eszköz neve slave. Az SCL jelvezetéken minden eszköz megkapja az órajelet, az SDA vezetéken pedig az adat közlekedik. A mester először mindig megadja a slave címét, és az így kiválasztott slave felé küld, vagy onnan fogad adatot az SDA jelvezetékkel.

A kommunikáció során ismerni kell az eszköz címét. Ha ez nem derül ki a műszaki dokumentációból, akkor úgynevezett port scener programot célszerű használni. Ez sorban végigkérdezgeti az összes lehetséges címet az I2C buszon, és ha van ott valaki, akkor az válaszol, ha nincs akkor nem válaszol. Jó ez a szabványos működés. A program a soros monitoron tájékoztat az eredményről. Itt a forrása:

#include <Wire.h>
byte error, address;
int nDevices;

void setup()
{
  Wire.begin();               //I2C busz inicializálása
  Serial.begin(9600);    //soros port inicializálása
}
void loop()
{
  Serial.println("I2C kerses indul:"); //bemutatkozunk a soros porton
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    // Ez a program a Write.endTransmisstion függvényt használja,
    // hogy megnézze, van-e eszköz a vonalon. Ha nincs akkor hibával
    // tér vissza a függvény (error>0), ha a visszatérési érték 4 (error=4)
    // akkor van eszköz a vonalon, de ismeretlen hiba történt (ezt nem tudom, mikor 
    // fordulhat elő, még nem tpasztaltam)
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0)
    {
      Serial.print("Eszköz címe:");
      Serial.print("  DEC:");
      Serial.print(address,DEC);
      Serial.print(" HEX:0x");  //a hexadecimális számokat 0x-el szoktuk kezdeni (jelölni)
      if (address<16) {Serial.print("0");} //vezető nulla, mert a hexa számok két szájeggyel néznek ki jól
      Serial.print(address,HEX);
      Serial.print("  BIN:0b");  //a bináris számokat 0b-vel szoktuk kezdeni (jelölni)
      Serial.println(address,BIN);
      nDevices++;
    }
    if (error == 4)
    {
      Serial.print("Isemertelen hiba:");
      Serial.print("  DEC:");
      Serial.print(address,DEC);
      Serial.print(" HEX:0x");  //a hexadecimális számokat 0x-el szoktuk kezdeni (jelölni)
      if (address<16) {Serial.print("0");} //vezető nulla, mert a hexa számok két szájeggyel néznek ki jól
      Serial.print(address,HEX);
      Serial.print("  BIN:0b");  //a bináris számokat 0b-vel szoktuk kezdeni (jelölni)
      Serial.println(address,BIN);
      nDevices++;
    }
  }
  if (nDevices == 0)
    Serial.println("Nincs eszkoz a buszon");
  else
    Serial.println("kereses vege!");
  delay(10000);           // 5 másodperc múlva újra elkezdünk keresni
}

Itt be is fejezhetnénk az I2C kommunikáció bevezető ismereteit, és rátérhetnénk egy konkrét példára. Ha érdekelnek még további finomságok, akkor olvass tovább, ha nem, akkor kattints ide, és meglátod, hogyan kell használni a karekteres LCD kijelzőt!

Az I2C busz sebessége

Az, hogy valami gyors, vagy éppen lassú, az nagyon relatív. Ha egy LCD kijelzőre kell 32 karakternyi információt kiírni, akkor ahhoz másodpercenként néhányszor száz karakter átvitele is elég gyors. Ha egy ramba kell több száz Kbyte infót átmozgatni, akkor az előbbi sebesség idegesítően lassú lesz. Az I2C busz meglehetősen sok értéktelen adatot is mozgat, hiszen az eszközöket címezni kell és meg kell azt is mondani a master-nek a számukra, hogy mit kell csinálni, és csak ez után jön az értékes adat. Azonban ennek eredménye a kényelem. Hogy mire is képes az I2C busz, az az órajel frekvenciájától függ. Elméletben a végtelenbe növelhetnénk a frekvenciát, de vannak határok. Pl. ott van a rendelkezésre álló órajel, ami a processzor órajele. Ez UNO esetén 16Mhz. Viszont a belső áramkörök miatt, ez nem lehet az I2C busz órajele, mindössze max. 1Mhz fér ki a csövön, azaz a SCL vezetéken.

A konkrét órajelet egy úgynevezett TWBR regiszter értékébe írt 0-255 közötti számmal lehet leosztani, azaz lassítani. Miért kéne lassítani? Azért mert minél nagyobb az órajel, annál érzékenyebb a rendszer a jelvezetékek hosszára. A hosszú vezetékeken több zavarjel, kéretlen impulzus gyűlik össze, és a hosszúsággal együtt nő a kapacitásnak nevezett elektromos paraméter értéke. A vezeték kapacitása hatással van a jelalakra, és a szép négyszög alakul jelből egyre lekerekítettebb, göbölyűbb jelalak lesz. Ez pedig elbizonytalanítja a jelek érzékelését. Egy idő után már a jelvezetéken terjedő jeleket a vevőegység nem képes felismerni, vagy hibás adatot érzékel. Ha tehát messzire kell elvinni az infót, akkor nincs mit tenni, csökkenteni kell a frekvenciát. A frekvencia csökkentésével a egyre hosszabbak lesznek azok az időszakok amikor a jel nem változik. A jelek változásakor (fel és lefutó élek) a jelalak továbbra is torz, de hagyunk időt, hogy beálljon a jelszint és a vevő csak ekkor olvassa le a jelvezeték feszültségét.

Az I2C busz frekvenciája alapértelmezetten 100Khz egy UNO esetén. A TWBR regiszter értékének változtatásával ezt a frekvenciát tudjuk növelni és csökkenteni. Az én gyakorlati tapasztalatom, hogy 100Khz frekvencia esetén kb 8 méteres (riasztó vezeték) kábellel még működött az átvitel. Nem vizsgáltam, hogy mi történik ha beindítjuk a porszívót, és sérül-e az átvitt infó. Szerintem igen, ezért ilyen hosszú vezetéket lehetőleg nem használnék. Amikor a kísérletet végeztem, még nem tudtam, hogy lehet növelni a frekvenciát, valamint hosszabb vezetékem sem volt, így csak ennyit tudok állítani: 8m kábelen még működik egy LCD kijelző az Arduino UNO-ról. A nagyon okosok szerint az 1Mhz órajel frekvencia esetén 10cm a maximális vezetékhossz. Ezt nem hittem el, és jól tettem, mert ennél azért jóval hosszabb is lehet. Példaként egy valós áramkörben egy BME280 szenzort kérdezgettem (3m vezeték) és egy LCD kijelző is működött (30cm vezeték), kipróbáltam a frekvencia állítgatását. 1Mhz frekin még működött az LCD. Egy alkalommal (4-5 próbálkozás) sikerült a BME280 lekérdezése, de legtöbbször nem. A frekvenciát egészen 500Khz-ig keltett csökkenteni, hogy minden működjön.

A frekvencia beállításához tudni kell, hogy a TWBR regiszter mely értékeihez, milyen órajel tartozik. Íme ehhez egy általam kreált táblázat néhány értékkel:

AdatVégrehajtási időFrekvencia
0387ms 1MHz
1543ms 888,888KHz
2571ms 800KHz
4611ms 666,666KHz
6660ms 571,428KHz
8709ms 500KHz
12 805ms 400KHz
16901ms 333,333KHz
321283ms 200KHz
642033ms 111,111KHz
722243ms 100KHz
1283551ms 58,823KHz
2556555ms 30,418KHz

A végrehajtási idő az alább található programra vonatkozik. A programot olyan hardveren futtattam, ahol az Arduino-t és az LCD kijelzőt kb. 20 cm-es vezetékek kötötték össze.
10×16 karaktert írtam ki az LCD kijelzőre. A 160 karakter átvitele 1 Mhz buszfrekvencián kb. 0,4 másodperc, míg 30,4Khz buszfrekvencián 6,5 másodperc. Miközben a program fut, „0” betűk rohangálnak a képernyőn. 30,4 Khz frekvenciánál szemmel jól követhető módon futnak végig az “0” betűk a kijelző első sorában. 1 Mhz esetén már semmit nem látni, csak halvány elmosódott pixeleket, mivel már meg sem tudja jeleníteni a rövid időre megjelenő betűt a kijelző. Az LCD fizikai működése ehhez lassú!

És ime a példa program 30,4 Khz buszfrekvencia beállításával. Sajnos ebben már használok LCD kijelzőt, amiről még csak a későbbiekben lesz szó (ha sorban haladsz az általam kijelölt úton). FONTOS!!! tapasztalati tudnivaló: A TWBR regiszter érték megadását nem lehetett a setup részbe beírni, mert ott nem reagált rá a program. Mintha akkor állítaná be a fordító a regiszter értékét a default 72-re, amikor a program futása a loop ciklushoz ér, és ez felülírja amit a setup-ban megadtunk. Kipróbáltam, hogy a loop-ban csak egyszer adtam ki az értékmegadást, így is működött. Azonban ehhez egy if-et kellett berakni a loop elejére, ami a processzornak nagyobb munka, mint egy értékmegadás. Így energiatakarékossági okokból feleslegesen minden loop elején beállítom az értéket. Legyünk zöldek, mentsük meg a világot!

Kezdőknek még annyit, hogy a processzor regisztereire közvetlenül lehet a nevükkel hivatkozni a programban. Ezt én is csak most tudtam meg ezen példa kapcsán. Pl. ha a programban a frekvencia kiírását végző sorban:   
Serial.println(F_CPU/(TWBR*2+16));
az F_CPU változó név a 16.000.000 értéket képvisel, ami az órajel frekvencia azUNO esetén. Sok ilyen változó illetve regiszter van, biztosan óvatosnak kell lenni velük!

#include <Wire.h>    //I2C library
#include <LiquidCrystal_I2C.h>  //I2C LCD kezelő könyvtár         
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 0x3F, a 2 soros 0x27 
int ido=0;

void setup() 
{
  Serial.begin(9600);
  Wire.begin();          // I2C busz használat indítása
  lcd.begin(20,4);       //LCD inicializálása
  lcd.backlight();       //háttérvilágítás bekapcsolása
  lcd.clear();
}

void loop()
{
  ido=millis();
  //TWBR = ((F_CPU / frekvencia) - 16) / 2;
  TWBR = 255;
  for (int i=0;i<10;i++)
  {
    lcd.setCursor(0,0);lcd.print("0              ");
    lcd.setCursor(0,0);lcd.print(" 0             ");
    lcd.setCursor(0,0);lcd.print("  0            ");
    lcd.setCursor(0,0);lcd.print("   0           ");
    lcd.setCursor(0,0);lcd.print("    0          ");
    lcd.setCursor(0,0);lcd.print("     0         ");
    lcd.setCursor(0,0);lcd.print("      0        ");
    lcd.setCursor(0,0);lcd.print("       0       ");
    lcd.setCursor(0,0);lcd.print("        0      ");
    lcd.setCursor(0,0);lcd.print("         0     ");
    lcd.setCursor(0,0);lcd.print("          0    ");
    lcd.setCursor(0,0);lcd.print("           0   ");
    lcd.setCursor(0,0);lcd.print("            0  ");
    lcd.setCursor(0,0);lcd.print("             0 ");
    lcd.setCursor(0,0);lcd.print("              0");
 }
  Serial.print("TWBR eteke:");
  Serial.println(TWBR);
  Serial.print("I2C orajel ferkvencia:");
  Serial.println(F_CPU/(TWBR*2+16));
  Serial.print("Vegrehajtasi ido:");
  Serial.println(millis()-ido);
  Serial.println("");
}

Még egy fontos tanulság a programból. Az int típusú változó értékhatára kisebb mint a millis() függvény által visszaadott szám, ami unsigned long típusú. Ha elegendő ideig várunk (egészen pontosan 64.000 milisecundum-ig), akkor furcsaságot fogunk tapasztalani, ugyanis a mért időt hibásan jelzi ki. Ezt azzal lehet kivédeni, hogy a program 4. sorában “int ido“; helyett “unsigned long“-ot írunk. Én most itt direkt hagytam hibásan, hogy okuljunk belőle! Így is el lehet cseszni egy programot! A net-ről beszerzett programokban sok ilyen hibát találtam. Egyes programok nagyon-nagyon sok helyen fellelhetők, mert különböző cikkekben idézgetik. Láthatóan senki nem veszi a fáradságot, hogy ténylegesen ki is próbálja ezeket. Nos, az én programjaimban is lehetnek ilyen hibák.

Van a frekvencia beállításra más lehetőség is. A Wire program könyvtárnak van ugyanis egy frekvencia beállító függvénye:
Wire.setClock(frekvencia)
Ebben a függvényben közvetlenül beírhatunk egy frekvencia értéket. A program valamely közeli frekvenciát fogja beállítani. Pl. én beírtam a 30.500-at. A valós beállított frekvencia 30.534 lett, ekkor a TWBR értéke 254. Vigyázat, 30.418 alatti értéknél 800.000Khz frekvenciát állítbe, nem jól működik. Nekem ezért a TWBR-el történő beállítás szimpatikusabb.

Most, hogy ennyi mindent megtudtunk az I2C kommunikációról, érdemes lenne a gyakorlatban is használni. Mi sem egyszerűbb ennél. Be kell szerezni egy 2 soros, soronként 16 karakteres LCD kijelzőt (500Ft). Önmagában a kijelző nem I2C kommunikációval működik (de lehetne Arduino-val használni, csak sok kivezetést kellene felhasználni), kell még hozzá egy LCD meghajtó áramkör. Ezt kifejezetten a kijelző meghajtására készítették, nagyon le fogja egyszerűsíteni az életünket. Ha szeretnéd látni hogyan, akkor kattints ide!

Kommunikáció a külvilággal

Intelligens eszközöknek (moduloknak) nevezem azokat, melyek önállóan végeznek el feladatokat, és a begyűjtött adatokat valamilyen módon továbbítják Arduino felé, illetve az Arduino-tól kapott adatokat valamilyen módon feldolgozzák. Ilyen eszközök a hőmérséklet, páratartalom és légnyomásmérők, és még rengeteg érzékelő, és nyilván intelligens eszköznek tekinthető az én önkénes meghatározásomban egy LCD karakteres vagy grafikus kijelző is. Ezeknek az eszközöknek ismertető jele, hogy összetett adatokat közvetítenek vagy várnak működésük során, és nem egy vagy több kivezetés állapotát kapcsolgatják vagy figyelik bambán, mint egy kapcsoló, vagy egy led. Az összetettebb adatok átvitele már bonyolultabb folyamat. Az adatcsere kommunikációt kíván!

Lássunk hát példát a kommunikációs lehetőségekre. Sokféle megoldást találtak ki az informatika néhány évtizedes történetében. Ezekből három félét hardveresen is beleépítettek ATmega328P chipbe. A hardveres beépítés azt jelenti, hogy programunkból csak az adatot kell küldeni, vagy éppen várni kell az adat megérkezésére, de nem kell azzal foglalkozni, hogyan is történik a továbbítás. A használathoz egyetlen megkötés, hogy előre meghatározott kivezetéseket kell használnunk.

A legegyszerűbb kommunikációs megoldás a soros port. Szakirodalomban UART-nak szokták becézni. Azért is egy alap kommunikációs forma, mert pl. az Arduino-ba a programot is ezzel a soros porttal töltjük fel a PC-ről. A PC-n található USB csatlakozó is egy soros port, csak egy fokkal modernebb és összetettebb, mint az, amit az Arduino-ban találunk. Az Arduino Uno-ra ráépítették az USB-soros átalakítót, azért is tudunk közvetlenül programokat tölteni rá, illetve adatokat is tudunk továbbítani a PC és az Arduino kártya között. Az ATmega328 chip használata esetében nekünk magunknak kell egy USB-soros átalakítót építenünk, ha a PC-re akarunk adatokat továbbítani, vagy a PC-ben található “mezei” soros portot kellene használnunk. Már nem minden PC-ben van soros port, időközben elavult, így marad az USB port.

A soros kommunikációról egyenlőre elég annyit tudni, hogy két vezetékkel kötjük össze a beszélgető partnereket, egyiken adás, a másikon vétel zajlik. Kell még egy föld vezeték is az eszközök között, ami egy harmadik vezeték, de ezt nem szokták külön számolni, mert általában a tápfeszültség két vezetékének egyike a földvezeték, és automatikusan rendelkezésre áll. Távoli berendezések esetén azonban ez is kelleni fog. Tehát a kettő, az valójában három.
Az átvitelre szánt adatokat egy start, és egy stop jelzéssel látjuk el, a kettő között találhatók az értékes adatok. A részletek most nem érdekesek. Nem kell tudni, hogy miként működik, elég ha használni tudod. Itt találsz konkrét példákat a soros port használatára. Ha részletesebben is szeretnéd megismerni a soros kommunikációt, akkor kattints ide!

Az egyszerű soros kapcsolat igen buta, csak egy-egy eszköz tud egyszerre beszélgetni a két vezeték felhasználásával. Ha az Arduino-nak több különböző külső modullal is beszélgetnie kell, hamar elfogynak a kivezetések, ráadásul csak egy hardveresen beépített soros portunk van. Erre a problémára jelent megoldást az I2C kapcsolat. Ebben már az egyes eszközök címmel rendelkeznek, és ugyanazon a két vezetéken többen is kommunikálhatnak. Nyilván ennek a megoldásnak a hátránya, hogy az értékes adatok mellett egy csomó adminisztráció is áramlik (címek, parancsok stb.), és ezért a kommunikáció sokkal lassúbb, de valamit valamiért! Igen elterjedt az I2C, már szinte minden eszközbe beépítik. Használtam már hőmérőt, LCD kijelzőt, eeprom-ot is ezzel a kommunikációval. Itt láthatsz egy példát a használatára és a fontosabb tulajdonságaira, részletesebben pedig itt ismerheted meg a működését. Az I2C busz természetesen arra is alkalmas, hogy két Arduino alaplap és program cseréljen adatokat. Gyakorlati példát találsz itt!

A soros kommunikáció azért lassú, mert egy vezetéken zajlik az információ átvitele. Az I2C előnye, hogy akár 127 eszközt is felfűzhetsz párhuzamosan egy vezetékpárra, de még a soros portnál is lassúbb lehet a rengeteg adminisztratív adatcsere miatt. Ha valamilyen okból nagy sebességre van szükség, akkor egyik sem az igazi. Erre az eshetőségre találták ki az SPI kommunikációt. Ez az adatátviteli megoldás minimum 3 vezetékkel működik. Van egy oda-vissza adatvezeték pár, és egy órajel vezeték. Az órajel vezeték megmondja, mikor van értékes adat a jelvezetéken. Ettől az átvitel sokkal gyorsabb. Nem kell start és stop bit, az egyes átvitt byte-ok között nem kell szünet, sőt teljesen folyamatos lehet az adatáramlás. Ha több eszközzel is kell egyszerre kommunikálni, akkor minden egyes eszközhöz kell még egy olyan vezeték, ami jelzi, hogy vele akarunk dolgozni. Vagyis eszközönként kell még egy-egy kiválasztó vezeték. Láthatóan szaporodik az összeköttetések száma, de cserébe nagyobb sebességet kapunk. Ennek eredménye, hogy a grafikus kijelzőkön akár animációkat is megjeleníthetünk, vagy az SD kártyáról zenét tudunk lejátszani. Mindez átlagos soros porton vagy I2C-n keresztül nem menne. Már el is árultam melyek azok a modulok, melyeknél várhatóan SPI kommunikációt használunk. Ha ide kattintasz, akkor láthatsz példát arra, hogyan is lehet használni egy SPI kapcsolattal működő eszközt, az SD kártyát. Ha többet szeretnél megtudni az SPI működéséről, akkor kattints ide!

Van azonban még egy fontos felhasználás, ez pedig az ATmega328 programozása. Nem kell feltétlenül soros portot használni a program betöltésre. A soros porton keresztül történő programozás egyébként is feltételezi, hogy a chip-be úgynevezett bootloader programot töltöttek, ami nagyon kényelmessé teszi az esetleges programbetöltési igényeinket. A chip-be épített SPI arra is jó, hogy betöltsük a programot akár úgy is, hogy a chip már be is lett forrasztva egy konkrét áramkörbe. Mindössze néhány vezetéket kell valamilyen módon csatlakoztatni a chip megfelelő kivezetései, és egy programozó közé. Ezeket a csatlakozásokat a gyártók előre elő is készítik. Magán az Arduino-n is megtaláljuk ezeket a kivezetéseket. A programozók ugyanezzel a csatlakozóval rendelkeznek, és ha kell összedugjuk a kettőt, már mehet is a program közvetlenül a chip-be. Nem kell semmi más, még tápfeszültséget is a programozó ad a chip-nek.

Ha az is érdekel, hogyan lehet az Arduino-t ISP buszon keresztül programozni, akkor kattints ide!

Még közel sem értünk a végére! Az Arduino alaplapoknak még nagyon sok tulajdonságát érdemes megismerni. Itt olvashatsz a megszakításokról, amivel ritkán előforduló eseményeket érdemes figyelni és hatásukra beavatkozni a folyamatokba.

Ha elemes kapcsolásokat szeretnél készíteni, akkor az Arduino elalvási funkcióit kell megismerned.

Ha programod túl összetett, és időnként lefagy, de nem találod a megoldást, nem szabad elkeseredni. A watchdog funkció segíteni fog!

FM24C04A külső FRAM használata

Előbb-utóbb kevésnek fog bizonyulni az ATmega328 memória készlete, ezért rákényszerülünk majd külső memóriák használatára. Ha a működési sebesség nem kritikus, akkor az I2C buszon kommunikáló külső memória chip-ek vagy modulok a legkényelmesebbek. Ha nem szeretnéd a tárolt adatokat a tápfesz kikapcsolásakor elveszíteni, akkor eeprom vagy fram memóriát kell használnod. Hogy eldönthesd mikor melyiket célszerű használnod, ismerd meg a memória típusok működését.

Készítettem egy bonyolult programot, ami hőmérséklet mérési eredményeket állított elő 3 másodpercenként. A mérési eredményeket átlagoltam, azaz a memóriába 3 másodpercenként írtam újra az adatokat. Végül az átlag értékeket óránként egy SD kártyára írtam. Elméletben minden tökéletesen működött. Azonban a program időnként (havonta egy-két alakalommal) lefagyott. Nem komoly probléma egy amatőr szerkezet esetén, nyomtam egy reset gombot és minden ment tovább. Azonban nagyon bosszantott, hogy folyamatosan figyelni kellett a szerkezetre. A lefagyást okozó körülményt nem sikerült megtalálni, pedig a programban minden lehetséges részt többször is átalakítottam. Végül feladtam, és elhatároztam, hogy a watcdog áramkörre bízom az újraindítást. Ha a program lefagy, a vatchdog újraindítja, és észre sem veszem, hogy történt valami. Az egy órás intervallumban a mérési eredmények átlagolása rövidebb ideig tart ugyan, de még így is jobb a helyzet, mintha esetleg órákig nem veszem észre a lefagyást, és nem tárolok egyáltalán mérési eredményeket. Gondolkodni kezdtem, hogyan lehetne ezt a problémát megoldani. Az ATmega328 EEPROM memóriája erre alkalmatlan, mert a 100ezer írást nagyon gyorsan elhasználom. Első ötletem, hogy keresni kell egy másik eszközt az adattárolásra. Nézzük csak, mennyi írásról van szó? Három másodpercenként mérek és tárolok, azaz 20 írás percenként. Ez napi 28000 írás, ez évi 10 millió. Ha legalább 10 évig szeretném használni az eszközömet, akkor 100millió írásra alkalmas eszközt kell keresni, vagy tudomásul kell venni, hogy havonta cserélni kell a memória elemet (EEPROM esetén). Esetleg szóba jöhet egy SRAM, amit elemmel vagy akkumulátorral védek az áramszünetek okozta adatvesztéstől.

Melyik az az eszköz, ami elvisel 100 millió írást? A fenti ismertető alapján az FRAM a nyerő. Nyilván elég lusta vagyok hozzá, hogy akkumulátort építsek egy ram-ra, így SRAM-ot nem fogok használni. Az adatlap alapján 100 éves használati időre számíthatok, így rögvest meg is rendeltem 5db-ot 250Ft-ért. Azonban csak felület szerelt kivitelben létezik, ezért kénytelen voltam hozzá rendelni áramköri lemezt (350Ft 10db), amire a rendelkezésemre álló durva eszközökkel is van remény, hogy beforraszthatom. Féltem a forrasztástól, de mind az öt ramot sikerült hibátlanul beépíteni. Az eredmény így néz ki forrasztás után:

Természetesen létezik hozzá könyvtár. Nem is igazán kell kifejezetten FRAM-hoz való könyvtár, hiszen az EEPROM és a statikus ram is ugyanúgy működik. Természetesen most a kicsi kapacitású eszközökről beszélek, azokról melyeknek a memória címe 8 bites. Bár létezik FRAM-ból is sokkal nagyobb (pl. 256kbit), azok címzése már 16 bites, pont úgy mint a hasonló méretű EEPROM-ok esetében. A címzés egyébként csak annyit jelent, hogy az I2C buszon az eszköz megszólítása után, egy vagy két byte-on a címet kell kiírni az eszközbe, és ezt követően lehet írni vagy olvasni a tartalmat. Ennél a típusnál a cím 8 bites, tehát egy I2C busz írási művelettel kiküldjük a címet, és jöhetnek az adatok.

Az I2C busz címet két chip kivezetéssel összesen négyféle címre tudjuk beállítani. Így összesen négy eszközt köthetünk a buszra. Mivel egy FM24C04B FRAM 512byte kapacitású, a két külső cím-vezeték mellett van egy harmadik cím bit is, amivel az FRAM-on belül tudunk a két 256 byte-os lap közül választani. Tehát az eszköz címzése így néz ki:

Ha portscenner programot futtatunk, akkor két címen is válaszol, a decimális 80 és 81 címeken (Ha az A2 és A1 földre van kötve)! Gyakorlatilag olyan, mintha két eszközünk is lenne az I2C buszon, csak ezt egyetlen tokba építették össze. Egy címen 256 byte memóriát tudunk címezni, a két ezközből jön össze az 512 byte (4Kbit). Mivel két hardveresen beállítható címvezeték van, összesen négy FRAM chip-et lehet az I2C buszhoz kapcsolni. Tehát 2Kbyte (16Kbit) memóriát tudunk így kialakítani. Adatgyűjtésre nagyon kevés, tehát marad ilyen esetben az SD kártya, de átmeneti mérési eredmények tárolásához nagyos sok. Ha elveszti a rendszer a tápfeszültséget, akkor minden tárolt adat megmarad, és ez nagy előny lehet. Gyakorlatilag a programban rendelkezésre álló ram területet tudjuk vele kiterjeszteni, de az alérés a belső ram-hoz képest csigalassú!

Találtam egy kifejezetten FM24C04A FRAM-hoz készült könyvtárat, de nem tetszett. Az volt vele a bajom, hogy külön kellett a 256 byte-os lap címeket beállítani, ráadásul a byte, int és long típusú változókra külön függvényeket készített a könyvtár írója. Ez persze nem baj, csak kicsit kényelmesebbnek éreztem, ha egy függvényem lesz az olvasásra, egy pedig az írásra. Nem akartam a lap címzésekkel foglalkozni, egyben szerettem volna kezelni a 0 – 511 címtartományt. Így aztán csináltam magamnak egy kombinált függvényt, ami mindig long típusban adja vissza a ram-ból kiolvasott értéket. A függvényeknek van egy változó típus paramétere, amivel beállítható, hogy hány bájton tárolja az adatot, amit megadtunk, illetve hány bájtról olvassa ki az adatot a megadott kezdőcímtől. A függvény igen buta jelenleg, nem ellenőrzi az I2C buszon zajló dolgokat, hibákat nem kezel. Funkcionálisan jól működik, a példa programban igyekeztem minden értékhatárra egy-egy mintát elhelyezni.

A példa programot még fejlesztgetem. Hamarosan gyakorlatban is használni fogom, és akkor még biztosan javítgatok rajta. Most még lehetnek benne hibák is!

#include <Wire.h>

//Az i2c címet az A2 és A1 kivezetések állapota határozza meg
//Például, ha A2 = VCC és A1 = GND, írjunk: FRAM fram (0b10);
byte A0A1=0b00;
byte _i2cAddress= (0b101000 | A0A1)<<1;
byte Page=0;

              
void setup() {
Wire.begin();  
  Serial.begin(9600);
  Serial.println("indul...");
  //Írás a 0. lapra a ramban
  fram_write(0,0,1);             //byte 0
  fram_write(1,255,1);           //byte 255
  fram_write(10,0,2);            // Int 0
  fram_write(12,-32768,2);       //Int -32768 
  fram_write(14,32767,2);        //Int 32767
  fram_write(20,0,3);            //Long 0
  fram_write(24,-2147483648,3);  //Long -2,147,483,648
  fram_write(28,2147483647,3);   //Long 2,147,483,647
  //Írás a 1. lapra a ramban
  fram_write(256,0,1);           //byte 0
  fram_write(257,255,1);         //byte 255
  fram_write(266,0,2);           // Int 0
  fram_write(268,-32768,2);      //Int -32768 
  fram_write(270,32767,2);       //Int 32767
  fram_write(272,0,3);           //Long 0
  fram_write(276,-2147483648,3); //Long -2,147,483,648
  fram_write(280,2147483647,3);  //Long 2,147,483,647
  //olvasás a 0. lapról 
  Serial.print("Byte visszaolvasva 0. page (0 - 255):");
  Serial.print(fram_read(0,1));
  Serial.print(" - ");Serial.println(fram_read(1,1));
  Serial.print("Int visszaolvasva 0. page (min >> 0 >> max):");
  Serial.print(fram_read(12,2));
  Serial.print(" >> ");Serial.print(fram_read(10,2));
  Serial.print(" >> ");Serial.println(fram_read(14,2));
  Serial.print("Long visszaolvasva 0. page (min >> 0 >> max):");
  Serial.print(fram_read(24,3));
  Serial.print(" >> ");Serial.print(fram_read(20,3));
  Serial.print(" >> ");Serial.println(fram_read(28,3));
  //olvasás a 1. lapról 
  Serial.print("Byte visszaolvasva 1. page (0 - 255):");
  Serial.print(fram_read(256,1));
  Serial.print(" - ");Serial.println(fram_read(257,1));
  Serial.print("Int visszaolvasva 1. page (min >> 0 >> max):");
  Serial.print(fram_read(266,2));
  Serial.print(" >> ");Serial.print(fram_read(268,2));
  Serial.print(" >> ");Serial.println(fram_read(270,2));
  Serial.print("Long visszaolvasva 1. page (min >> 0 >> max):");
  Serial.print(fram_read(276,3));
  Serial.print(" >> ");Serial.print(fram_read(272,3));
  Serial.print(" >> ");Serial.println(fram_read(280,3));
}

void loop() {
  
}

long fram_read(int fram_cim, byte fram_adat_tipus) {
// fram_cim 0-511
// fram_adat_tipus 1-byte,2-int,3-long
byte page=0;
if (fram_cim<256) {page=0;} else {page=1;fram_cim=fram_cim-256;}
    Wire.beginTransmission(_i2cAddress | (page&1));
    Wire.write(fram_cim);
    Wire.endTransmission(); 
    switch (fram_adat_tipus) {
    case 1:
      Wire.requestFrom(_i2cAddress | (page&1),1);
      return Wire.read();
      break;
    case 2:
      Wire.requestFrom(_i2cAddress | (page&1),2);
      return int(Wire.read()) | int(Wire.read())<<8;
      break;
    case 3:
      Wire.requestFrom(_i2cAddress | (page&1),4);
      return long(Wire.read()) | long(Wire.read())<<8 | long(Wire.read())<<16 | long(Wire.read())<<24 ;
      break;
    }
}

void fram_write(int fram_cim, long fram_adat, byte fram_adat_tipus) {
// fram_cim 0-511
// fram_adat long, amit a függvémy szükség szerint byte, vagy int-re alakít
// fram_adat_tipus 1-byte,2-int,3-long
byte page=0;
byte adat0;
byte adat1;
byte adat2;
byte adat3;
if (fram_cim<256) {page=0;} else {page=1;fram_cim=fram_cim-256;}
    Wire.beginTransmission(_i2cAddress | (page&1));
    Wire.write(fram_cim);
    switch (fram_adat_tipus) {
    case 1:
      Wire.write(fram_adat);
      Wire.endTransmission(); 
      break;
    case 2:
      adat0=fram_adat & 0xFF;
      adat1=(fram_adat>>8) & 0xFF;
      Wire.write(adat0);
      Wire.write(adat1);
      Wire.endTransmission(); 
      break;
    case 3:
      adat0=fram_adat & 0xFF;
      adat1=(fram_adat>>8) & 0xFF;
      adat2=(fram_adat>>16) & 0xFF;
      adat3=(fram_adat>>24) & 0xFF;
      Wire.write(adat0);
      Wire.write(adat1);
      Wire.write(adat2);
      Wire.write(adat3);
      Wire.endTransmission(); 
      break;
    }
}

Miközben dolgoztam az FRAM megismerésén, gondolkodtam azon is, hogyan tudnám a legkényelmesebben felhasználni programjaimban. Végül is a külső ram azért van, hogy tápfeszültség kiesés esetén is megőrizze a szükséges adatokat. Még egy bonyolult programban sincs sok ilyen adat (legalább is nekem nem sikerült megfelelően bonyolult problémát kitalálnom). Azonban az adatok amiket nem szeretném ha elfelejtene a rendszer, nem egyszerű számok, hanem valamilyen feldolgozott értékek. Pl. a napi hőmérséklet minimuma, maximuma, átlaga, mérések száma stb. Ezért írtam egy olyan függvényt, illetve függvény gyűjteményt, aminek a használatakor nem kell foglalkoznom az adatok értékelésével, közvetlenül a számomra szükséges feldolgozott adatot kaphatom vissza. Talán mások számára is hasznos lehet, ezért a forráskódok menüpontba beraktam ennek a függvény gyűjteménynek a leírását és programját. Ha érdekel, akkor itt megtalálod!

Két Arduino összekapcsolása I2C-vel

Előfordulhat, hogy két Arduino között kel adatokat cserélni. Az alábbi megoldást az egy interneten megjelenő cikkben találtam. Sajnos a forrást már nem tudom megjelölni. Az összeköttetés létrehozásának alapja, hogy az Arduino panelek A4 és A5 kivezetéseit kössük össze. Nem szabad elfelejteni a föld vezetéket sem, ha az Arduino panelek egymástól független táplálásúak. Ha ugyanarra a tápegységre kötjük mindkettőt, akkor a közös föld egyértelmű.

Az egyik Arduino lesz a mester, aki küldi az adatokat, míg a másik a slave, aki fogadja. Mivel az I2C buszon több slave eszköz is lehet, a slave eszközöknek önálló és megkülönböztethető címmel kell rendelkeznie. Az RTL óra panel esetében pl. ez a cím egy gyárilag beállított cím, amit „huzalozással” meg is lehet változtatni. Az Arduino esetén a címet szoftveresen kell beállítani.

Elméletileg az I2C buszon több master is lehet. Sajnos nem találtam arra vonatkozóan semmilyen információt, hogy az I2C busz kezelésére szolgáló függvény könyvtárak tudják-e kezelni a két mastert a buszon. Valószínűnek tartom, hogy nem. Azonban nem kizárt, hogy a wire.begin() (masterterként működik) és a Wire.begin(8) (8-as című slave-ként működik) utasításokat lehet egy programon belül váltogatni, és egy Arduino szerepe egy programon belül váltogatható. Persze adminisztrálni kell, hogy milyen szerepben van a másik Arduino, nehogy ketten egyszerre legyenek masterek. Ezt nem próbáltam ki eddig.

További észrevételem, hogy a Wire.beginTransmission(8);  és a Wire.endTransmission(); programsorok között kiadható Wire.Write()-ok száma korlátozott. Úgy tapasztaltam, hogy 32 byte vihető át egyszerre. Egy programban ennél több infót kelett átvinnem, ezért kénytelen voltam részekre bontani az átvitelt. Ekkor a Wire.beginTransmission(8) után következő buszra kiírt byte egy jelző byte volt, ami a slave-nak megmondta, hogy éppen melyik blokkot viszem át. A slave oldalon az adatok fogadására készített függvény ebből tudta, hogy az érkező adatokkal mely változókat kell tölteni!

Az alábbi példa programban a slave egy analóg bemeneten mért értéket long változóba olvas be. Direkt long, hogy látható legyen, hogyan kell byte-onként átvinni az értéket.  Amikor a master lekérdezte a slave-től az adatot, összerakja a byte-onként érkezett tartalmat egy long változóba és kiírja a soros monitorra, majd lássuk mi jött. A fogadott értékeket visszaküldi a slave-nak, hogy erre is lássunk példát.

A master programja

/*
 * Ez a program demo két arduino I2C porton keresztül történő adatcseréjének bemutatására készült.
 * Ez itt a master programja. A mester másodpercenként adatot kér a slave-től, ami egy -500 és 523 közötti
 * long számot bont fel és byte-onként elküld. A mester programja összerakja a 4 byte-ot egy long
 * változóba és kiírja, valamint a 4 byte-ot azonnal vissza is küldi a slave-nak!
 */
#include<Wire.h>           
byte fogad1;  //ebbe a vátozóba olvassuk be a slave által küldött 1. byte-ot
byte fogad2;  //ebbe a vátozóba olvassuk be a slave által küldött 2. byte-ot
byte fogad3;  //ebbe a vátozóba olvassuk be a slave által küldött 3. byte-ot
byte fogad4;  //ebbe a vátozóba olvassuk be a slave által küldött 4. byte-ot
void setup()
{
  Serial.begin(9600);       // soros port inicializálása            
  Wire.begin();                 // I2C kommunikáció inicializálása, nincs cím, ezért master
}
void loop()
{
    Wire.requestFrom(8,4);      // a master kér négy bájtot a slave-től
    fogad1 = Wire.read();          //beolvassuk sorban a slave által küldött byte-okat   
    fogad2 = Wire.read();           
    fogad3 = Wire.read();           
    fogad4 = Wire.read();           
    Serial.print("Slave-tol fogadott:");    //kiírjuk a soros portra a fogadott byte-okat
    Serial.print(fogad1);Serial.print(",");Serial.print(fogad2);Serial.print(",");
    Serial.print(fogad3);Serial.print(",");Serial.print(fogad4);Serial.print("->");
    //kiírjuk a soros portra a long-nak összerakott értéket
    Serial.println(byteToLong(fogad1,fogad2,fogad3,fogad4));
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(fogad1);         // küldi a a byte-okat sorban a slave felé
    Wire.write(fogad2);        
    Wire.write(fogad3);                         
    Wire.write(fogad4);                         
    Wire.endTransmission();     // vége az átvitelnek
    delay(1000);                                     
}   
long byteToLong(long inp1, long inp2, long inp3, long inp4)
//Ez a funkció 4 byte-ból csinál egy long változót és visszaadja az eredményt
{
  //4 byte long változóvá alakítása
  return ((inp1 << 0) & 0xFF) + ((inp2 << 8) & 0xFFFF) + ((inp3 << 16) & 0xFFFFFF) + ((inp4 << 24) & 0xFFFFFFFF);
}


Mint az alábbi példából látható lesz, a slave programja megszakításokkal dolgozik. Fut a loop-ban egy program, és csinálja amit kell. Ezt a programot a master részéről érkező adatkérés, illetve a mastertől küldött adatküldés megszakítja, és mindig a megfelelő függvény kerül meghívásar.


Slave programja:

/*
 * Ez a program demo két arduino I2C porton keresztül történő adatcseréjének bemutatására készült.
 * Ez itt a slave programja. A master adatkérésekor megméri az A0 portra kötött feszültséget
 * beolvassa egy long változóba, azt felbontja 4 byte-ra és byte-onként elküldi a masternek.
 * A mester programja (vissza) küld 4 byte-ot, amit a program fogadást követen long számmá alakít
 */
#include<Wire.h>
byte fogad1;
byte fogad2;
byte fogad3;
byte fogad4;
void setup()
{ 
  Serial.begin(9600);           // soros port inicializálása          
  Wire.begin(8);                // I2C kommunikáció inicializálása 8-as eszköz címmel (mivel slave, címet kell megadni)
  Wire.onReceive(slave_fogad);  //Ezt a funkciót hívja az Arduino, amikor adatot kap a mastertől
  Wire.onRequest(slave_kuld);   //Ezt a funkciót hívja meg az Arduino, amikor a master adatot kér a slave-től
  analogReference(DEFAULT);     //A tápfeszt veszi referenciának (5V)az analóg bemenetre kötött feszültség mérésekor
}
void loop()
{  
}
void slave_fogad ()     
//Ez a függvény akkor indul, amikor a slave értesítést kap, hogy a master adatot fog küldeni
{
  //4 byte-ot várunk a mastertől, és azt olvassuk be
  fogad1 = Wire.read();           
  fogad2 = Wire.read();           
  fogad3 = Wire.read();           
  fogad4 = Wire.read();
  //kiirjuk a soros portra a fogadott byte-okat          
  Serial.print("Mastertől fogadott:");    //Prints in Serial Monitor
  Serial.print(fogad1);Serial.print(",");Serial.print(fogad2);Serial.print(",");
  Serial.print(fogad3);Serial.print(",");Serial.print(fogad4);Serial.print("->");
  //visszaalakítjuk a fogadott 4 byte-ot long számmá és kiirjuk a soros portra
  Serial.println(byteToLong(fogad1,fogad2,fogad3,fogad4));
}
void slave_kuld()                            
//Ez a függvény akkor indul, amikor a master adatot kér a slave-től
{
  long kuldendo=analogRead(A0)-500; //a kipróbáláshoz az analóg bemenetet használjuk -500 és 523 között
  Serial.print("Masternek kuldve:");Serial.println(kuldendo);
  //felbontjuk 4 byte-ra a küldendő long értéket
  byte out1 = (kuldendo & 0xFF);
  byte out2 = ((kuldendo >> 8) & 0xFF);
  byte out3 = ((kuldendo >> 16) & 0xFF);
  byte out4 = ((kuldendo >> 24) & 0xFF);
  Wire.write(out1);              
  Wire.write(out2);
  Wire.write(out3);
  Wire.write(out4);
}
long byteToLong(long inp1, long inp2, long inp3, long inp4)
//Ez a funkció 4 byte-ból csinál egy long változót és visszaadja az eredményt
{
  //4 byte long változóvá alakítása
  return ((inp1 << 0) & 0xFF) + ((inp2 << 8) & 0xFFFF) + ((inp3 << 16) & 0xFFFFFF) + ((inp4 << 24) & 0xFFFFFFFF);
}

Kísérletezgetés közben jöttem rá, hogy nem mindegy hol helyezzük el a slave programjában az adatok írását ( wire.write()-okat ), mert tapasztalatom szerint kizárólag a master által kezdeményezett adatküldés kiszolgáló függvényében lehetnek. Ez a fenti példában ez a slave_kuld() függvény. Kipróbáltam olyan megoldást is, amiben a slave_kuld() függvény csak egy jelzést ad arra, hogy a master adatokat vár. Valahol a program egy másik részén akartam az adatokat elküldeni, nevezetesen a loop() ciklusban, amikor nekem megfelelő, és nem akkor amikor pont kéri a mester a slave_kuld() meghívásával. Sajnos ez így nem működött. Az adatok küldése megtörtént, de a master-hez 4db 255 tartalmú byte érkezett meg. Szóval az slave_kuld() függvénybe kell tenni a wire.write sorokat!! Elképzelhető, hogy valamit nem vettem észre, ezért még nem adom fel, de jelen tudásom szerint, csak így lehet küldeni!

A két Arduino közötti kommunikációt azért kellett megismernem, mert készítettem egy időjárás jelző állomást, aminek a vezérlő programja nem fért el egyetlen Arduino nano alaplapban. Így szükség volt kettőre, melyeknek kommunikálniuk kellett. Ha érdekel, akkor itt találod meg a leírásokat és mindkét alaplap programját.

I2C kommunikáció működése

Forrás: Topor Zoltán cikke a hobbielektronika weboldalon https://www.hobbielektronika.hu/cikkek/kommunikacio_alapjai_-_soros_adatatvitel.html?pg=5

Az II2C vagy IIC kétvezetékes szinkron adatátviteli rendszer, melyet a Philips cég dolgozott ki, integrált áramkörök összekapcsolására. Két jelvezetékkel működik: SCL (órajel) és SDA (adat).

Alapvetően egy Master és egy vagy több Slave kommunikál egymással, de a rendszerben több Master is lehet, ezzel a működéssel most nem foglalkozunk. Mindig a Master küldi az órajelet az SCL vonalra. Az órajel szabványos sebessége 400 KHz, de lehet magasabb órajelet is alkalmazni. Az Arduino Uno 1 MHz körüli frekvencia alkalmazására képes. Az SDA és az SCL vonalakon is található egy-egy felhúzó ellenállás. Az az eszköz, amelyik éppen adatokat ad, lehúzza a jelvezetéket 0V-ra. Ezt a 0V feszültséget minden vonalra kötött eszköz érzékeli, de csak az foglalkozik vele, akinek az adattovábbítás szól (akit a Master megcímzett, de ez maga a Master is lehet ha adtot kér). Az alábbi ábrán egy konkrét áramköri megoldás látható a jelszintek kezelésére. Nem fontos megérteni az áramkör működését, de gondoltam így tudományosabb lehetek.

Az adatátvitelt a Master kezdeményezi, Start jelzéssel, ami az SDA vonal megadott ideig tartó 0V-ra történő lehúzása. Ekkor minden Slave figyelni kezd. A mester ekkor megad egy 7 bites eszköz címet, majd egy bittel azt is jelzi, hogy írni akar a Slave felé vagy olvasni akar adatot a Slave-től. A Slave ACK (Acknowledge) jellel visszaigazolja a vételt és ezután következik az írási vagy olvasási ciklus. Az adattranszfer végét a Master Stop-al jelzi. Egy teljes adattranszfer követhető az alábbi ábrán. A rendszer eredetileg 7 bites címekkel működik, az újabb igényeknek megfelelően később bővítették ki 10 bites címekre.

Látható az ábrából, hogy egyszerre több byte adat is átvihető egymás után. Ez jelentősen gyorsítja az átvitelt, hiszen nem kell minden egyes byte átvitele előtt a Master-nak megcímezni a Slave-t, akinek küldeni akar, vagy akitől fogadni szeretne. Természetesen a Master-nek tudnia kell, hogy hány byte-ot akar fogadni a Slave-tól. Ha a Master ad adatot a Slave-nak, akkor a Slave addig veszi az adatokat, amíg Stop jelzést nem kap.

Egy kitüntetett címérték az általános hívási cím; ha ezt küldi ki a Master, üzenete minden Slave-nek szól. Ha a Slave küld adatot (Master olvasás), akkor az adat után a Master adja ki a nyugtázó impulzust (ACK), amit a Slave érzékel.

Azon szerencsések közé tartozol, akik már tudják, hogyan működik az I2C busz. Ha szeretnél egy ennél sokkal gyorsabb adatátviteli módszert is megismerni, akkor kattints ide!

Ha szeretnél példát látni arra, hogyan használhatod az I2C kommunikációt Arduino programban, akkor kattints ide! Itt a busz sebességének beállítására is találsz információt.

Ha arra is kíváncsi vagy, hogyan tud egymással kommunikálni kér Arduino UNO vagy Arduino nano, akkor azt itt ismerheted meg!

Időjárás állomás

Szerettem volna a kertünkben egy hátsó kis épület falára egy hőmérőt. Mondjuk volt is egy ott, de higanyszálas, amit már nem látok csak szemüveggel. Így azt találtam ki, hogy teszek a falra egy régi hordóból kialakított „disztárgyat”, ami mutatni fogja a hőmérsékletet marhanagy számokkal. A hordó fenekére vágtam egy 20x15cm-es négyezt alakú lyukat, beleapplikáltam egy piros plexi lapot, és ezzel kész is voltam a legnehezebb feladattal. Ezzel megvoltam egy nap alatt, a továbbiakra már csak egy év kellett!

Az első elképzelés az volt, hogy kijelzem a hőmérsékletet, légnyomást és a páratartalmat. Ezekre a feladatokra vettem egy DHT22 hőmérséklet és páratartalom mérőt, mindezt beszereltem egy alumínium lemezből hajtogatott hengerbe, aminek mindkét végére egy-egy bolhapiacon „szerzett” teaszűrőt applikáltam, hogy ne menjenek bele a bogarak. Az egész egység a kisház árnyékos sarkára került. Így néz ki:

A képen beltéri, épület, piszkos, kicsi látható

Automatikusan generált leírás

Vettem még egy BMP180 légnyomásmérőt is, de ezt nem kellet a kültéri dobozba beszerelni, gondoltam ez bárhol lehet, mivel a légnyomás kiegyenlítődik mindenhol, így az Arduino Nano kivezetéseire került közvetlenül a vezérlő dobozában.

A kijelzéshez vásároltam egy csomó 7 szegmenses kijelzőt, amiket 74HC595 léptető regiszterrel szándékoztam meghajtani. Így is lett. A kijelzők hátuljára applikáltam az IC-ket valahogy így (itt még félkész):

A képen beltéri, elektronika, áramkör, asztal látható

Automatikusan generált leírás

Viszont elég furán néz ki a három számcsoport egymás alatt, ha nem látod, mit jelentenek az egyes értékek. Így vettem hozzá 4db MAX7219 meghajtóval működő led mátrix kijelzőt is. Mindegyik számcsoport mögé tettem egyet, illetve a légnyomás adata mögé kettőt, mert a „mbr” (milibár) felirat nem fért el egy kijelzőn. Így nézett ki az első verzió hátulról:

…és előlről:

A képen épület, aláírás, fából készült, ülő látható

Automatikusan generált leírás

Evés közben jön meg az étvágy, rájöttem, hogy a középső számcsoport nem csak a légnyomás kijelzésre lesz jó, mérni szerettem volna a hőmérsékletet a kisházban (ez egy szerszámos kamra szerű kis épület a kertünkben), egy pincében és egy aknában is. Utóbbiakhoz elég nagy távolságra kellett elvinni az érzékelőket, a leghosszabb vezeték 25 méteresre sikerült. Ide már biztosan a Dallas DS18B20 egyvezetékes hőmérő chipre lesz szükség. Ezt nagyon könnyű volt szerelni, mindössze a pince párájától kellett megvédeni valahogy a szenzorokat. Miután kiderült, hogy működik, ragasztópisztolyból kifolyatott műanyag bevonattal láttam el:

A képen beltéri, ülő, asztal, étel látható

Automatikusan generált leírás

Kicsit homályos a felvételen a szenzor, de az a fekete pont a végében a Dallas DS18B20 chip, amit teljesen befed a ráfolyatott műanyag. Ez éppen nem a pincében van, hanem a kisház befőttes üvegei között, de már itt is láttam télen vízcseppet a szenzoron, így biztosan szükséges a pára elleni védelem!

Mivel a házfalra időnként rásüt a nap, mérni kell a megvilágítást és szabályozni a fényerőt. Ezzel sokat kínlódtam, mert a mátrix és a 7 szegmens kijelzők fényereje nagyon más. Kísérletezgetéssel kellett kitalálni, hogy a 74HC595 pwm szabályozott fényereje fokozatonként azonos legyen a MAX7219 tizenhat megfelelő fényerő fokozatának fényerejével.

Ezek voltak az alapvető építőkockák. Kellett még tápegység és a vezérlő egység egy Arduino nano-val. Ezt egy olcsó kis villanyszerelő dobozba szereltem:

A képen zacskó látható

Automatikusan generált leírás

A képen a légnyomásmérő is látszik, a doboz alján lóg a levegőben. Csak úgy fityeg egy vezeték darabon.

Természetesen minden részegységet első verzióban a konyhaasztalon próbáltam ki, megírogattam a vezérlő programokat, és csak a végén került minden a végleges helyére.

A programot nem mutatom meg, mert nem ez lett a végleges verzió. Két év használat során kiderült, hogy a számjegyek kicsik, a lakásunk ablakából nem látszik jól, még nagyobb számjegy kellene. Ráadásul a légnyomás és a páratartalom inkább dicsekvésre alkalmas adat (mármint lehet dicsekedni vele, hogy van), és nem igazán nézegetjük. Így mindhármat egyszerre kijelezni felesleges. Elég a hőmérséklet folyamatos kijelzése, és ezen kívül egyetlen számsor, amin váltakozva jelenítem meg a különböző mért adatokat. Ezért jelentősen átalakítottam a kijelzőt. Az új verzió kinézete:

A képen asztal, ülő, torta, fából készült látható

Automatikusan generált leírás
A képen ülő, aláírás, óra, méter látható

Automatikusan generált leírás

Nem látható a képen, de ez már más tekintetben is korszerűbb verzió. A kijelzőket foglalatba raktam. Ennek oka, hogy az első verzióban nem gondoltam a meleg káros hatására. Amikor nyáron a nap rásüt a berendezésre, belül a plexi mögött iszonyú meleg lesz. Az első verziót egy műanyag lapra szereltem, ami hullámosra „olvadt”:

Nem csak a műanyag lap nem bírta a strapát. A 7 szegmenses kijelzők jelentős részében sorba tönkrementek a szegmensek. A MAX7219 mátrix kijelzőiben is sorban aludtak ki a ledek, de ezt könnyen lehetett cserélni, mert eleve foglalatban volt a mátrix kijelző a saját elektronikája felett. A végén már nem lehetett kitalálni, hogy milyen számot mutat a kijelző. Igaz, ez csak a második nyáron következett be. Ideiglenesen úgy oldottam meg a problémát, hogy a programot átalakítottam, és kihagytam azokat a kijelzőket, amik nem működnek. A végén már csak a középső sor működött.

A 7 szegmens kijelzők foglalatát nyákba forrasztható csatlakozó sorból csináltam. Sajnos elsőre nem lett jó, mert kiderült, hogy a kijelzők kivezetései túl vékonyak ehhez, és nem csatlakoztak biztonságosan. Ha nyomkodtam a kijelzőt, hol egyik, hol másik szegmens aludt ki. A megoldás az lett, hogy a kijelzők kivezetéseihez vékony drótot forrasztottam, ami meg túl vastag lett, de így is bele lehetett ügyeskedni a foglalatba. Még nem kellett kijelzőt cserélni, remélem két három cserét ki fog bírni a foglalat. Vettem tartalékot bőven, hogy még vénebb koromban is cserélgethessem a kipurcant kijelzőket. A mátrix kijelzőkkel semmi baj, ott eleve foglalattal csatlakozott a matrix kijelző a MAX7219 vezérlő panelhoz. Mondjuk ehhez is vettem 10db kijelző mátrixot tartalékba. Nem kell rosszallóan ingatni a fejeket, ali-n 1000Ft-ba került! Csak el ne felejtsem, hogy van, mert esetleg veszek még, ha nem találom meg, és akkor már tényleg drága lesz az üzemeltetés a villanyszámlával együtt. Egyébként kb. 5W-ot fogyaszt az egész kütyü, ami egy év alatt kb 45 Kwh, a jelenlegi árakon ez kb. 2200Ft. Minden nap ránézünk legalább kétszer (ketten vagyunk), vagyis egy hőmérséklet mérés ára 1,5 Ft. Sajnos ez nem egy nyerő adat, az LCD kijelzős fali hőmérőnk költsége kb. évi 100Ft, mert abban csak egy elem kell egy-két évente, így ha azt nézegetnénk akkor 0,07Ft-ba kerülne (bocsi, de egy komoly projekt gazdasági számításokat is tartalmaz, ezt most letudtam)!

Az új fejlesztésben máshoz is kedvet kaptam, illetve másra is kényszert éreztem. Említettem, hogy nem nagyon nézegettük a légnyomás és páratartalom adatot. Ezek pillanatnyi értéke ugyanis nem sokat mond egy átlagembernek. A változás az már igen, de ki emlékszik arra, hogy mennyi volt tegnap a légnyomás. Ezért szerettem volna a mért adatokat rögzíteni egy SD kártyán. Ez komoly nehézségeket okozott. No nem azért, mert bonyolult az SD kártya írása! Sajnos az SD kártya kezelő könyvtárak mindegyike kb. 10Kbyte memóriát vett el a Nano-ban rendelkezésre álló 32K-ból. Egész egyszerűen kifogytam a memóriából, nem fért el a program. Nagyszerű ötletem támadt, két Nano kell, az egyik mér és kijelez, a másik SD kártyára írja az adatokat. Ugrott újabb 1000Ft.

De az SD kártyára írásnak van még egy következménye: kellett egy működő óra ahhoz, hogy utólag értelmesen felhasználhassam az adatokat. Ezzel nincs probléma, de ha van benne RTC óra, akkor kelleni fog egy LCD kijelző, mert időnként be kell állítani az órát. Nem egyszerű ennyi cuccot egybeépíteni! Még annyi változás történt, hogy a DHT22 szenzort egy másik típusra cseréltem (BME280), ezzel csökkent az alkatrészek száma, mert hőmérséklet, légnyomás és páratartalom mérés is benne van. Mivel I2C buszt használ, nem vett el további kivezetéseket a Nano-ból. Ez egyébként csak az egyszerűbb szerelés miatt volt szempont.

Végül néhány fotó a majdnem kész központi vezérlő egységről:

A képen beltéri, monitor, ülő, számítógép látható

Automatikusan generált leírás
A képen számítógép, áramkör látható

Automatikusan generált leírás

A beltéri központi vezérlőn látható az LCD, amin folyamatosan kijelzem a dátumot, időt, és váltogatom a szenzorok mért értékeit kb. 2másodpercenként.

Az alsó sorban látható W:22 azt jelenti, hogy 22 perc múlva fogok legközelebb írni az SD kártyára. Ez azért kellett, mert az SD kártyát időnként kiveszem, és laptopban kiolvasom az adatokat, aztán visszahelyezem, és így látom mennyi időm van, hogy ne veszítsek adatot. A kártyáról nem törlök, megtelni nem fog, mert csak 16Gb-os kártyát kaptam a boltban. 3 hónap alatt 171Kb helyet használtam el, tehát még 93 ezer évig regisztrálhatom. Talán James T. Kirk kapitány erre jár, és adataimmal megmentheti a földet!

Néhány szerelési tapasztalat még a végére. Az első verzióban sokat szívtam azzal, hogy a kábeleket az egyik végén beforrasztottam (a kijelzőnél). A másik végét egy csavarkötéses nano foglalatba kötöttem be. Nem nyerő megoldás. Sok volt a vezeték, nem lehetett áttekinteni a kábelek bekötését. Nagyon nehéz volt szerelni. Egyébként a csavarkötéses foglalat tök jó, csak ebben az esetben nem vált be, mert legalább 10-szer kellett ki be szerelnem az egészet, mert elsőre semmi nem működött jól. Tulajdonképpen az volt a baj, hogy már nagyon akartam látni a kész kütyüt, és túl hamar tettem ki a falra. Még nem volt minden készen, és egy falra felszerelt cuccot nehezebb fejleszteni, mint próbapanelen! Az új generációs megoldásban vettem gyárilag előre szerelt csatlakozókat. 2-3-5 eresek mindegyikéből 10-10 db-ot kaptam 300Ft-ért. Ez egy összedugható csatlakozó mindkét végén színes vezetékekkel. Ezek azok a színes kábelek a vezérlő belsejéről készült fotón. A vezetékeket előre beforrasztottam a nyáklemezre, és a szerelési oldalon műanyag ragasztóból folyattam rá anyagot, hogy ne lehessen túl könnyen kiszakítani.

DS18B20 hőmérőkkel is sokat kellett vesződnöm, bár leginkább egy figyelmetlenség miatt. Amikor kezdtem vele a kísérletezgetést, 10K-s ellenállás akadt a kezembe, és ezt használtam felhúzó ellenállásnak. Ez túl nagy érték, de a 10cm-es vezetékkel jól működött a 4db hőmérő. Aztán beszereltem a hosszabb vezetékeket, és persze nem működött. Arra emlékeztem a leírásokból, hogy lehet csökkenteni az ellenállás értékeket, és le is csökkentettem 4,7K-ra. Na ezzel már ment egy-egy hőmérő, de egy vezetékre többet nem lehetett rákötni. Így minden Dallas hőmérőt külön Nano kivezetéshez kötöttem, ami elvett 4 kivezetést, de jótékony hatása volt a szerelhetőségre is, mert mint kiderült az egy vezeték – egy csatlakozó megoldás jobb mint a sok vezeték – egy csatlakozó megoldás. Így néz ki a szenzorok csatlakozó sora a panelen:

A képen áramkör látható

Automatikusan generált leírás

A második verziónál ezért eleve külön kivezetést terveztem minden Dallas hőmérőnek. Szerelgetés közben, amikor újra olvastam egy leírást, észrevettem, hogy eleve 4,7K felhúzó ellenállást írt a cikk, és azt lehet csökkenteni. Le is csökkentettem 1,5K-ra. Remekül mentek is a hőmérők, még hosszabb vezetékkel is, és mind a négyet lehetett egy bemenetre kötni párhuzamosan. Azonban már nem változtattam a kialakításon, mert így egyszerűbb volt szerelni a vezetékeket.

DHT22 szenzort mint említettem lecseréltem egy BME280-ra, így annak a csatlakozója végül feleslegessé vált, most üresen tátong a lyuk!

A program is megfelelően bonyolult lett a végére. Lássunk egy leltárt, mi is került bele:

Az egyik Nano a master ezekkel a funkciókkal:

  • BME280 szenzor kiolvasása
  • Dallas DS18B20 szenzorok kiolvasása
  • RTC3231 óra modul kezelése
  • 2×16 karakteres LCD kijelző vezérlése
  • Egy nyomógomb kezelése, amivel az LCD-n megjelenő adatokat lehet váltogatni, és órát beállítani.
  • Egy hibajelző led villogtatása a hiba jellegétől függő ritmusban
  • A slave Nano felé adattovábbítás, és annak lekérdezése
  • Mért értékek átlagolása
  • 7 Szegmens led kijelzők meghajtása 74HC595 shift regiszterekkel
  • MAX7219 mátrix kijelzők meghajtása
  • Fénymérő fotótranzisztor lekérdezgetése, fényérték triggerelés, hogy ne villogjon a kijelző fényerő váltás határokon.
  • Fényerő állítás 7 szegmens és led mátrix kijelzőkön.

A másik slave Nano feladatai:

  • Fogadja az adatokat a master Nano-tól
  • SD kártya írása
  • SD kártya írási hibák visszajelzése master-nak.

Mindez így igen egyszerűnek látszik, az is, de egyben már dögösebb program. A program minden fontosabb részletét meg tudod ismerni a modulokról szóló menüpontban. Ahhoz, hogy ezt a szerkezetet meg tudjam építeni kb 5 év tapasztalatára volt szükségem. Ha valaki arra adná a fejét, hogy csináljon valami hasonlót, akkor a leírásaim alapján vélhetőleg kevesebb időre lesz szüksége, a nagy szívások jelentős részét leírtam ezen a weblapon. Persze most a kezdőkről beszélek, a gyakorlott profiknak ez ujjgyakorlat.

Kapcsolási rajzot még mindig nem tudok prezentálni, mert még mindig nem tanultam meg egyik erre szolgáló program használatát sem. Azonban annyira kommersz modulokat használtam, és annyira semmi extra nincs benne, hogy a forráskód kommentjeiből összeáll a kép. Kötve hiszem, hogy valaki egy-az egyben szeretné produkálni a szerkezetet, hiszen ehhez rá kellene vennie édesapámat, hogy adja neki a mások boroshordóját, hogy abból megcsinálhassa a külső „dobozt”. Nem hiszem, hogy menni fog! Esetleg az öcsémnek adná oda!

De ha mégis meg akarná valaki érteni a program működését, még néhány infót elárulok. Az egyszem nyomógomb működése a következő:

  • Első megnyomásra bekapcsolja az LCD háttérvilágítását
  • Második megnyomásra mutatja a DS1-DS4 Dallas szenzorok állapotát (OK vagy nem, működés közben kihúzható és vissza dugható)
  • Harmadik megnyomásra mutatja az SD kártyára írás állapotát, és a légnyomásmérő (külső hőmérő és pártartalommérő) állapotát.
  • Negyedik megnyomásra mutatja az utolsó SD kártya állományok írási időpontját (napi egyszer írok egy állományba éjfélkor egy napi átlagot, valamint egész óránként egy másik naponta külön kezdett állományba az egy órás átlagot)
  • 5-11. megnyomásra sorban mutatja az óra pillanatnyi dátumát és időpontját, és ha hosszan nyomom a gombot, lépteti az éppen mutatott adatot. Ha megfelelő értékhez ért, elengedem a gombot, és beállítja az órában. Kicsit macerás a beállítás, mert pl. a percek beállításánál jó sokáig pörgeti a számot 0-59-ig, és ha nem figyelsz, nyomhatod még egy kört. Az év-nél ha jól emlékszem csak 2050-ig lehet pörgetni a számot, ezt követően vissza áll 2020-ra és onnan indul a számlálás újra.

Ez kb. így néz ki a kijelzőn nyomogatás közben:

Ha befejeztem az összes érték állítását (év, hónap, nap, hét nap, óra, perc) várni kell 5 másodpercet, és visszaáll az alap kijelzésre. Mutatja a dátumot, időpontot és pörgeti ciklikusan a szenzorok mért pillanatnyi adatait.

A led kijelző villogással jelzi, ha valamelyik szenzort nem tudja lekérdezni a program. Ha az SD kártya nincs a helyén, vagy nem elérhető, akkor ezt az infót a slave Nano visszaadja a masternak 10 másodpercenként egyszer, és ekkor is villog a led. Ha több hiba is van egyszerre, akkor gyorsabban villog.

Ha már ilyen sok képet beraktam, következzen még néhány a fő kijelzőről, amint váltogatja a mért adatokat:

Elméletileg minden szenzort le lehet húzni a csatlakozóról menet közben. Ha visszadugom, felismeri, és megy minden tovább. Ha nincs szenzor feldugva, vagy nem működik, akkor a mért érték helyett a hőmérsékletnél 99fok, a páratartalomnál -99, a légnyomásnál 888 kerül kijelzésre, mert ezek láthatóan nem valós értékek.

SD kártyára kétféle állományt készítek. Van egy év file, ennek neve pl. „EV2020.CSV” valamint van egy nap file. Ebből minden nap egy újat csinálok, neve pl. „NP200506.CSV” (az évnek csak az utolsó két számjegye szerepel). Ebbe az állományban egy nap 24-szer írok, óránként. A rögzített adatok az elmúlt egy óra átlag értékei. Az év állományban az elmúlt nap átlag értékei találhatók. A külső hőmérséklet minimumát és maximumát is rögzítem mindkét állományban.

Ime egy példa az év állományból:

Datum;Ido;Homerseklet;Homerseklet_min;Homerseklet_max;Paratartalom;Legnyomas;Kishaz;Akna also;Akna felso;Pince;
2020.02.11;23:59:00;23.35;22.55;24.30;45.15;982.37;23.39;22.58;-99.90;21.95;
2020.02.12;23:59:00;23.62;23.08;24.09;47.01;994.89;22.90;21.94;-99.89;22.01;
2020.02.13;23:59:00;23.00;21.45;23.46;45.18;994.80;22.15;22.08;-99.90;23.51;
2020.02.14;23:59:00;24.76;23.79;25.25;44.44;998.73;22.10;99.89;23.88;22.21;
2020.02.15;23:59:00;23.41;22.08;24.49;44.49;1004.63;22.21;22.84;23.11;22.32;


…és egy nap állomány:

Datum;Ido;Homerseklet;Homerseklet_min;Homerseklet_max;Paratartalom;Legnyomas;Kishaz;Akna also;Akna felso;Pince;
2020.06.05;01:00:00;18.09;17.93;18.29;67.22;977.27;19.46;11.08;5101.57;13.85;
2020.06.05;02:00:00;17.94;17.84;18.08;65.31;977.14;19.25;11.09;836.09;13.86;
2020.06.05;03:00:00;17.87;17.50;18.08;65.44;977.00;19.05;11.07;464.34;13.87;
2020.06.05;04:00:00;17.39;17.35;17.45;68.61;976.60;18.88;11.08;322.19;13.87;
2020.06.05;05:00:00;17.19;16.94;17.36;67.54;976.46;18.69;11.09;248.12;13.87;
2020.06.05;06:00:00;16.96;16.85;17.09;66.86;976.45;18.48;11.10;1533.11;13.87;
2020.06.05;07:00:00;17.33;17.08;17.70;60.87;976.43;18.34;11.08;630.51;13.87;
2020.06.05;08:00:00;17.85;17.51;18.11;58.44;976.74;18.31;11.10;402.98;13.87;
2020.06.05;09:00:00;18.56;18.09;18.73;55.44;977.15;18.31;11.09;296.60;13.87;
2020.06.05;10:00:00;18.78;18.24;19.61;60.68;977.59;18.32;11.09;6272.83;13.87;
2020.06.05;11:00:00;18.98;18.69;19.41;66.24;977.67;18.43;11.09;947.25;13.87;
2020.06.05;12:00:00;18.52;18.18;18.78;70.76;978.03;18.29;11.11;517.77;13.88;
2020.06.05;13:00:01;18.94;18.59;19.19;69.59;977.96;18.39;11.12;358.79;13.89;
2020.06.05;14:00:00;19.67;19.14;20.29;63.54;977.85;18.62;11.11;277.07;13.91;
2020.06.05;15:00:00;21.80;19.48;22.75;57.16;977.41;19.23;11.12;1747.54;13.92;
2020.06.05;16:00:01;22.66;22.40;23.04;53.24;977.30;20.12;11.12;706.81;13.93;
2020.06.05;17:00:00;23.07;22.56;23.73;55.14;977.37;20.51;11.12;446.63;13.93;
2020.06.05;18:00:00;21.47;19.92;22.62;66.24;977.36;20.28;11.12;328.37;13.93;
2020.06.05;19:00:00;19.32;18.65;19.87;78.46;978.58;19.71;11.12;7706.46;13.93;
2020.06.05;20:00:00;18.19;17.76;18.65;80.22;979.54;19.43;11.12;1062.11;13.94;
2020.06.05;21:00:00;17.52;17.20;17.92;82.47;980.37;19.04;11.12;575.95;13.95;
2020.06.05;22:00:00;17.05;16.84;17.26;84.55;981.30;19.00;11.12;397.68;13.96;
2020.06.05;23:00:00;16.93;16.83;16.99;85.55;981.93;18.77;11.12;305.16;13.97;
2020.06.05;00:00:00;16.73;16.68;16.81;86.26;982.39;18.56;11.12;1974.38;13.97;


Most vettem észre, hogy az akna alsó értékben valami marhaság van, úgy tűnik van programhiba, amit nem vettem észre. Bocsi, javítani fogom.

A master programja:

/**************************************************************************************************
 * Kivezetések gyüjteménye:
 * A0 véglegesben pince DS18b20 hőmérő panelen DS1 felirat
 * A1 véglegesben akna felső DS18b20 hőmérő panelen DS2 felirat 
 * A2 véglegesben akna alsó DS18b20 hőmérő panelen DS3 felirat
 * A3 véglegesben kisház DS18b20 hőmérő panelen DS3 felirat
 * A4 - SDA jelvezeték I2C kommunikációhoz
 * A5 - SLC jelvezeték I2C kommunikációhoz
 * A6 - véglegesben fénymérő
 * D2 - latch pin (ST_CP)  74HC595
 * D3 - clockPin (SH_CP) 74HC595  
 * D4 - dataPin (DS) 74HC595 
 * D5 - ChipEnable (CE)74HC595   
 * D6 - LOAD (CS) MAX7219 ledmatrix      
 * D7 - CLK MAX7219 ledmatrix 
 * D8 - DataIn MAX7219 ledmatrix
 * D9 - DHT22/DHT11 Data
 * D10 - kijelzo ki/be nyomógomb bemenet (fejlesztéskor használtam, hogy éjszaka ne világítson
 */

#include<avr/wdt.h> //WatchDog header betöltése

//I2C busz kezelése 
#include <Wire.h>


//Fram cím adatai és cella típus felsorolása
byte A0A1=0b00;
byte _i2cAddress= (0b101000 | A0A1)<<1;
enum cella_tipus { MIN, MAX, SUM, AVG, STO };
//napi átlaghoz tároló cellák címeinek felsorolása
/*const byte v_kulso_n=0;
const byte v_kulso_n_min=1;
const byte v_kulso_n_max=2;
const byte v_para_n=3;
const byte v_para_n_min=4;
const byte v_para_n_max=5;
const byte v_legny_n=6;
const byte v_legny_n_min=7;
const byte v_legny_n_max=8;
const byte v_pince_n=9;
const byte v_aknaa_n=10;
const byte v_aknaf_n=11;
const byte v_kishaz_n=12;
const byte v_kishaz_n_min=13;
const byte v_kishaz_n_max=14;*/
//orankenti átlaghoz tároló cellák címeinek felsorolása
const byte v_kulso_o=15;
const byte v_kulso_o_min=16;
const byte v_kulso_o_max=17;
const byte v_para_o=18;
const byte v_para_o_min=19;
const byte v_para_o_max=20;
const byte v_legny_o=21;
const byte v_legny_o_min=22;
const byte v_legny_o_max=23;
const byte v_pince_o=24;
const byte v_aknaa_o=25;
const byte v_aknaf_o=26;
const byte v_kishaz_o=27;
const byte v_kishaz_o_min=28;
const byte v_kishaz_o_max=29;
long kuldendo1; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo2; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo3; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo4; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo5; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     

//mérési eredményekhez
float kulso_homerseklet=0;
float paratartalom=0;
float legnyomas=1000;
float pince_homerseklet=0;
float akna_homerseklet_also=0;
float akna_homerseklet_felso=0;
float kishaz_homerseklet=0;

//BME280 érzékelőhöz
//#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme; // I2C

//LCD kijelző kezelése
#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, a 4 soros LCD-m címe 3F, kétsors címe 27

//óra modul programjai és változó deklarációi
//==========================================
#include <DS3231.h>
DS3231 Clock;
bool Century=false;
bool h12=true;
bool PM;
byte ev=0;
byte ho=0;
byte nap=0;
byte ora=0;
byte perc=0;
byte masodperc=0;
String str_tmp;
String utolso_nap_iras="Nap file:Nemvolt";
String utolso_ev_iras="Ev file:Nem volt";
byte sd_hiba=2; //A slvae küldi vissza ezt az értéket az adat fogadás után, és visszajelzi, hogy sikerült-e az SD írás 0=ok, 1=hiba, 2=nincs adat

// DALLAS hőmérő chip-ek bekötése, használatuk előkészítése
//=========================================================
    // Dallas onwire hőmérő chip (felülrőlnézve balról jobbra haladva)
    //  18B20 kivezetés   Vezeték            Arduino UNO/NANO kivezetés
    //  GDD               kék vezeték        GND
    //  DATA              sárga vezeték      A3, A1, A6 kivezetéseken 
    //  VDD               piros vezeték      +5V
  #include <OneWire.h>          //Dallas 18b20 hőmérő kezeléshez one-wire busz
  #define chip_max_num 2        //2 chipet kezelünk egy vezeteken maximum
  byte dallas_addr[8];                 //aktuális chip címének a tárolására
  byte dallas1_addr_t[chip_max_num][8]; //chip címek tárolására
  byte dallas2_addr_t[chip_max_num][8]; //chip címek tárolására
  byte dallas3_addr_t[chip_max_num][8]; //chip címek tárolására
  byte dallas4_addr_t[chip_max_num][8]; //chip címek tárolására
  byte dallas_chip_num=0;              //aktuális chip sorszáma
  byte dallas_data[9];                 //kiolvasott adtok tárolására
  OneWire  ds1(A0);              // a A0. kivezetéshez kell kapcsolni pince hőmérőjét 
  OneWire  ds2(A1);              // a A1. kivezetéshez kell kapcsolni az akna felső hőmérőjét 
  OneWire  ds3(A2);              // a A2. kivezetéshez kell kapcsolni az akna alsó hőmérőjét 
  OneWire  ds4(A3);              // a A3. kivezetéshez kell kapcsolni az kisház hőmérőjét 
// Fényérzékelő ellenállás bekötése
//---------------------------------
    //fényellenálláskivezetés Vezeték   Arduino UNO/NANO kivezetés
    //  Fényellenállás 1              (fehér vezeték)    A0
    //  Fényellenállás 2              (kék vezeték)      GND
    //  felhúzó ellenállás            (1Kohm és A0-ra)   +5V
    // mért aadatok a bekötött ellenállásosztó kimenetén kölönböző fényviszonyoknál
    // felhúzó ellenállás 1kohm a +5v-ra, fényellenállás a föld felé
    // tápfeszt veszi referenciának
      // Mért érték sötétben: 1000 vagy több
      // Mért érték erős napsütésben kb 400 vagy kissebb
int mert_fenyero[10]={0,0,0,0,0,0,0,0,0,0}; //az utolso 10 mért fenyero erteke
int fenyertek=0; //az utolso 10 mért fenyerő érték átlaga


//MAX7219-4DM 8x8 led matrix modul************************
//--------------------------------------------------------
    // Led matrix (matrix feliratú kábel):
    //  MAX7219 kivezetés   Vezeték           Arduino UNO/NANO kivezetés
    //  DataIn             (piros vezeték)    D8 (pin13)
    //  CLK                (fekete vezeték)   D7 (pin12)
    //  LOAD (CS)          (zöld vezeték)     D6 (pin11) 

#include "LedControl.h"       
LedControl lc=LedControl(8,7,6,3); //kimeneteket beállítja magának
// karakterképek tárolása tömbökben
//fok celsius karakterkép egy modulon
//const byte lc_celsius[8]={B01100000,B10010000,B01100000,B00000000,B01111100,B10000010,B10000010,B01000100};   
//  Milibar   Százalék   Pince     Akna   Akna felső Akna alsó   ház
// B..11111. B11...1.. B00000000 B00000000 B00111110 B00000000 B00000000
// B..1..... B11..1... B00000000 B00000000 B01001000 B00111110 B00000000
// B...1111. B...1.... B00000000 B00000000 B01001000 B01001000 B00000000
// B..1..... B..1..11. B01111110 B00111110 B00111110 B01001000 B01111110
// B...1111. B.1...11. B01001000 B01001000 B00000000 B00111110 B00011000
// B........ B........ B01001000 B01001000 B01110000 B00000000 B00011000
// B1111111. B........ B00110000 B00000000 B00000000 B00001110 B01111110
// B..1...1. B........ B00000000 B00111110 B00000000 B00000000 B00000000
// B..1...1. Fényerő * Celsius
// B...111.. B01000100 
// B........ B00101000 
// B..11111. B00111000 
// B...1.... B11111110 
// B..1..... B00111000 
// B..1..... B00101000 
//           B01000100 
// led matrix vezérlő parancsok mintapéldák:
// lc.setRow(index,sor,B10100000);
// lc.setLed(index,sor,oszlop,true); fénypont bekapcsolása
// lc.setLed(index,sor,oszlop,false);  fénypont kikapcsolása
// lc.setColumn(0,oszlop,B10100000);

//hétszegmens kijelző vezérlés előkészítés
//----------------------------------------
// 7 szegmens kijelzó (7szegmens feliratú kábel) 74CH595 chip meghajtóval
// 10 modul sorma kötve, soros bitenkénti (modulonkénti 8 bit)beléptetéssel
//  74CH595 kivezetés  Vezeték           Arduino UNO/NANO kivezetés
//  latch pin (ST_CP)  kék vezeték       D2
//  clockPin (SH_CP)   fehér vezeték     D3
//  dataPin (DS)       sárga vezeték     D4
//  ChipEnable (CE)    zöld vezeték      D5

// 7 szegmens karakterkép előállításhoz segédlet
// --A--
// F   B
// --G--
// E   C 
// --D-- P (P=tizedespont)
// számjegyek kijelzéséhez szükséges aktív szegmensek:
// 0=ABCDEF,1=BC,2=ABGED,3=ABGCD,4=BCFG,5=ACDFG,6=ACDEFG,7=ABC,8=ABCDEFG,9=ABCDFG
// Mask: ABFGDPCE (a 8 bites bitfolyamban ebben a sorrendben lett vezetékezve
//       a 74CH595 IC kivezetése hozzákötve a kijelző modul egyes szegmenseihez 
//      tizedespont balról a 6. bit
//karakterképek definiálása: 0-9-ig számjegyek, 10-üres, 11-minuszjel, 12-dupla aláhúzás
const byte karakterkep[13]={B11101011,  //0
                      B10000001,  //1
                      B01110011,  //2
                      B11010011,  //3
                      B10011001,  //4
                      B11011010,  //5
                      B11111010,  //6
                      B10000011,  //7
                      B11111011,  //8
                      B11011011,  //9
                      B00000000,  //ÜRES
                      B00010000,  //MINUSZ
                      B00010010}; //DUPLA ALÁHUZAS
// próbáltam spóromli a memóriával, ezért direktben írtam be a programba, de nem ált be
//const int latchPin = 2;   //pin2 (ST_CP) 74HC595 
//const int clockPin = 3;   //clock pin (SH_CP) 74HC595
//const int dataPin = 4;    //Data in (DS) 74HC595
//const int ChipEnable = 5; //CE kivezetés 74HC95                          


// a mért értéket karakterképének bitsorozta a 7 segment kijelzőbe léptetéshez (4 számjegy)
byte bitsor0[4];  //0 - külső hőmérséklet
byte bitsor1[4];  //1 - páratartalom
byte bitsor2[4];  //2 - légnyomás
byte bitsor3[4];  //3 - pince hőmérséklet
byte bitsor4[4];  //4 - akna hőmérséklet also
byte bitsor5[4];  //4 - akna hőmérséklet felso
byte bitsor6[4];  //5 - kisház hőmérséklet
byte eszkoz_num=0;     //az aktuálisan lekérdezett eszköz indexe, megeggyezik a bitsor változó számjegyével
long meres_ido=millis(); //15 másodpercenkénti mérés időzítéséhez segédváltozó 
long kijelzovaltas_ido=millis(); //az alsó sor értékeinek váltogatásának időzítéséhez
long fenyero_ido_tmp=millis();  //a fényerő beállításának időzítéséhez (1 másodpercenként)
long sd_lekerd_ido=millis();  //az SD kártya ellenőrzésének időzítéséhez (I2C buszin kérdezzük le a slave-től)
bool tarolas=false;
bool tarolasnap=false;



//a kijelzo nyomógombbal történő ki és bekapcsolásához kellenek
bool elengedve=LOW; //a nyomógomb elengedett állapotát jelzi, akkor HIGH, ha egy nyomvatartás után (nyomva=HIGH) elősször megszakad a ontaktus
long elenged_ido=millis(); //az elengedés prellmenetsítéséhez az időmérésre
bool nyomva=LOW;  //ha HIG, akkor nyomva van a nyomógomb, az első kontaktus HIGH-re állítja, elengedés után lesz LOW-ra visszaállítva
long nyomva_ido=millis();  //az megnyomás prellmentesítéséhez időmérésre
bool lcd_vilagitas=LOW;
bool kijelzo_kapcsolas=LOW; //ha 1, akkor éppen végrehajtottunk egy kijelző állapotváltást, és nyomógomb elengedésig megakadályozza, hogy ez megismétlődjön
bool kijelzo_ki_be=HIGH; //a kijelzo ki és bakapcsolt állapotát tárolja
long adatkuldes_ido=millis();
bool ora_setup=LOW;
long ora_setup_ido=millis();
byte setup_num=0;
byte ora_setup_ertek=0;
byte ora_setup_ujertek=0;
bool setup_num_novel=HIGH; //ha növelni lehet egy elengedés után a setup_num-ot, akkor HIGH. Érték változtatás után lesz LOW, hogy ne növekedjen a setup_num értéke
bool mert_ertek_kijelzes=HIGH; //mérési eredmények lcd-re írásának engedélyezés HIGH értékkel
byte kijelzes_num=1;   //az aktuálisan kijelzett érték indexe, megeggyezik a bitsor változó számjegyével
long adatfogadas_ido=millis()+6000; //percenként fogadjuk a mert adatokat a slave-tól, ugyaekkor mérjuk a légnyomást is
long lcd_frissit_ido_tmp=millis()+1000;  //az lcd adatkiírás frissítéshez és a fényerő beállításának időzítéséhez (1 másodpercenként)
long lcd_kijelzo_ido=millis();
byte lcd_eszkoz_num=0;     //az aktuálisan kijelzett eszköz indexe az lcd kijelzőn frissített mert adathoz

bool ds1_on=LOW; //DS1 hűmérő incializálva lett-e, HIGH esetén igen
bool ds2_on=LOW; //DS2 hűmérő incializálva lett-e, HIGH esetén igen
bool ds3_on=LOW; //DS3 hűmérő incializálva lett-e, HIGH esetén igen
bool ds4_on=LOW; //DS4 hűmérő incializálva lett-e, HIGH esetén igen
bool ds1_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool ds2_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool ds3_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool ds4_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool bme280_on=LOW; //bme280 légnyomás, páratrtalom és hőmérséklet mérő inicializálva lett-e, HIGH esetén igen

void setup()
{
  wdt_enable(WDTO_4S);  //engedélyezük a watcdog-ot 4 sekundum várakozással 
                        //4sec után a chip ujaindítja magát, ha nincs közben
                        // wdt_reset() függvényhívás, ami ujraindítja a timert

  
  //74HC595 előkészítése
  pinMode(2,OUTPUT); //latch pin 74HC595
  pinMode(4,OUTPUT); //data pin (DS) 74HC595
  pinMode(3,OUTPUT); //Clock pin 74HC595
  pinMode(5,OUTPUT);  //chipenabla (CE) 74HC595
  //kijelző önteszt
  analogWrite(5,128);  //kezdő fényerő beállítás a hétszegmen kijelzőn. Csak a bekapcsolási folyamat alatt 
  digitalWrite(2, LOW); //shift regiszter kimenetek lezárása (latchPin=0)
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet szazas
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet tizes
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet egyes
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet tizedes
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom ezres (mindíg üres)
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom szazas (mindíg üres)
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom tizes
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom egyes
  digitalWrite(2, HIGH); //shift regiszter kimeneteire a beléptetett infó kiírása (latchPin=1)
  // 8x8 matrix felebresztese
  lc.shutdown(0,false);lc.shutdown(1,false);lc.shutdown(2,false);
  lc.setIntensity(0,8);lc.setIntensity(1,8);lc.setIntensity(2,8);
  // matrix kijelző önteszt
  for (int i=0;i<3;i++) { for (int j=0;j<8;j++) {lc.setRow(i,j,B11111111);} }

  Wire.begin(); //I2C inicializálása
  //***************************************Ezt csak egyszer kell lefuttatni, aztán ki kell kommentezni***************************
  /*/FRAM cellak elokeszítése naponkénti átlaghoz
  multimemo(v_kulso_n,AVG);multimemo(v_kulso_n_min,MIN);multimemo(v_kulso_n_max,MAX);
  multimemo(v_para_n,AVG);multimemo(v_para_n_min,MIN);  multimemo(v_para_n_max,MAX);
  multimemo(v_legny_n,AVG);multimemo(v_legny_n_min,MIN);multimemo(v_legny_n_max,MAX);
  multimemo(v_pince_n,AVG);multimemo(v_aknaa_n,AVG);multimemo(v_aknaf_n,AVG);
  multimemo(v_kishaz_n,AVG);multimemo(v_kishaz_n_min,MIN);multimemo(v_kishaz_n_max,MAX);
  //FRAM cellak elokeszítése orankenti átlaghoz
  multimemo(v_kulso_o,AVG);multimemo(v_kulso_o_min,MIN);multimemo(v_kulso_o_max,MAX);
  multimemo(v_para_o,AVG);multimemo(v_para_o_min,MIN);multimemo(v_para_o_max,MAX);
  multimemo(v_legny_o,AVG);multimemo(v_legny_o_min,MIN);multimemo(v_legny_o_max,MAX);
  multimemo(v_pince_o,AVG);multimemo(v_aknaa_o,AVG);multimemo(v_aknaf_o,AVG);
  multimemo(v_kishaz_o,AVG);multimemo(v_kishaz_o_min,MIN);multimemo(v_kishaz_o_max,MAX);
  multimemo(30,STO);multimemo(31,STO);multimemo(32,STO);
  //Ezt pedig célszerű második lépésben kikommentezni, ha már történt néhány mérés, mert akkor azok adatait
  //fogja tárolni és az előző napi átlag és min max lekérdezéshez nem nullát fog írni. Egyébként az előző
  //napi adatok minden nap egyszer, éjfélkor kerülnek tárolásra, tehát egy nap múlva lesz benne valós adat
  long szam=multimemo(v_kulso_n);multimemo(30,szam);
  szam=multimemo(v_kulso_n_min);multimemo(31,szam);
  szam=multimemo(v_kulso_n_max);multimemo(32,szam);*/
  //*********************************************************************************************************************************

  //LCD inicializálása
  lcd.begin(16,2);
  lcd.clear();
  lcd.backlight();      //háttérvilágítás bekapcsolása
  
  //Serial.begin(9600);
 
  bme280_lekerdez();
  pince_homerseklet=onewire_meres(1);
  akna_homerseklet_felso=onewire_meres(2);
  akna_homerseklet_also=onewire_meres(3);
  kishaz_homerseklet=onewire_meres(4);

  //ide jönnek majd az első mérések, és egy 0. változó feltöltés, hogy rögtön indulhasson az értékek kijelzése
  szamjegybontas(kulso_homerseklet,0,0); //kulső hőmérséklet karakterképe bitsor0 tömbbe
  szamjegybontas(paratartalom,1,1); //páratartalom karakterképe bitsor1 tömbbe
  szamjegybontas(legnyomas,2,1); //legnyomás karakterképe bitsor2 tömbbe
  szamjegybontas(pince_homerseklet,3,0); //pince hőmérséklet karakterképe bitsor3 tömbbe
  szamjegybontas(akna_homerseklet_also,4,0); //akna hőmérséklet karakterképe bitsor4 tömbbe
  szamjegybontas(kishaz_homerseklet,5,0); //kisház hőmérséklet karakterképe bitsor5 tömbbe 

  //felső sor matrix kijelzőjére "°C" felirat
  lc.setRow(2,0,B00000000);
  lc.setRow(2,1,B01000110);
  lc.setRow(2,2,B10101001);
  lc.setRow(2,3,B10101000);
  lc.setRow(2,4,B01001000);
  lc.setRow(2,5,B00001000);
  lc.setRow(2,6,B00001001);
  lc.setRow(2,7,B00000110);
  
  lcd.noBacklight();      //LCD háttérvilágítás kikapcsolása

  pinMode(10,INPUT);  //nyomógomb erre a bemenetre van kötve

  // átküldjük a slave-ra 6-os kóddal a pillanatnyi időt és dátumot
  // A slave SD-re írja az elindulás dátumát és időpontját START.CSV nevű állományba.
  // Ezzel rögzítjük, ha reset, bekapcsolás, vagy watcdog újraindítja a rendszert
  ev=Clock.getYear();
  ho=Clock.getMonth(Century);
  nap=Clock.getDate();
  ora=Clock.getHour(h12,PM);
  perc=Clock.getMinute();  
  masodperc=Clock.getSecond();
  Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
  Wire.write(8);
  Wire.write(ev);
  Wire.write(ho);
  Wire.write(nap);
  Wire.write(ora);
  Wire.write(perc);
  Wire.write(masodperc);
  Wire.endTransmission();     // vége az átvitelnek
  Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé  

}

void loop()
{
TWBR = 255; //I2C buszsebesség 30Khz
  
  /*****************************************************************************
   * 10 másodpercenként lekérdezzük, hogy működik-e az SD kártya. Az SD kártya
   * írás állapotát az LCD-n le lehet kérdezni (OK=sikerül, ERR=nem sikerült).  
   * Elküldjük az erzekelökről, hogy van-e hiba. Ha van érzékelő hiba
   * akkor villogni fog a led a kijelző panelen. Az LCD kijelzőn is kiírjuk
   * a hiba állapotot érzékelőnként (SD1a,SD1b,SD2,SD3: OK=működik,
   *                                           OFF=nem található,
   *                                           CRC=crc hiba az érzékelő olvasásakor
   *****************************************************************************/
  if (millis()>sd_lekerd_ido+10000)
  {
    Wire.requestFrom(8,1);      // a master kér 1 byte-ot a slave-től
    sd_hiba= Wire.read();       //beolvassuk a slave által küldött byte-ot    
    sd_lekerd_ido=millis();
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(9); //a 9-es küldési kód jelzi a hiba átvitelt
    if (ds1_crc_hiba or ds2_crc_hiba or ds3_crc_hiba or !bme280_on or !ds1_on or !ds2_on or !ds3_on or !ds4_on) //ha bármelyik érzékelő hibás, akkor villogjon a led 
      {Wire.write(1);}  //valamelyik érzékelővel hiba van
    else
      {Wire.write(0);} //nincs hiba az érzékelőkkel
    Wire.endTransmission();     // vége az átvitelnek
  } 
  /****************************************************************************
   *másodpercenként frissítjük a kijelzőn az időt
   ****************************************************************************/
  if (millis()>lcd_frissit_ido_tmp+1000)
    {
      // Mivel ezt a programrészt másodpercenkét futtatjuk, felhasználhatjuk arra, hogy másodpercenként 
      // reseteljük a wotchdog számlálóját. Ha a program lefagy, és nem hajtódik végre a wdt_reset, akkor
      // 4 másodperc után újraindul az Arduino nano programja, mintha resetet nyomtunk volna
      wdt_reset(); //alaphelyzetbe állítjuk a a watcdog timer-t

      fenyero_beallitas(); //fenymérés és fényerő beállítás 
      if (ora_setup==LOW) 
      {
        if (ora==0 and perc==0 and (masodperc==0 or masodperc==1))
        {
          str_tmp="";
          ev=Clock.getYear();
          ho=Clock.getMonth(Century);
          nap=Clock.getDate();
          if (ho<10) {str_tmp=str_tmp+"0"+String(ho)+"/";} else {str_tmp=str_tmp+String(ho)+"/";}
          if (nap<10) {str_tmp=str_tmp+"0"+String(nap);} else {str_tmp=str_tmp+String(nap)+" ";}
          lcd.setCursor(0,0);lcd.print(str_tmp);
        }
        ora=Clock.getHour(h12,PM);
        perc=Clock.getMinute();  //percenként fogunk mérni és ez kell a perc változás észrevételéhez
        masodperc=Clock.getSecond();
        if (ora<10) {str_tmp=" 0"+String(ora)+":";} else {str_tmp=" "+String(ora)+":";}
        if (perc<10) {str_tmp=str_tmp+"0"+String(perc)+":";} else {str_tmp=str_tmp+String(perc)+":";}
        if (masodperc<10) {str_tmp=str_tmp+"0"+String(masodperc)+"  ";} else {str_tmp=str_tmp+String(masodperc)+"  ";}
        lcd.setCursor(5,0);lcd.print(str_tmp);
        lcd.setCursor(0,1);lcd.print("W:   ");
        if (60-perc<10) {lcd.setCursor(2,1);lcd.print(String(60-perc)+" ");} else {lcd.setCursor(2,1);lcd.print(String(60-perc));}
        if (masodperc>10) {tarolas=true;tarolasnap=true;}  //azrt kell, hogy a tarolas csak egyszer fusson le az adott percben
      }
      lcd_frissit_ido_tmp=millis();
    }
  
  /*********************************************************************************************************************************
   * A nyomógomb megnyomásával váltogatjuk a kijelző tartalmát
   *********************************************************************************************************************************/
  //megnyomta a nyomógombot, de még lehet, hogy prelles
  if (digitalRead(10)==LOW and nyomva==LOW) {nyomva_ido=millis();nyomva=HIGH;lcd.backlight();}    //megynomta a setup gombot
  //ha 50msec mulva is nyomva tartja,akkor ez már nem prelles, biztosn nyomva van, lehet belépni a setup folyamatba
  if (digitalRead(10)==LOW and millis()>nyomva_ido+50 and nyomva==HIGH) {ora_setup_ido=millis();ora_setup=HIGH;}
  //elengedte a nyomógombot, de még lehet, hogy prelles
  if (digitalRead(10)==HIGH and elengedve==LOW and nyomva==HIGH) {elenged_ido=millis();elengedve=HIGH;}
  //már 70msec óta elenged, biztosan nem prelles, beállítási értéket váltunk, lehet várni az új megnyomásra
  if (digitalRead(10)==HIGH and elengedve==HIGH and millis()>elenged_ido+50) 
  {
    nyomva=LOW;
    elengedve=LOW;
    if (ora_setup==HIGH) //setup módban vagyunk, lehet beállítani a következő setup értéket és várakozni a váltásra vagy nyomva tartás esetén a beállításra
    {
      if (setup_num_novel==HIGH) {setup_num=setup_num+1;if (setup_num==15){setup_num=9;}}
      setup_num_novel=HIGH; //ha változtatás után nem kellett növelni, legözelebb már kell
      switch (setup_num) {
        case 1: //csak a világítást kapcsoljuk be a kijelzőn, és egy percig így is marad
          mert_ertek_kijelzes=HIGH; //mehet tovább a mérési eredmények kijlzése
          lcd_vilagitas=HIGH;  //ha éppen világit az LCD, akkor nem akarja majd újra bekapcsolni az első lenyomással, helyette
                               //rögtön a setup_num értéke 2 lesz, tehát kijelzi az érzékelők állapotát
          break;
       case 2: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Tegnapi atlag:  ");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(7,1);lcd.print((float)multimemo(30)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
       case 3: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Tegnapi min/max:");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(0,1);lcd.print((float)multimemo(31)/100);
          lcd.setCursor(7,1);lcd.print("/");
          lcd.setCursor(9,1);lcd.print((float)multimemo(32)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
       case 4: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Jelenlegi atlag:");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(7,1);lcd.print((float)multimemo(0)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
       case 5: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Jelenl. min/max:");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(0,1);lcd.print((float)multimemo(1)/100);
          lcd.setCursor(7,1);lcd.print("/");
          lcd.setCursor(9,1);lcd.print((float)multimemo(2)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 6: //onewire hőmérők állapot visszajelzée
          lcd.setCursor(0,0);lcd.print("DS1:OK  DS2:OK  ");
          lcd.setCursor(0,1);lcd.print("DS3:OK  DS4:OK  ");
          lcd.setCursor(4,0);
          if (ds1_crc_hiba) {lcd.print("CRC");} if (!ds1_on) {lcd.print("OFF");} 
          lcd.setCursor(12,0);
          if (ds2_crc_hiba) {lcd.print("CRC");} if (!ds2_on) {lcd.print("OFF");} 
          lcd.setCursor(4,1);
          if (ds3_crc_hiba) {lcd.print("CRC");} if (!ds3_on) {lcd.print("OFF");} 
          lcd.setCursor(12,1);
          if (ds4_crc_hiba) {lcd.print("CRC");} if (!ds4_on) {lcd.print("OFF");} 
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 7: //SD működés, és légnyomásmérő működés visszajelzés
          lcd.setCursor(0,0);lcd.print("SD:              ");
          lcd.setCursor(0,1);lcd.print("Legny.mero:      ");
          lcd.setCursor(3,0);
          switch (sd_hiba) 
          {
            case 0:
              lcd.print("OK         ");break;
            case 1:
              lcd.print("Error      ");break;
            case 2:
              lcd.print("nincs adat ");break;
          }
         lcd.setCursor(12,1);
         switch (bme280_on) 
          {
            case 0:
              lcd.print("OFF   ");break;
            case 1:
              lcd.print("ON     ");break;
          }
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 8: //az utolsó napi állomány írási időpontját írjuk ki. Ha hiba volt, akkor nem dátum, hanem error jelzés van a változóban
          lcd.setCursor(0,0);lcd.print(utolso_nap_iras);
          lcd.setCursor(0,1);lcd.print(utolso_ev_iras);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 9:
          lcd.setCursor(0,0);lcd.print("Ora beallitas:  ");
          lcd.setCursor(0,1);lcd.print("Ev:    20"+String(Clock.getYear())+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 10:
         lcd.setCursor(0,1);lcd.print("Ho:      "+String(Clock.getMonth(Century))+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 11:
          lcd.setCursor(0,1);lcd.print("Nap:     "+String(Clock.getDate())+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 12:
          lcd.setCursor(0,1);lcd.print("Hetnapja:");
          lcd.setCursor(9,1);
          switch (Clock.getDoW()) 
            {
              case 1:
                lcd.print("Hetfo");break;
              case 2:
                lcd.print("Kedd ");break;
              case 3:
                lcd.print("Szerd");break;
              case 4:
                lcd.print("Csut ");break;
              case 5:
                lcd.print("Pent ");break;
              case 6:
                lcd.print("Szomb");break;
              case 7:
                lcd.print("Vasar");break;
            }
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 13:
          lcd.setCursor(0,1);lcd.print("Ora:     "+String(Clock.getHour(h12,PM))+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 14:
          lcd.setCursor(0,1);lcd.print("Perc:    "+String(Clock.getMinute())+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
      }
    }
  } 
  //ha 5 másodpercre elengedi a nyomógombpt  setup folyamaton belül, akkor visszaállítjuk az eredeti állapotot
  if (digitalRead(10)==HIGH and millis()>ora_setup_ido+5000 and ora_setup) 
  {
    ora_setup=LOW;nyomva=LOW;
//    setup_num=0;
    if (lcd_vilagitas==LOW) {setup_num=0;} else {setup_num=1;} //ha nem világit a kijelző, akkor első megnyomásar be kell kapcsolni
                                                               //ha világít, akkor első lenyomásar már tartalmat kell váltani
    str_tmp="";  // be kell frissíteni a dátumot az lcd kijelzőn, mert lehet, hogy változott, és egyébként csak óránként frissítem
    if (Clock.getMonth(Century)<10) {str_tmp=str_tmp+"0"+String(Clock.getMonth(Century))+"/";} else {str_tmp=str_tmp+String(Clock.getMonth(Century))+"/";}
    if (Clock.getDate()<10) {str_tmp=str_tmp+"0"+String(Clock.getDate());} else {str_tmp=str_tmp+String(Clock.getDate());}
    lcd.setCursor(0,0);lcd.print(str_tmp);
    mert_ertek_kijelzes=HIGH; //mehet tovább a mérési eredmények kijlzése
  }
  //ha 60 másodperce elengedte a nyomógombpt akkor a háttérvilágítást is kikapcsoljuk
  if (digitalRead(10)==HIGH and millis()>ora_setup_ido+60000 and ora_setup==LOW) 
  {
    lcd_vilagitas=LOW;
    lcd.noBacklight();
  }
  //egy másodpercig nyomvatartotta, változtatjuk az adott értéket, elengedéskor az állapotot beállítjuk az órába
  if (digitalRead(10)==LOW and ora_setup==HIGH and nyomva==HIGH and millis()>nyomva_ido+1000) 
  {
     switch (setup_num) {
        case 9:
          ora_setup_ertek=Clock.getYear();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,19,40);
          Clock.setYear(ora_setup_ujertek);
          break;
        case 10:
          ora_setup_ertek=Clock.getMonth(Century);
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,1,12);
          Clock.setMonth(ora_setup_ujertek);
          break;
        case 11:
          ora_setup_ertek=Clock.getDate();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,1,31);
          Clock.setDate(ora_setup_ujertek);
          break;
        case 12:
          ora_setup_ertek=Clock.getDoW();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,1,7);
          Clock.setDoW(ora_setup_ujertek);
          break;
        case 13:
          ora_setup_ertek=Clock.getHour(h12,PM);
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,0,23);
          Clock.setHour(ora_setup_ujertek);
          break;
        case 14:
          ora_setup_ertek=Clock.getMinute();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,0,59);
          Clock.setMinute(ora_setup_ujertek);
          Clock.setSecond(0);
          break;
      }
      ora_setup_ido=millis();
  }
  /*********************************************************************************************************************
   * egy másodpercenként kiolvassuk az órát, frissítjük az ora, perc és masodperc változókat, kiírjuk az lcd-re        *
   * az időt, a dátumot csak óránként frissítjük. Ugyanekkor megmérjük a fényerőt és ehhez igazítjuk a kijelzők fényrejét *
   *********************************************************************************************************************/
    //kétmásodpercenként más eszköz mérési eredményét jelenítjük meg az LCD kijelzőn, hogy ott is láthatóak legyenek az adatok
    if (millis()>lcd_kijelzo_ido+2000 and mert_ertek_kijelzes==HIGH)  
    {
      lcd.setCursor(5,1);
      switch (lcd_eszkoz_num) {
        case 0:   
          lcd.print(" Kulso:     ");
          lcd.setCursor(12,1);lcd.print(kulso_homerseklet);
          lcd_eszkoz_num++;
          break;
        case 1:     
          lcd.print("  Para:   % ");
          lcd.setCursor(12,1);lcd.print((byte)paratartalom);
          lcd_eszkoz_num++;
          break;
        case 2:   
          lcd.print(" Legny:     ");
          lcd.setCursor(12,1);lcd.print((int)legnyomas);
          lcd_eszkoz_num++;
          break;
        case 3:  
          lcd.print(" Pince:      ");
          lcd.setCursor(12,1);lcd.print(pince_homerseklet);
          lcd_eszkoz_num++;
          break;
        case 4:   
          lcd.print("Akna A:      ");
          lcd.setCursor(12,1);lcd.print(akna_homerseklet_also);
          lcd_eszkoz_num++;
          break;
        case 5:   
          lcd.print("Akna F:      ");
          lcd.setCursor(12,1);lcd.print(akna_homerseklet_felso);
          lcd_eszkoz_num++;
          break;
        case 6:   
          lcd.print("Kishaz:      ");
          lcd.setCursor(12,1);lcd.print(kishaz_homerseklet);
          lcd_eszkoz_num++;
          break;
        case 7:   
          lcd.print("  Feny:      ");
          lcd.setCursor(12,1);lcd.print(analogRead(A6));
          lcd_eszkoz_num++;
          break;
      }
      if (lcd_eszkoz_num>7) { lcd_eszkoz_num=0;}
      lcd_kijelzo_ido=millis();
    }

 
  /*********************************************************************************************************
   * Ez a programrész 15 másodpercenként végez el egy mérést mindíg más senzoron. a teljes mérési ciklus    *
   * minden eszköz végig mérésével 5x15=75 sec, ennyi időközönként kérdezünk le egy-egy szenzort            *
   *********************************************************************************************************/
  if (millis()>meres_ido+1500)  //15 másodpercenként mindig más eszközből olvassuk ki az aktuális meresi eredményt
  {
    switch (eszkoz_num) {
      case 0:   //külső hőmérséklet, páratartalom és légnyomás mérés és értékek kijelzés előkészítése
        bme280_lekerdez();
        if (bme280_on)
        {
          //minden tárolt értéknek a 100 szorosát használjuk, mert így majd a végén 100-al osztva újra tizedes értékben
          //kapjuk meg az eredményt (long változót tárolunk, ami nem tud tizedes értéket tárolni, viszont a hőmérséklet
          //tezedes jegyet is tartalmaz. AZ osztást az SD kártyára tároláskor végezzük egységesen minden adatra, azért a
          //páratartalom és a légnyomás esetén is szorzunk 100-al, ott nem kellene egyébként, mert egész értékek.

          if (kulso_homerseklet>-50 and kulso_homerseklet<50) {  //időnként valami fals érték jöhetett be méréskor, mert a napi átalgokban 
                                                                 //vad értékeket tárolt az SD kártya. Ezért a vizsgálat, ha baromság jön be azt 
                                                                 //inkább kihagyom
            multimemo(v_kulso_o,kulso_homerseklet*100);
            multimemo(v_kulso_o_min,kulso_homerseklet*100);
            multimemo(v_kulso_o_max,kulso_homerseklet*100);
            multimemo(0,kulso_homerseklet*100);
            multimemo(1,kulso_homerseklet*100);
            multimemo(2,kulso_homerseklet*100);
          }

          multimemo(v_para_o,paratartalom*100);
          multimemo(v_para_o_min,paratartalom*100);
          multimemo(v_para_o_max,paratartalom*100);
          multimemo(3,paratartalom*100);
          multimemo(4,paratartalom*100);
          multimemo(5,paratartalom*100);

          multimemo(v_legny_o,legnyomas*100);
          multimemo(v_legny_o_min,legnyomas*100);
          multimemo(v_legny_o_max,legnyomas*100);
          multimemo(6,legnyomas*100);
          multimemo(7,legnyomas*100);
          multimemo(8,legnyomas*100);
          szamjegybontas(kulso_homerseklet,0,0); //kulső hőmérséklet karakterképe bitsor0 tömbbe
          szamjegybontas(paratartalom,1,1); //páratartalom karakterképe bitsor1 tömbbe
          szamjegybontas(legnyomas,2,1); //legnyomás karakterképe bitsor2 tömbbe
          eszkoz_num++;
        }
        break;
      case 1:   //pince hőmérséklet
        if (ds1_on and !ds1_crc_hiba)
        {
          pince_homerseklet=onewire_meres(1);
          multimemo(v_pince_o,pince_homerseklet*100);
          multimemo(9,pince_homerseklet*100);
        }
        eszkoz_num++;
        szamjegybontas(pince_homerseklet,3,0); //pince hőmérséklet karakterképe bitsor3 tömbbe
      case 2:   //akna hőmérséklet_felso
        if (ds2_on and !ds2_crc_hiba)
        {
          akna_homerseklet_felso=onewire_meres(2);
          multimemo(v_aknaf_o,akna_homerseklet_felso*100);
          multimemo(11,akna_homerseklet_felso*100);
        }
        szamjegybontas(akna_homerseklet_felso,5,0); //akna hőmérséklet karakterképe bitsor5 tömbbe
        eszkoz_num++;
        break;
      case 3:   //akna hőmérséklet_alsó
        if (ds3_on and !ds3_crc_hiba)
        {
          akna_homerseklet_also=onewire_meres(3);
          multimemo(v_aknaa_o,akna_homerseklet_also*100);
          multimemo(10,akna_homerseklet_also*100);
        }
        szamjegybontas(akna_homerseklet_also,4,0); //akna hőmérséklet karakterképe bitsor4 tömbbe
        eszkoz_num++;
        break;
      case 4:   //kisház hőmérséklet
        if (ds4_on and !ds4_crc_hiba)
        {
          kishaz_homerseklet=onewire_meres(4);
          multimemo(v_kishaz_o,kishaz_homerseklet*100);
          multimemo(v_kishaz_o_min,kishaz_homerseklet*100);
          multimemo(v_kishaz_o_max,kishaz_homerseklet*100);
          multimemo(12,kishaz_homerseklet*100);
          multimemo(13,kishaz_homerseklet*100);
          multimemo(14,kishaz_homerseklet*100);
        }
        szamjegybontas(kishaz_homerseklet,6,0); //kisház hőmérséklet karakterképe bitsor6 tömbbe
        eszkoz_num++;
        break;
   }
    if (eszkoz_num>4) { eszkoz_num=0;}
    meres_ido=millis();
  }
  /**********************************************************************************************************************
   * Ez a programrész 2 másodpercenként már értéket küld az alső hétszegmens kijelző sorra, é az alsó matrix kijelzőre  *
   **********************************************************************************************************************/
  if (millis()>kijelzovaltas_ido+2000) {  //2 másodpercenként váltjuk a kijelző also sorában kijelzett értékeket
    //adatok kijelzőre írása
    digitalWrite(2, LOW); //shift regiszter kimenetek lezárása (latchPin=0)
    shiftOut(4, 3, MSBFIRST, bitsor0[0]);            //külső hőmérséklet szazas
    shiftOut(4, 3, MSBFIRST, bitsor0[1]);            //külső hőmérséklet tizes
    shiftOut(4, 3, MSBFIRST, bitsor0[2]);            //külső hőmérséklet egyes
    shiftOut(4, 3, MSBFIRST, bitsor0[3]);            //külső hőmérséklet tizedes
    switch (kijelzes_num) {
      case 1:   //páratartalom
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00000000);
        lc.setRow(0,3,B00000000);
        lc.setRow(0,4,B00000000);
        lc.setRow(0,5,B00000000);
        lc.setRow(0,6,B00000000);
        lc.setRow(0,7,B00000000);
        lc.setRow(1,0,B00000000);
        lc.setRow(1,1,B00110000);
        lc.setRow(1,2,B00110010);
        lc.setRow(1,3,B00000100);
        lc.setRow(1,4,B00001000);
        lc.setRow(1,5,B00010000);
        lc.setRow(1,6,B00100110);
        lc.setRow(1,7,B00000110);
        shiftOut(4, 3, MSBFIRST, bitsor1[0]);            //páratartalom ezres (mindíg üres)
        shiftOut(4, 3, MSBFIRST, bitsor1[1]);            //páratartalom szazas (mindíg üres)
        shiftOut(4, 3, MSBFIRST, bitsor1[2]);            //páratartalom tizes
        shiftOut(4, 3, MSBFIRST, bitsor1[3]);            //páratartalom egyes
        kijelzes_num++;
        break;
      case 2:   //légnyomás
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00000000);
        lc.setRow(0,3,B11001011);
        lc.setRow(0,4,B00101100);
        lc.setRow(0,5,B00101000);
        lc.setRow(0,6,B00101000);
        lc.setRow(0,7,B11001000);
        lc.setRow(1,0,B00000000);
        lc.setRow(1,1,B00000001);
        lc.setRow(1,2,B00000001);
        lc.setRow(1,3,B01101001);
        lc.setRow(1,4,B01010101);
        lc.setRow(1,5,B01010101);
        lc.setRow(1,6,B01010101);
        lc.setRow(1,7,B01010101);
        shiftOut(4, 3, MSBFIRST, bitsor2[0]);            //légnyomás ezres
        shiftOut(4, 3, MSBFIRST, bitsor2[1]);            //légnyomás szazas
        shiftOut(4, 3, MSBFIRST, bitsor2[2]);            //légnyomás tizes
        shiftOut(4, 3, MSBFIRST, bitsor2[3]);            //légnyomás egyes
        kijelzes_num++;
        break;
      case 3:   //pince hőmérséklet
        //P betű a 0-ás kijelzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00011100);
        lc.setRow(0,3,B00010010);
        lc.setRow(0,4,B00010010);
        lc.setRow(0,5,B00011100);
        lc.setRow(0,6,B00010000);
        lc.setRow(0,7,B00010000);
        lc.setRow(1,0,B00000000);
        //°C felirat az 1-es kijelzőre
        lc.setRow(1,1,B01000110);
        lc.setRow(1,2,B10101001);
        lc.setRow(1,3,B10101000);
        lc.setRow(1,4,B01001000);
        lc.setRow(1,5,B00001000);
        lc.setRow(1,6,B00001001);
        lc.setRow(1,7,B00000110);
        shiftOut(4, 3, MSBFIRST, bitsor3[0]);            //pince hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor3[1]);            //pince hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor3[2]);            //pince hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor3[3]);            //pince hőmérséklet tizedes
        kijelzes_num++;
        break;
      case 4:   //akna hőmérséklet also
        //Akna also felirat 0-as kijzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00110000);
        lc.setRow(0,3,B01001000);
        lc.setRow(0,4,B01001000);
        lc.setRow(0,5,B01111000);
        lc.setRow(0,6,B01001011);
        lc.setRow(0,7,B01001011);
        shiftOut(4, 3, MSBFIRST, bitsor4[0]);            //akna hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor4[1]);            //akna hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor4[2]);            //akna hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor4[3]);            //akna hőmérséklet tizedes
        kijelzes_num++;
        break;
      case 5:   //akna hőmérséklet felso
        //Akna felső felirat 0-as kijzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00110011);
        lc.setRow(0,3,B01001011);
        lc.setRow(0,4,B01001000);
        lc.setRow(0,5,B01111000);
        lc.setRow(0,6,B01001000);
        lc.setRow(0,7,B01001000);
        shiftOut(4, 3, MSBFIRST, bitsor5[0]);            //akna hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor5[1]);            //akna hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor5[2]);            //akna hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor5[3]);            //akna hőmérséklet tizedes
        kijelzes_num++;
        break;
      case 6:   //kisház hőmérséklet
        //H betű a 0-ás kijelzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00010010);
        lc.setRow(0,3,B00010010);
        lc.setRow(0,4,B00011110);
        lc.setRow(0,5,B00011110);
        lc.setRow(0,6,B00010010);
        lc.setRow(0,7,B00010010);
        shiftOut(4, 3, MSBFIRST, bitsor6[0]);            //kisház hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor6[1]);            //kisház hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor6[2]);            //kisház hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor6[3]);            //kisház hőmérséklet tizedes
        kijelzes_num++;
        break;
    }
    digitalWrite(2, HIGH);  //beléptetett érték kiengedése a shiftregiszter kimenetére (latchPin=1)
    if (kijelzes_num>6) { kijelzes_num=1;}
    kijelzovaltas_ido=millis();
  } 

  
  /*****************************************************************************************************************
   *  Óránként egy alkalommal átküldjük a slave-nak a mért eredményeket (min és max értékkel)
   *  Ha sikerült az átküldés után feltételezzük, hogy sikerült az írás az SD kártyára, és nullázzuk
   *  az átlagértékek segéd változói (összeg és mérésszám). Ha nem volt SD írási hiba, akkor megjegyezzük
   *  az utolsó sd írás időpontját, ha volt hiba akkor a hiba tényét (utolso_nap_iras változóba).
   *****************************************************************************************************************/
  //óránként egyszer átküldjük az órás átlag adatokat a slavenak. Küldés után utan töröljük a változókat, hogy ujra kezdődhessen az átlagolás
  if (perc==0 and masodperc<10 and tarolas)  
  //if (masodperc<10 and tarolas)  
  {
    adatkuldes(0);
    tarolas=false;
    //töröljuk az orankénti átlagoláshoz használt FRAM cellákat
    multimemo(v_kulso_o,AVG);
    multimemo(v_kulso_o_min,MIN);
    multimemo(v_kulso_o_max,MAX);
    multimemo(v_para_o,AVG);
    multimemo(v_para_o_min,MIN);
    multimemo(v_para_o_max,MAX);
    multimemo(v_legny_o,AVG);
    multimemo(v_legny_o_min,MIN);
    multimemo(v_legny_o_max,MAX);
    multimemo(v_pince_o,AVG);
    multimemo(v_aknaa_o,AVG);
    multimemo(v_aknaf_o,AVG);
    multimemo(v_kishaz_o,AVG);
    multimemo(v_kishaz_o_min,MIN);
    multimemo(v_kishaz_o_max,MAX);
    if (sd_hiba==0)
    {
      //összeállítjuk az utolsó nap file dátumának szövegét az LCD-n történő kiíráshoz
      if (Clock.getHour(h12, PM)<10) {str_tmp="0"+String(Clock.getHour(h12, PM))+":";} else {str_tmp=String(Clock.getHour(h12, PM))+":";}
      if (Clock.getMinute()<10) {str_tmp=str_tmp+"0"+String(Clock.getMinute())+":";} else {str_tmp=str_tmp+String(Clock.getMinute());}
      //if (Clock.getSecond()<10) {str_tmp=str_tmp+"0"+String(Clock.getSecond());} else {str_tmp=str_tmp+String(Clock.getSecond());}
      utolso_nap_iras="  Nap file:"+str_tmp;
    }
    else
    {
      utolso_nap_iras="Nap file:SD error";
    }
  }

 
  /*****************************************************************************************************************
   *  Minden nap 23.50-kor átküldjük a slave-nak a mért eredményeket (min és max értékkel)
   *  Ha sikerült az átküldés után feltételezzük, hogy sikerült az írás az SD kártyára, és nullázzuk
   *  az átlagértékek segéd változói (összeg és mérésszám). Ha nem volt SD írási hiba, akkor megjegyezzük
   *  az utolsó sd írás időpontját, ha volt hiba akkor a hiba tényét (utolso_ev_iras változóba).
   *****************************************************************************************************************/
  if ((ora==23 and perc==59) and masodperc<10 and tarolasnap)  
  //if ((perc==35) and masodperc<10 and tarolasnap)  
  {
    adatkuldes(1);
    long szam=multimemo(0);multimemo(30,szam);
    szam=multimemo(1);multimemo(31,szam);
    szam=multimemo(2);multimemo(32,szam);
    tarolasnap=false;
    //töröljuk az naponkénti átlagoláshoz használt FRAM cellákat
    multimemo(0,AVG);
    multimemo(1,MIN);
    multimemo(2,MAX);
    multimemo(3,AVG);
    multimemo(4,MIN);
    multimemo(5,MAX);
    multimemo(6,AVG);
    multimemo(7,MIN);
    multimemo(8,MAX);
    multimemo(9,AVG);
    multimemo(10,AVG);
    multimemo(11,AVG);
    multimemo(12,AVG);
    multimemo(13,MIN);
    multimemo(14,MAX);
    if (sd_hiba==0)
    {
      //összeállítjuk az utolsó év file dátumának szövegét az LCD-n történő kiíráshoz
      if (Clock.getHour(h12, PM)<10) {str_tmp="0"+String(Clock.getHour(h12, PM))+":";} else {str_tmp=String(Clock.getHour(h12, PM))+":";}
      if (Clock.getMinute()<10) {str_tmp=str_tmp+"0"+String(Clock.getMinute())+":";} else {str_tmp=str_tmp+String(Clock.getMinute());}
      //if (Clock.getSecond()<10) {str_tmp=str_tmp+"0"+String(Clock.getSecond());} else {str_tmp=str_tmp+String(Clock.getSecond());}
      utolso_ev_iras="  Ev file: "+str_tmp;
    }
    else
    {
      utolso_ev_iras="Ev file:SD error";
    }
  } 

}


/***************************************************************************************
 * Csak 32 byte-ot lehet egyszerre egy menetben átvinni, a következő beyte-ok sérülnek *
 * nem tudom miért. Ezért két menetre bontottam az órás illetve a napi átlag küldésétt *
 * status a legelso byte:     1-datum idő átvitel 7 byte
 *                            2-az órás adatok 1 mért adatok 30 byte
 *                            3-az órás adatok 2 mért adatok 30 byte
 *                            4-a napi mért adatok 1 30 byte
 *                            5-a napi mért adatok 2 30 byte
 * elősször mindig a dátumot és az időt, és utánna az órás vagy napi mért adaokat küldöm át                           
 ***************************************************************************************/
void adatkuldes(bool allomany)
{
    
//    Serial.println("kuldes");
  Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
  Wire.write(1);
  Wire.write(ev);
  Wire.write(ho);
  Wire.write(nap);
  Wire.write(ora);
  Wire.write(perc);
  Wire.write(masodperc);
  Wire.endTransmission();     // vége az átvitelnek
  if (allomany==0)
  {
    kuldendo1=multimemo(v_kulso_o);
    kuldendo2=multimemo(v_kulso_o_min);
    kuldendo3=multimemo(v_kulso_o_max);
    kuldendo4=multimemo(v_para_o);
    kuldendo5=multimemo(v_para_o_min);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(2);
    kuld_4byte(kuldendo1);  //100-al már szorozunk a tároláskor, és most az átküldéskor is 100-al kéne, így most nem szorzunk, de nem is osztunk
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4); 
    kuld_4byte(kuldendo5); 
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(v_para_o_max);
    kuldendo2=multimemo(v_legny_o);
    kuldendo3=multimemo(v_legny_o_min);
    kuldendo4=multimemo(v_legny_o_max);
    kuldendo5=multimemo(v_kishaz_o);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(3);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(v_kishaz_o_min);
    kuldendo2=multimemo(v_kishaz_o_max);
    kuldendo3=multimemo(v_aknaa_o);
    kuldendo4=multimemo(v_aknaf_o);
    kuldendo5=multimemo(v_pince_o);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(4);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
  }
  else
  {
    kuldendo1=multimemo(0);
    kuldendo2=multimemo(1);
    kuldendo3=multimemo(2);
    kuldendo4=multimemo(3);
    kuldendo5=multimemo(4);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(5);
    kuld_4byte(kuldendo1);  //100-al már szorozunk a tároláskor, és most az átküldéskor is 100-al kéne, így most nem szorzunk, de nem is osztunk
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4); 
    kuld_4byte(kuldendo5); 
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(5);
    kuldendo2=multimemo(6);
    kuldendo3=multimemo(7);
    kuldendo4=multimemo(8);
    kuldendo5=multimemo(12);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(6);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(13);
    kuldendo2=multimemo(14);
    kuldendo3=multimemo(10);
    kuldendo4=multimemo(11);
    kuldendo5=multimemo(9);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(7);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
  }
  Wire.endTransmission();     // vége az átvitelnek
  delay(50); //várunk 50msec-et, hogy a slave befejezhesse az SD írást, és kiderüljön sikerült-e
  Wire.requestFrom(8,1);      // a master kér 1 byte-ot a slave-től
  sd_hiba= Wire.read();       //beolvassuk a slave által küldött byte-ot    
}

void kuld_4byte(long kuldendo)
{
    byte out1=(kuldendo & 0xFF);
    byte out2=((kuldendo >> 8) & 0xFF);
    byte out3=((kuldendo >> 16) & 0xFF);
    byte out4=((kuldendo >> 24) & 0xFF);
    Wire.write(out1);               
    Wire.write(out2);
    Wire.write(out3);
    Wire.write(out4);
}


/****************************************************************************************************
 * Ha óra beállításkor nyomva tartja a nyomógombot, akkor folyamatosan számoltatja felfelé 
 * az éppen beállított adat értékét. Ha elérte a maximumot, akkor nullázza az értéket.
 ****************************************************************************************************/
byte ertekporgetes(byte o_ert, byte o_min, byte o_max)
{
  do
  {
    wdt_reset(); //alaphelyzetbe állítjuk a a watcdog timer-t. Erre itt is szükség van, különben az óra beállítás
                 //alatt is resetet generál a watchdog, mert a fő ciklus nem fut miközben ebben a függvényvben tartozkodik
                 //a program
    o_ert=o_ert+1;setup_num_novel=LOW;
    if (o_ert>o_max) {o_ert=o_min;}
    lcd.setCursor(9,1); 
    if (setup_num==8)
    {
      switch (o_ert) 
      {
        case 1:
          lcd.print("Hetfo");break;
        case 2:
          lcd.print("Kedd ");break;
        case 3:
          lcd.print("Szerd");break;
        case 4:
          lcd.print("Csut ");break;
        case 5:
          lcd.print("Pent ");break;
        case 6:
          lcd.print("Szomb");break;
        case 7:
          lcd.print("Vasar");break;
      }
    } 
    else
    {
      lcd.print(String(o_ert)+"  ");
    }
    delay(700);
  } while (digitalRead(10)==LOW);
  return o_ert;
} 


/****************************************************************************************************
 * Ez a funkció 4 byte-ból csinál egy long változót és visszadja az eredményt
 ****************************************************************************************************/
long byteToLong(long inp1, long inp2, long inp3, long inp4)
{
  //4 byte long változóvá alakítása
  return ((inp1 << 0) & 0xFF) + ((inp2 << 8) & 0xFFFF) + ((inp3 << 16) & 0xFFFFFF) + ((inp4 << 24) & 0xFFFFFFFF);
}

/****************************************************************************************************
 * inicializálja és lekérdezi a BME280 senzort. Ha a senzor inicializálása nem sikerül
 * akkor a függvény újbóli meghívásakor ismét megkisérli az inicializálást, Ha sikerül, akkor 
 * rögtön mér is egyet.
 * Ha már inincializálva van, akkor csak mér. Ha közben megszakad a BME280-al a kapcsolat
 * akkor a hőmérséklet lekérdezés -145.75 fokod ad vissza tapasztalatom szerint, ezért ekkor
 * a mérési eredméynek hamisak, és legközelebb újra megpróbáljuk inicializálni
 ****************************************************************************************************/
void bme280_lekerdez()
{
  if (!bme280_on)
  {
    if (bme.begin(0x76, &Wire)) 
    {         
      bme280_on=HIGH;  //bme280 inicializálása sikerült
      //rögtön mérünk is egyet
      kulso_homerseklet=bme.readTemperature();
      paratartalom=bme.readHumidity();
      legnyomas=bme.readPressure()/100; 
    } 
    else
    {
      kulso_homerseklet=99.9;paratartalom=99;legnyomas=0; 
    }
  }
  else
  {
    kulso_homerseklet=bme.readTemperature();
    paratartalom=bme.readHumidity();
    legnyomas=bme.readPressure()/100; 
    if (kulso_homerseklet<-100) 
    {
      bme280_on=LOW;  //ha nincs bme280 csatlakoztatva, akkor -145 fokot ad vissza, legközelebb megpróbáljuk ujra inincializálni
      kulso_homerseklet=99.9;paratartalom=99;legnyomas=0;
    }
  }
}


/**************************************************************************************************************
 * Dallas onewire hőmérők felderítése és mérése. A legeslegelső függvényhíváskor felderítjük az egyes 
 * bemenetekre kapcsolt dallas chip-ek címeit, és letároljuk egy tömbbe. Ha nem sikerül a címfelderítés
 * akkor a kövtekező függvényhívások során újra és újra próbálkozunk. Így az adatvonalak vezetékeit
 * akár menetközben ki lehet húzni, a csatlakoztatást követő  ütemezett méréskor újra felderítjük a címet. Így pl
 * lehet chip-et cserélni működés közben. 
 * HA a címfelderítés sikerült, akkor ténylegesen mérünk is egyet. Minden függvényhíváskor csak egy eszközt 
 * kérdezünk le, és ennek kiválasztásához bemenő paraméter a ds_index változó, ami négy értéket vehet fel.
 * index   Chip    mérési helyszín
 * 1       DS1     pince hőmérő 
 * 2       DS2     akna felső hőmérő
 * 3       DS3     akna alsó hőmérő
 * 4       DS4     kisház hőmérő
 **************************************************************************************************************/
float onewire_meres(byte ds_index)
{
  // DS1 felderítse és címének tárolása, ha még nem történt meg  
  if(!ds1_on) 
  {
    dallas_chip_num=0;  //aktuális chip sorszáma
    ds1_on=LOW;
    while(ds1.search(dallas_addr)) //kisház hőmérő
    {
      //elmásoljuk a kiderített chip ROM címet
      for (byte j=0;j<8;j++) {dallas1_addr_t[dallas_chip_num][j]=dallas_addr[j];} 
      dallas_chip_num++;
      ds1_on=HIGH; //megtaláltuk az eszközt, lehet mérni, amig nincs meg, próbálkozunk a kereséssel
    }  
  }
  // DS2 felderítse és címének tárolása, ha még nem történt meg  
  if(!ds2_on) 
  {
    dallas_chip_num=0;  //aktuális chip sorszáma
    ds2_on=LOW;
    while(ds2.search(dallas_addr)) //kisház hőmérő
    {
      //elmásoljuk a kiderített chip ROM címet
      for (byte j=0;j<8;j++) {dallas2_addr_t[dallas_chip_num][j]=dallas_addr[j];} 
      dallas_chip_num++;
      ds2_on=HIGH; //megtaláltuk az eszközt, lehet mérni, amig nincs meg, próbálkozunk a kereséssel
    }  
  }
  // DS3 felderítse és címének tárolása, ha még nem történt meg 
  if(!ds3_on) 
  {
    dallas_chip_num=0;  //aktuális chip sorszáma
    ds3_on=LOW;
    while(ds3.search(dallas_addr)) //kisház hőmérő
    {
      //elmásoljuk a kiderített chip ROM címet
      for (byte j=0;j<8;j++) {dallas3_addr_t[dallas_chip_num][j]=dallas_addr[j];} 
      dallas_chip_num++;
      ds3_on=HIGH; //megtaláltuk az eszközt, lehet mérni, amig nincs meg, próbálkozunk a kereséssel
    }  
  }
  // DS4 felderítse és címének tárolása, ha még nem történt meg 
  if(!ds4_on) 
  {
    dallas_chip_num=0;  //aktuális chip sorszáma
    ds4_on=LOW;
    while(ds4.search(dallas_addr)) //kisház hőmérő
    {
      //elmásoljuk a kiderített chip ROM címet
      for (byte j=0;j<8;j++) {dallas4_addr_t[dallas_chip_num][j]=dallas_addr[j];} 
      dallas_chip_num++;
      ds4_on=HIGH; //megtaláltuk az eszközt, lehet mérni, amig nincs meg, próbálkozunk a kereséssel
    }  
  }
  //***************************************************************************************************************
  switch (ds_index)
  {
    case 1:
      if (ds1_on) //DS1 csatlakoztatva van
      {
        //Serial.println("ds1 meres indul");
        for (byte j=0;j<8;j++) {dallas_addr[j]=dallas1_addr_t[0][j];} //Másoljuk az aktuális ROM címet az dallas_addr tömbbe
        ds1.reset();ds1.select(dallas_addr);ds1.write(0x44, 1);delay(1000); //eszöz megcímzése és mérés indítása
        ds1.reset();ds1.select(dallas_addr);ds1.write(0xBE);  // Chip memóriájánbak olvasása
        for ( byte l = 0; l < 9; l++) {dallas_data[l] = ds1.read();}    // 9 bytot olvasunk ki
          if ( OneWire::crc8( dallas_data, 8) != dallas_data[8]) {ds1_crc_hiba=HIGH;return 88.8;}
          else {ds1_crc_hiba=LOW;return (float) (((dallas_data[1] << 8) | dallas_data[0])/16.0);}  //mert ertek visszaadása CRC rendben
       }
        else {return 99.99;}
       break;
      //***************************************************************************************************************
    case 2:
      if (ds2_on)
      {
        for (byte j=0;j<8;j++) {dallas_addr[j]=dallas2_addr_t[0][j];} //Másoljuk az aktuális ROM címet az dallas_addr tömbbe
        ds2.reset();ds2.select(dallas_addr);ds2.write(0x44, 1);delay(800); 
        ds2.reset();ds2.select(dallas_addr);ds2.write(0xBE); 
        for ( byte l = 0; l < 9; l++) {dallas_data[l] = ds2.read();}   
        if ( OneWire::crc8( dallas_data, 8) != dallas_data[8]) {ds2_crc_hiba=HIGH;return 88.8;}
        else {ds2_crc_hiba=LOW;return (float) (((dallas_data[1] << 8) | dallas_data[0])/16.0);}
      }
      else {return 99.9;}
      break;
      //***************************************************************************************************************
    case 3:
      if (ds3_on)
      {
        for (byte j=0;j<8;j++) {dallas_addr[j]=dallas3_addr_t[0][j];} //Másoljuk az aktuális ROM címet az dallas_addr tömbbe
        ds3.reset();ds3.select(dallas_addr);ds3.write(0x44, 1);delay(800);
        ds3.reset();ds3.select(dallas_addr);ds3.write(0xBE);
        for ( byte l = 0; l < 9; l++) {dallas_data[l] = ds3.read();} 
        if ( OneWire::crc8( dallas_data, 8) != dallas_data[8]) {ds3_crc_hiba=HIGH;return 88.8;}
        else {ds3_crc_hiba=LOW;return (float) (((dallas_data[1] << 8) | dallas_data[0])/16.0);}
      }
      else {return 99.9;}
      break;
    case 4:
      if (ds4_on)
      {
        for (byte j=0;j<8;j++) {dallas_addr[j]=dallas4_addr_t[0][j];} //Másoljuk az aktuális ROM címet az dallas_addr tömbbe
        ds4.reset();ds4.select(dallas_addr);ds4.write(0x44, 1);delay(800);
        ds4.reset();ds4.select(dallas_addr);ds4.write(0xBE);
        for ( byte l = 0; l < 9; l++) {dallas_data[l] = ds4.read();} 
        if ( OneWire::crc8( dallas_data, 8) != dallas_data[8]) {ds4_crc_hiba=HIGH;return 88.8;}
        else {ds4_crc_hiba=LOW;return (float) (((dallas_data[1] << 8) | dallas_data[0])/16.0);}
      }
      else {return 99.9;}
      break;
  }
}


/****************************************************************************************************
 * Megméri az A6 bemenetre kötött fototrnzisztor és ellenállásosztóban az ellenállás 
 * feszültségét. Ellenállás értéke 1Kohm. A fototranzisztor kb 5000Lux, nál teljesen kinyit
 * ekkor az ellenálláson közel 5V feszültség mérhető. Sötétben a feszültség 0V
 ****************************************************************************************************/
void fenyero_beallitas(){
    int ledmatfeny=0;
    int hszegfeny=0;
    for (byte j=9;j>0;j--) {mert_fenyero[j]=mert_fenyero[j-1];}
    int fenyero=analogRead(A6);
    mert_fenyero[0]=fenyero;
    fenyertek=(mert_fenyero[0]+mert_fenyero[1]+mert_fenyero[2]+mert_fenyero[3]+mert_fenyero[4]+mert_fenyero[5]+mert_fenyero[6]+mert_fenyero[7]+mert_fenyero[8]+mert_fenyero[9])/10;
    if (fenyertek<=20) {hszegfeny=245;ledmatfeny=0;}
    if (fenyertek<=40 & fenyertek>20) {hszegfeny=245;ledmatfeny=1;}  
    if (fenyertek<=70 & fenyertek>40) {hszegfeny=220;ledmatfeny=2;}  
    if (fenyertek<=110 & fenyertek>70) {hszegfeny=198;ledmatfeny=3;}  
    if (fenyertek<=150 & fenyertek>110) {hszegfeny=175;ledmatfeny=4;}  
    if (fenyertek<=180 & fenyertek>150) {hszegfeny=158;ledmatfeny=5;}  
    if (fenyertek<=230 & fenyertek>180) {hszegfeny=140;ledmatfeny=6;}  
    if (fenyertek<=285 & fenyertek>230) {hszegfeny=123;ledmatfeny=7;}  
    if (fenyertek<=320 & fenyertek>285) {hszegfeny=105;ledmatfeny=8;}  
    if (fenyertek<=380 & fenyertek>320) {hszegfeny=88;ledmatfeny=9;}  
    if (fenyertek<=440 & fenyertek>380) {hszegfeny=70;ledmatfeny=10;}  
    if (fenyertek<=500 & fenyertek>440) {hszegfeny=53;ledmatfeny=11;}  
    if (fenyertek<=560 & fenyertek>500) {hszegfeny=35;ledmatfeny=12;}  
    if (fenyertek<=630 & fenyertek>560) {hszegfeny=24;ledmatfeny=13;}  
    if (fenyertek<=700 & fenyertek>630) {hszegfeny=12;ledmatfeny=14;}  
    if (fenyertek>700) {hszegfeny=0;ledmatfeny=15;} 
    analogWrite(5,hszegfeny);
    // fényerő beállítás 0 minimum, 15 maximum 
    lc.setIntensity(0,ledmatfeny);lc.setIntensity(1,ledmatfeny);
    lc.setIntensity(2,ledmatfeny);lc.setIntensity(3,ledmatfeny);
} 


/****************************************************************************************************
 * A függvénynek átadott számértéket annak típusától függően feldolgozza és bitsorozattá
 * alakítja a 74HC595 IC-be történő beléptetéshez. MInden szenzor által mért adatnak külön
 * tömbbe kerülnek az adatai. A légnyomás és a páratartalom csak egész érték lehet, a többi
 * tizedes, tehát a tizedespontot is ki be kell kapcsolni. A vezető nullákat kioltja és
 * kirakja a minusz jelet is
 ****************************************************************************************************/
void szamjegybontas(float szamertek,int ertekindex, int egeszertek){
/*ez a függvány a mérési eredményt négy számjegyre bontja a 7 szegmenses LED kijelzőkbe léptetéses formában
  átvett változók jelentése:
    szamertek : az átalakításra váró mért érték
    ertekindex : az er edmény tárolására szolgáló tömb indexe
    egeszertek : ha értéke 1, akkor lénynomás vagy páratartalom értékét kell átalakítani, ami csak pozitív szám lehet, és nem kell tizedesjegy kijelzés
  Az indexértékkel kiválasztott nevű tömböt tölt a számjegyek bitképével. A tömbök nevei: bitsor0=külső hőmérséklet.....
  A tömbök indexértékeinek jelentése:
    bitsorX[0] -> balrol az első számjegy
    bitsorX[1] -> balrol az második számjegy
    bitsorX[2] -> balrol az harmadik számjegy
    bitsorX[3] -> balrol az negyedik számjegy
  amennyiben a kijelzésre kerülő számjegy nagyobb mint 100 (ez a légnyomás érék), előjel kijelzés nem lehetséges és nincs tizedes érték
  amennyiben a kijelzére kerülő szám egyjegyű, a vezető nullát kioltjuk, illetve helyére kerül a minuszjel ha van. */ 
  float eszamertek=szamertek; //eredeti szamertek tárolására
  float proba;
  int tizedes;
  int ezres=0;    
  int szazas=0;
  int tizes=0;
  int egyes=0;
  boolean tizedespont=false;
  if (egeszertek==1) {  //ez csak a legnyomás lehet vagy paratartalom
    ezres=szamertek/1000;
    szazas=(szamertek-(ezres*1000))/100;
    tizes=(szamertek-(ezres*1000)-(szazas*100))/10;
    egyes=szamertek-(ezres*1000)-(szazas*100)-(tizes*10);
    if (szamertek<100) {  szazas=10;} //vezető nulla kioltása az szazas számjegyben
    if (szamertek<1000) { ezres=10;} //vezető nulla kioltása az ezres szamjegyben
  }
  else {  //az átalakítandó szám lehet pozitiv és negatív, és tizedesjegy értéket kell kijelezni
    if (szamertek<0) {  szazas=11;szamertek=0-szamertek;} else {  szazas=10;}
    tizes=szamertek/10;
    egyes=szamertek-(tizes*10);
    //vezető nullák kioltása (az 10-es érték üres kijelzés), illetve minusz előjel (11-es érték minusz jel)
    if (tizes==0 && eszamertek>=0) {  tizes=10;}
    if (szamertek<10 && eszamertek<0) { tizes=11;szazas=10;} //minusz előjel berakása a százas helyett a tizesbe
    proba=(szamertek-(int)szamertek);
    proba=(proba*10)+0.001;
    tizedes=(int)proba;
    tizedespont=true;
  }
  switch (ertekindex) {
    case 0:   //0 - külső hőmérséklet 
      bitsor0[0]=karakterkep[szazas];
      bitsor0[1]=karakterkep[tizes];
      bitsor0[2]=karakterkep[egyes]|B00000100;
      bitsor0[3]=karakterkep[tizedes];
      break;
    case 1:   //1 - páratartalom
      bitsor1[0]=karakterkep[ezres];
      bitsor1[1]=karakterkep[szazas];
      bitsor1[2]=karakterkep[tizes];
      bitsor1[3]=karakterkep[egyes];
      break;
    case 2:   //2 - légnyomás
      bitsor2[0]=karakterkep[ezres];
      bitsor2[1]=karakterkep[szazas];
      bitsor2[2]=karakterkep[tizes];
      bitsor2[3]=karakterkep[egyes];
      break;
    case 3:   //3 - pince hőmérséklet
      bitsor3[0]=karakterkep[szazas];
      bitsor3[1]=karakterkep[tizes];
      bitsor3[2]=karakterkep[egyes]|B00000100;
      bitsor3[3]=karakterkep[tizedes];
      break;
    case 4:   //4 - akna hőmérséklet
      bitsor4[0]=karakterkep[szazas];
      bitsor4[1]=karakterkep[tizes];
      bitsor4[2]=karakterkep[egyes]|B00000100;
      bitsor4[3]=karakterkep[tizedes];
      break;
    case 5:   //5 - kisház hőmérséklet
      bitsor5[0]=karakterkep[szazas];
      bitsor5[1]=karakterkep[tizes];
      bitsor5[2]=karakterkep[egyes]|B00000100;
      bitsor5[3]=karakterkep[tizedes];
      break;
    case 6:   //5 - kisház hőmérséklet
      bitsor6[0]=karakterkep[szazas];
      bitsor6[1]=karakterkep[tizes];
      bitsor6[2]=karakterkep[egyes]|B00000100;
      bitsor6[3]=karakterkep[tizedes];
      break;
  } 
}

long multimemo(byte cim) {
/***************************************************************************************************************
 * Ez a multimemo függvény akkor hívódik meg, ha csak egyetlen paramétert adtunk meg a függvény                *
 * meghívásakor (csak olvasni akarunk a tároló cellából)                                                       *
 * A három paraméteres multimemo függvényt hívja meg, de default értékkel tölti fel a maradék két paramétert   *
 * A megadott tároló cella címről adtot fogunk olvasni.                                                        *
 ***************************************************************************************************************/
  long adat=0;
  bool iras=0;
  return(multimemo(cim,adat,iras));
}


long multimemo(byte cim, long adat) {
/***************************************************************************************************************
 * Ez a multimemo függvény akkor hívódik meg, ha két paramétert adunk meg, a cella címét és a beírandó         *
 * adatot, vagyis írni akarunk a cellába. A tároló cella formázását és alaphelyzetbe állítását végző           *
 * két paraméteres multimemo függvénytől az különbözteti meg, hogy ennek második paramétere long típusú.       *
 * A három paraméteres multimemo függvényt hívja meg, de a harmadik paramétert (iras vagy olvasás) default     *
 * értékkel tölti fel (iras=1, azaz írunk). A megadott tároló cella címébe fogjuk az adtot beírni.             *
 ***************************************************************************************************************/
  bool iras=1;
  return(multimemo(cim,adat,iras));
}

long multimemo(byte cim, long adat, bool iras) {
/*****************************************************************************************************************************************************************
 * Ez a multimemo függvény akkor hívódik meg, ha három paraméterrel hívjuk. Ez végzi a tárolandó adat feldolgozását, tárolását, és visszaadja az eredményt       *  
 * visszatérő értékként. A tároló cella típusát az első tárolt byte adja meg, ezt az a multimemo függvény írja, melynek két paramétere van, és a második         *
 * cella_tipus (enum-al definiált felsorolás) típusú adatot vár.                                                                                                 *
 * A függvény bemenő paraméterei:                                                                                                                                *
 *    cim: 0-73 memória cella, egy cella 7 byte,                                                                                                                 *
 *    adat: long típusú 4 byte-os adat, amit beírunk a kijelölt memória cellába                                                                                  *
 *    iras: ez mondja meg, hogy írunk vagy olvasunk a tároló cellából. 0-olvasunk, 1-írunk                                                                       *
 * A tároló cella szerkezete:                                                                                                                                    *
 *    0. byte típus: 0=minimum cella, 1=maximum cella, 2=summa cella, 3=átlag cella, 4=tároló cella                                                              *
 *    1-4. byte adat long adattípus, ez a tényleges tárolt long adat 4 byte-on                                                                                   *
 *    5-6. byte átlag esetén az összegzett adatok száma int adattípusú Csak az átlagoló tároló cella használja, ebben számolja az írások számát.                 *
 *****************************************************************************************************************************************************************/
  int xcim=cim*7;
  byte page=0;
  byte adat0;
  byte adat1;
  byte adat2;
  byte adat3;
  if (xcim<256) {page=0;} else {page=1;xcim=xcim-256;}
  Wire.beginTransmission(_i2cAddress | (page&1));
  Wire.write(xcim);
  Wire.endTransmission(); 
  Wire.requestFrom(_i2cAddress | (page&1),7);
  byte xtipus=Wire.read();  //elkérjük az első byte-ot, ami a tárolt dat típusát adja meg
  long xadat=long(Wire.read()) | long(Wire.read())<<8 | long(Wire.read())<<16 | long(Wire.read())<<24 ; //elkérjük és betöltjük az adat aktuális értékét
  int xdb=int(Wire.read()) | int(Wire.read())<<8;  //elkérjük és betöltjük az adatok számát
  
  if (iras==0 and xtipus!=3) {return(xadat);}  //ha olvasás és nem átlagot kértünkvolt a művelet, akkor visszadjuk a kiolvasott long értéket
  if (iras==0 and xtipus==3) {if (xdb>0) {return((long)(xadat/xdb));} else {return(0);}}  //ha olvasás és átlagot kértünk, akkor osztani is kell, de csak ha xdb nem 0
  
  if (xtipus==0 and iras==1){ //ha írás és minimum cella
      if (xadat>adat) { //csak akkor írjuk be az új adatot, ha az eddig tárolt adatnál kissebb
        multimemo_adatiras(page,xcim,adat); //visszaírjuk az új adatot a tároló cellába
        return(adat);
      }
      else {return(xadat);} //visszadjuk a cellában tárolt minimum értéket
    }  
      
  if (xtipus==1 and iras==1){  //ha írás és maximum cella
      if (xadat<adat) {  //csak akkor írjuk be az új adatot, ha az eddig tárolt adatnál nagyobb
         multimemo_adatiras(page,xcim,adat); //visszaírjuk az új adatot a tároló cellába
        return(adat);
      }
      else {return(xadat);} //visszadjuk a cellában tárolt maximum értéket
    }  
      
  if (xtipus==2 and iras==1){  //ha írás és summa cella
    adat=xadat+adat;  //összeadjuk az új adatot ez eddig beírt adatok (tárolt) összegével
    multimemo_adatiras(page,xcim,adat);  //visszaírjuk az új adatot a tároló cellába
    return(adat); //visszadjuk az új cella értéket
  } 

  if (xtipus==3 and iras==1){  //ha írás és átlag cella
    adat=xadat+adat;  //összeadjuk az új adatot ez eddig beírt adatok (tárolt) összegével
    xdb=xdb+1; //növeljük a beírások számát 1-el
    Wire.beginTransmission(_i2cAddress | (page&1));Wire.write(xcim+1); //a cella első byte-ját már nem kell írni, azt beállított a cella reset, azért cim+1-től írunk
    Wire.write(adat & 0xFF);Wire.write((adat>>8) & 0xFF);Wire.write((adat>>16) & 0xFF);Wire.write((adat>>24) & 0xFF); //beírjuk az új összegzett adatot
    Wire.write(xdb & 0xFF);Wire.write((xdb>>8) & 0xFF);  //beírjuk az írások számát
    Wire.endTransmission(); 
    return((long)(adat/xdb));  //visszadjuk az új átlag értéket
  } 

  if (xtipus==4 and iras==1){  //ha írás és sima tároló cella
    multimemo_adatiras(page,xcim,adat);
    return(adat);
  } 
}

void multimemo_adatiras(byte page, byte xcim, long adat) {
// az átlag cella kivételével mindet ugyanúgy kell beírni, ezért a konkrét írás ebben a közösen használt függvényben lett megvalósítva
  Wire.beginTransmission(_i2cAddress | (page&1));Wire.write(xcim+1); //a cella első byte-ját már nem kell írni, azt beállított a cella reset, azért cim+1-től írunk
  Wire.write(adat & 0xFF);Wire.write((adat>>8) & 0xFF);Wire.write((adat>>16) & 0xFF);Wire.write((adat>>24) & 0xFF);
  Wire.endTransmission(); 
}

long multimemo(byte cim, cella_tipus tipus) {
/*******************************************************************************************************************************************************************  
 * Ez a multimemó függvény akkor hívódik meg, ha két paraméterrel hívjuk, és a második cella_tipus típusú változó, amit enum-al hoztunk létre a program elején.    *
 * A függvény elvégzi egy cella típusának beállítását (a cella első byte-ja), és alaphelyzetbe állítja a tárolt adatokat.                                          *
 * Bemenő paraméterek:                                                                                                                                             *
 *    cim: 0-73 db memória cella, egy cella 7 byte,                                                                                                                *
 *    típus: felsorolás típusú (enum) paraméter, ami meghatározza a cella tárolási módját. Lehetséges értékei 0-4 között.                                          *
 * Típus által képzett cella típusok, és azok tárolási módja, az elvégzett műveletek leírása:                                                                      *
 *  MIN: Az íráskor megkapott adatot csak akkor tárolja, ha az kissebb mint az éppen tárolt adat. Mielőtt elkezdjük a minimumot gyüjteni, be kell írni             *
 *       a lehető legnagyobb long értéket, különben nem biztos, hogy megjegyzi a legelső értéket. A számláló cellarészt nem használja.                             *
 *  MAX: Az íráskor megkapott adatot csak akkor tárolja, ha az nagyobb mint az éppen tárolt adat. Mielőtt elkezdjük a maximumott gyüjteni, be kell írni            *
 *       a lehető legkissebb long értéket, különben nem biztos, hogy megjegyzi a legelső értéket.                                                                  *
 *  SUM: Képzi a beírt adatok összegét. Alapértelmezetten a tartalma 0.                                                                                            *
 *  AVG: képzi a beírt adatok átlagát. Minden beít értéket szummáz az adat mezőben, számolja a beírások számát, és kiolvasáskor osztja az adatot a beírás számmal  *
 *       Alpértelmezetten az adat és a számláló is 0.                                                                                                              *
 *  STO: csak úgy simán tárolja az adatot, nem csinál vele semmit, ha volt bent előtte adat, azt felülírja. Alepértelmezett tartalma 0.                            *
 *******************************************************************************************************************************************************************/
  int xcim=cim*7;
  byte page;
  if (xcim<256) {page=0;} else {page=1;xcim=xcim-256;}
  Wire.beginTransmission(_i2cAddress | (page&1));
  Wire.write(xcim);
  if (tipus==MIN){
      // 2147483647 kezdő értéket írunk a tároló cellába, mert nincs nagyobb szám long esetén, így csak ennél kisebbek jöhetnek.
      Wire.write(0);Wire.write((long)2147483647 & 0xFF);Wire.write(((long)2147483647>>8) & 0xFF);Wire.write(((long)2147483647>>16) & 0xFF);Wire.write(((long)2147483647>>24) & 0xFF);
      Wire.write(0);Wire.write(0);
      Wire.endTransmission(); 
      return(0);
    }    
  if (tipus==MAX){
      // -2147483648 kezdő értéket írunk a tároló cellába, mert nincs kisebb szám long esetén, így csak ennél kisebbek jöhetnek.
      Wire.write(1);Wire.write((long)-2147483648 & 0xFF);Wire.write(((long)-2147483648>>8) & 0xFF);Wire.write(((long)-2147483648>>16) & 0xFF);Wire.write(((long)-2147483648>>24) & 0xFF);
      Wire.write(0);Wire.write(0);
      Wire.endTransmission(); 
      return(0);
    }    
  if (tipus==SUM or tipus==AVG or tipus==STO) {
      //össegző mező esetén csak simán törölni kell mindent
      Wire.write(tipus);Wire.write(0);Wire.write(0);Wire.write(0);Wire.write(0);
      Wire.write(0);Wire.write(0);
      Wire.endTransmission(); 
      return(0);
    }  
    Wire.endTransmission(); 
}

Ennek a programnak a mérete a fordító szerint 30380byte és így 340byte flash marad szabadon. A ram-ból 1498byte-ot használ, és így 550byte marad szabadon. Elkezdtem a programot optimalizálni. Megszüntettem a Dallas hőmérők kezelésére írt külön függvényt, így megszűnt az 1 másodperces delay() használat is. Helyette a mérésk időzítésébe tettem be a chip-ek vezérlését. A mérés indítást mindig egy előző szenzor mérése előtt indítom. Mivel minden chip külön Arduino bemeneten van, nem használom a Dallas chip-ek címzését, ez eddig is felesleges volt, csak nem vettem észre. Íme a kisebb és optimálisabb program:

/**************************************************************************************************
 * Kivezetések gyüjteménye:
 * A0 véglegesben pince DS18b20 hőmérő panelen DS1 felirat
 * A1 véglegesben akna felső DS18b20 hőmérő panelen DS2 felirat 
 * A2 véglegesben akna alsó DS18b20 hőmérő panelen DS3 felirat
 * A3 véglegesben kisház DS18b20 hőmérő panelen DS3 felirat
 * A4 - SDA jelvezeték I2C kommunikációhoz
 * A5 - SLC jelvezeték I2C kommunikációhoz
 * A6 - véglegesben fénymérő
 * D2 - latch pin (ST_CP)  74HC595
 * D3 - clockPin (SH_CP) 74HC595  
 * D4 - dataPin (DS) 74HC595 
 * D5 - ChipEnable (CE)74HC595   
 * D6 - LOAD (CS) MAX7219 ledmatrix      
 * D7 - CLK MAX7219 ledmatrix 
 * D8 - DataIn MAX7219 ledmatrix
 * D9 - DHT22/DHT11 Data
 * D10 - kijelzo ki/be nyomógomb bemenet (fejlesztéskor használtam, hogy éjszaka ne világítson
 */

#include<avr/wdt.h> //WatchDog header betöltése

//I2C busz kezelése 
#include <Wire.h>


//Fram cím adatai és cella típus felsorolása
byte A0A1=0b00;
byte _i2cAddress= (0b101000 | A0A1)<<1;
enum cella_tipus { MIN, MAX, SUM, AVG, STO };
//napi átlaghoz tároló cellák címeinek felsorolása
/*const byte v_kulso_n=0;
const byte v_kulso_n_min=1;
const byte v_kulso_n_max=2;
const byte v_para_n=3;
const byte v_para_n_min=4;
const byte v_para_n_max=5;
const byte v_legny_n=6;
const byte v_legny_n_min=7;
const byte v_legny_n_max=8;
const byte v_pince_n=9;
const byte v_aknaa_n=10;
const byte v_aknaf_n=11;
const byte v_kishaz_n=12;
const byte v_kishaz_n_min=13;
const byte v_kishaz_n_max=14;*/
//orankenti átlaghoz tároló cellák címeinek felsorolása
const byte v_kulso_o=15;
const byte v_kulso_o_min=16;
const byte v_kulso_o_max=17;
const byte v_para_o=18;
const byte v_para_o_min=19;
const byte v_para_o_max=20;
const byte v_legny_o=21;
const byte v_legny_o_min=22;
const byte v_legny_o_max=23;
const byte v_pince_o=24;
const byte v_aknaa_o=25;
const byte v_aknaf_o=26;
const byte v_kishaz_o=27;
const byte v_kishaz_o_min=28;
const byte v_kishaz_o_max=29;
long kuldendo1; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo2; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo3; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo4; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     
long kuldendo5; //segédváltozó az adatküldéshez, első lépésben ebbe olvassuk be a memória cella tartalmát, mert az is i2c-t használ     

//mérési eredményekhez
float kulso_homerseklet=0;
float paratartalom=0;
float legnyomas=1000;
float pince_homerseklet=0;
float akna_homerseklet_also=0;
float akna_homerseklet_felso=0;
float kishaz_homerseklet=0;

//BME280 érzékelőhöz
//#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme; // I2C

//LCD kijelző kezelése
#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, a 4 soros LCD-m címe 3F, kétsors címe 27

//óra modul programjai és változó deklarációi
//==========================================
#include <DS3231.h>
DS3231 Clock;
bool Century=false;
bool h12=true;
bool PM;
byte ev=0;
byte ho=0;
byte nap=0;
byte ora=0;
byte perc=0;
byte masodperc=0;
String str_tmp;
String utolso_nap_iras="Nap file:Nemvolt";
String utolso_ev_iras="Ev file:Nem volt";
byte sd_hiba=2; //A slvae küldi vissza ezt az értéket az adat fogadás után, és visszajelzi, hogy sikerült-e az SD írás 0=ok, 1=hiba, 2=nincs adat

// DALLAS hőmérő chip-ek bekötése, használatuk előkészítése
//=========================================================
    // Dallas onwire hőmérő chip (felülrőlnézve balról jobbra haladva)
    //  18B20 kivezetés   Vezeték            Arduino UNO/NANO kivezetés
    //  GDD               kék vezeték        GND
    //  DATA              sárga vezeték      A3, A1, A6 kivezetéseken 
    //  VDD               piros vezeték      +5V
  #include <OneWire.h>          //Dallas 18b20 hőmérő kezeléshez one-wire busz

  byte ds_data[9];               //kiolvasott adtok tárolására
  OneWire  ds1(A0);              // a A0. kivezetéshez kell kapcsolni pince hőmérőjét 
  OneWire  ds2(A1);              // a A1. kivezetéshez kell kapcsolni az akna felső hőmérőjét 
  OneWire  ds3(A2);              // a A2. kivezetéshez kell kapcsolni az akna alsó hőmérőjét 
  OneWire  ds4(A3);              // a A3. kivezetéshez kell kapcsolni az kisház hőmérőjét 
// Fényérzékelő ellenállás bekötése
//---------------------------------
    //fényellenálláskivezetés Vezeték   Arduino UNO/NANO kivezetés
    //  Fényellenállás 1              (fehér vezeték)    A0
    //  Fényellenállás 2              (kék vezeték)      GND
    //  felhúzó ellenállás            (1Kohm és A0-ra)   +5V
    // mért aadatok a bekötött ellenállásosztó kimenetén kölönböző fényviszonyoknál
    // felhúzó ellenállás 1kohm a +5v-ra, fényellenállás a föld felé
    // tápfeszt veszi referenciának
      // Mért érték sötétben: 1000 vagy több
      // Mért érték erős napsütésben kb 400 vagy kissebb
int mert_fenyero[10]={0,0,0,0,0,0,0,0,0,0}; //az utolso 10 mért fenyero erteke
int fenyertek=0; //az utolso 10 mért fenyerő érték átlaga


//MAX7219-4DM 8x8 led matrix modul************************
//--------------------------------------------------------
    // Led matrix (matrix feliratú kábel):
    //  MAX7219 kivezetés   Vezeték           Arduino UNO/NANO kivezetés
    //  DataIn             (piros vezeték)    D8 (pin13)
    //  CLK                (fekete vezeték)   D7 (pin12)
    //  LOAD (CS)          (zöld vezeték)     D6 (pin11) 

#include "LedControl.h"       
LedControl lc=LedControl(8,7,6,3); //kimeneteket beállítja magának
// karakterképek tárolása tömbökben
//fok celsius karakterkép egy modulon
//const byte lc_celsius[8]={B01100000,B10010000,B01100000,B00000000,B01111100,B10000010,B10000010,B01000100};   
//  Milibar   Százalék   Pince     Akna   Akna felső Akna alsó   ház
// B..11111. B11...1.. B00000000 B00000000 B00111110 B00000000 B00000000
// B..1..... B11..1... B00000000 B00000000 B01001000 B00111110 B00000000
// B...1111. B...1.... B00000000 B00000000 B01001000 B01001000 B00000000
// B..1..... B..1..11. B01111110 B00111110 B00111110 B01001000 B01111110
// B...1111. B.1...11. B01001000 B01001000 B00000000 B00111110 B00011000
// B........ B........ B01001000 B01001000 B01110000 B00000000 B00011000
// B1111111. B........ B00110000 B00000000 B00000000 B00001110 B01111110
// B..1...1. B........ B00000000 B00111110 B00000000 B00000000 B00000000
// B..1...1. Fényerő * Celsius
// B...111.. B01000100 
// B........ B00101000 
// B..11111. B00111000 
// B...1.... B11111110 
// B..1..... B00111000 
// B..1..... B00101000 
//           B01000100 
// led matrix vezérlő parancsok mintapéldák:
// lc.setRow(index,sor,B10100000);
// lc.setLed(index,sor,oszlop,true); fénypont bekapcsolása
// lc.setLed(index,sor,oszlop,false);  fénypont kikapcsolása
// lc.setColumn(0,oszlop,B10100000);

//hétszegmens kijelző vezérlés előkészítés
//----------------------------------------
// 7 szegmens kijelzó (7szegmens feliratú kábel) 74CH595 chip meghajtóval
// 10 modul sorma kötve, soros bitenkénti (modulonkénti 8 bit)beléptetéssel
//  74CH595 kivezetés  Vezeték           Arduino UNO/NANO kivezetés
//  latch pin (ST_CP)  kék vezeték       D2
//  clockPin (SH_CP)   fehér vezeték     D3
//  dataPin (DS)       sárga vezeték     D4
//  ChipEnable (CE)    zöld vezeték      D5

// 7 szegmens karakterkép előállításhoz segédlet
// --A--
// F   B
// --G--
// E   C 
// --D-- P (P=tizedespont)
// számjegyek kijelzéséhez szükséges aktív szegmensek:
// 0=ABCDEF,1=BC,2=ABGED,3=ABGCD,4=BCFG,5=ACDFG,6=ACDEFG,7=ABC,8=ABCDEFG,9=ABCDFG
// Mask: ABFGDPCE (a 8 bites bitfolyamban ebben a sorrendben lett vezetékezve
//       a 74CH595 IC kivezetése hozzákötve a kijelző modul egyes szegmenseihez 
//      tizedespont balról a 6. bit
//karakterképek definiálása: 0-9-ig számjegyek, 10-üres, 11-minuszjel, 12-dupla aláhúzás
const byte karakterkep[13]={B11101011,  //0
                      B10000001,  //1
                      B01110011,  //2
                      B11010011,  //3
                      B10011001,  //4
                      B11011010,  //5
                      B11111010,  //6
                      B10000011,  //7
                      B11111011,  //8
                      B11011011,  //9
                      B00000000,  //ÜRES
                      B00010000,  //MINUSZ
                      B00010010}; //DUPLA ALÁHUZAS
// próbáltam spóromli a memóriával, ezért direktben írtam be a programba, de nem ált be
//const int latchPin = 2;   //pin2 (ST_CP) 74HC595 
//const int clockPin = 3;   //clock pin (SH_CP) 74HC595
//const int dataPin = 4;    //Data in (DS) 74HC595
//const int ChipEnable = 5; //CE kivezetés 74HC95                          


// a mért értéket karakterképének bitsorozta a 7 segment kijelzőbe léptetéshez (4 számjegy)
byte bitsor0[4];  //0 - külső hőmérséklet
byte bitsor1[4];  //1 - páratartalom
byte bitsor2[4];  //2 - légnyomás
byte bitsor3[4];  //3 - pince hőmérséklet
byte bitsor4[4];  //4 - akna hőmérséklet also
byte bitsor5[4];  //4 - akna hőmérséklet felso
byte bitsor6[4];  //5 - kisház hőmérséklet
byte eszkoz_num=0;     //az aktuálisan lekérdezett eszköz indexe, megeggyezik a bitsor változó számjegyével
long meres_ido=millis(); //15 másodpercenkénti mérés időzítéséhez segédváltozó 
long kijelzovaltas_ido=millis(); //az alsó sor értékeinek váltogatásának időzítéséhez
long fenyero_ido_tmp=millis();  //a fényerő beállításának időzítéséhez (1 másodpercenként)
long sd_lekerd_ido=millis();  //az SD kártya ellenőrzésének időzítéséhez (I2C buszin kérdezzük le a slave-től)
bool tarolas=false;
bool tarolasnap=false;



//a kijelzo nyomógombbal történő ki és bekapcsolásához kellenek
bool elengedve=LOW; //a nyomógomb elengedett állapotát jelzi, akkor HIGH, ha egy nyomvatartás után (nyomva=HIGH) elősször megszakad a ontaktus
long elenged_ido=millis(); //az elengedés prellmenetsítéséhez az időmérésre
bool nyomva=LOW;  //ha HIG, akkor nyomva van a nyomógomb, az első kontaktus HIGH-re állítja, elengedés után lesz LOW-ra visszaállítva
long nyomva_ido=millis();  //az megnyomás prellmentesítéséhez időmérésre
bool lcd_vilagitas=LOW;
bool kijelzo_kapcsolas=LOW; //ha 1, akkor éppen végrehajtottunk egy kijelző állapotváltást, és nyomógomb elengedésig megakadályozza, hogy ez megismétlődjön
bool kijelzo_ki_be=HIGH; //a kijelzo ki és bakapcsolt állapotát tárolja
long adatkuldes_ido=millis();
bool ora_setup=LOW;
long ora_setup_ido=millis();
byte setup_num=0;
byte ora_setup_ertek=0;
byte ora_setup_ujertek=0;
bool setup_num_novel=HIGH; //ha növelni lehet egy elengedés után a setup_num-ot, akkor HIGH. Érték változtatás után lesz LOW, hogy ne növekedjen a setup_num értéke
bool mert_ertek_kijelzes=HIGH; //mérési eredmények lcd-re írásának engedélyezés HIGH értékkel
byte kijelzes_num=1;   //az aktuálisan kijelzett érték indexe, megeggyezik a bitsor változó számjegyével
long adatfogadas_ido=millis()+6000; //percenként fogadjuk a mert adatokat a slave-tól, ugyaekkor mérjuk a légnyomást is
long lcd_frissit_ido_tmp=millis()+1000;  //az lcd adatkiírás frissítéshez és a fényerő beállításának időzítéséhez (1 másodpercenként)
long lcd_kijelzo_ido=millis();
byte lcd_eszkoz_num=0;     //az aktuálisan kijelzett eszköz indexe az lcd kijelzőn frissített mert adathoz

bool ds1_on=LOW; //DS1 hűmérő incializálva lett-e, HIGH esetén igen
bool ds2_on=LOW; //DS2 hűmérő incializálva lett-e, HIGH esetén igen
bool ds3_on=LOW; //DS3 hűmérő incializálva lett-e, HIGH esetén igen
bool ds4_on=LOW; //DS4 hűmérő incializálva lett-e, HIGH esetén igen
bool ds1_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool ds2_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool ds3_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool ds4_crc_hiba=LOW; //High esetén DS1 Dallas chip CRC hibát eredményezett olvasáskor
bool bme280_on=LOW; //bme280 légnyomás, páratrtalom és hőmérséklet mérő inicializálva lett-e, HIGH esetén igen

void setup()
{
  wdt_enable(WDTO_4S);  //engedélyezük a watcdog-ot 4 sekundum várakozással 
                        //4sec után a chip ujaindítja magát, ha nincs közben
                        // wdt_reset() függvényhívás, ami ujraindítja a timert

  
  //74HC595 előkészítése
  pinMode(2,OUTPUT); //latch pin 74HC595
  pinMode(4,OUTPUT); //data pin (DS) 74HC595
  pinMode(3,OUTPUT); //Clock pin 74HC595
  pinMode(5,OUTPUT);  //chipenabla (CE) 74HC595
  //kijelző önteszt
  analogWrite(5,128);  //kezdő fényerő beállítás a hétszegmen kijelzőn. Csak a bekapcsolási folyamat alatt 
  digitalWrite(2, LOW); //shift regiszter kimenetek lezárása (latchPin=0)
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet szazas
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet tizes
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet egyes
  shiftOut(4, 3, MSBFIRST, B11111111);            //külső hőmérséklet tizedes
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom ezres (mindíg üres)
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom szazas (mindíg üres)
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom tizes
  shiftOut(4, 3, MSBFIRST, B11111111);            //páratartalom egyes
  digitalWrite(2, HIGH); //shift regiszter kimeneteire a beléptetett infó kiírása (latchPin=1)
  // 8x8 matrix felebresztese
  lc.shutdown(0,false);lc.shutdown(1,false);lc.shutdown(2,false);
  lc.setIntensity(0,8);lc.setIntensity(1,8);lc.setIntensity(2,8);
  // matrix kijelző önteszt
  for (int i=0;i<3;i++) { for (int j=0;j<8;j++) {lc.setRow(i,j,B11111111);} }

  Wire.begin(); //I2C inicializálása
  //***************************************Ezt csak egyszer kell lefuttatni, aztán ki kell kommentezni***************************
  /*/FRAM cellak elokeszítése naponkénti átlaghoz
  multimemo(v_kulso_n,AVG);multimemo(v_kulso_n_min,MIN);multimemo(v_kulso_n_max,MAX);
  multimemo(v_para_n,AVG);multimemo(v_para_n_min,MIN);  multimemo(v_para_n_max,MAX);
  multimemo(v_legny_n,AVG);multimemo(v_legny_n_min,MIN);multimemo(v_legny_n_max,MAX);
  multimemo(v_pince_n,AVG);multimemo(v_aknaa_n,AVG);multimemo(v_aknaf_n,AVG);
  multimemo(v_kishaz_n,AVG);multimemo(v_kishaz_n_min,MIN);multimemo(v_kishaz_n_max,MAX);
  //FRAM cellak elokeszítése orankenti átlaghoz
  multimemo(v_kulso_o,AVG);multimemo(v_kulso_o_min,MIN);multimemo(v_kulso_o_max,MAX);
  multimemo(v_para_o,AVG);multimemo(v_para_o_min,MIN);multimemo(v_para_o_max,MAX);
  multimemo(v_legny_o,AVG);multimemo(v_legny_o_min,MIN);multimemo(v_legny_o_max,MAX);
  multimemo(v_pince_o,AVG);multimemo(v_aknaa_o,AVG);multimemo(v_aknaf_o,AVG);
  multimemo(v_kishaz_o,AVG);multimemo(v_kishaz_o_min,MIN);multimemo(v_kishaz_o_max,MAX);
  multimemo(30,STO);multimemo(31,STO);multimemo(32,STO);
  //Ezt pedig célszerű második lépésben kikommentezni, ha már történt néhány mérés, mert akkor azok adatait
  //fogja tárolni és az előző napi átlag és min max lekérdezéshez nem nullát fog írni. Egyébként az előző
  //napi adatok minden nap egyszer, éjfélkor kerülnek tárolásra, tehát egy nap múlva lesz benne valós adat
  long szam=multimemo(v_kulso_n);multimemo(30,szam);
  szam=multimemo(v_kulso_n_min);multimemo(31,szam);
  szam=multimemo(v_kulso_n_max);multimemo(32,szam);*/
  //*********************************************************************************************************************************

  //LCD inicializálása
  lcd.begin(16,2);
  lcd.clear();
  lcd.backlight();      //háttérvilágítás bekapcsolása
  
  //Serial.begin(9600);
 
  bme280_lekerdez();
  pince_homerseklet=0.0;
  akna_homerseklet_felso=0.0;
  akna_homerseklet_also=0.0;
  kishaz_homerseklet=0.0;

  //ide jönnek majd az első mérések, és egy 0. változó feltöltés, hogy rögtön indulhasson az értékek kijelzése
  szamjegybontas(kulso_homerseklet,0,0); //kulső hőmérséklet karakterképe bitsor0 tömbbe
  szamjegybontas(paratartalom,1,1); //páratartalom karakterképe bitsor1 tömbbe
  szamjegybontas(legnyomas,2,1); //legnyomás karakterképe bitsor2 tömbbe
  szamjegybontas(pince_homerseklet,3,0); //pince hőmérséklet karakterképe bitsor3 tömbbe
  szamjegybontas(akna_homerseklet_also,4,0); //akna hőmérséklet karakterképe bitsor4 tömbbe
  szamjegybontas(kishaz_homerseklet,5,0); //kisház hőmérséklet karakterképe bitsor5 tömbbe 

  //felső sor matrix kijelzőjére "°C" felirat
  lc.setRow(2,0,B00000000);
  lc.setRow(2,1,B01000110);
  lc.setRow(2,2,B10101001);
  lc.setRow(2,3,B10101000);
  lc.setRow(2,4,B01001000);
  lc.setRow(2,5,B00001000);
  lc.setRow(2,6,B00001001);
  lc.setRow(2,7,B00000110);
  
  lcd.noBacklight();      //LCD háttérvilágítás kikapcsolása

  pinMode(10,INPUT);  //nyomógomb erre a bemenetre van kötve

  // átküldjük a slave-ra 6-os kóddal a pillanatnyi időt és dátumot
  // A slave SD-re írja az elindulás dátumát és időpontját START.CSV nevű állományba.
  // Ezzel rögzítjük, ha reset, bekapcsolás, vagy watcdog újraindítja a rendszert
  ev=Clock.getYear();
  ho=Clock.getMonth(Century);
  nap=Clock.getDate();
  ora=Clock.getHour(h12,PM);
  perc=Clock.getMinute();  
  masodperc=Clock.getSecond();
  Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
  Wire.write(8);
  Wire.write(ev);
  Wire.write(ho);
  Wire.write(nap);
  Wire.write(ora);
  Wire.write(perc);
  Wire.write(masodperc);
  Wire.endTransmission();     // vége az átvitelnek
  Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé  

}

void loop()
{
TWBR = 128; //I2C buszsebesség 58Khz
  
  /*****************************************************************************
   * 10 másodpercenként lekérdezzük, hogy működik-e az SD kártya. Az SD kártya
   * írás állapotát az LCD-n le lehet kérdezni (OK=sikerül, ERR=nem sikerült).  
   * Elküldjük az erzekelökről, hogy van-e hiba. Ha van érzékelő hiba
   * akkor villogni fog a led a kijelző panelen. Az LCD kijelzőn is kiírjuk
   * a hiba állapotot érzékelőnként (SD1a,SD1b,SD2,SD3: OK=működik,
   *                                           OFF=nem található,
   *                                           CRC=crc hiba az érzékelő olvasásakor
   *****************************************************************************/
  if (millis()>sd_lekerd_ido+10000)
  {
    Wire.requestFrom(8,1);      // a master kér 1 byte-ot a slave-től
    sd_hiba= Wire.read();       //beolvassuk a slave által küldött byte-ot    
    sd_lekerd_ido=millis();
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(9); //a 9-es küldési kód jelzi a hiba átvitelt
    if (ds1_crc_hiba or ds2_crc_hiba or ds3_crc_hiba or !bme280_on or !ds1_on or !ds2_on or !ds3_on or !ds4_on) //ha bármelyik érzékelő hibás, akkor villogjon a led 
      {Wire.write(1);}  //valamelyik érzékelővel hiba van
    else
      {Wire.write(0);} //nincs hiba az érzékelőkkel
    Wire.endTransmission();     // vége az átvitelnek
  } 
  /****************************************************************************
   *másodpercenként frissítjük a kijelzőn az időt
   ****************************************************************************/
  if (millis()>lcd_frissit_ido_tmp+1000)
    {
      // Mivel ezt a programrészt másodpercenkét futtatjuk, felhasználhatjuk arra, hogy másodpercenként 
      // reseteljük a wotchdog számlálóját. Ha a program lefagy, és nem hajtódik végre a wdt_reset, akkor
      // 4 másodperc után újraindul az Arduino nano programja, mintha resetet nyomtunk volna
      wdt_reset(); //alaphelyzetbe állítjuk a a watcdog timer-t

      fenyero_beallitas(); //fenymérés és fényerő beállítás 
      if (ora_setup==LOW) 
      {
        if (ora==0 and perc==0 and (masodperc==0 or masodperc==1))
        {
          str_tmp="";
          ev=Clock.getYear();
          ho=Clock.getMonth(Century);
          nap=Clock.getDate();
          if (ho<10) {str_tmp=str_tmp+"0"+String(ho)+"/";} else {str_tmp=str_tmp+String(ho)+"/";}
          if (nap<10) {str_tmp=str_tmp+"0"+String(nap);} else {str_tmp=str_tmp+String(nap)+" ";}
          lcd.setCursor(0,0);lcd.print(str_tmp);
        }
        ora=Clock.getHour(h12,PM);
        perc=Clock.getMinute();  //percenként fogunk mérni és ez kell a perc változás észrevételéhez
        masodperc=Clock.getSecond();
        if (ora<10) {str_tmp=" 0"+String(ora)+":";} else {str_tmp=" "+String(ora)+":";}
        if (perc<10) {str_tmp=str_tmp+"0"+String(perc)+":";} else {str_tmp=str_tmp+String(perc)+":";}
        if (masodperc<10) {str_tmp=str_tmp+"0"+String(masodperc)+"  ";} else {str_tmp=str_tmp+String(masodperc)+"  ";}
        lcd.setCursor(5,0);lcd.print(str_tmp);
        lcd.setCursor(0,1);lcd.print("W:   ");
        if (60-perc<10) {lcd.setCursor(2,1);lcd.print(String(60-perc)+" ");} else {lcd.setCursor(2,1);lcd.print(String(60-perc));}
        if (masodperc>10) {tarolas=true;tarolasnap=true;}  //azrt kell, hogy a tarolas csak egyszer fusson le az adott percben
      }
      lcd_frissit_ido_tmp=millis();
    }
  
  /*********************************************************************************************************************************
   * A nyomógomb megnyomásával váltogatjuk a kijelző tartalmát
   *********************************************************************************************************************************/
  //megnyomta a nyomógombot, de még lehet, hogy prelles
  if (digitalRead(10)==LOW and nyomva==LOW) {nyomva_ido=millis();nyomva=HIGH;lcd.backlight();}    //megynomta a setup gombot
  //ha 50msec mulva is nyomva tartja,akkor ez már nem prelles, biztosn nyomva van, lehet belépni a setup folyamatba
  if (digitalRead(10)==LOW and millis()>nyomva_ido+50 and nyomva==HIGH) {ora_setup_ido=millis();ora_setup=HIGH;}
  //elengedte a nyomógombot, de még lehet, hogy prelles
  if (digitalRead(10)==HIGH and elengedve==LOW and nyomva==HIGH) {elenged_ido=millis();elengedve=HIGH;}
  //már 70msec óta elenged, biztosan nem prelles, beállítási értéket váltunk, lehet várni az új megnyomásra
  if (digitalRead(10)==HIGH and elengedve==HIGH and millis()>elenged_ido+50) 
  {
    nyomva=LOW;
    elengedve=LOW;
    if (ora_setup==HIGH) //setup módban vagyunk, lehet beállítani a következő setup értéket és várakozni a váltásra vagy nyomva tartás esetén a beállításra
    {
      if (setup_num_novel==HIGH) {setup_num=setup_num+1;if (setup_num==15){setup_num=9;}}
      setup_num_novel=HIGH; //ha változtatás után nem kellett növelni, legözelebb már kell
      switch (setup_num) {
        case 1: //csak a világítást kapcsoljuk be a kijelzőn, és egy percig így is marad
          mert_ertek_kijelzes=HIGH; //mehet tovább a mérési eredmények kijlzése
          lcd_vilagitas=HIGH;  //ha éppen világit az LCD, akkor nem akarja majd újra bekapcsolni az első lenyomással, helyette
                               //rögtön a setup_num értéke 2 lesz, tehát kijelzi az érzékelők állapotát
          break;
       case 2: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Tegnapi atlag:  ");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(7,1);lcd.print((float)multimemo(30)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
       case 3: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Tegnapi min/max:");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(0,1);lcd.print((float)multimemo(31)/100);
          lcd.setCursor(7,1);lcd.print("/");
          lcd.setCursor(9,1);lcd.print((float)multimemo(32)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
       case 4: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Jelenlegi atlag:");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(7,1);lcd.print((float)multimemo(0)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
       case 5: //átlag érték kijelzés
          lcd.setCursor(0,0);lcd.print("Jelenl. min/max:");
          lcd.setCursor(0,1);lcd.print("                ");
          lcd.setCursor(0,1);lcd.print((float)multimemo(1)/100);
          lcd.setCursor(7,1);lcd.print("/");
          lcd.setCursor(9,1);lcd.print((float)multimemo(2)/100);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 6: //onewire hőmérők állapot visszajelzée
          lcd.setCursor(0,0);lcd.print("DS1:OK  DS2:OK  ");
          lcd.setCursor(0,1);lcd.print("DS3:OK  DS4:OK  ");
          lcd.setCursor(4,0);
          if (ds1_crc_hiba) {lcd.print("CRC");} if (!ds1_on) {lcd.print("OFF");} 
          lcd.setCursor(12,0);
          if (ds2_crc_hiba) {lcd.print("CRC");} if (!ds2_on) {lcd.print("OFF");} 
          lcd.setCursor(4,1);
          if (ds3_crc_hiba) {lcd.print("CRC");} if (!ds3_on) {lcd.print("OFF");} 
          lcd.setCursor(12,1);
          if (ds4_crc_hiba) {lcd.print("CRC");} if (!ds4_on) {lcd.print("OFF");} 
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 7: //SD működés, és légnyomásmérő működés visszajelzés
          lcd.setCursor(0,0);lcd.print("SD:              ");
          lcd.setCursor(0,1);lcd.print("Legny.mero:      ");
          lcd.setCursor(3,0);
          switch (sd_hiba) 
          {
            case 0:
              lcd.print("OK         ");break;
            case 1:
              lcd.print("Error      ");break;
            case 2:
              lcd.print("nincs adat ");break;
          }
         lcd.setCursor(12,1);
         switch (bme280_on) 
          {
            case 0:
              lcd.print("OFF   ");break;
            case 1:
              lcd.print("ON     ");break;
          }
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 8: //az utolsó napi állomány írási időpontját írjuk ki. Ha hiba volt, akkor nem dátum, hanem error jelzés van a változóban
          lcd.setCursor(0,0);lcd.print(utolso_nap_iras);
          lcd.setCursor(0,1);lcd.print(utolso_ev_iras);
          mert_ertek_kijelzes=LOW; //nem mehet tovább a mérési eredmények kijelzése, 5 sec mulva fog visszakapcsolni ezek kijelzése
          break;
        case 9:
          lcd.setCursor(0,0);lcd.print("Ora beallitas:  ");
          lcd.setCursor(0,1);lcd.print("Ev:    20"+String(Clock.getYear())+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 10:
         lcd.setCursor(0,1);lcd.print("Ho:      "+String(Clock.getMonth(Century))+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 11:
          lcd.setCursor(0,1);lcd.print("Nap:     "+String(Clock.getDate())+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 12:
          lcd.setCursor(0,1);lcd.print("Hetnapja:");
          lcd.setCursor(9,1);
          switch (Clock.getDoW()) 
            {
              case 1:
                lcd.print("Hetfo");break;
              case 2:
                lcd.print("Kedd ");break;
              case 3:
                lcd.print("Szerd");break;
              case 4:
                lcd.print("Csut ");break;
              case 5:
                lcd.print("Pent ");break;
              case 6:
                lcd.print("Szomb");break;
              case 7:
                lcd.print("Vasar");break;
            }
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 13:
          lcd.setCursor(0,1);lcd.print("Ora:     "+String(Clock.getHour(h12,PM))+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
        case 14:
          lcd.setCursor(0,1);lcd.print("Perc:    "+String(Clock.getMinute())+"     ");
          mert_ertek_kijelzes=LOW; //a meresi eredmények kijelzését a setup idejére leállítjuk
          break;
      }
    }
  } 
  //ha 5 másodpercre elengedi a nyomógombpt  setup folyamaton belül, akkor visszaállítjuk az eredeti állapotot
  if (digitalRead(10)==HIGH and millis()>ora_setup_ido+5000 and ora_setup) 
  {
    ora_setup=LOW;nyomva=LOW;
//    setup_num=0;
    if (lcd_vilagitas==LOW) {setup_num=0;} else {setup_num=1;} //ha nem világit a kijelző, akkor első megnyomásar be kell kapcsolni
                                                               //ha világít, akkor első lenyomásar már tartalmat kell váltani
    str_tmp="";  // be kell frissíteni a dátumot az lcd kijelzőn, mert lehet, hogy változott, és egyébként csak óránként frissítem
    if (Clock.getMonth(Century)<10) {str_tmp=str_tmp+"0"+String(Clock.getMonth(Century))+"/";} else {str_tmp=str_tmp+String(Clock.getMonth(Century))+"/";}
    if (Clock.getDate()<10) {str_tmp=str_tmp+"0"+String(Clock.getDate());} else {str_tmp=str_tmp+String(Clock.getDate());}
    lcd.setCursor(0,0);lcd.print(str_tmp);
    mert_ertek_kijelzes=HIGH; //mehet tovább a mérési eredmények kijlzése
  }
  //ha 60 másodperce elengedte a nyomógombpt akkor a háttérvilágítást is kikapcsoljuk
  if (digitalRead(10)==HIGH and millis()>ora_setup_ido+60000 and ora_setup==LOW) 
  {
    lcd_vilagitas=LOW;
    lcd.noBacklight();
  }
  //egy másodpercig nyomvatartotta, változtatjuk az adott értéket, elengedéskor az állapotot beállítjuk az órába
  if (digitalRead(10)==LOW and ora_setup==HIGH and nyomva==HIGH and millis()>nyomva_ido+1000) 
  {
     switch (setup_num) {
        case 9:
          ora_setup_ertek=Clock.getYear();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,19,40);
          Clock.setYear(ora_setup_ujertek);
          break;
        case 10:
          ora_setup_ertek=Clock.getMonth(Century);
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,1,12);
          Clock.setMonth(ora_setup_ujertek);
          break;
        case 11:
          ora_setup_ertek=Clock.getDate();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,1,31);
          Clock.setDate(ora_setup_ujertek);
          break;
        case 12:
          ora_setup_ertek=Clock.getDoW();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,1,7);
          Clock.setDoW(ora_setup_ujertek);
          break;
        case 13:
          ora_setup_ertek=Clock.getHour(h12,PM);
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,0,23);
          Clock.setHour(ora_setup_ujertek);
          break;
        case 14:
          ora_setup_ertek=Clock.getMinute();
          ora_setup_ujertek=ertekporgetes(ora_setup_ertek,0,59);
          Clock.setMinute(ora_setup_ujertek);
          Clock.setSecond(0);
          break;
      }
      ora_setup_ido=millis();
  }
  /*********************************************************************************************************************
   * egy másodpercenként kiolvassuk az órát, frissítjük az ora, perc és masodperc változókat, kiírjuk az lcd-re        *
   * az időt, a dátumot csak óránként frissítjük. Ugyanekkor megmérjük a fényerőt és ehhez igazítjuk a kijelzők fényrejét *
   *********************************************************************************************************************/
    //kétmásodpercenként más eszköz mérési eredményét jelenítjük meg az LCD kijelzőn, hogy ott is láthatóak legyenek az adatok
    if (millis()>lcd_kijelzo_ido+2000 and mert_ertek_kijelzes==HIGH)  
    {
      lcd.setCursor(5,1);
      switch (lcd_eszkoz_num) {
        case 0:   
          lcd.print(" Kulso:     ");
          lcd.setCursor(12,1);lcd.print(kulso_homerseklet);
          lcd_eszkoz_num++;
          break;
        case 1:     
          lcd.print("  Para:   % ");
          lcd.setCursor(12,1);lcd.print((byte)paratartalom);
          lcd_eszkoz_num++;
          break;
        case 2:   
          lcd.print(" Legny:     ");
          lcd.setCursor(12,1);lcd.print((int)legnyomas);
          lcd_eszkoz_num++;
          break;
        case 3:  
          lcd.print(" Pince:      ");
          lcd.setCursor(12,1);lcd.print(pince_homerseklet);
          lcd_eszkoz_num++;
          break;
        case 4:   
          lcd.print("Akna A:      ");
          lcd.setCursor(12,1);lcd.print(akna_homerseklet_also);
          lcd_eszkoz_num++;
          break;
        case 5:   
          lcd.print("Akna F:      ");
          lcd.setCursor(12,1);lcd.print(akna_homerseklet_felso);
          lcd_eszkoz_num++;
          break;
        case 6:   
          lcd.print("Kishaz:      ");
          lcd.setCursor(12,1);lcd.print(kishaz_homerseklet);
          lcd_eszkoz_num++;
          break;
        case 7:   
          lcd.print("  Feny:      ");
          lcd.setCursor(12,1);lcd.print(analogRead(A6));
          lcd_eszkoz_num++;
          break;
      }
      if (lcd_eszkoz_num>7) { lcd_eszkoz_num=0;}
      lcd_kijelzo_ido=millis();
    }

 
  /*********************************************************************************************************
   * Ez a programrész 15 másodpercenként végez el egy mérést mindíg más senzoron. a teljes mérési ciklus    *
   * minden eszköz végig mérésével 5x15=75 sec, ennyi időközönként kérdezünk le egy-egy szenzort            *
   *********************************************************************************************************/
  if (millis()>meres_ido+15000)  //15 másodpercenként mindig más eszközből olvassuk ki az aktuális meresi eredményt
  {
    switch (eszkoz_num) {
      case 0:   //külső hőmérséklet, páratartalom és légnyomás mérés és értékek kijelzés előkészítése
        //Indítunk egy mérést a ds1 dallas chip-en, hogy a következő mérési eszkönél már legyen mért eredményünk
        //Közben eltelik 15 másodperc, elkészül az eredmény a chip-ben, és így várni sem kell 750mses-et az eredméynre delay()-el
        ds1_on=false;
        if (ds1.reset()==1) {    //itt 1-el jelez, ha a reset jelre válaszol egy DS18b20, tehát van a vonalon chip. Ha nincs a vonalon DS18b20, akkor ez 0.
           ds1.skip();           //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
           ds1.write(0x44,1);    //elinditunk egy mérést a chip-ben
           ds1_on=true;         //ezzel jelezzük, hogy elindítottunk egy mérést, 750msec múlva ki lehet olvasni a mért hőmérsékletet
         }
         else {pince_homerseklet=99.9;}  //ds1_on változót nem állítjuk true-ra, így várunk amíg lesz chip a vonalon, 
                                         //a kijelzőn a 99,9 fok jelzi, hogy nincs chip a vonalon

        bme280_lekerdez();
        if (bme280_on)
        {
          //minden tárolt értéknek a 100 szorosát használjuk, mert így majd a végén 100-al osztva újra tizedes értékben
          //kapjuk meg az eredményt (long változót tárolunk, ami nem tud tizedes értéket tárolni, viszont a hőmérséklet
          //tezedes jegyet is tartalmaz. AZ osztást az SD kártyára tároláskor végezzük egységesen minden adatra, azért a
          //páratartalom és a légnyomás esetén is szorzunk 100-al, ott nem kellene egyébként, mert egész értékek.

          if (kulso_homerseklet>-50 and kulso_homerseklet<50) {  //időnként valami fals érték jöhetett be méréskor, mert a napi átalgokban 
                                                                 //vad értékeket tárolt az SD kártya. Ezért a vizsgálat, ha baromság jön be azt 
                                                                 //inkább kihagyom
            multimemo(v_kulso_o,kulso_homerseklet*100);
            multimemo(v_kulso_o_min,kulso_homerseklet*100);
            multimemo(v_kulso_o_max,kulso_homerseklet*100);
            multimemo(0,kulso_homerseklet*100);
            multimemo(1,kulso_homerseklet*100);
            multimemo(2,kulso_homerseklet*100);
          }

          multimemo(v_para_o,paratartalom*100);
          multimemo(v_para_o_min,paratartalom*100);
          multimemo(v_para_o_max,paratartalom*100);
          multimemo(3,paratartalom*100);
          multimemo(4,paratartalom*100);
          multimemo(5,paratartalom*100);

          multimemo(v_legny_o,legnyomas*100);
          multimemo(v_legny_o_min,legnyomas*100);
          multimemo(v_legny_o_max,legnyomas*100);
          multimemo(6,legnyomas*100);
          multimemo(7,legnyomas*100);
          multimemo(8,legnyomas*100);
          szamjegybontas(kulso_homerseklet,0,0); //kulső hőmérséklet karakterképe bitsor0 tömbbe
          szamjegybontas(paratartalom,1,1); //páratartalom karakterképe bitsor1 tömbbe
          szamjegybontas(legnyomas,2,1); //legnyomás karakterképe bitsor2 tömbbe
          eszkoz_num++;
        }
        break;
      case 1:   //pince hőmérséklet
        //Indítunk egy mérést a ds2 dallas chip-en, hogy a következő mérési eszkönél már legyen mért eredményünk
        //Közben eltelik 15 másodperc, elkészül az eredmény a chip-ben, és így várni sem kell 750mses-et az eredméynre delay()-el
        ds2_on=false;
        if (ds2.reset()==1) {    //itt 1-el jelez, ha a reset jelre válaszol egy DS18b20, tehát van a vonalon chip. Ha nincs a vonalon DS18b20, akkor ez 0.
           ds2.skip();           //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
           ds2.write(0x44,1);    //elinditunk egy mérést a chip-ben
           ds2_on=true;         //ezzel jelezzük, hogy elindítottunk egy mérést, 750msec múlva ki lehet olvasni a mért hőmérsékletet
         }
         else {akna_homerseklet_felso=99.9;}  //a ds2_on változót nem állítjuk true-ra, így várunk amíg lesz chip a vonalon, 
                                              //a kijelzőn a 99,9 fok jelzi, hogy nincs chip a vonalon

        // a mérés elindítás a bme280 szenzor kiolvasása előtt történik, itt már csak ki kell olvasni a mért eredményeket
        //ha a mérés indításakor kiderült, hogy nincs a vezetéken dallas chip, akkor nem is olvasunk ki semmit, kihagyjuk az egészet
        //Ezt a ds1_on változó jelzi
        if (ds1_on) {
          ds1.reset();             //kezdjük a kommunikációt
          ds1.skip();              //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
          ds1.write(0xBE);         // Chip memóriájánbak olvasása következik
          for ( byte i=0;i<9;i++) {ds_data[i]=ds1.read();}    
          if ( OneWire::crc8(ds_data,8)!=ds_data[8]) {ds1_crc_hiba=true;pince_homerseklet=88.8;}   //crc hibát a 88,8 fok jelzi
          else {pince_homerseklet=(float) (((ds_data[1]<<8)|ds_data[0])/16.0);ds1_crc_hiba=false;}  //ez egy jó mérési adat
        }
        if (ds1_on and !ds1_crc_hiba)   //ha működik a hűmérő chi, és nincs crc hiba, akkor átalgolunk
        {
          multimemo(v_pince_o,pince_homerseklet*100);
          multimemo(9,pince_homerseklet*100);
        }
        eszkoz_num++;
        szamjegybontas(pince_homerseklet,3,0); //pince hőmérséklet karakterképe bitsor3 tömbbe
      case 2:   //akna hőmérséklet_felso
        //Indítunk egy mérést a ds3 dallas chip-en, hogy a következő mérési eszkönél már legyen mért eredményünk
        //Közben eltelik 15 másodperc, elkészül az eredmény a chip-ben, és így várni sem kell 750mses-et az eredméynre delay()-el
        ds3_on=false;
        if (ds3.reset()==1) {    //itt 1-el jelez, ha a reset jelre válaszol egy DS18b20, tehát van a vonalon chip. Ha nincs a vonalon DS18b20, akkor ez 0.
           ds3.skip();           //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
           ds3.write(0x44,1);    //elinditunk egy mérést a chip-ben
           ds3_on=true;         //ezzel jelezzük, hogy elindítottunk egy mérést, 750msec múlva ki lehet olvasni a mért hőmérsékletet
         }
         else {akna_homerseklet_also=99.9;}  //a ds3_on változót nem állítjuk true-ra, így várunk amíg lesz chip a vonalon, 
                                             //a kijelzőn a 99,9 fok jelzi, hogy nincs chip a vonalon

        //A mérés elindítás a ds1 dallas szenzor kiolvasása előtt történik, itt már csak ki kell olvasni a mért eredményeket
        //Ha a mérés indításakor kiderült, hogy nincs a vezetéken dallas chip, akkor nem is olvasunk ki semmit, kihagyjuk az egészet
        //Ezt a ds2_on változó jelzi
        if (ds2_on) {
          ds2.reset();             //kezdjük a kommunikációt
          ds2.skip();              //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
          ds2.write(0xBE);         // Chip memóriájánbak olvasása következik
          for ( byte i=0;i<9;i++) {ds_data[i]=ds2.read();}    
          if ( OneWire::crc8(ds_data,8)!=ds_data[8]) {ds2_crc_hiba=true;akna_homerseklet_felso=88.8;}   //crc hibát a 88,8 fok jelzi
          else {akna_homerseklet_felso=(float) (((ds_data[1]<<8)|ds_data[0])/16.0);ds2_crc_hiba=false;}  //ez egy jó mérési adat
        }
        if (ds2_on and !ds2_crc_hiba)  //ha működik a hűmérő chi, és nincs crc hiba, akkor átalgolunk
        {
          multimemo(v_aknaf_o,akna_homerseklet_felso*100);
          multimemo(11,akna_homerseklet_felso*100);
        }
        szamjegybontas(akna_homerseklet_felso,5,0); //akna hőmérséklet karakterképe bitsor5 tömbbe
        eszkoz_num++;
        break;
      case 3:   //akna hőmérséklet_alsó
        //Indítunk egy mérést a ds4 dallas chip-en, hogy a következő mérési eszkönél már legyen mért eredményünk
        //Közben eltelik 15 másodperc, elkészül az eredmény a chip-ben, és így várni sem kell 750mses-et az eredméynre delay()-el
        ds4_on=false;
        if (ds4.reset()==1) {    //itt 1-el jelez, ha a reset jelre válaszol egy DS18b20, tehát van a vonalon chip. Ha nincs a vonalon DS18b20, akkor ez 0.
           ds4.skip();           //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
           ds4.write(0x44,1);    //elinditunk egy mérést a chip-ben
           ds4_on=true;          //ezzel jelezzük, hogy elindítottunk egy mérést, 750msec múlva ki lehet olvasni a mért hőmérsékletet
         }
         else {kishaz_homerseklet=99.9;}  //a ds3_on változót nem állítjuk true-ra, így várunk amíg lesz chip a vonalon, 
                                          //a kijelzőn a 99,9 fok jelzi, hogy nincs chip a vonalon

        //A mérés elindítás a ds2 dallas szenzor kiolvasása előtt történik, itt már csak ki kell olvasni a mért eredményeket
        //Ha a mérés indításakor kiderült, hogy nincs a vezetéken dallas chip, akkor nem is olvasunk ki semmit, kihagyjuk az egészet
        //Ezt a ds3_on változó jelzi
        if (ds3_on) {
          ds3.reset();             //kezdjük a kommunikációt
          ds3.skip();              //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
          ds3.write(0xBE);         // Chip memóriájánbak olvasása következik
          for ( byte i=0;i<9;i++) {ds_data[i]=ds3.read();}    
          if ( OneWire::crc8(ds_data,8)!=ds_data[8]) {ds3_crc_hiba=true;akna_homerseklet_also=88.8;}   //crc hibát a 88,8 fok jelzi
          else {akna_homerseklet_also=(float) (((ds_data[1]<<8)|ds_data[0])/16.0);ds3_crc_hiba=false;}  //ez egy jó mérési adat
        }
        if (ds3_on and !ds3_crc_hiba)  //ha működik a hűmérő chi, és nincs crc hiba, akkor átalgolunk
        {
          multimemo(v_aknaa_o,akna_homerseklet_also*100);
          multimemo(10,akna_homerseklet_also*100);
        }
        szamjegybontas(akna_homerseklet_also,4,0); //akna hőmérséklet karakterképe bitsor4 tömbbe
        eszkoz_num++;
        break;
      case 4:   //kisház hőmérséklet
        //A mérés elindítás a ds3 dallas szenzor kiolvasása előtt történik, itt már csak ki kell olvasni a mért eredményeket
        //Ha a mérés indításakor kiderült, hogy nincs a vezetéken dallas chip, akkor nem is olvasunk ki semmit, kihagyjuk az egészet
        //Ezt a ds4_on változó jelzi
        if (ds4_on) {
          ds4.reset();             //kezdjük a kommunikációt
          ds4.skip();              //ezzel azt mondjuk a chip-nek, hogy egyedül van a vonalon, nem adunk meg eszköz címet, mindenképpen csinálja amira utasítást kap
          ds4.write(0xBE);         // Chip memóriájánbak olvasása következik
          for ( byte i=0;i<9;i++) {ds_data[i]=ds4.read();}    
          if ( OneWire::crc8(ds_data,8)!=ds_data[8]) {ds4_crc_hiba=true;kishaz_homerseklet=88.8;}   //crc hibát a 88,8 fok jelzi
          else {kishaz_homerseklet=(float) (((ds_data[1]<<8)|ds_data[0])/16.0);ds4_crc_hiba=false;}  //ez egy jó mérési adat
        }
        if (ds4_on and !ds4_crc_hiba)   //ha működik a hűmérő chi, és nincs crc hiba, akkor átalgolunk
        {
          multimemo(v_kishaz_o,kishaz_homerseklet*100);
          multimemo(v_kishaz_o_min,kishaz_homerseklet*100);
          multimemo(v_kishaz_o_max,kishaz_homerseklet*100);
          multimemo(12,kishaz_homerseklet*100);
          multimemo(13,kishaz_homerseklet*100);
          multimemo(14,kishaz_homerseklet*100);
        }
        szamjegybontas(kishaz_homerseklet,6,0); //kisház hőmérséklet karakterképe bitsor6 tömbbe
        eszkoz_num++;
        break;
   }
    if (eszkoz_num>4) { eszkoz_num=0;}
    meres_ido=millis();
  }
  /**********************************************************************************************************************
   * Ez a programrész 2 másodpercenként már értéket küld az alső hétszegmens kijelző sorra, é az alsó matrix kijelzőre  *
   **********************************************************************************************************************/
  if (millis()>kijelzovaltas_ido+2000) {  //2 másodpercenként váltjuk a kijelző also sorában kijelzett értékeket
    //adatok kijelzőre írása
    digitalWrite(2, LOW); //shift regiszter kimenetek lezárása (latchPin=0)
    shiftOut(4, 3, MSBFIRST, bitsor0[0]);            //külső hőmérséklet szazas
    shiftOut(4, 3, MSBFIRST, bitsor0[1]);            //külső hőmérséklet tizes
    shiftOut(4, 3, MSBFIRST, bitsor0[2]);            //külső hőmérséklet egyes
    shiftOut(4, 3, MSBFIRST, bitsor0[3]);            //külső hőmérséklet tizedes
    switch (kijelzes_num) {
      case 1:   //páratartalom
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00000000);
        lc.setRow(0,3,B00000000);
        lc.setRow(0,4,B00000000);
        lc.setRow(0,5,B00000000);
        lc.setRow(0,6,B00000000);
        lc.setRow(0,7,B00000000);
        lc.setRow(1,0,B00000000);
        lc.setRow(1,1,B00110000);
        lc.setRow(1,2,B00110010);
        lc.setRow(1,3,B00000100);
        lc.setRow(1,4,B00001000);
        lc.setRow(1,5,B00010000);
        lc.setRow(1,6,B00100110);
        lc.setRow(1,7,B00000110);
        shiftOut(4, 3, MSBFIRST, bitsor1[0]);            //páratartalom ezres (mindíg üres)
        shiftOut(4, 3, MSBFIRST, bitsor1[1]);            //páratartalom szazas (mindíg üres)
        shiftOut(4, 3, MSBFIRST, bitsor1[2]);            //páratartalom tizes
        shiftOut(4, 3, MSBFIRST, bitsor1[3]);            //páratartalom egyes
        kijelzes_num++;
        break;
      case 2:   //légnyomás
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00000000);
        lc.setRow(0,3,B11001011);
        lc.setRow(0,4,B00101100);
        lc.setRow(0,5,B00101000);
        lc.setRow(0,6,B00101000);
        lc.setRow(0,7,B11001000);
        lc.setRow(1,0,B00000000);
        lc.setRow(1,1,B00000001);
        lc.setRow(1,2,B00000001);
        lc.setRow(1,3,B01101001);
        lc.setRow(1,4,B01010101);
        lc.setRow(1,5,B01010101);
        lc.setRow(1,6,B01010101);
        lc.setRow(1,7,B01010101);
        shiftOut(4, 3, MSBFIRST, bitsor2[0]);            //légnyomás ezres
        shiftOut(4, 3, MSBFIRST, bitsor2[1]);            //légnyomás szazas
        shiftOut(4, 3, MSBFIRST, bitsor2[2]);            //légnyomás tizes
        shiftOut(4, 3, MSBFIRST, bitsor2[3]);            //légnyomás egyes
        kijelzes_num++;
        break;
      case 3:   //pince hőmérséklet
        //P betű a 0-ás kijelzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00011100);
        lc.setRow(0,3,B00010010);
        lc.setRow(0,4,B00010010);
        lc.setRow(0,5,B00011100);
        lc.setRow(0,6,B00010000);
        lc.setRow(0,7,B00010000);
        lc.setRow(1,0,B00000000);
        //°C felirat az 1-es kijelzőre
        lc.setRow(1,1,B01000110);
        lc.setRow(1,2,B10101001);
        lc.setRow(1,3,B10101000);
        lc.setRow(1,4,B01001000);
        lc.setRow(1,5,B00001000);
        lc.setRow(1,6,B00001001);
        lc.setRow(1,7,B00000110);
        shiftOut(4, 3, MSBFIRST, bitsor3[0]);            //pince hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor3[1]);            //pince hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor3[2]);            //pince hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor3[3]);            //pince hőmérséklet tizedes
        kijelzes_num++;
        break;
      case 4:   //akna hőmérséklet also
        //Akna also felirat 0-as kijzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00110000);
        lc.setRow(0,3,B01001000);
        lc.setRow(0,4,B01001000);
        lc.setRow(0,5,B01111000);
        lc.setRow(0,6,B01001011);
        lc.setRow(0,7,B01001011);
        shiftOut(4, 3, MSBFIRST, bitsor4[0]);            //akna hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor4[1]);            //akna hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor4[2]);            //akna hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor4[3]);            //akna hőmérséklet tizedes
        kijelzes_num++;
        break;
      case 5:   //akna hőmérséklet felso
        //Akna felső felirat 0-as kijzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00110011);
        lc.setRow(0,3,B01001011);
        lc.setRow(0,4,B01001000);
        lc.setRow(0,5,B01111000);
        lc.setRow(0,6,B01001000);
        lc.setRow(0,7,B01001000);
        shiftOut(4, 3, MSBFIRST, bitsor5[0]);            //akna hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor5[1]);            //akna hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor5[2]);            //akna hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor5[3]);            //akna hőmérséklet tizedes
        kijelzes_num++;
        break;
      case 6:   //kisház hőmérséklet
        //H betű a 0-ás kijelzőre
        lc.setRow(0,0,B00000000);
        lc.setRow(0,1,B00000000);
        lc.setRow(0,2,B00010010);
        lc.setRow(0,3,B00010010);
        lc.setRow(0,4,B00011110);
        lc.setRow(0,5,B00011110);
        lc.setRow(0,6,B00010010);
        lc.setRow(0,7,B00010010);
        shiftOut(4, 3, MSBFIRST, bitsor6[0]);            //kisház hőmérséklet szazas
        shiftOut(4, 3, MSBFIRST, bitsor6[1]);            //kisház hőmérséklet tizes
        shiftOut(4, 3, MSBFIRST, bitsor6[2]);            //kisház hőmérséklet egyes
        shiftOut(4, 3, MSBFIRST, bitsor6[3]);            //kisház hőmérséklet tizedes
        kijelzes_num++;
        break;
    }
    digitalWrite(2, HIGH);  //beléptetett érték kiengedése a shiftregiszter kimenetére (latchPin=1)
    if (kijelzes_num>6) { kijelzes_num=1;}
    kijelzovaltas_ido=millis();
  } 

  
  /*****************************************************************************************************************
   *  Óránként egy alkalommal átküldjük a slave-nak a mért eredményeket (min és max értékkel)
   *  Ha sikerült az átküldés után feltételezzük, hogy sikerült az írás az SD kártyára, és nullázzuk
   *  az átlagértékek segéd változói (összeg és mérésszám). Ha nem volt SD írási hiba, akkor megjegyezzük
   *  az utolsó sd írás időpontját, ha volt hiba akkor a hiba tényét (utolso_nap_iras változóba).
   *****************************************************************************************************************/
  //óránként egyszer átküldjük az órás átlag adatokat a slavenak. Küldés után utan töröljük a változókat, hogy ujra kezdődhessen az átlagolás
  if (perc==0 and masodperc<10 and tarolas)  
  //if (masodperc<10 and tarolas)  
  {
    adatkuldes(0);
    tarolas=false;
    //töröljuk az orankénti átlagoláshoz használt FRAM cellákat
    multimemo(v_kulso_o,AVG);
    multimemo(v_kulso_o_min,MIN);
    multimemo(v_kulso_o_max,MAX);
    multimemo(v_para_o,AVG);
    multimemo(v_para_o_min,MIN);
    multimemo(v_para_o_max,MAX);
    multimemo(v_legny_o,AVG);
    multimemo(v_legny_o_min,MIN);
    multimemo(v_legny_o_max,MAX);
    multimemo(v_pince_o,AVG);
    multimemo(v_aknaa_o,AVG);
    multimemo(v_aknaf_o,AVG);
    multimemo(v_kishaz_o,AVG);
    multimemo(v_kishaz_o_min,MIN);
    multimemo(v_kishaz_o_max,MAX);
    if (sd_hiba==0)
    {
      //összeállítjuk az utolsó nap file dátumának szövegét az LCD-n történő kiíráshoz
      if (Clock.getHour(h12, PM)<10) {str_tmp="0"+String(Clock.getHour(h12, PM))+":";} else {str_tmp=String(Clock.getHour(h12, PM))+":";}
      if (Clock.getMinute()<10) {str_tmp=str_tmp+"0"+String(Clock.getMinute())+":";} else {str_tmp=str_tmp+String(Clock.getMinute());}
      //if (Clock.getSecond()<10) {str_tmp=str_tmp+"0"+String(Clock.getSecond());} else {str_tmp=str_tmp+String(Clock.getSecond());}
      utolso_nap_iras="  Nap file:"+str_tmp;
    }
    else
    {
      utolso_nap_iras="Nap file:SD error";
    }
  }

 
  /*****************************************************************************************************************
   *  Minden nap 23.50-kor átküldjük a slave-nak a mért eredményeket (min és max értékkel)
   *  Ha sikerült az átküldés után feltételezzük, hogy sikerült az írás az SD kártyára, és nullázzuk
   *  az átlagértékek segéd változói (összeg és mérésszám). Ha nem volt SD írási hiba, akkor megjegyezzük
   *  az utolsó sd írás időpontját, ha volt hiba akkor a hiba tényét (utolso_ev_iras változóba).
   *****************************************************************************************************************/
  if ((ora==23 and perc==59) and masodperc<10 and tarolasnap)  
  //if ((perc==35) and masodperc<10 and tarolasnap)  
  {
    adatkuldes(1);
    long szam=multimemo(0);multimemo(30,szam);
    szam=multimemo(1);multimemo(31,szam);
    szam=multimemo(2);multimemo(32,szam);
    tarolasnap=false;
    //töröljuk az naponkénti átlagoláshoz használt FRAM cellákat
    multimemo(0,AVG);
    multimemo(1,MIN);
    multimemo(2,MAX);
    multimemo(3,AVG);
    multimemo(4,MIN);
    multimemo(5,MAX);
    multimemo(6,AVG);
    multimemo(7,MIN);
    multimemo(8,MAX);
    multimemo(9,AVG);
    multimemo(10,AVG);
    multimemo(11,AVG);
    multimemo(12,AVG);
    multimemo(13,MIN);
    multimemo(14,MAX);
    if (sd_hiba==0)
    {
      //összeállítjuk az utolsó év file dátumának szövegét az LCD-n történő kiíráshoz
      if (Clock.getHour(h12, PM)<10) {str_tmp="0"+String(Clock.getHour(h12, PM))+":";} else {str_tmp=String(Clock.getHour(h12, PM))+":";}
      if (Clock.getMinute()<10) {str_tmp=str_tmp+"0"+String(Clock.getMinute())+":";} else {str_tmp=str_tmp+String(Clock.getMinute());}
      //if (Clock.getSecond()<10) {str_tmp=str_tmp+"0"+String(Clock.getSecond());} else {str_tmp=str_tmp+String(Clock.getSecond());}
      utolso_ev_iras="  Ev file: "+str_tmp;
    }
    else
    {
      utolso_ev_iras="Ev file:SD error";
    }
  } 

}


/***************************************************************************************
 * Csak 32 byte-ot lehet egyszerre egy menetben átvinni, a következő beyte-ok sérülnek *
 * nem tudom miért. Ezért két menetre bontottam az órás illetve a napi átlag küldésétt *
 * status a legelso byte:     1-datum idő átvitel 7 byte
 *                            2-az órás adatok 1 mért adatok 30 byte
 *                            3-az órás adatok 2 mért adatok 30 byte
 *                            4-a napi mért adatok 1 30 byte
 *                            5-a napi mért adatok 2 30 byte
 * elősször mindig a dátumot és az időt, és utánna az órás vagy napi mért adaokat küldöm át                           
 ***************************************************************************************/
void adatkuldes(bool allomany)
{
    
//    Serial.println("kuldes");
  Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
  Wire.write(1);
  Wire.write(ev);
  Wire.write(ho);
  Wire.write(nap);
  Wire.write(ora);
  Wire.write(perc);
  Wire.write(masodperc);
  Wire.endTransmission();     // vége az átvitelnek
  if (allomany==0)
  {
    kuldendo1=multimemo(v_kulso_o);
    kuldendo2=multimemo(v_kulso_o_min);
    kuldendo3=multimemo(v_kulso_o_max);
    kuldendo4=multimemo(v_para_o);
    kuldendo5=multimemo(v_para_o_min);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(2);
    kuld_4byte(kuldendo1);  //100-al már szorozunk a tároláskor, és most az átküldéskor is 100-al kéne, így most nem szorzunk, de nem is osztunk
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4); 
    kuld_4byte(kuldendo5); 
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(v_para_o_max);
    kuldendo2=multimemo(v_legny_o);
    kuldendo3=multimemo(v_legny_o_min);
    kuldendo4=multimemo(v_legny_o_max);
    kuldendo5=multimemo(v_kishaz_o);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(3);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(v_kishaz_o_min);
    kuldendo2=multimemo(v_kishaz_o_max);
    kuldendo3=multimemo(v_aknaa_o);
    kuldendo4=multimemo(v_aknaf_o);
    kuldendo5=multimemo(v_pince_o);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(4);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
  }
  else
  {
    kuldendo1=multimemo(0);
    kuldendo2=multimemo(1);
    kuldendo3=multimemo(2);
    kuldendo4=multimemo(3);
    kuldendo5=multimemo(4);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(5);
    kuld_4byte(kuldendo1);  //100-al már szorozunk a tároláskor, és most az átküldéskor is 100-al kéne, így most nem szorzunk, de nem is osztunk
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4); 
    kuld_4byte(kuldendo5); 
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(5);
    kuldendo2=multimemo(6);
    kuldendo3=multimemo(7);
    kuldendo4=multimemo(8);
    kuldendo5=multimemo(12);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(6);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
    Wire.endTransmission();     // vége az átvitelnek
    /**********************************************************/
    kuldendo1=multimemo(13);
    kuldendo2=multimemo(14);
    kuldendo3=multimemo(10);
    kuldendo4=multimemo(11);
    kuldendo5=multimemo(9);
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(7);
    kuld_4byte(kuldendo1);
    kuld_4byte(kuldendo2);
    kuld_4byte(kuldendo3);
    kuld_4byte(kuldendo4);
    kuld_4byte(kuldendo5);
  }
  Wire.endTransmission();     // vége az átvitelnek
  delay(50); //várunk 50msec-et, hogy a slave befejezhesse az SD írást, és kiderüljön sikerült-e
  Wire.requestFrom(8,1);      // a master kér 1 byte-ot a slave-től
  sd_hiba= Wire.read();       //beolvassuk a slave által küldött byte-ot    
}

void kuld_4byte(long kuldendo)
{
    byte out1=(kuldendo & 0xFF);
    byte out2=((kuldendo >> 8) & 0xFF);
    byte out3=((kuldendo >> 16) & 0xFF);
    byte out4=((kuldendo >> 24) & 0xFF);
    Wire.write(out1);               
    Wire.write(out2);
    Wire.write(out3);
    Wire.write(out4);
}


/****************************************************************************************************
 * Ha óra beállításkor nyomva tartja a nyomógombot, akkor folyamatosan számoltatja felfelé 
 * az éppen beállított adat értékét. Ha elérte a maximumot, akkor nullázza az értéket.
 ****************************************************************************************************/
byte ertekporgetes(byte o_ert, byte o_min, byte o_max)
{
  do
  {
    wdt_reset(); //alaphelyzetbe állítjuk a a watcdog timer-t. Erre itt is szükség van, különben az óra beállítás
                 //alatt is resetet generál a watchdog, mert a fő ciklus nem fut miközben ebben a függvényvben tartozkodik
                 //a program
    o_ert=o_ert+1;setup_num_novel=LOW;
    if (o_ert>o_max) {o_ert=o_min;}
    lcd.setCursor(9,1); 
    if (setup_num==8)
    {
      switch (o_ert) 
      {
        case 1:
          lcd.print("Hetfo");break;
        case 2:
          lcd.print("Kedd ");break;
        case 3:
          lcd.print("Szerd");break;
        case 4:
          lcd.print("Csut ");break;
        case 5:
          lcd.print("Pent ");break;
        case 6:
          lcd.print("Szomb");break;
        case 7:
          lcd.print("Vasar");break;
      }
    } 
    else
    {
      lcd.print(String(o_ert)+"  ");
    }
    delay(700);
  } while (digitalRead(10)==LOW);
  return o_ert;
} 


/****************************************************************************************************
 * Ez a funkció 4 byte-ból csinál egy long változót és visszadja az eredményt
 ****************************************************************************************************/
long byteToLong(long inp1, long inp2, long inp3, long inp4)
{
  //4 byte long változóvá alakítása
  return ((inp1 << 0) & 0xFF) + ((inp2 << 8) & 0xFFFF) + ((inp3 << 16) & 0xFFFFFF) + ((inp4 << 24) & 0xFFFFFFFF);
}

/****************************************************************************************************
 * inicializálja és lekérdezi a BME280 senzort. Ha a senzor inicializálása nem sikerül
 * akkor a függvény újbóli meghívásakor ismét megkisérli az inicializálást, Ha sikerül, akkor 
 * rögtön mér is egyet.
 * Ha már inincializálva van, akkor csak mér. Ha közben megszakad a BME280-al a kapcsolat
 * akkor a hőmérséklet lekérdezés -145.75 fokod ad vissza tapasztalatom szerint, ezért ekkor
 * a mérési eredméynek hamisak, és legközelebb újra megpróbáljuk inicializálni
 ****************************************************************************************************/
void bme280_lekerdez()
{
  if (!bme280_on)
  {
    if (bme.begin(0x76, &Wire)) 
    {         
      bme280_on=HIGH;  //bme280 inicializálása sikerült
      //rögtön mérünk is egyet
      kulso_homerseklet=bme.readTemperature();
      paratartalom=bme.readHumidity();
      legnyomas=bme.readPressure()/100; 
    } 
    else
    {
      kulso_homerseklet=99.9;paratartalom=99;legnyomas=0; 
    }
  }
  else
  {
    kulso_homerseklet=bme.readTemperature();
    paratartalom=bme.readHumidity();
    legnyomas=bme.readPressure()/100; 
    if (kulso_homerseklet<-100) 
    {
      bme280_on=LOW;  //ha nincs bme280 csatlakoztatva, akkor -145 fokot ad vissza, legközelebb megpróbáljuk ujra inincializálni
      kulso_homerseklet=99.9;paratartalom=99;legnyomas=0;
    }
  }
}

/****************************************************************************************************
 * Megméri az A6 bemenetre kötött fototrnzisztor és ellenállásosztóban az ellenállás 
 * feszültségét. Ellenállás értéke 1Kohm. A fototranzisztor kb 5000Lux, nál teljesen kinyit
 * ekkor az ellenálláson közel 5V feszültség mérhető. Sötétben a feszültség 0V
 ****************************************************************************************************/
void fenyero_beallitas(){
    int ledmatfeny=0;
    int hszegfeny=0;
    for (byte j=9;j>0;j--) {mert_fenyero[j]=mert_fenyero[j-1];}
    int fenyero=analogRead(A6);
    mert_fenyero[0]=fenyero;
    fenyertek=(mert_fenyero[0]+mert_fenyero[1]+mert_fenyero[2]+mert_fenyero[3]+mert_fenyero[4]+mert_fenyero[5]+mert_fenyero[6]+mert_fenyero[7]+mert_fenyero[8]+mert_fenyero[9])/10;
    if (fenyertek<=20) {hszegfeny=245;ledmatfeny=0;}
    if (fenyertek<=40 & fenyertek>20) {hszegfeny=245;ledmatfeny=1;}  
    if (fenyertek<=70 & fenyertek>40) {hszegfeny=220;ledmatfeny=2;}  
    if (fenyertek<=110 & fenyertek>70) {hszegfeny=198;ledmatfeny=3;}  
    if (fenyertek<=150 & fenyertek>110) {hszegfeny=175;ledmatfeny=4;}  
    if (fenyertek<=180 & fenyertek>150) {hszegfeny=158;ledmatfeny=5;}  
    if (fenyertek<=230 & fenyertek>180) {hszegfeny=140;ledmatfeny=6;}  
    if (fenyertek<=285 & fenyertek>230) {hszegfeny=123;ledmatfeny=7;}  
    if (fenyertek<=320 & fenyertek>285) {hszegfeny=105;ledmatfeny=8;}  
    if (fenyertek<=380 & fenyertek>320) {hszegfeny=88;ledmatfeny=9;}  
    if (fenyertek<=440 & fenyertek>380) {hszegfeny=70;ledmatfeny=10;}  
    if (fenyertek<=500 & fenyertek>440) {hszegfeny=53;ledmatfeny=11;}  
    if (fenyertek<=560 & fenyertek>500) {hszegfeny=35;ledmatfeny=12;}  
    if (fenyertek<=630 & fenyertek>560) {hszegfeny=24;ledmatfeny=13;}  
    if (fenyertek<=700 & fenyertek>630) {hszegfeny=12;ledmatfeny=14;}  
    if (fenyertek>700) {hszegfeny=0;ledmatfeny=15;} 
    analogWrite(5,hszegfeny);
    // fényerő beállítás 0 minimum, 15 maximum 
    lc.setIntensity(0,ledmatfeny);lc.setIntensity(1,ledmatfeny);
    lc.setIntensity(2,ledmatfeny);lc.setIntensity(3,ledmatfeny);
} 


/****************************************************************************************************
 * A függvénynek átadott számértéket annak típusától függően feldolgozza és bitsorozattá
 * alakítja a 74HC595 IC-be történő beléptetéshez. MInden szenzor által mért adatnak külön
 * tömbbe kerülnek az adatai. A légnyomás és a páratartalom csak egész érték lehet, a többi
 * tizedes, tehát a tizedespontot is ki be kell kapcsolni. A vezető nullákat kioltja és
 * kirakja a minusz jelet is
 ****************************************************************************************************/
void szamjegybontas(float szamertek,int ertekindex, int egeszertek){
/*ez a függvány a mérési eredményt négy számjegyre bontja a 7 szegmenses LED kijelzőkbe léptetéses formában
  átvett változók jelentése:
    szamertek : az átalakításra váró mért érték
    ertekindex : az er edmény tárolására szolgáló tömb indexe
    egeszertek : ha értéke 1, akkor lénynomás vagy páratartalom értékét kell átalakítani, ami csak pozitív szám lehet, és nem kell tizedesjegy kijelzés
  Az indexértékkel kiválasztott nevű tömböt tölt a számjegyek bitképével. A tömbök nevei: bitsor0=külső hőmérséklet.....
  A tömbök indexértékeinek jelentése:
    bitsorX[0] -> balrol az első számjegy
    bitsorX[1] -> balrol az második számjegy
    bitsorX[2] -> balrol az harmadik számjegy
    bitsorX[3] -> balrol az negyedik számjegy
  amennyiben a kijelzésre kerülő számjegy nagyobb mint 100 (ez a légnyomás érék), előjel kijelzés nem lehetséges és nincs tizedes érték
  amennyiben a kijelzére kerülő szám egyjegyű, a vezető nullát kioltjuk, illetve helyére kerül a minuszjel ha van. */ 
  float eszamertek=szamertek; //eredeti szamertek tárolására
  float proba;
  int tizedes;
  int ezres=0;    
  int szazas=0;
  int tizes=0;
  int egyes=0;
  boolean tizedespont=false;
  if (egeszertek==1) {  //ez csak a legnyomás lehet vagy paratartalom
    ezres=szamertek/1000;
    szazas=(szamertek-(ezres*1000))/100;
    tizes=(szamertek-(ezres*1000)-(szazas*100))/10;
    egyes=szamertek-(ezres*1000)-(szazas*100)-(tizes*10);
    if (szamertek<100) {  szazas=10;} //vezető nulla kioltása az szazas számjegyben
    if (szamertek<1000) { ezres=10;} //vezető nulla kioltása az ezres szamjegyben
  }
  else {  //az átalakítandó szám lehet pozitiv és negatív, és tizedesjegy értéket kell kijelezni
    if (szamertek<0) {  szazas=11;szamertek=0-szamertek;} else {  szazas=10;}
    tizes=szamertek/10;
    egyes=szamertek-(tizes*10);
    //vezető nullák kioltása (az 10-es érték üres kijelzés), illetve minusz előjel (11-es érték minusz jel)
    if (tizes==0 && eszamertek>=0) {  tizes=10;}
    if (szamertek<10 && eszamertek<0) { tizes=11;szazas=10;} //minusz előjel berakása a százas helyett a tizesbe
    proba=(szamertek-(int)szamertek);
    proba=(proba*10)+0.001;
    tizedes=(int)proba;
    tizedespont=true;
  }
  switch (ertekindex) {
    case 0:   //0 - külső hőmérséklet 
      bitsor0[0]=karakterkep[szazas];
      bitsor0[1]=karakterkep[tizes];
      bitsor0[2]=karakterkep[egyes]|B00000100;
      bitsor0[3]=karakterkep[tizedes];
      break;
    case 1:   //1 - páratartalom
      bitsor1[0]=karakterkep[ezres];
      bitsor1[1]=karakterkep[szazas];
      bitsor1[2]=karakterkep[tizes];
      bitsor1[3]=karakterkep[egyes];
      break;
    case 2:   //2 - légnyomás
      bitsor2[0]=karakterkep[ezres];
      bitsor2[1]=karakterkep[szazas];
      bitsor2[2]=karakterkep[tizes];
      bitsor2[3]=karakterkep[egyes];
      break;
    case 3:   //3 - pince hőmérséklet
      bitsor3[0]=karakterkep[szazas];
      bitsor3[1]=karakterkep[tizes];
      bitsor3[2]=karakterkep[egyes]|B00000100;
      bitsor3[3]=karakterkep[tizedes];
      break;
    case 4:   //4 - akna hőmérséklet
      bitsor4[0]=karakterkep[szazas];
      bitsor4[1]=karakterkep[tizes];
      bitsor4[2]=karakterkep[egyes]|B00000100;
      bitsor4[3]=karakterkep[tizedes];
      break;
    case 5:   //5 - kisház hőmérséklet
      bitsor5[0]=karakterkep[szazas];
      bitsor5[1]=karakterkep[tizes];
      bitsor5[2]=karakterkep[egyes]|B00000100;
      bitsor5[3]=karakterkep[tizedes];
      break;
    case 6:   //5 - kisház hőmérséklet
      bitsor6[0]=karakterkep[szazas];
      bitsor6[1]=karakterkep[tizes];
      bitsor6[2]=karakterkep[egyes]|B00000100;
      bitsor6[3]=karakterkep[tizedes];
      break;
  } 
}

long multimemo(byte cim) {
/***************************************************************************************************************
 * Ez a multimemo függvény akkor hívódik meg, ha csak egyetlen paramétert adtunk meg a függvény                *
 * meghívásakor (csak olvasni akarunk a tároló cellából)                                                       *
 * A három paraméteres multimemo függvényt hívja meg, de default értékkel tölti fel a maradék két paramétert   *
 * A megadott tároló cella címről adtot fogunk olvasni.                                                        *
 ***************************************************************************************************************/
  long adat=0;
  bool iras=0;
  return(multimemo(cim,adat,iras));
}


long multimemo(byte cim, long adat) {
/***************************************************************************************************************
 * Ez a multimemo függvény akkor hívódik meg, ha két paramétert adunk meg, a cella címét és a beírandó         *
 * adatot, vagyis írni akarunk a cellába. A tároló cella formázását és alaphelyzetbe állítását végző           *
 * két paraméteres multimemo függvénytől az különbözteti meg, hogy ennek második paramétere long típusú.       *
 * A három paraméteres multimemo függvényt hívja meg, de a harmadik paramétert (iras vagy olvasás) default     *
 * értékkel tölti fel (iras=1, azaz írunk). A megadott tároló cella címébe fogjuk az adtot beírni.             *
 ***************************************************************************************************************/
  bool iras=1;
  return(multimemo(cim,adat,iras));
}

long multimemo(byte cim, long adat, bool iras) {
/*****************************************************************************************************************************************************************
 * Ez a multimemo függvény akkor hívódik meg, ha három paraméterrel hívjuk. Ez végzi a tárolandó adat feldolgozását, tárolását, és visszaadja az eredményt       *  
 * visszatérő értékként. A tároló cella típusát az első tárolt byte adja meg, ezt az a multimemo függvény írja, melynek két paramétere van, és a második         *
 * cella_tipus (enum-al definiált felsorolás) típusú adatot vár.                                                                                                 *
 * A függvény bemenő paraméterei:                                                                                                                                *
 *    cim: 0-73 memória cella, egy cella 7 byte,                                                                                                                 *
 *    adat: long típusú 4 byte-os adat, amit beírunk a kijelölt memória cellába                                                                                  *
 *    iras: ez mondja meg, hogy írunk vagy olvasunk a tároló cellából. 0-olvasunk, 1-írunk                                                                       *
 * A tároló cella szerkezete:                                                                                                                                    *
 *    0. byte típus: 0=minimum cella, 1=maximum cella, 2=summa cella, 3=átlag cella, 4=tároló cella                                                              *
 *    1-4. byte adat long adattípus, ez a tényleges tárolt long adat 4 byte-on                                                                                   *
 *    5-6. byte átlag esetén az összegzett adatok száma int adattípusú Csak az átlagoló tároló cella használja, ebben számolja az írások számát.                 *
 *****************************************************************************************************************************************************************/
  int xcim=cim*7;
  byte page=0;
  byte adat0;
  byte adat1;
  byte adat2;
  byte adat3;
  if (xcim<256) {page=0;} else {page=1;xcim=xcim-256;}
  Wire.beginTransmission(_i2cAddress | (page&1));
  Wire.write(xcim);
  Wire.endTransmission(); 
  Wire.requestFrom(_i2cAddress | (page&1),7);
  byte xtipus=Wire.read();  //elkérjük az első byte-ot, ami a tárolt dat típusát adja meg
  long xadat=long(Wire.read()) | long(Wire.read())<<8 | long(Wire.read())<<16 | long(Wire.read())<<24 ; //elkérjük és betöltjük az adat aktuális értékét
  int xdb=int(Wire.read()) | int(Wire.read())<<8;  //elkérjük és betöltjük az adatok számát
  
  if (iras==0 and xtipus!=3) {return(xadat);}  //ha olvasás és nem átlagot kértünkvolt a művelet, akkor visszadjuk a kiolvasott long értéket
  if (iras==0 and xtipus==3) {if (xdb>0) {return((long)(xadat/xdb));} else {return(0);}}  //ha olvasás és átlagot kértünk, akkor osztani is kell, de csak ha xdb nem 0
  
  if (xtipus==0 and iras==1){ //ha írás és minimum cella
      if (xadat>adat) { //csak akkor írjuk be az új adatot, ha az eddig tárolt adatnál kissebb
        multimemo_adatiras(page,xcim,adat); //visszaírjuk az új adatot a tároló cellába
        return(adat);
      }
      else {return(xadat);} //visszadjuk a cellában tárolt minimum értéket
    }  
      
  if (xtipus==1 and iras==1){  //ha írás és maximum cella
      if (xadat<adat) {  //csak akkor írjuk be az új adatot, ha az eddig tárolt adatnál nagyobb
         multimemo_adatiras(page,xcim,adat); //visszaírjuk az új adatot a tároló cellába
        return(adat);
      }
      else {return(xadat);} //visszadjuk a cellában tárolt maximum értéket
    }  
      
  if (xtipus==2 and iras==1){  //ha írás és summa cella
    adat=xadat+adat;  //összeadjuk az új adatot ez eddig beírt adatok (tárolt) összegével
    multimemo_adatiras(page,xcim,adat);  //visszaírjuk az új adatot a tároló cellába
    return(adat); //visszadjuk az új cella értéket
  } 

  if (xtipus==3 and iras==1){  //ha írás és átlag cella
    adat=xadat+adat;  //összeadjuk az új adatot ez eddig beírt adatok (tárolt) összegével
    xdb=xdb+1; //növeljük a beírások számát 1-el
    Wire.beginTransmission(_i2cAddress | (page&1));Wire.write(xcim+1); //a cella első byte-ját már nem kell írni, azt beállított a cella reset, azért cim+1-től írunk
    Wire.write(adat & 0xFF);Wire.write((adat>>8) & 0xFF);Wire.write((adat>>16) & 0xFF);Wire.write((adat>>24) & 0xFF); //beírjuk az új összegzett adatot
    Wire.write(xdb & 0xFF);Wire.write((xdb>>8) & 0xFF);  //beírjuk az írások számát
    Wire.endTransmission(); 
    return((long)(adat/xdb));  //visszadjuk az új átlag értéket
  } 

  if (xtipus==4 and iras==1){  //ha írás és sima tároló cella
    multimemo_adatiras(page,xcim,adat);
    return(adat);
  } 
}

void multimemo_adatiras(byte page, byte xcim, long adat) {
// az átlag cella kivételével mindet ugyanúgy kell beírni, ezért a konkrét írás ebben a közösen használt függvényben lett megvalósítva
  Wire.beginTransmission(_i2cAddress | (page&1));Wire.write(xcim+1); //a cella első byte-ját már nem kell írni, azt beállított a cella reset, azért cim+1-től írunk
  Wire.write(adat & 0xFF);Wire.write((adat>>8) & 0xFF);Wire.write((adat>>16) & 0xFF);Wire.write((adat>>24) & 0xFF);
  Wire.endTransmission(); 
}

long multimemo(byte cim, cella_tipus tipus) {
/*******************************************************************************************************************************************************************  
 * Ez a multimemó függvény akkor hívódik meg, ha két paraméterrel hívjuk, és a második cella_tipus típusú változó, amit enum-al hoztunk létre a program elején.    *
 * A függvény elvégzi egy cella típusának beállítását (a cella első byte-ja), és alaphelyzetbe állítja a tárolt adatokat.                                          *
 * Bemenő paraméterek:                                                                                                                                             *
 *    cim: 0-73 db memória cella, egy cella 7 byte,                                                                                                                *
 *    típus: felsorolás típusú (enum) paraméter, ami meghatározza a cella tárolási módját. Lehetséges értékei 0-4 között.                                          *
 * Típus által képzett cella típusok, és azok tárolási módja, az elvégzett műveletek leírása:                                                                      *
 *  MIN: Az íráskor megkapott adatot csak akkor tárolja, ha az kissebb mint az éppen tárolt adat. Mielőtt elkezdjük a minimumot gyüjteni, be kell írni             *
 *       a lehető legnagyobb long értéket, különben nem biztos, hogy megjegyzi a legelső értéket. A számláló cellarészt nem használja.                             *
 *  MAX: Az íráskor megkapott adatot csak akkor tárolja, ha az nagyobb mint az éppen tárolt adat. Mielőtt elkezdjük a maximumott gyüjteni, be kell írni            *
 *       a lehető legkissebb long értéket, különben nem biztos, hogy megjegyzi a legelső értéket.                                                                  *
 *  SUM: Képzi a beírt adatok összegét. Alapértelmezetten a tartalma 0.                                                                                            *
 *  AVG: képzi a beírt adatok átlagát. Minden beít értéket szummáz az adat mezőben, számolja a beírások számát, és kiolvasáskor osztja az adatot a beírás számmal  *
 *       Alpértelmezetten az adat és a számláló is 0.                                                                                                              *
 *  STO: csak úgy simán tárolja az adatot, nem csinál vele semmit, ha volt bent előtte adat, azt felülírja. Alepértelmezett tartalma 0.                            *
 *******************************************************************************************************************************************************************/
  int xcim=cim*7;
  byte page;
  if (xcim<256) {page=0;} else {page=1;xcim=xcim-256;}
  Wire.beginTransmission(_i2cAddress | (page&1));
  Wire.write(xcim);
  if (tipus==MIN){
      // 2147483647 kezdő értéket írunk a tároló cellába, mert nincs nagyobb szám long esetén, így csak ennél kisebbek jöhetnek.
      Wire.write(0);Wire.write((long)2147483647 & 0xFF);Wire.write(((long)2147483647>>8) & 0xFF);Wire.write(((long)2147483647>>16) & 0xFF);Wire.write(((long)2147483647>>24) & 0xFF);
      Wire.write(0);Wire.write(0);
      Wire.endTransmission(); 
      return(0);
    }    
  if (tipus==MAX){
      // -2147483648 kezdő értéket írunk a tároló cellába, mert nincs kisebb szám long esetén, így csak ennél kisebbek jöhetnek.
      Wire.write(1);Wire.write((long)-2147483648 & 0xFF);Wire.write(((long)-2147483648>>8) & 0xFF);Wire.write(((long)-2147483648>>16) & 0xFF);Wire.write(((long)-2147483648>>24) & 0xFF);
      Wire.write(0);Wire.write(0);
      Wire.endTransmission(); 
      return(0);
    }    
  if (tipus==SUM or tipus==AVG or tipus==STO) {
      //össegző mező esetén csak simán törölni kell mindent
      Wire.write(tipus);Wire.write(0);Wire.write(0);Wire.write(0);Wire.write(0);
      Wire.write(0);Wire.write(0);
      Wire.endTransmission(); 
      return(0);
    }  
    Wire.endTransmission(); 
}

Ennek a programnak a mérete 29948byte, tehát 772 byte maradt szabadon. Azonban a ram számít, mert ebben az esetben már 623byte maradt szabadon. Az a 100byte nem sok, de mégis a teljes ram 5%-a, esetleg számít majd, és kevesebbet kell dolgoznia a watchdog-nak. “Kis lépés az embernek, de nagy lépés…” nekem. Azt már tudom, hogy a ds4 vonalon a crc hibát nem a program és a ram szabad méretének hiánya okozta (lásd később), de hátha mégis számít valamit. Közben míg az átalakításokat csináltam, kiderült hogy a szenzorokkal történő mérések közötti időt valamikor véletlenül 15 másodpercről 1,5 másodpercre állítottam. Nem kizárt, hogy ez sem tetszett a Dallas chip-nek. Valahol olvastam, hogy a túl gyakori mérés problémákat okoz?! Most ismét várakozunk, hogy javult-e valami. Néhány nap!

A slave programja:

/************************************************************
 * Kivezetések felsorolása:
 *  D9 LED error kijelző
 *  D10 SD card CS
 *  D11 SD card MOSI
 *  D12 SD card MISO
 *  D13 SD Card SCK
 *  
 */


//I2C busz kezelése 
#include <Wire.h>

// SD kártya modul programjai és változó deklarációi
//==================================================
#include <SD.h>
Sd2Card card;
File temp_adat;


String str_tmp;
bool oraadat_kesz=LOW;  //ha az I2C-n megérkezik egy óra mérési adata, akkor HIGH értékkel jelzi, hogy lehet SD-re írni
bool napadat_kesz=LOW;  //ha az I2C-n megérkezik egy nap mérési adata, akkor HIGH értékkel jelzi, hogy lehet SD-re írni
byte sd_hiba=1; //ha az sd van és sikerült az iras akkor 0, hiba esetén 1, visszaküldésre kerül masternek
byte erzekelo_hiba=0;  //a master küld jelzést, ha valamelyik érzékelővel hiba van 1=hiba, 0=nincs hiba


long hiba_ellenorzes=millis();  //sd kártya ellenőrzésének időzítéséhez, percenként ellenőrizzük, hogy rendben van-e
long led_villog=millis();
long led_villog_gyors=millis();
bool led_villog1=LOW;
bool led_villog2=LOW;

//az I2C-n érkezett időpont adatok tárolásához
byte ora=0;
byte perc=0;
byte masodperc=0;
byte ev=0;
byte ho=0;
byte nap=0;
//AzI2C-n érkezett adatok tárolásához
float kulso_homerseklet;
float kulso_homerseklet_min;
float kulso_homerseklet_max;
float paratartalom;
float legnyomas;
float pince_homerseklet;
float akna_homerseklet_also;
float akna_homerseklet_felso;
float kishaz_homerseklet;

//segéd változók az I2C adatfogadáshoz
byte fogad1;
byte fogad2;
byte fogad3;
byte fogad4; 

void setup()
{
  pinMode(9,OUTPUT);
  //Serial.begin(9600);
  if (!card.init(SPI_HALF_SPEED, 10)) {
    sd_hiba=1;
  } else {
    sd_hiba=0;
  } 
  delay(100);
  Wire.begin(8);                // I2C kommunikáció inicializálása 8-as eszköz címmel (mivel slave, címet kell megadni)
  Wire.onReceive(slave_fogad);  //Ezt a funkciót hivja az Arduino, amikor adatot kap a mastertől
  Wire.onRequest(slave_kuld);   //Ezt a funkciót hívja meg az Arduino, amikor a master adatot kér a slave-től 
  delay(100);
}

void loop()
{
  if (millis()>hiba_ellenorzes+10000)
  {
    if (!SD.begin(10)) {sd_hiba=1;} else {sd_hiba=0;}
    hiba_ellenorzes=millis();  
  }

  if ((millis()>led_villog+1000) and sd_hiba)
  {
    if (!led_villog1) {led_villog1=HIGH;}
    else {led_villog1=LOW;}
    led_villog=millis();
    //Serial.println(led_villog1);
  }
  if ((millis()>led_villog_gyors+100) and erzekelo_hiba)
  {
    if (!led_villog2) {led_villog2=HIGH;}
    else {led_villog2=LOW;}
    led_villog_gyors=millis();
    //Serial.println(led_villog2);
  }
  if (sd_hiba and erzekelo_hiba) {if (led_villog1) {digitalWrite(9,led_villog2);}}
  if (sd_hiba and !erzekelo_hiba) {digitalWrite(9,led_villog1);}
  if (!sd_hiba and erzekelo_hiba) {digitalWrite(9,led_villog2);}
  if (!sd_hiba and !erzekelo_hiba) {digitalWrite(9,LOW);}
  

  if (oraadat_kesz)
  {
    str_tmp="NP"+String(ev);
    if (ho<10) {str_tmp=str_tmp+"0"+String(ho);} else {str_tmp=str_tmp+String(ho);}
    if (nap<10) {str_tmp=str_tmp+"0"+String(nap)+".CSV";} else {str_tmp=str_tmp+String(nap)+".CSV";}
    if (!SD.begin(10)) {sd_hiba=1;} else {sd_hiba=0;}
    if (!SD.exists(str_tmp)) 
    {
      temp_adat = SD.open(str_tmp, FILE_WRITE);
      temp_adat.println("Datum;Ido;Homerseklet;Homerseklet_min;Homerseklet_max;Paratartalom;Legnyomas;Kishaz;Akna also;Akna felso;Pince;");
    }
    else 
    {  
      temp_adat = SD.open(str_tmp, FILE_WRITE);
    }      
    str_tmp="20"+String(ev)+".";
    if (ho<10) {str_tmp=str_tmp+"0"+String(ho)+".";} else {str_tmp=str_tmp+String(ho)+".";}
    if (nap<10) {str_tmp=str_tmp+"0"+String(nap);} else {str_tmp=str_tmp+String(nap);}
    temp_adat.print(str_tmp+";");
    if (ora<10) {str_tmp="0"+String(ora)+":";} else {str_tmp=String(ora)+":";}
    if (perc<10) {str_tmp=str_tmp+"0"+String(perc)+":";} else {str_tmp=str_tmp+String(perc)+":";}
    if (masodperc<10) {str_tmp=str_tmp+"0"+String(masodperc);} else {str_tmp=str_tmp+String(masodperc);}
    temp_adat.print(str_tmp+";");temp_adat.print(kulso_homerseklet);temp_adat.print(";");
    temp_adat.print(kulso_homerseklet_min);temp_adat.print(";");
    temp_adat.print(kulso_homerseklet_max);temp_adat.print(";");
    temp_adat.print(paratartalom);temp_adat.print(";");
    temp_adat.print(legnyomas);temp_adat.print(";");
    temp_adat.print(kishaz_homerseklet);temp_adat.print(";");
    temp_adat.print(akna_homerseklet_also);temp_adat.print(";");
    temp_adat.print(akna_homerseklet_felso);temp_adat.print(";");
    temp_adat.print(pince_homerseklet);temp_adat.println(";");
    temp_adat.flush();
    temp_adat.close();
    oraadat_kesz=LOW;
  }
  if (napadat_kesz)
  {
    str_tmp="EV20"+String(ev)+".CSV";
    if (!SD.begin(10)) {sd_hiba=1;} else {sd_hiba=0;}
    if (!SD.exists(str_tmp)) 
    {
      temp_adat = SD.open(str_tmp, FILE_WRITE);
      temp_adat.println("Datum;Ido;Homerseklet;Homerseklet_min;Homerseklet_max;Paratartalom;Legnyomas;Kishaz;Akna also;Akna felso;Pince;");
    }
    else 
    {  
      temp_adat = SD.open(str_tmp, FILE_WRITE);
    }      
    str_tmp="20"+String(ev)+".";
    if (ho<10) {str_tmp=str_tmp+"0"+String(ho)+".";} else {str_tmp=str_tmp+String(ho)+".";}
    if (nap<10) {str_tmp=str_tmp+"0"+String(nap);} else {str_tmp=str_tmp+String(nap);}
    temp_adat.print(str_tmp+";");
    if (ora<10) {str_tmp="0"+String(ora)+":";} else {str_tmp=String(ora)+":";}
    if (perc<10) {str_tmp=str_tmp+"0"+String(perc)+":";} else {str_tmp=str_tmp+String(perc)+":";}
    if (masodperc<10) {str_tmp=str_tmp+"0"+String(masodperc);} else {str_tmp=str_tmp+String(masodperc);}
    temp_adat.print(str_tmp+";");temp_adat.print(kulso_homerseklet);temp_adat.print(";");
    temp_adat.print(kulso_homerseklet_min);temp_adat.print(";");
    temp_adat.print(kulso_homerseklet_max);temp_adat.print(";");
    temp_adat.print(paratartalom);temp_adat.print(";");
    temp_adat.print(legnyomas);temp_adat.print(";");
    temp_adat.print(kishaz_homerseklet);temp_adat.print(";");
    temp_adat.print(akna_homerseklet_also);temp_adat.print(";");
    temp_adat.print(akna_homerseklet_felso);temp_adat.print(";");
    temp_adat.print(pince_homerseklet);temp_adat.println(";");
    temp_adat.flush();
    temp_adat.close();
    napadat_kesz=LOW;
  }
}


void slave_fogad ()      
//Ez a fuggvény akkor indul, amikor a slave értesítést kap, hogy a master adatot fog küldeni
{
  //beolvassuk a bevezeto adatokat
  byte R_status=Wire.read(); 
  if (R_status==9) 
  {           
    erzekelo_hiba=Wire.read();
  }
  if (R_status==1) 
  {           
    ev=Wire.read();            
    ho=Wire.read();            
    nap=Wire.read(); 
    ora=Wire.read(); 
    perc=Wire.read(); 
    masodperc=Wire.read();
  }
  if (R_status==2)
  { 
    //jönnek az átlagolt mérési adtok
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kulso_homerseklet=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kulso_homerseklet_min=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kulso_homerseklet_max=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    paratartalom=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
  }
  if (R_status==3)
  { 
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    legnyomas=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kishaz_homerseklet=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    akna_homerseklet_also=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    akna_homerseklet_felso=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    pince_homerseklet=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    oraadat_kesz=HIGH;
  }
  if (R_status==4)
  { 
    //jönnek az átlagolt mérési adtok
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kulso_homerseklet=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kulso_homerseklet_min=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kulso_homerseklet_max=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    paratartalom=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
  }
  if (R_status==5)
  { 
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    legnyomas=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    kishaz_homerseklet=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    akna_homerseklet_also=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    akna_homerseklet_felso=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    fogad1 = Wire.read();fogad2 = Wire.read();fogad3 = Wire.read();fogad4 = Wire.read();            
    pince_homerseklet=((float)byteToLong(fogad1,fogad2,fogad3,fogad4))/100;
    napadat_kesz=HIGH;
  }

}

void slave_kuld()                             
//Ez a függvény akkor indul, amikor a master adatot kér a slave-től
{ 
    Wire.write(sd_hiba);               
}

long byteToLong(long inp1, long inp2, long inp3, long inp4)
//Ez a funkció 4 byte-ból csinál egy long változót és visszadja az eredményt
{
  //4 byte long változóvá alakítása
  return ((inp1 << 0) & 0xFF) + ((inp2 << 8) & 0xFFFF) + ((inp3 << 16) & 0xFFFFFF) + ((inp4 << 24) & 0xFFFFFFFF);
}


Eddigi tapasztalatok, szívások: Összességében működik az időjárás állomásunk, használható, örülünk neki, de elég sok gond van vele. Eleinte 2-3 naponta lefagyott! A program átnézésekor kiderült, hogy a mátrix kijelzőt rosszul vezérlem. Oszloponként írtam, ami nem optimális. Áttértem a soronkénti írásra, és azóta a lefagyások száma sokat csökkent, de sajnos néhány hetente még most is előfordul. Ezért elkezdtem foglalkoznia watcdog használatával. Sajnos a reset törli a változókat, ezért azt találtam ki, hogy egy külső I2C busszal működő RAM-ban gyújtom az adatokat. Kb. egy hónapja készültem el a megoldással, és az FRAM kezeléséről írtam is egy külön cikket (a közölt forrásban már ezek mind benne vannak). Jelenleg már bármikor reset gombot nyomhatok, és a watchdog is újra indíthat, adat nem veszik el. Az FRAM nagyon jól bevált eddig, élettartama szinte végtelen, nem kell hozzá elemes táplálás, nem felejt kikapcsoláskor sem.

Utólag kiegészítettem a programot még egy állománnyal az SD kártyán, ami ez elindulások számát gyűjti. Szerettem volna látni, hogy a watchdog milyen gyakran indítja újra a programot lefagyás miatt. Még nem tudom az eredményt, mert a program módosítások miatt olyan sokat állítottam le, hogy nem tudom mikor volt a watchdog és mikor én.

Persze maradt még probléma bőven. Az egyik Dallas hőmérő vacakol. Kb 1-2 óra működés után CRC hibát produkál. A reset ekkor segít egy időre, de aztán újra jelentkezik a hiba. Lehetséges, hogy az okozza a bajt, hogy a program már csak cipőkanállal fér be a memóriába. Kb. 400byte van szabadon a flash-ban, és az SRAM is szinte tele van. Ekkor már láthatóan bizonytalan a működés, a lefagyások is ezért vannak tippjeim szerint. Hogy ezt kiderítsem, csináltam egy butított verziót a programból. Mindent kiszedtem, ami nem kell (SD kárta, FRAM, mátrix kijelzők használata stb.), szinte csak a mérések és az LCD kijelző meghajtása maradt. A memória használat 50% alá csökkent. Most várom, mi történik. Ha most napokig nem lesz CRC hiba, akkor már tudom mit kell tennem. Jöhet a dobozban pihenő ATmega alaplap. Abban van memória bőven, nem fogja a stack összerondítani a változókat. Ha ettől nem szűnik meg a hiba, akkor cserélem a Dallas hőmérőt. Jelezni fogom, mi volt a megoldás! (Sajnos nem szűnt meg a hiba, de még nem volt időm kiértékelni az eseményeket. Hobbyból csökkenteni szeretném a program méretét, és ezt a Dallas DS18B20 programrészek átalakításával kezdtem el, már be is írtam oda tapasztalataimat, illetve azt, hogyan lehet egyszerűbb a program, ha egyetlen chip van egy Arduino bemeneten. Valószínűleg cserélni fogom a hőmérő chip-et, de épp beköszöntött az esős évszak, várom, hogy jobb idő legyen!)

Továbbfejlesztési terveim is vannak. Szeretnék szélsebességet és csapadékmennyiséget mérni. Mindkettőhöz kapható mérőeszköz marhadrágán. Ezért kísérletezgetek egy pc ventilátorból és alumínium lemezből hajtogatott szélsebesség mérővel. Működik, csak nagyon nagy szélsebességnél indul meg. Legfeljebb valakitől karácsonyra szélsebességmérőt kérek ajándékba. A csapadékmennyiség mérés egyszerűbbnek tűnik, csak ahhoz meg még nem volt időm hozzáfogni! A csapadék mérés azért is fontos, mert az idén belekezdtem egy automata locsolórendszer fejlesztésébe. Ha esik az eső, nem akarok locsolni!

Biztosan feltűnt, hogy a fő kijelzőn nem látszik soha az idő. Ennek oka, hogy az idő kijelzésnél nem akartam a fényerő szabályozással vesződni, ezért ezt a megoldást választottam:

…és szeretném megelőzni a kérdést: nincs több hordóm!!

Sok sikert, ha megépítesz valami hasonlót!