DS3231 óra riasztás használata

Nem gondoltam, hogy valaha szükségem lesz a DS3231 óra alapos megismerésére, azonban mindennek eljön egyszer az ideje. Szeretnék egy beállítható idővel működő mérés adatgyűjtőt készíteni, ami adatokat rögzít SD kártyára. Ráadásul szeretném, ha akkumulátorról működne, amihez az Arduino-t alvás állapotában kell tartani két mérés között. Ehhez a DS3231 óra megszakítás funkciójára lesz szükség. Be kell programozni egy időpontot, és amikor ez elérkezik az óra felébreszti az Arduino-t, illetve ez biztosan egy ATmega328P chip lesz, mert az Arduino nano, vagy uno nagyon sokat fogyaszt. A DS3231 működésének megismerése bonyolultabb volt, mint gondoltam.

Lássuk először a részletes működést. Elővettem az adatlapját, és lefordítottam magamnak a szükséges részeket. Sajnos a fordítás során több mindent félreértettem, ezeket a hibákat javítottam, ahol kiderült. Bízom benne, hogy már jó a szöveganyag, és lehet vele dolgozni. Talán másnak is hasznos!

A DS3231 címezhető regiszterei:

Az alábbi ábra a DS3231 regisztereit mutatja. Több bájtos hozzáférés során, amikor a cím mutatója eléri a regiszter terület végét (12h), a következő beolvasott regiszter újra a 00h lesz. Az I2C START esetén, vagy ha több bájt olvasásakor a cím újra 00h címre kerül, az aktuális idő átkerül egy második regiszterkészletbe. Az időinformációt ezekből a másodlagos regiszterekből olvassa ki, miközben az óra továbbra is futhat. Ez kiküszöböli azt a problémát, hogy a regiszterek olvasása közben egyes bitek megváltozhatnak, ami miatt a kiolvasott érték nem lesz következetes (pl. 59 másodperc kiolvasása után az eltelt rövid időben a perc kiolvasásakor a perc már 1-el megnövekedhet, míg a másodperc 00-ra változik, ezért a kiolvasott érték 1 perccel későbbi időpontot mutatna).

Idő is dátum regiszterek (00h-06h):

Az idő és a naptár adatait a megfelelő regiszter bájtok kiolvasásával kapjuk meg. Az idő és a dátum adatokat a megfelelő regiszter bájtok írásával lehet beállítani. Az idő- és naptár regiszterek tartalma bináris kódolású decimális (BCD) formátumban kerül tárolásra.Magyarázatok a regiszterekben található jelző bitetekhez (12/24, AM/PM, Century), és a regiszter értékek kezeléséhez:

  • A DS3231 12 vagy 24 órás üzemmódban is működtethető. Az aktuális idő óráját mutató regiszter (02h) 6. bitje határozza meg, hogy 12 vagy 24 órás üzemmódban működik az óra. Ha ez 1, akkor a 12 órás üzemmód érvényes. 12 órás üzemmódban az 5. bit az AM/PM bit határozza meg, hogy a mutatott idő óra értéke délelőttöt vagy délutánt jelent. Amikor értéke 1, az délutánt (PM) jelent. 24 órás módban az 5. bit a második 10 órás bit, mert ekkor az óra tízes számjegye már nem csak 0 és 1 (0-12 óráig), hanem 2 is lehet (20–23 óráig), amihez két bitre van szükség.
  • Az évek kijelzésekor csak az év utolsó két számjegye kerül tárolásra. Az itt található értéket alapértelmezetten 2000-hez kell hozzáadni. Az évszázad bitjét (a 05h hónap regiszter 7. bitjét) akkor kell használni, amikor az éveket mutató regiszter (06h) értékét majd a jövőben 2100-hoz kell hozzáadni, illetve amikor majd 2099 év után az óra átfordul 2100-ra, akkor ez automatikusan bebillen 1-re.
  • A hét napjának változtatása éjfélkor történik. A hét napjának megfelelő értékeket a felhasználó határozza meg, de szekvenciának kell lennie (azaz ha 1 egyenlő vasárnap, akkor 2 egyenlő hétfővel és így tovább).
  • A nem logikus idő, dátumbevitel és hét napjának beírása meghatározatlan működést eredményez.
  • Az idő- és dátumregiszterek olvasásakor vagy írásakor a DS3231 másodlagos (felhasználói) puffereket használ a hibák elkerülésére a belső regiszterek frissítésekor. A visszaszámláló láncot a másodperc regiszter írása törli. Az átfutási problémák elkerülése érdekében az óra, perc és dátum regisztereket 1 másodpercen belül be kell állítani. Az 1 Hz-es négyszöghullámú kimenet, ha engedélyezve van, a másodperc érték beírását követő 500 ms után vált, feltéve, hogy az oszcillátor már fut.

Riasztási regiszterek (07h-0Dh):

Kétféle módon jelzi a DS3231 ha riasztás történt. Egyrészt lekérdezhető a status regiszter (0Fh) A1F és A2F bitjeiből, melyek 1-el jelzik, ha volt riasztás, másrészt ha az INT/SQW kimenet nem a négyszögjel kimenetnek van állítva, akkor alacsony szinttel jelzi a riasztási eseményt. Az INT/SQW névben az INT-et aláhúzassal kellene jelölni, mert ez jelöli, hogy az alacsony szint az aktív. Azonban az aláhúzás a weblapon nem jelent meg, nem küzdöttem vele. A DS3231 két dátum/idő riasztást tartalmaz. A riasztások bekapcsolhatók a riasztás engedélyezésével (AI1E és AI2E a kontroll regiszterben) és a kontrollregiszter INTCN bitjével. Az INT/SQW kimenet aktiválásához egyeznie kell a beállított biteknek az aktuális idő megfelelő bitjeivel (00h-02h, 03h vagy 04h). Az 1. riasztást úgy lehet beállítani, hogy a 07h-tól 0Ah-ig terjedő regiszterek bitjeit állítjuk be:

Az egyes dátum/idő riasztási regiszterek 7. bitjei maszkbitek. Ha az összes riasztáshoz tartozó maszk bit 0, akkor minden beállított értéket figyel az óra. Akkor lesz ilyen esetben riasztás, ha az aktuális idő megfelelő regisztereinek bitjei megegyeznek a dátum/idő riasztás regiszterekben tárolt bitekkel. Ha a maszk biteket 1-re állítjuk, akkor az adott időpont adatot nem nem vesz részt a riasztásban. például, ha az A1M1 maszk bit 1, akkor a hét vagy hónap napjának egyezőségét nem figyeli a rendszer, azaz bármilyen napon bekövetkezik a riasztás, ha az óra, perc és másodperc értékek megegyeznek. A lehetséges beálltásokat az alábbi táblázat tartalmazza.

A fenti táblázat a lehetséges beállításokat mutatja. A táblázatban nem felsorolt ​​konfigurációk logikátlan működést eredményeznek. A DY/DT bit (a hét napja vagy hónap napja regiszter 6. bitje) beállítja, hogy a regiszter 0–5 bitjeiben tárolt riasztási érték a hét napját vagy a hónap dátumát jelenti. Ha a DY/DT = 0, akkor a riasztás a hónap napjával való egyeztetéskor következik be. Ha a DY/DT = 1, akkor a riasztás a hét napjával való egyezéskor következik be.
Ha az aktuális idő regisztereinek bitjei megegyeznek a riasztási regiszter bitjeivel, akkor a megfelelő A1F vagy A2F riasztási jelzőbit 1-re billen. Ha a bit értéke 1, az aktiválja INT/SQW jelet. Az egyezést másodpercenként teszteli a DS3231. Ha azt szeretnénk, hogy a riasztást követően az INT/SQW kimenet újra magas legyen, akkor törölni kell a riasztást az A1F és az A2F bitekben is. Ha nem töröljük, akkor egy következő ciklikus riasztást nem tud jelezni a DS3231.

A 2. riasztást úgy lehet beállítani, hogy a 0Bh – 0Dh regiszterek bitjeit állítjuk be:

Ez a riasztási regiszter abban különbözik az 1. riasztási regisztertől, hogy a másodperc egyezését nem figyeli. A lehetséges riasztási eseteket a következő táblázat tartalmazza:

Control Register (0Eh)

7. bit: Oszcillátor (EOSC) engedélyezése. Ha 0-ra állítjuk, az oszcillátor működik. Ha 1-re állítjuk és a VBAT kivezetésről kap feszültséget a DS3231, akkor az oszcillátor leáll. Ez a bit 0, amikor az IC tápfeszültségét bekapcsoljuk. Ha a DS3231 IC-t a VCC kivezetés táplálja, az oszcillátor mindig be van kapcsolva, az EOSC bit állapotától függetlenül. Ha az EOSC le van tiltva (értéke 1), és a VBAT-ról működik a chip, az összes regiszter tartalma statikus az óra nem jár, de az utolsó érvényes időt őrzik a regiszterek, amíg az elem teljesen le nem merül.

6. bit: Négyszöghullám kimenet engedélyezése (BBSQW) akkumulátoros táplálás esetén. Ha az 1-re van állítva, és a DS3231 a VBAT kivezetésen keresztül kap feszültséget, az INT/SQW kimeneten megjelenik a négyszögjel, akkor is ha nincs feszültség a VCC kivezetésen. Az adatlap eredeti szövegének következő mondata ennek a bitnek a magyarázatához: “When BBSQW is logic 0, the INT/SQW pin goes high impedance when VCC falls below the power-fail trip point.”
Én ezt így fordítottam (értelmeztem): Amikor a BBSQW  0, az INT/SQW kivezetés nagy impedanciát mutat, amikor a VCC a minimális feszültségszint alá esik (kikapcsoljuk a tápfeszt és elemről tápláljuk az IC-t). Arra számítottam, hogy ez utóbbi mondat akkor is igaz, ha nem négyszögjel jön ki a vezetéken, hanem a megszakítás jelet szeretném felhasználni. A kísérleteim alapján azonban ez nem igaz. A mikor lekapcsolom a feszültséget a Vcc lábról, és az IC a Vbat bemeneten kap feszültséget elemről, az INT/SQW kimenet azonnal földre húzza magát. Nem hinném, hogy valamit elrontottam, mert ez egy nagyon egyszerű kapcsolás volt. Az INT/SQW kimenetet egy Arduino lábra kötöttem, ami bemenet. Ezt a bemenetet folyamatosan kérdezgettem, és néztem az állapotát. A DS3231 modulon van beépített felhúzó ellenállás, de gondoltam, hátha rossz, ezért kívülről raktam rá egyet. Ekkor is lemegy földre. Kipróbáltam úgy is, hogy ez a bit 1, de a ugyanaz az eredmény. Ha vissza adom a tápfeszt, azonnal magasra megy, és a risztás is bekövetkezik, tehát az IC működése rendben van attól, hogy egy időre nem volt tápfesz csak elemről. Ez elkeserítő számomra, mert ezek szerint elemről táplált állapotban nem képes megszakítás lábbal riasztani. Bár a leírás csak a négyszögjel kibocsátásról szól, de reménykedtem, hogy ettől még a megszakítás üzemmódban is működik a láb. Úgy tűnik nem. Egyébként a leírás szerint ez a bit 0, amikor a tápfeszültséget bekapcsoljuk.

5. bit: Hőmérséklet (CONV) konvertálása. Ennek a bitnek az 1-re állítása elindítja a hőmérséklet mérést, és a hőmérséklet regiszter (11h és 12h) értékének frissítését, és végrehajtja a TCXO algoritmust, hogy frissítse a kapacitási tömböt az oszcillátorban (ez nem tudom, hogy pontosan mit jelent, de feltehetőleg az óra pontosságának javításához van köze). Ez csak akkor történhet meg, ha az átalakítás még nem zajlik. A felhasználónak ellenőriznie kell a BSY állapotbitet, mielőtt a vezérlőt új TCXO végrehajtás elindítására kényszeríti. A felhasználó által kezdeményezett hőmérséklet-átalakítás nem befolyásolja a belső 64 másodperces frissítési ciklust. A felhasználó által kezdeményezett hőmérséklet-átalakítás kb. 2ms-ig nem befolyásolja a BSY-bitet. A CONV bit az íródástól az átalakítás befejezéséig 1-en marad, ez után CONV és  BSY 0-ra megy. A CONV bitet kell használni a felhasználó által kezdeményezett konverzió állapotának figyelésére.

Bit 4. és 3.: Rate Select (RS2 és RS1). Ezek a bitek szabályozzák a négyszöghullám frekvenciáját, amikor a négyszöghullám engedélyezve van. Az alábbi táblázat bemutatja az RS bittel választható négyszöghullámú frekvenciákat. Mindkét bit alapértelmezetten 1 (8,192 kHz), amikor a tápfeszültséget bekapcsoljuk.

Bit 2: Megszakító vezérlés (INTCN). Ez a bit vezérli az INT/SQW kivezetést. Ha az INTCN bit értéke 0, négyszög jelet ad ki az INT/SQW kivezetés. Ha az INTCN bit értéke 1, akkor az aktuális idő regiszterek és a riasztási regiszterek közötti egyeztetés 0-ra állítja az INT/SQW kimenetet (ha a riasztás szintén engedélyezve van). A megfelelő riasztási jelzőt mindig az INTCN bit állapotától függetlenül állítják be. Az INTCN bit értéke alapértelmezetten 1, amikor a tápfeszültséget bekapcsoljuk.

Bit 1: 2. riasztás megszakítás engedélyezése (A2IE). Ha az 1-re van állítva ez a bit, az lehetővé teszi a 2. riasztás jelző (A2F) számára, hogy INT/SQW kimenetet 0-ra állítsa megszakításkor (ha INTCN = 1). Ha az A2IE bit értéke 0 vagy az INTCN értéke 0, akkor az A2F bit nem kezdeményez megszakítási jelet az INT/SQW kivezetésen. Az A2IE bit alapértelmezetten le van tiltva (0), amikor a tápfeszültséget bekapcsoljuk.

Bit 0: 2. riasztás megszakítás engedélyezése (A1IE). Ha az 1-re van állítva ez a bit, az lehetővé teszi a 1. riasztás jelző (A1F) számára, hogy INT/SQW kimenetet 0-ra állítsa megszakításkor (ha INTCN = 1). Ha az A1IE bit értéke 0 vagy az INTCN értéke 0, akkor az A2F bit nem kezdeményez megszakítási jelet az INT/SQW kivezetésen. Az A1IE bit alapértelmezetten le van tiltva (0), amikor a tápfeszültséget bekapcsoljuk.

Status Register (0Fh)

7. bit: Oszcillátor Stop Flag (OSF). Ebben a bitben az 1 azt jelzi, hogy az oszcillátor vagy leállt, vagy egy ideig leállt. Ez felhasználható az az óra pontosságának megítélésére. Az alábbiakban esetekben válthat az OSF 1-re:

  1. A tápellátás most kapcsoltuk be.
  2. A VCC-n és a VBAT-n egyike sem elegendő az oszcillátor működéséhez.
  3. Az EOSC bit kikapcsolt akkumulátorral támogatott módban volt miközben VBAT-ról történt a táplálás.
  4. A kristályra gyakorolt külső hatások (azaz zaj, szivárgás stb.) miatt leállt az osszcillátor.

Ez a bit az 1-en marad, amíg nem töröljük.

3. bit: Engedélyezi a 32 kHz-es kimenetet (EN32 kHz). Ez a bit vezérli a 32 kHz-es kivezetés állapotát. Ha az 1-re van állítva, a 32 kHz-es kivezetés engedélyezve van, és 32,768 kHz négyszöghullámú jelet ad ki. Ha 0-ra állítjuk, a 32 kHz-es kivezetés nagy impedanciájú állapotba kerül. Ennek a bitnek a kezdeti bekapcsolási állapota 1, és egy 32,768 kHz négyszöghullámú jel jelenik meg a 32 kHz-es kivezetésnél a DS3231 bekapcsolása után (ha az oszcillátor működik).

2. bit: Foglalt (BSY). Ez a bit azt jelzi, hogy az eszköz elfoglalt a TCXO funkciók végrehajtásában. 1-re állítódik, amikor a hőmérséklet-érzékelőre történő átalakítási folyamatban van, majd törlődik, amikor az eszköz 1 perces szünetet tart a következő mérésig.

1. bit: 2. riasztás jelző (A2F). Azt jelzi, hogy a 2. riasztáshoz tartozó idő megegyezett az óra aktuális idejével. Ha az A2IE bit 1, és az INTCN bit 1, akkor az INT/SQW kivezetés megszakítást jelez ennek a bitnek a hatására. Az A2F bitet törölni kell egy riasztást követően. Ezt a bitet csak a 0-ra lehet írni. Ha megpróbáljuk 1-re írni, az érték változatlan marad.

0. bit: 1. riasztás jelző (A1F). Azt jelzi, hogy a 2. riasztáshoz tartozó idő megegyezett az óra aktuális idejével. Ha az A1IE bit 1, és az INTCN bit 1, akkor az INT/SQW kivezetés megszakítást jelez ennek a bitnek a hatására. Az A1F bitet törölni kell egy riasztást követően. Ezt a bitet csak a 0-ra lehet írni. Ha megpróbáljuk 1-re írni, az érték változatlan marad.

Példa program az INT/SQW kimenet megszakításokkal történő használatára:

A programot bőven elláttam kommentekkel. Sok magyarázatot nem is fűznék hozzá. Ez a program még nem küldi alvásba az ATmega328P chip-et, és még a DS3231 elemes működése sincs benne megoldva. Annyit érdemes tudni, hogy elemes táplálás esetén alapértelmezetten az INT/SQW kimenet magas impedanciás állapotba kerül, azaz nincs rajta megszakítás jel. Ezt külön be kell kapcsolni, hogy működjön, és az általam használt DS3231 könyvtár ezt nem tartalmazza, tehát meg kell írni. Erre még nem volt időm, bár egyáltalán nem látszik bonyolultnak. Még érdemes megnézni a DS3231 könyvtár forráskódját is Github-on. Link a forrás első sorában. Ez volt az első amit letöltöttem magamnak. A leírások és példa programok elég szerények, de használható. Rengeteg DS3231 kezelő könyvtár létezik, de egyikben sem találtam állítási lehetőséget az elemes táplálásban történő INT/SQW kimenet beállítására, így maradtam ennél a könyvtárnál. Ha készen lesz a függvény, még annyit fog változni a program, hogy az ATmega chip egyik lábáról fogja a tápfeszt kapni a DS3231, hogy az I2C kommunikáció idejére be lehessen kapcsolni, amikor a következő riasztási időpontot beállítom majd.

#include <DS3231.h> // https://github.com/NorthernWidget/DS3231 beépített függvéynek forrása a linken megnézhető
#include <Wire.h>
DS3231 Clock;

byte Year,Month,Date,Hour,Minute,Second,DoW; //óra beállításkor illetve óra kiolvasás segéd változói, Dow a hét napja (1-7)
bool Century=false; //évszázad jelzése, ha 0 akkor 2000-2099, ha 1 akkor 2100-2199 
bool h12; //24 órás időpontot használunk
bool PM; //ha 12 órást használnánk, ez jelezné a délutánt (1-el)
byte ADay; //riasztásban a hét napja (1-7), vagy a hónap napja (1-31)
byte AHour, AMinute, ASecond, ABits;  //risztási időpont változói
bool ADy; //risztásban ez jelzi, hogy hét napja (1) vagy hónap napja (0) lesz figyelve
bool A12h, Apm; //12 órás üzemmód esetén A12h true, jelen programban 24 órás üzemmódot használunk


void setup() {
  Wire.begin();
  Serial.begin(9600);

  pinMode(13, OUTPUT); //a 13-as kivezetésen van a LED
  digitalWrite(13, LOW); //led nem világít

 /******************************************************************************************* 
  * óra beállítás, csak egyszer kell beállítani aztán az elem őrzi az időt a DS3231-en   
  * miután egyszer lefordítottuk és le töltöttük a programot, az alábbi rész kikommentezhető
  *******************************************************************************************/
  /* Clock.setClockMode(false); //óra üzemmód 24 órás
    Clock.setSecond(0);
    Clock.setMinute(14);
    Clock.setHour(7);
    Clock.setDate(5);
    Clock.setMonth(7);
    Clock.setYear(20);
    Clock.setDoW(7); 
  */
    
  // kiolvassuk az időt és kiírjuk a sorosportra
  Serial.println("Ora időpontja");
  Serial.println("-------------");
  //kiolvassuk az óra idopontját
  Year=Clock.getYear();
  Month=Clock.getMonth(Century);
  Date=Clock.getDate();
  Hour =Clock.getHour(h12, PM);
  Minute =Clock.getMinute();
  Second =Clock.getSecond();
  DoW=Clock.getDoW();
  //kiírjuk a sorosportra, hogy lássuk is
  Serial.print("Het napja:");Serial.print(DoW, DEC);Serial.print(" datum:");
  Serial.print("2");if (Century) {Serial.print("1");} else {Serial.print("0");}
  Serial.print(Year, DEC);Serial.print("-");Serial.print(Month, DEC);Serial.print("-");Serial.print(Date, DEC);
  Serial.print(" ido:");
  Serial.print(Hour, DEC);Serial.print(':');Serial.print(Minute, DEC);Serial.print(':');Serial.print(Second, DEC);
  if (h12) {if (PM) {Serial.print(" PM ");} else {Serial.print(" AM ");}} else {Serial.println(" 24h ");}
  Serial.println();
  
  /******************************************************************************************************
  * risztás beállító függvény paraméterezéséhez segítség:
  * setA1Time(nap/hét napja, óra, merc, másodperc, alarm mód, map/hét napja flag, H12 flag, PM flag)
  * nap/hét napja = 0-30 vagy 0-7
  * óra = 0-12 vagy 0-24 (H12=1 essetén 12 órás)
  * perc=0-59
  * másodperc=0-59
  * alarm mód:
  *  0b1111 - minden másodpercben riaszt
  *  0b1110 - mikor a másodperc megeggyezik riaszt
  *  0b1100 - mikor a perc és a másodperc megeggyezik riaszt
  *  0b1000 - mikor az óra, perc és a másodperc megeggyezik riaszt
  *  0b0000 - mikor a hét napja vagy a hónap napja,az óra, perc és a másodperc megeggyezik riaszt 
  * nap/hét napja flag:
  *  0 - hónap napján riaszt (1-31)
  *  1 - hét napján riaszt (1-7)
  * H12 falg:
  *  0 - 12 órás idő megadás
  *  1 - 24 órás idő megadás
  * PM flag (csak akkor, ha H12=1):
  *  0 - délelőtt
  *  1 - délután
  **********************************************************************************************************/
  //Riasztási funkciók beállítása
  
  // beállítási példa: a hét ugyanazon napán (beállítás napja), a pillanatnyi idő + 1 perc időpontban lesz risztás, 24 órás üzemmód
  // Vigyázni, mert ha pont óra 59. percében adjuk ki, akkor a risztásben a perc 60 lesz, ami hüjeség
  //Clock.setA1Time(DoW, Hour, Minute+1, Second, 0b0000, true, false, false);
  
  //beállítási példa: a hét napja vagy a hónap napja nem számít, óra nem számít, perc nem számí, ha másodperc megeggyezik, akkor lesz risztás, 24 órás üzemmód
  // Vigyázni, mert ha pont óra 59. percében adjuk ki, akkor a risztásben a perc 60 lesz, ami hüjeség
  Clock.setA1Time(DoW, Hour, Minute+1, Second, 0b1110, true, false, false);
  
  Clock.turnOnAlarm(1);  //engedélyezzük a 1. risztást 
  Clock.turnOffAlarm(2); //a 2. riasztást most nem használjuk
    
  //lekérdezzük a risztási időpontot és kiírjuk a sorosportra
  Serial.println("Riasztási idopont");
  Serial.println("-----------------");
  Clock.getA1Time(ADay, AHour, AMinute, ASecond, ABits, ADy, A12h, Apm); //lekérdezzük a risztási adatokat
  if (ADy) {Serial.print("Het napja:");} else {Serial.print("Honap napja:");} Serial.print(ADay, DEC);
  Serial.print(" Idopont:");Serial.print(AHour, DEC);Serial.print(':');Serial.print(AMinute, DEC);Serial.print(':');Serial.print(ASecond, DEC);Serial.print(' ');
  if (A12h==LOW) {Serial.print("24h ");} else {if (Apm) {Serial.print("PM ");} else {Serial.print("AM ");}}
  if (Clock.checkAlarmEnabled(1)) {Serial.print("Risztas engedelyezve");} else {Serial.print("Riasztas tiltva");}
  Serial.print(" Alarm bits:");Serial.println(ABits,BIN);Serial.println();



 /*****************************************************************************************
  * Megszakítási bemenetek előkészítése
  * belső felhúzó ellenállás bekapcsolva, mert a DS3231 INT/SQW kimenete
  * magas impedanciás állapotban van ha nincs risztás, és a felhúzó ellenállás húzza fel 1-re,
  * risztáskor földre húzza az INT/SQW kimenetet 
  *****************************************************************************************/
  delay(500);
  pinMode(2,INPUT); //megszakítás 0 bemenet, erre kötjük a DS3231 INT/SQW kimenetét
  digitalWrite(2,HIGH);
  pinMode(3,INPUT); //megszakítás 1 bemenet, ezen nyomógomb lesz a risztásjelzés törléséhez
  digitalWrite(3,HIGH);

  /*****************************************************************************************************************
   megszakítások beállításának paraméterezéséhez segítség:
   attachInterrupt(kivezetés száma, meghívott függvény, esemény mód)
   esemény módok:
     LOW: az interrupt bemenet alacsony állapotban van
     CHANGE: ha a bemenet értéke változik, magasról alacsonyra vagy alacsonyról magasra
     RISING: ha a bemeneti jel alacsonyról magasra változik
     FALLING: ha a bemeneti jel magasról alacsonyra vált.
   A megszakítás közben a késleltetés [delay()] nem működik, és a millis() által visszaadott érték nem növekszik.
   A megszakítás végrehajtása közben a soros portra érkező adatok elveszhetnek.
   Valamennyi változót, amelyet módosítani szeretne a megszakításban, volatile-ként kell definiálnia.
   ****************************************************************************************************************/
  attachInterrupt(0, int_0, FALLING); //a kettes bemeneten lefutó élre (1->0) meghívódik a int_0 függvény (INT/SQW risztást ad)
  attachInterrupt(1, int_1, FALLING); //a hármas bemeneten lefutó élre (1->0) meghívódik a int_1 függvény (megnyomtuk a nyomógombot)

  /*
   * nem tudok rájönni, hogy miért, de a program elindulásakor egyszer meghívódik az int_0() függvény, és kigyúllad a led
   * Ezért kénytelen vagyok kikapcsolni a led-et azzal, hogy meghívom az int_1() függvényt. Lehetne úgy is, hogy simán 
   * kikapcsolom a led-et a kimenet LOW-ra állításával. Kinek hogyan teszik jobban. Ettől kezdve a megszakítás jól működik
   */
  //int_1();
  digitalWrite(13,LOW);    
}

void loop() {
  //lekérdezzük a D2 állapotát, vajon risztás után magas, vagy alacson (alacsony)
  if (digitalRead(2)) {Serial.println("2 high");} else {Serial.println("2 low");}
  
  // lekérdezi a risztás jelző állapotát, és egyben törli is a risztás jelző bitet IDS3231 status regizter (0Fh) A1F
  // ha nem töröljük a risztas jelző bitet, akkor az INT/SQW kivezetés magas marad, és nem történik következő risztás
  // ezt az int_0() függvénybe kelett volna rakni, de akkor nem tudtam volna lekérdezni a D2 bemenet állapotát
  // így látható, hogy riztás után mindaddig magas az INT/SQW, amíg nem töröljük
  Clock.checkIfAlarm(1); 
   
  delay(1000);
}

void int_0() {
  // a 2-es bemeneten érkezett egy 0 jelszint, beállítjuk a led_allpot wáltozót 1-re
  Serial.println("Megszakitas 0, led be");
  digitalWrite(13, HIGH);
}

void int_1() {
  // a 3-es bemeneten érkezett egy 0 jelszint, beállítjuk a led_allpot wáltozót 0-ra
  Serial.println("Megszakitas 1, led ki");
  digitalWrite(13, LOW);
}

Mint azt a kontrol regiszter 6. bitjénél (BBSQW) leírtam, nekem nem sikerült a DS3231-et elemmel táplált üzemmódban arra rábírnom, hogy az INT/SQW lábon megszakítás jelet küldjön. Vagyis a modult csak bekapcsolt Vcc állapotban lehet használni. Ez egyébként önmagában még nem baj, mert az áramfelvétele még így is igen kicsi a DS3231 chip-nek. A leírás szerint ha nem kommunikálok vele I2C buszon keresztül, 200 mikroA körüli áramot fogyaszt. Már ez is elegendően kicsi érték lenne. De nézzük, mit tud a DS3231 modul. Gyanítható, hogy a rajta lévő led, ami bekapcsolt Vcc esetén világít, már eleve használhatatlanná teszi a modult. És igen, 8 mA az áramfelvétel, de nem túl pontos árammérőm van, ezért ez csak nagyságrend. Ha kiforrasztom a led-et, akkor ez kisebb lesz pár milliA-el. De biztosan nem megy le 200 mikroA környékére. Ezért nézegetni kezdtem a modul kapcsolási rajzát, ami szerencsére fellelhető a neten:

Az R2 ellenállás értéke 1 Kohm, tehát itt 1mA alatti áramokra lehet számítani. Semmi más nincs ami fogyasszon. Illetve ott van még az AT24C32 eeprom. Azonban az adatlapja alapján ha nem írom és nem olvasom, akkor 35mikroA az áramfelvétele. Itt elakadtam. Vagy szétbarmolom a modult, és tönkre teszem a led-et, vagy keresek egy másik modult, amin csak óra van, nincs rajta led. Ezt tettem. Most várom, hogy megérkezzen aliexpresz-en. Addig pihi van!

Ha azonban nem lényeges az áramfelvétel, a fenti kapcsolás tökéletesen működik!

Elemes kapcsolások alapjai

Egy kis elmélettel kezdünk. Az ATmega328P chip és az összes többi társa működés közben jelentősnek mondható, 10mA nagyságrendű áramot vesz fel. Megmértem, ha semmit nem kötök rá, akkor 12,5mA. Tegyük fel, hogy akkumulátorról akarjuk táplálni. Vegyünk pl. egy klasszikus 18650-es lítium aksit. Az egyik onkrét típus (HTCNR18650) adatlapja szerint feszültsége 3,6V, kapacitása 2200mAh. A feszültsége az adatlap alapján teljesen feltöltött állapotban kb. 4V, és merítéskor folyamatosan csökken kb. 3V-ig, ahol egy teljes lemerítés elleni védelem le fogja kapcsolni. Ez a teljes lemerítés elleni védelem azért kell, mert a lítium aksik túlmerítés hatására szeretnek kigyulladni. Ki ne emlékezne a Samsung eszközökre, amik maguktól elégtek. A lítium aksi ráadásul nem csak az éghető anyagot tartalmazza, van benne oxigén is, így hiába dobod be egy vödör vízbe, tovább ég vidáman.
De tegyük fel, hogy még nem ég, és teljesen feltöltöttük. Ez azt jelenti, hogy 12,5mA terheléssel 2200/12,5=176 órát, vagyis kb. egy hetet üzemelne az áramkör. Ez elég kevés. Ha pl. egy konyhai időzítőt szeretnénk üzemeltetni, hetente tölteni kellene, akkor is ha egyszer sem időzítünk vele. A chip csak várakozik arra, hogy megnyomjuk az start gombot, és közben sokat fogyaszt. Tegyük fel, hogy a konyhai időzítőnk miközben működik (visszaszámol egy kijelzővel) 100mA áramot fogyaszt. Ebbe az áramfelvételbe akár világító led kijelző használata is beleférhet. Ha egy időzítés 10perc, akkor hányszor használhatnánk? 2200/100=22óra. Ha tojásfőzéskor időzítünk vele, akkor egy időzítés 10 perc, tehát 132-szer használhatjuk. Ha minden nap főzünk tojást, ez akkor is legalább négy hónap, és ez kielégítő eredmény. Négy havonta fel kell tölteni az aksit!
Nincs más dolgunk, mint az időzítések közötti szünetekben valahogyan el kell érni, hogy ne vegyen fel a chip 12,5mA áramot. Ezek a szünetek akár napokig is tarthatnak (én pl. csak nagyon ritkán eszek főt tojást), Lehetne egy főkapcsoló is, amivel teljesen lekapcsoljuk az áramkört az időzítőn, de ez nem elegáns. Küldjük inkább a chip-et aludni.

Ha kikapcsoljuk a program végrehajtásra szolgáló áramköröket, az AD konvertert, és minden más alvás időben szükségtelen eszközt, akkor az áramfelvétel jelentősen csökkenni fog. Az ATmega328 adatlapja szerint egészen 4,2mikroA-ig lehet lemenni. Ekkor már csak egy belső oszcillátor időzítővel, valamint két bemenetet figyelő áramkör működik. Ha a belső időzítő elszámolt egy beállított értékig (maximum 8 másodperc), vagy a két bemenet valamelyikére jelet kapcsolunk, akkor a chip felébred, és csinálhat valamit. Aztán újra el lehet küldeni aludni, amikor csak akarjuk. A 4,2mikroA áram igen kicsi. Meddig is működik az akkumulátorról? 2200mA/4,2mikroA=~524000 óra, vagyis kb. 21000 nap, azaz 60 év. Ez persze csak elméleti érték, sok mindennel nem számoltam. Sajnos az akkumulátorok és ez elemek merülnek maguktól terhelés nélkül is, így nem számíthatunk 60 évre, esetleg akkumulátornál egy-két évre, de egy lítium gombelemnél ez 7 év is lehet. A drágább lítium elemekre a gyártók éveket garantálnak. Egy konkrét típusnál 20% kapacitáscsökkenés eléréséhez 7 évet adtak meg emlékeim szerint.

Na akkor nézzük végre hogyan is kell elaltatni a chip-et. Nyilvánvaló, hogy erre van kész könyvtár. Egy elalvásról szóló internetes leírásban ezt találtam meg: https://github.com/rocketscream/Low-Power
Nagyon fontos!!! Ez a könyvtár nem kezeli az ATmega8 chip-et. Nekem ebből több darab is van raktáron, és ez elszomorított, de ez van. Vannak más könyvtárak, melyek lényegesen több vezérlő típusra fel lettek készítve.
Használathoz le kell tölteni a github-on megtalálható állományt, átnevezni „LowPower.zip”-re, és a könyvtár kezelőben mint zip állományt beimportálni. Sajnos ez a könyvtár az Arduino Ide könyvtár kezelőjében nincs benne tapasztalatom szerint. De így is jó, nem túl bonyolult az importálás.

A példaprogramokat a fájl/példák menüben tudjuk elérni:

Nem igényel sok magyarázatot a használata, két mintapéldát próbáltam ki, ezeket kissé át is alakítottam. Direkt a kis áramú kísérletekhez vettem egy 50mA-es 4 tizedesjegyet mutató panelműszert. Biztosan nem egy labor műszer, de legalább értékes számjegyeket lehetett leolvasni, és nem 0-át mint a multiméteremről. Ennek a műszernek a felbontása 0,1mikroA. LED-et kötöttem a chip-re, és figyeltem az áramfelvétel alvó és működő állapotban is.

Időzített bekapcsolás:

#include "LowPower.h" // LowPower könyvtár használata

void setup()
{
  pinMode(2, OUTPUT); // led a 13-as kivezetésre kerül
}

void loop() 
{
    // az ATmega328P chip alvó bódba megy 4 másodpercre
    // kikapcsolja az  ADC és a BOD modulokat a chipben
    // áramfelvétel 6,2 mikroA (a műszerem 0,1 mikroA érzékenységű)
    LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); 
    // 4 másodperc után itt folytatódik a program
    // bakapcsolt led esetén kb 17,6 mA áramfelvétel (nálam ebből a led 7,5mA)
    // a chip áramfelvétele kb 12-13 mA
    digitalWrite(2, HIGH);   
    delay(10000);   
    // ha nem kapcsolom ki a LED-et, akkor elalváskor a chip kimenet magas értéken marad
    // és az áramfelvétel kb. 6,7 mA vagyis a LED továbbra is fogyaszt, miközben a chip alszik
    // a chip kimenetei továbbra is működnek, csal a belső program végrehajtó logika alszik                    
    digitalWrite(2, LOW);   
}

A következő eredmények születtek:

  • Alvó módban az áramfelvétel 6,2 mikroA
  • Amikor a LED világit (10sec), az áramfelvétel 17,6 mA
  • Lehúztam a LED-et, ekkor az áramfelvétel 12 mA, tehát a LED 5mA körüli áramot vesz fel.

Néhány hasznos tudnivaló a programhoz:

  • A lehetséges időzített alvási időtartamok 15ms, 30ms, 60ms, 120ms, 250ms, 500ms, 1s, 2s, 4s, 8s
  • Ha egy kimenetet magas állapotban hagyunk elalváskor, akkor az magas is marad, és a rá kötött áramkörök áramot vesznek fel. Pl. egy led világít alvó chip esetén is. Feltehetőleg ennek az az oka, hogy a chip alváskor a 16Mhz órajelet kapcsolja ki, és statikus állapotban befagy a végrehajtás, ott ahol tartott. Ilyenkor a chipet felépítő MOSFET áramkörök 0 teljesítményt vesznek fel, ennek köszönhető a kicsi fogyasztás. Azonban a chip tápfeszültsége be van kapcsolva, a kivezetések áramkörei működnek.
  • Az ADC az analóg digitális konvertert jelenti, azt külön ki kell kapcsolni, mert feltehetőleg nem a 16Mhz órajelről kapja az órajelet, és tovább működne. Ezért az ADC-OFF a powerDown függvényben.
  • A BOD áramkör a chip tápfeszültségét figyeli. Alapértelmezetten 2,7 voltnál kapcsolná ki teljesen a chip-et, így most erre sincs szükségünk., hisz az aksi védőáramköre nem sokkal 3V alatt magától kikapcsol. Feltételezem, hogy virtuális akkumulátorunkon van védő áramkör, nem akarjuk, hogy fejlesztő laborunk leégjen. Hagyományos cink elemeknél a BOD hiánya gondot okozhatna, mert azok 0V-ig merülnek, így bizonytalan lenne a helyzet 2,7V környékén. Azonban az elemekből túlmerítéskor kifolyó sav a nagyobb károkat okozna, mint a bizonytalan működés.

Bekapcsolás az egyik chip kivezetésre kapcsolt feszültséggel:

#include "LowPower.h" //LowPower könyvtár használata

void setup() {
  pinMode(2, INPUT);  //2-es kivezetés bemenet ez a 0-as megszakítás bemenete
  pinMode(13, OUTPUT);//13-as kivezetésre kötjük a LED-et
}

void loop() {
  // a 0-ás megszakítást használjuk, ami a 2-es láb
  // az esemény a megszakitas() függvényt hívja meg (és felébreszti a chip-et)
  // LOW, akkor hajtódik végre, a a láb logikai alacsony szinten van,
  // CHANGE, akkor halytódik végre, ha a láb logikai szintje megváltozik
  // RISING, akkor hajtódik végre, ha a láb logikai szintje alacsonyról magasra vált
  // FALLING, akkor hajtódik végre, ha a láb logikai szintje magasról alacsonyra vált 
  attachInterrupt(0, megszakitas, LOW);
    
  // elküldjük aludni a chip-et.
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 
    
  // tiltjuk a megszakítást, nehogy a következő programrészeket újra 
  // megszakítsa a nyomógomb ismételt lenyomása.
  detachInterrupt(0); 
   
  delay(4000);   //Ez nem müködne a megszakitas() függvényen belül  
  digitalWrite(13, LOW);   

}

void megszakitas()
{
  digitalWrite(13, HIGH); 
}

Fontos tudnivaló, hogy a megszakítással meghívott függvényekben nem működik a delay() függvény. Mivel a programban azt akartam, hogy a led 4 másodpercig világítson, a delay() csak a loop()-ba kerülhetett. Egyébként a led bekapcsolása kerülhetett volna oda is, csak így látható a programban, hogy a megszakitas() függvény is csinál valami!

Megszakítások

Bajban vagyok a megszakításokkal, mert szeretek mindig valamilyen értelmes gyakorlati megvalósítást bemutatni. Azonban a megszakításokat ez idáig szinte semmire nem használtam a gyakorlatban. Elméletileg tudom mire jók, példa programot is építettem, ami alább olvasható, de nem találtam semmi olyan szituációt, ahol hasznát vehetném. A távoli jövőben, és egy közeli galaxisban azonban már körvonalazódik egy feladat, amiben szerepe lehet. Kitaláltam, hogy készítek egy adatrögzítő áramkört. Az lenne a feladata, hogy beállítható időnként (mondjuk percenként, vagy tíz percenként, óránként stb.) beolvasson egy adatot és tárolja SD kártyán. A gyakorlati példa a hűtőszekrény hőmérsékletének a megfigyelése lenne. Ehhez egy RTC órát szeretnék használni, amiben leprogramozom a következő mérési időpontot, az óra a SQW kimenetén adni fog egy riasztást, aminek hatására a program elvégzi a mérést. Azért szeretnék RTC órát használni, mert közben az Arduino két mérés közt aludni fog, hogy ne fogyasszon sokat, hiszen egy ilyen áramkörnél az akkumulátoros táplálás a célszerű. Nem vezetékezhetem be a hűtőszekrényt!!

Annak, aki igazán kezdő, és még nem is hallott a megszakításokról, kezdjük egy kis elmélettel. A C-ben fő programunk egy folyamatos ciklusban fut. Ha tehát egy esemény bekövetkezését fel akarjuk deríteni, akkor a fő ciklusba kell beépíteni egy lekérdezést. Előfordul azonban, hogy a főprogramunk olyan funkciót lát el, ami csak hosszú időközönként hajtódik végre. Pl. tegyük fel, hogy óra programot írtunk, aminek a feladata, hogy percenként egyszer frissítse fel a kijelző tartalmát. Természetesen adja magát, hogy a loop() függvényünk végére tegyünk egy delay(60000); utasítást, és a következő futás előtt várakozzunk egy percet. Ekkor azonban egy teljes percig várakozunk, a program nem csinál semmit, vagyis egy ilyen fő ciklusban csak percenként egyszer tudnánk megnézni, hogy történt-e valamilyen esemény. Ha a figyelt eseményre gyorsan kell beavatkozni, akkor a percenként egyszeri figyelés nagyon ritka. Egy ilyen szituációban jön jól a megszakításkezelés használata.

A program is sokat egyszerűsödhet ennek a technikának a használatával. Fut ugyanis a fő ciklus, és ha érkezik a chip megfelelő kivezetésére egy jel, megszakad a fő ciklus végrehajtása, és egy függvény fut le. Ha végrehajtódott a függvény, a főprogramunk fut tovább. Az ATmega vezérlőkben a megszakítások arra is használhatók, hogy az alvó chip-et felébresszék. Erről bővebben az elemes működésnél olvashatsz.

Az ATmega328 vezérlőnek két kivezetése van, ami megszakításokat kezel. Ezek a D2 és D3 kivezetések. A D2 kivezetésre az interrup0, a D3 kivezetésre pedig az interrup1 megszakítás paraméterezhető. Ez annyit jelent, hogy a programban meg kell adni azoknak a függvényeknek a nevét, amiket az adott lábon történő esemény megtörténtekor meg fog hívni a vezérlő. Mindkét kivezetésre külön külön kapcsolhatjuk be a megszakítás kezelését. Erre szolgál az attachInterrup()  nevű függvény aminek három paramétere van:

  • megszakítás sorszáma (0 vagy 1), ez dönti el, hogy melyik bemeneti lábat figyeli a chip
  • meghívásra kerülő függvény neve a programon belül
  • a bemeneti esemény figyelésének módja

Egy digitális bemenet alacsony és magas értékeket tud megkülönböztetni, tehát nem akármit lehet figyelni. Megszakítást okozhat, ha alacsony bemeneti állapotról magas állapotra vált a bemenet, vagy fordítva. Összesen négy figyelési módot tud megkülönböztetni a megszakításkezelés:

  • LOW: a bemenet alacsony állapotban van
  • CHANGE: ha a bemenet értéke változik, magasról alacsonyra vagy alacsonyról magasra
  • RISING: ha a bemenet alacsonyról magasra változik (felfutó él)
  • FALLING: ha a bemenet magasról alacsonyra vált (lefutó él)

Ha egy nyomógomb lenyomását akarjuk érzékelni, akkor nem mindegy, hogy ezek közül melyiket alkalmazzuk. Tegyük fel, hogy a nyomógombot úgy kötjük be, hogy egy felhúzó ellenállás a bemenetet folyamatosan magas szinten tartja, és a gombnyomás a bemenetet a földre húzza le. Ha azt a pillanatot akarjuk „elkapni” amikor megnyomjuk a nyomógombot, akkor használhatjuk a FALLING paraméter szerinti működést. Ha azonban a nyomógomb elengedésének a pillanatát szeretnénk felhasználni valamilyen folyamat vezérlésére, akkor a RISING-et kell használnunk, mert akkor fog a megszakításra megírt függvényünk elindulni, amikor elengedjük a nyomógombot, és a bemenet alacsony szintje magas szintre vált. Ha a nyomógomb megnyomásakor és elengedésekor is végre akarjuk hajtani a függvényt, akkor használjuk a CHANGE paramétert. Biztosan feltűnt, hogy nem említettem a LOW paramétert. Bevallom, nem értem ennek működését, mert a bemenet alacsony értéke egy statikus állapot, nincs benne semmi változás. Arra gyanakszom, hogy itt is azt a pillanatot lehet az interrupt indítására használni, amikor magasról alacsony jelszintre vált a bemeneti jel. Ez azonban a FALLING móddal egyezik meg. Egyszer majd biztosan kiderül a különbség, jelenleg nem érdekel.
A nyomógombos példám sántít, mert a valóságos nyomógombok prellesek. Erről a bemenetek kezelésénél olvashatsz. Egy valós nyomógomb esetében egynél többször változik a jelszint megnyomáskor és elengedéskor is, ezért ez így nem fog jól működni. Ha azonban egy érzékelőt kötsz a bemenetre, ami ténylegesen egy jelszint változást produkál valamilyen eseményre, akkor már használhatók ezek a megszakítás paraméterek, és mindegyik hatására mást fog csinálni a programod. Pl. ha egy mozgásérzékelőt használsz, ténylegesen megszámolhatod a helyiséget elhagyó személyek számát a FALLING használatával. Ne felejtsük el, hogy egy mozgásérzékelő akkor ad magas jelet a kimenetén, ha mozgást érzékel, és akkor esik le nullára a kimenete, ha egy beállított ideig nem történik körülötte semmi.

Lássuk a példa programot:

void setup() {
  Serial.begin(9600);
  Serial.println("elindul");
  pinMode(2, INPUT); //megszakítás 0 bemenet előkészítése
  pinMode(3, INPUT); //megszakítás 1 bemenet előkészítése

  pinMode(13, OUTPUT); //a 13-as kivezetésen van a LED
  digitalWrite(13, LOW); //led nem világít

  attachInterrupt(0, int_0, LOW); //a kettes bemeneten 0 jelenik meg akkor meghívódik a int_0 függvény
  attachInterrupt(1, int_1, LOW); //a hármas bemeneten 0 jelenik meg akkor meghívódik a int_1 függvény

}

void loop() {
  Serial.println("Dolgozik...");
  delay(500);
}

void int_0() {
  // a 2-es bemeneten érkezett egy 0 jelszint, bakapcsoljuk a led-et
  digitalWrite(13, HIGH);
}

void int_1() {
  // a 3-es bemeneten érkezett egy 0 jelszint, kikapcsoljuk a led-et
  digitalWrite(13, LOW);
}

A program nagyon egyszerű dolgot csinál. Bekapcsolja a soros portot, és másodpercenként egyszer kiírja a soros portra, hogy „Dolgozik…”! Futtatáskor természetesen el kell indítani a soros monitort, ha ezt látni akarjuk. Ez jelképezi azt az utasítás sorozatot, amit másodpercenként egyszer elindítok, és  csinál valamit. Mivel delay() függvényt használok a programban, az időzítés ideje alatt (egy másodpercig) az Arduino semmivel nem foglalkozik. Ha ide tennék be egy bemenet lekérdezést, akkor rossz esetben egy másodpercig nyomni kellene a nyomógombot, hogy a program észrevegye. Azonban a program attachInterrup() sorában bekapcsoltuk a megszakítás kezelést a 0-s és az 1-es megszakításra is. A 2-es bemenetre kötött nyomógomb megnyomása megszakítja a főprogram futását, HIGH-ra állítja a 13-as kimenetet, amin épp egy beépített led található. Ekkor a led világítani kezd. Ha az 1-es megszakítás által figyelt 3-as bemeneten nyomjuk meg a nyomógombot, akkor pedig kikapcsoljuk a led-et. Tehát a led azonnal felgyullad vagy elalszik, ha nyomogatjuk a gombokat, nem kell egy másodpercet várni megnyomott gombbal. Ha lakásvilágítást vezérelsz, ez nem hátrány.

Van egy picike csalás a programban. A nyomógomb ugyanis prelles, azaz egy megnyomásra többször is meghívja a neki megfelelő megszakítás függvényt. Vagyis a led-et egy megnyomással többször is be illetve kikapcsoljuk. Ez persze nem látszik, hisz már az első kontaktus is bekapcsolja a led-et, a többi már nem változtat semmit a led állapotán. Ha a megszakítás függvénybe is beteszünk egy soros portra történő írást, akkor viszont látni fogjuk, hogy a gomb megnyomásakor a soros portra küldött szöveg többször is megjelenik. Az én nyomógombommal két háromszor, de néha még négyszer is. Ez jelen esetben nem baj. Viszont következmény, hogy ebben a szituációban és programban tök mindegy, hogy melyik megszakítási módot használod. Kipróbáltam a LOW, CHANGE, RISING, FALLING mindegyikét. A leg rögtön reagált, vagy felgyulladt vagy elaludt attól függően, melyik gombot nyomtam meg.

Van néhány tulajdonsága a megszakításnak. A delay() nem használható egy megszakítás függvény végrehajtásakor. A millis() függvényben található érték sem növekszik tovább, amig a megszakítással meghívott függvény hajtódik végre. Ez teljesen logikus. A millis és a delay is megszakításokkal működik, és a megszakítás megszakítása olyan konfliktusokat okoz, amit ebben az egyszerű vezérlőben nem kezeltek. Ha pl. a főprogramban épp időzít egy dalay, és a megszakítás függvényben is szeretnél időzíteni, akkor két delay-ra lenne szükséged, melyek egymástól függetlenül tudják, hogy hol jártak az időzítésben. Igen ám, de ha az egyik delay időzítése megszakad egy megszakítás során, honnan fogja tudni, hogy mennyi időt töltött a vezérlő a másik függvényben, és ezért mennyivel kevesebbet kellene időzítenie, hogy az eredeti időt kapjuk az időzítés során.

A fenti konliktus sok-sok évvel ez előtt megtapasztaltam. Építettem, egy nagyon „profi” órát, amivel 4 kimenetet lehetett különböző időpontokban ki és bekapcsolni. TMS1122 volt az óra IC neve, ha jól emlékszem. Azt tapasztaltam, ha sokat szórakozok az órával, és programozgatom, nyomkodom a gombokat, akkor az óra késni kezd. Akkor nem értettem, hogy ennek mi az oka. Ma már tudom. A nyomógombokat belül megszakításokkal kezelte és az óra programban a millis()-nek megfelelő funkciók megszakadtak, és egy pici idő mindig elveszett! Jó ezt tudni, amikor a megszakítást olyan programban használjuk, amiben számít a végrehajtási idő!

Még egy elméleti tudnivaló van hátra, a megszakítás megszakításának megakadályozása. Ez akkor fordulhat elő, ha a megszakításkor meghívott függvény olyan hosszú ideig fut, hogy akár újabb megszakítás is érkezhet a bemenetre. Sajnos nem tudtam kipróbálni értelmes példával, de ennek a szitunak a kezelésére van három függvény:

  • detachInterrupt () – a megadott számú megszakításkezelést kikapcsolja
  • interrupts() – bekapcsolja a megszakítás kezelést, ha ki lettek kapcsolva
  • noInterrupts () – kikapcsolja a megszakításkezelést

Az utóbbi két függvény értelmezésem szerint mindenféle megszakítás kezelését kikapcsolja, tehát a millis() sem működik tovább. Ez akkor lehet hasznos, ha annyira időkritikus programkódot futtatunk, hogy már a belső megszakítások (a külső meg pláne) megzavarnák a működését.