- Alvó üzemmód működésének megismerése, alvó üzemmódok
- Fogyasztás alvó üzemmódokban
- Alvó üzemmód vezérlő regisztereinek működése, kódrészletek
- LOW-Power könyvtár és példaprogramok
- Áramfelvételi adatok alvásban (más weboldalról és saját mérés is)
————————————————————
Ebben a témakörben már született egy rövid leírás (megtekinthető itt), ami elegendő lehet azoknak, akik számára fontos, hogy elemről történő üzemeltetés alatt a vezérlő minél kevesebbet fogyasszon, de nem akarnak a részletekbe merüli.
Ha nagyon leegyszerűsítjük a dolgot, akkor annyi a lényeg, hogy kiadható egy „sleep” assembly utasítás, minek hatására a vezérlő felfüggeszti a működését (a felhasználói program végrehajtása szünetel), de nem minden funkcióját. A még működő funkciók teszik lehetővé a felébredést, azaz a program végrehajtásának folytatását. Pl. alvó állapotból a vezérlő egy adott idő után magától felébredhet, vagy az egyik megszakítás kimenetre kapcsolt alacsony jelszintre kapcsol be. Az energiatakarékos (alvó) üzemmódok azonban sokkal többféle lehetőséget adnak számunkra. Pl. felébredhet a vezérlő, ha megszóllítják az I2C buszon keresztül, vagy felébresztheti az analóg komparátor, ha az egyik analóg bemeneten a feszültség értéke meghalad egy előre beállított értéket stb. Rengeteg lehetőségünk van arra, hogy egyenként kapcsolgassuk ki és be a szükséges illetve szükségtelen funkciókat, és ezzel „testreszabjuk” áramkörünk fogyasztását.
Részemről azért kezdtem bele az ATMega328 adatlap böngészésébe, mert egy valós probléma megoldásába kezdtem. A házunk víz felhasználását szeretném monitorozni. Azonban a vízaknába nagyon nehézkesen tudnék tápellátást vezetékezni, ezért a cél az, hogy akkumulátorról üzemeljen a mérőberendezés, amit egy hydrogenerátor-akkumulátor combó lát el árammal és rádió adóval küldi az adatokat a beltéri egységre. Ha kinyitjuk a csapot valahol a lakásban, a hydrogenerátor áramot termel, és tölti az akkumulátort, és közben méri és küldi a fogyasztási adatokat. Ha nem nyitjuk ki a csapot, akkor meg alszik a vezérlő! Izgi feladatnak tűnik!
Az ATMega328 vezérlő több különböző alvó üzemmódot kínál, amelyek lehetővé teszik a felhasználó számára, hogy az energiafogyasztást az alkalmazás igényeihez igazítsa:
1. Tétlen üzemmód
2. ADC zajcsökkentési mód
3. Kikapcsolási mód
4. Energiatakarékos mód
5. Készenléti mód
6. Meghosszabbított készenléti mód
Hamarosan ki fog derülni, hogy melyik üzemmód mire is jó igazán!
Ha engedélyezve van a fuse bitekkel a BOD áramkör, aktívan figyeli a tápfeszültséget az alvó üzemmódban is, azonban bizonyos alvó üzemmódokban még a BOD is letiltható szoftveres úton. Ez fontos lehet, mert a BOD jelentős áramot fogyaszt (10-20 uA)!
Az alvó üzemmódok bekapcsolásához használhatunk előre elkészített program könyvtárakat (pl. Low-Power library), de saját magunk is bekapcsolhatjuk a megfelelő üzemmódot a vezérlő belső regisztereibe írt megfelelő értékekkel, és az alvást kiváltó assembly utasítással. Mindkét módszerről esni fog szó! Az alvó állapotból mindig valamilyen megszakítással ébred fel a vezérlő (lásd még esetleg a megszakításokról szóló részletes leírást itt).
Az alvó üzemmódok áramfelvételének csökkenését egyrészt a vezérlő megfelelő funkcionális egységeinek lekapcsolása eredményezi, de nagyrészt az egyes részegységek órajelének lekapcsolása. Alváskor a belső regiszterek értékét meg kell őrizni a folytatáshoz, csak éppen fel kell függeszteni a működést. Pontosan ezt eredményezi az órajel lekapcsolása. Mint az sokak számára ismeretes a vezérlőt felépítő kapcsoló tranzisztorok lezárt állapotukban szakadásnak tekinthetők, rajtuk gyakorlatilag nem folyik át áram. Így egy stabil LOW vagy HIGH érték tartásakor sincs áramfelvétel. Azonban a logikai érték megváltozásakor nagyon rövid ideig folyik egy kicsike áram a kapcsoló elemeken keresztül. A vezérlőben található tízezer tranzisztor (ez a szám az ATMega328-ra vonatkozik) órajel ütemére történő egyidejű kapcsolgatása okozza a vezérlő jelentősnek tekinthető néhány mA-es áramfelvételét. Ez az áramfelvétel nagyban függ a vezérlő órajelfrekvenciájától is. Ha tehát nem indokolt a 16Mhz órajel, pl. elég a belső 1Mhz-s RC oszcillátor a működéshez, akkor már ezzel is jelentősen csökkenthetjük az áramfelvételt. Ha pedig lekapcsoljuk az órajelet, akkor befagy a vezérlő működése, nincs átkapcsolás és nincs áramfelvétel.
Első lépésben ismerjük meg kicsit részletesebben az egyes üzemmódok működését
Tétlen üzemmód
A tételen üzemmód aktiválása, leállítja a CPU-t, de lehetővé teszi az SPI, USART, analóg komparátor, ADC, TWI interfész, időzítők, Watchdog, és a megszakítási rendszer működését. Ez az alvó üzemmód alapvetően leállítja a CPU és a flash memória órajelét, miközben a többi egység órajele továbbra is működik.
Az tétlen üzemmód lehetővé teszi, hogy a vezérlő felébredjen a külső és a belső megszakítások hatására, például az időzítő túlcsordulás és az USART átvitel kész megszakításokra. Ha nincs szükség az analóg komparátorral történő felébresztésre, az analóg komparátor kikapcsolható az analóg összehasonlító vezérlő és állapotregiszterében (ACSR) az ADC bittel. Ez tovább csökkenti az energiafogyasztást tétlen üzemmódban. Ha az ADC engedélyezve van, az üzemmódba való belépéskor automatikusan elindul egy átalakítás, és ha az átalakítás kész megszakítás engedélyezve van, akkor ez fel is ébreszti a vezérlőt.
ADC zajcsökkentési mód
Az ADC zajcsökkentési üzemmód célja nem az energia megtakarítás, hanem az, hogy az analóg átalakítás során csökkenjen a zaj, és ezzel pontosabb legyen a mérési eredmény. Ennek megfelelően az üzemmód leállítja a CPU-t, de lehetővé teszi az ADC, a külső megszakítások, a TWI interfész címegyezés, a TIMER2 (aszinkron módban külső órajelről) és a Watchdog működésének folytatását (ha engedélyezve van). Ez az alvó üzemmód alapvetően leállítja a I/O órajelet, a CPU órajelet és flash memória órajelét, miközben a többi egység órajele továbbra is működik.
Az órajelek kikapcsolása javítja az ADC zajkörnyezetét, és lehetővé teszi a nagyobb pontosságú méréseket. Ha az ADC engedélyezve van, az üzemmódba való belépéskor automatikusan elindul egy átalakítás, ami a mérés befejezésekor fel is ébreszti a vezérlőt (ha engedélyezve van az ADC átalakítás kész megszakítás). Az ADC átalakítás kész megszakításon kívül a külső reset, Watchdog reset, egy Watchdog megszakítás, BOD reset, TWI interfész címegyezés, TIMER2 megszakítások, SPM/EEPROM kész megszakítás, INT0 vagy INT1 külső megszakítás, port megszakítás (PIN change) képes felébreszteni az MCU-t az ADC zajcsökkentő módból.
Kikapcsolási mód
Ennek az alvási üzemmódnak a bekapcsolásakor vezérlő kikapcsolt állapotba lép. A külső reset, a TWI interfész cím figyelő és a watchdog továbbra is működik (ha engedélyezve van). Csak egy külső reset, vagy watchdog reset, vagy watchdog megszakítás, vagy BOD reset, vagy TWI interfész cím egyezés, vagy INT0 vagy INT1 külső megszakítás, vagy port megszakítás (PIN change) ébresztheti fel a vezérlőt. Ez az alvó üzemmód alapvetően leállítja az összes órajelet, csak az aszinkron modulok működnek tovább.
Megjegyzés: Ha egy INT0 vagy INT1 alacsony szint által kiváltott megszakítást használunk a felébresztéshez, a szükséges szintet elég hosszú ideig kell tartani ahhoz, hogy a vezérlő befejezze az ébresztést és bekövetkezzen a megszakítás is. Ha a szint eltűnik a megszakítás rutin meghívása előtt, a vezérlő felébred, de a megszakítás rutin nem kerül meghívásra. Az indítási időt a SUT és a CKSEL fuse bitek határozzák meg (további részletekért lásd az adatlapot).
A kikapcsolási módból való felébredéskor az ébresztési állapot késleltetve van, amíg az ébresztés hatályba nem lép. Ez lehetővé teszi, hogy az órajel újra induljon és stabillá váljon a leállítás után. Az ébresztési időszakot ugyanazok a CKSEL biztosíték bitek határozzák meg, amelyek meghatározzák a visszaállítási időtúllépési időszakot (további részletekért lásd az adatlapot).
Energiatakarékos mód
Ez az üzemmód megegyezik a kikapcsolással, egy kivétellel: ha a TIMER2 engedélyezve van, alvó állapotban is futni fog. Az eszköz fel tud ébredni az időzítő túlcsordulása vagy az TIMER2 összehasonlítás egyezés esemény után, ha a megfelelő TIMER2 megszakítást engedélyező bitek be vannak állítva a TIMSK2 regiszterben, és a globális megszakítás engedélyezve van SREG regiszterben („I” bit). TIMER2 részletes működése megtalálható itt! Ha az TIMER2 használatára nincs szükség, az energiatakarékos mód helyett a kikapcsolási mód ajánlott.
Készenléti üzemmód
Ez az üzemmód akkor használható, ha külső kvarc kristály adja az órajelet (fuse bitekkel ez lett kiválasztva). Ez az üzemmód megegyezik a kikapcsolással, azzal a kivétellel, hogy az oszcillátor folyamatosan működik. Készenléti állapotban a vezérlő hat órajelciklusban ébred fel.
Meghosszabbított készenléti mód
Ez az üzemmód akkor használható, ha külső kvarc kristály adja az órajelet (fuse bitekkel ez lett kiválasztva). Ez az üzemmód megegyezik az energiatakarékos üzemmóddal, azzal a kivétellel, hogy az oszcillátor folyamatosan működik. Meghosszabbított készenléti módból a készülék hat órajelciklusban ébred fel.
BOD letiltása
Amikor a Brown-out Detector (BOD) BODLEVEL fuse bitekkel engedélyezve van, a BOD aktívan figyeli a tápfeszültséget alvás közben. Az energiatakarékosság érdekében lehetőség van a BOD szoftverrel történő letiltására néhány alvó üzemmódban. Az alvó üzemmód energiafogyasztása ekkor ugyanazon a szinten lesz, mint amikor a BOD a fuse bitekkel globálisan letiltásra kerül. Ha a BOD szoftveresen le van tiltva, a BOD funkció azonnal kikapcsol az alvó üzemmódba lépéskor. Az alvó állapotból való felébredés után a BOD automatikusan újra működésbe lép. Ez biztosítja a biztonságos működést abban az esetben, ha a tápfeszültség közben csökken.
Ha a BOD le van tiltva, az alvó üzemmódból való felébredési idő körülbelül 60 μs lesz annak érdekében, hogy a BOD megfelelően működjön, mielőtt a vezérlő folytatja a kód végrehajtását.
A BOD-letiltást az MCUCR vezérlőregiszterben lehet elérni a 6. bit, a BODS (BOD Sleep) bittel. Ha ezt a bitet egyre állítjuk, a megfelelő alvó üzemmódokban kikapcsol a BOD, míg a nulla beírásával a BOD működni fog alvás közben is. Az alapértelmezett beállítás aktívan tartja a BOD-t, azaz a BODS bit nullára van állítva az MCUCR regiszterben.
Az alvó üzemmódba való lépést úgy lehet elérni, hogy első lépésben be kell állítani az SMCR regiszterben az SM2,SM1,SM0 bitek értékét az alábbi táblázat szerinti üzemmódra. Ezt követően az SE bitet egybe kell billenteni. Ezt követően lehet végrehajtani az assembly SLEEP utasítást.
Ha megszakítás történik, miközben a vezérlő alvó üzemmódban van, a vezérlő felébred. A vezérlő ezután az indítási időn kívül négy ciklusra leáll, végrehajtja a megszakítási rutint, és a SLEEP utáni utasítással folytatja a felhasználói programot. A regiszterek és az SRAM tartalma nem változik alvás közben. Ha alvó üzemmódban reset történik, a vezérlő felébred, és végrehajtja a reset folyamatot, és ezzel alaphelyzetbe állítja magát.
Az alábbi táblázat összefoglalja a hat különböző alvási üzemmód legfontosabb tulajdonságait:

Megjegyzések:
(1) Csak óraforrásként kiválasztott külső kristállyal vagy rezonátorral ajánlott.
(2) Ha a TIMER2 aszinkron módban fut (külső órajelről)
(3) INT1 és INT0 esetén csak szintmegszakítás.
(4) Aszinkron időzítő külső órajel bemenetre adott külső órajellel
(5) Lehetőség a Brownout Detector (BOD) modul szoftveres letiltására, ami tovább csökkenti az
áramfelvételt körülbelül 17 µA-rel.
Fogyasztás alvó üzemmódokban
Nem egyszerű megmondani, hogy melyik alvó üzemmódban mekkora áramfelvételre lehet számítani. Minden üzemmódban többféle funkciót lehet ki és bekapcsolni, így az áramfelvétel egy üzemmódon belül is többféle lehet. Már éppen elhatároztam, hogy megmérem a különböző esetekben az áramfelvételt, amikor találtam egy cikket a neten.
https://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
Az áramfelvételi adatokat a szerző egy Arduino Ultra Mini nevű alaplapon mérte 8Mhz órajellel és 3.3V tápfeszültséggel. Az Ultra Mini nekem nem ismerős, de Pro Mini alaplappal jómagam is rendelkezem, és a cikkben illetve (második részében) szereplő fotó erre nagyon hasonlít. Tehát ez kb. egy Pro mini alaplap lehetett, de itt az a fontos számunkra, hogy egy ATMega328 vezérlőről van szó. A cikk külön említi, hogy a BOD egység önmagában 20µA áramot fogyaszt, így azt mindenképpen érdemes legalább szoftveresen kikapcsolni.
Az alábbi táblázat legalább is irányadó egy valós feladat tervezésekor. A cikkíró részéről alkalmazott alacsony órajel oka egyrészt, hogy kisebb órajelen jelentősen csökken a vezérlő áramfelvétele, de alacsonyabb tápfeszültség esetén stabilabb az órajel generátor kisebb frekvencián, ezért is szokták csökkenteni. Az adatok nagyon értékesek, köszönet értük:

SLEEP mód regisztereinek részletei
Összesen három regiszter érintett az alvó üzemmódok bekapcsolásában:
- SMCR – üzemmód vezérlő regiszter, ebben lehet kiválasztani az elalvási üzemmódot, valamint egy bittel lehet engedélyezni az elalvás bekapcsolását. Ez utóbbi bitet a konkrét elalvás előtt be kell állítani.
- MCUCR – MCU vezérlő regiszter, ebben két bit érintett, ezekkel lehet a BOD-ot ki és bekapcsolni szoftveres úton (a fuse bitekkel teljesen kikapcsolható a BOD működése, bár pont az elemes táplálásnál nem biztos, hogy célszerű)
- PRR – teljesítéscsökkentési regiszter. Ebben a regiszterben adhatjuk meg egy-egy bittel az egyes ki és bekapcsolható funkciók működését alvás közben.
SMCR – Alvó üzemmód vezérlő regiszter
Az alvó üzemmód vezérlőregisztere vezérlőbiteket tartalmaz az energiagazdálkodáshoz.

- Bit [7:4]: Fenntartott bitek. Ezek a bitek nem használatosak az ATmega48A/PA/88A/PA/168A/PA/328/P vezérlőkben ben, és mindig nulla értéket képviselnek a regiszter kiolvasásakor.
- 3:1 bit – SM[2:0]: Alvó üzemmód. Ezek a bitek az öt rendelkezésre álló alvó üzemmód közül választhatnak, amit az alábbi. táblázat mutat:

- Bit 0 – SE: Alvó üzemmód engedélyezése. Az SE bitet egybe kell billenteni, hogy a vezérlő alvó üzemmódba lépjen a SLEEP utasítás végrehajtásakor. Annak elkerülése érdekében, hogy a vezérlő váratlanul alvó üzemmódba lépjen (hacsak nem ez a programozó célja) javasolt, hogy az SE bitet közvetlenül a SLEEP utasítás végrehajtása előtt billentse egybe a program, és ébredés után azonnal törölje azt.
MCUCR – MCU vezérlő regiszter

- 6. bit – BODS: BOD alvás. A BODS bitet egybe kell írni ahhoz, hogy alvás közben kikapcsoljuk a BOD-t. A BOD kikapcsolását egy időzített írási sorozattal, és a BODSE bit segítségével lehet végrehajtani. A BOD megfelelő alvó üzemmódban történő letiltásához először BODS és BODSE bitet egybe kell írni. Ezután a BODS bitet egybe, míg a BODSE bitet nullára kell írni négy órajelcikluson belül.
A BODS bit a beállítása után három órajelciklusig aktív. Azaz a tényleges SLEEP utasítást három órajel cikluson belül ki kell adni, különben a BODS bit automatikusan törlődik. - 5. bit – BODSE: BOD alvás engedélyezése. A BODSE lehetővé teszi a BODS vezérlőbit beállítását a BODS bit előzőekben leírt működése szerint.
A BODS és a BODSE bitek csak az ATmega48PA/88PA/168PA/328P picoPower eszközökhöz érhető el
PRR – Teljesítménycsökkentési regiszter

- 7. bit – PRTWI: TWI teljesítménycsökkentés. Ha egybe billentjük ezt a bitet, azzal leállítjuk a TWI-t órajelének kikapcsolásával. A TWI újbóli felébresztésekor a TWI-t újra kell inicializálni a megfelelő működés érdekében.
- 6. bit – PRTIM2: Teljesítménycsökkentő TIMER2. Ha ezt a bitet egybe írjuk, azzal szinkron módban leállítjuk a TIMER2 modult (AS2 0). Ha a TIMER2 engedélyezésre kerül, a működés ugyanúgy folytatódik, mint a leállítás előtt.
- 5. bit – PRTIM0: Teljesítménycsökkentő TIMER0. Ha ezt a bitet egybe billentük, az leállítja a TIMER0 modult. Ha a TIMER0 engedélyezésre kerül, a működés ugyanúgy folytatódik, mint a leállítás előtt.
- 4. bit – Fenntartva. Ez a bit fenntartott az ATmega48A/PA/88A/PA/168A/PA/328/P vezérlőkben, és mindig nullaként lesz olvasható.
- 3. bit – PRTIM1: Teljesítménycsökkentő TIMER1. Ha ezt a bitet egybe írjuk, azzal leállítjuk a TIMER1 modult. Ha a TIMER1 engedélyezésre kerül, a működés ugyanúgy folytatódik, mint a leállítás előtt.
- 2. bit – PRSPI: Teljesítménycsökkentő SPI interfész. Ha debugWIRE lapkára integrált hibakereső rendszert használ, ezt a bitet nem szabad egybe írni. Ha egybe írjuk ezt a bitet, azzal leállítjuk a SPI interfészt a órajelének kikapcsolásával. Az SPI újbóli felébresztésekor az SPI-t újra kell inicializálni a megfelelő működés biztosítása érdekében.
- 1. bit – PRUSART0: Teljesítménycsökkentés USART0. Ha ezt a bitet egybe írjuk, azzal leállítjuk az USART-ot a modul órajelének kikapcsolásával. Az USART újbóli felébresztésekor az USART-ot újra kell inicializálni a megfelelő működés biztosítása érdekében.
- 0. bit – PRADC: Teljesítménycsökkentő ADC. Ha ezt a bitet egybe írjuk, azzal leálljuk az ADC-t. Az ADC-t leállítás előtt le kell tiltani. Az analóg komparátor nem tudja használni az ADC bemeneti MUX-ot, ha az ADC le van állítva.
Alvó üzemmód életre keltése
A ha az alvó üzemmódot használni szeretnénk praktikus, ha előre megírt C vagy C++ függvényeket illetve makrókat használunk. Szerencsére a munka oroszlánrészét már az Arduino környezet megalkotói elvégezték helyettünk. Amikor az Arduino IDE fejlesztőt feltelepítjük, nagy mennyiségű előre megírt kódrészletet is megkapunk. Ezek lehetővé teszik számunkra az alvó üzemmód viszonylag egyszerű használatát. Ha kíváncsiak vagyunk a részletekre, akkor javaslom „sleep.h”, „power.h” kódok tanulmányozását. A saját gépemen ebben a könyvtárban találtam meg:
…AppData\Local\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include\avr
A három pont a felhasználóknak kijelölt könyvtárat jelképezi, ami ugyebár mindenkinél más de valahogy így kezdődik a gépen az útvonal: c:\users\…
Ha nagyon alaposan meg akarjuk érteni, akkor még szükség lehet az „io.h” állományra is (ugyanebben a könyvtárban), ami egy csomó definíciót tartalmaz, többek között a sleep módhoz tartozókat is. De mégsem az „io.h”, mert nem az tartalmazza a konkrét definíciókat, ezek vezérlőnként egyedi állományokban vannak. A kedvenc ATMega328P vezérlőre az „iom328p.h” állomány vonatkozik. Ebben vannak benne a kivezetések, megszakítások stb. definíciói. Ezek a definíciók mentenek meg attól, hogy fejből kelljen tudnunk, hogy pl. egy kivezetés melyik i/o regiszter melyik bitjére van kötve. A sleep módhoz tartozó definíciók így kezdődnek: „SLEEP_MODE_…”.
A „sleep.h” és „power.h” megértéshez éppen elegendő kommentet tartalmaz, azonban ezek elolvasása és értelmezése nem fehérembernek való feladat. Nem is kell ezzel foglalkoznunk, mert szerencsére találunk néhány kiegészítő könyvtárat, amit a szokásos módon a könyvtár kezelőben tudunk megkeresni és letölteni. De erről később, most szenvedjünk még egy kicsit!
Ha nem akarunk külső könyvtárat, akkor tanulmányozzuk kicsit a „sleep.h” állományt és írjuk meg első programunkat fapados technikával. Bevallom, az alábbi kódokat nem próbáltam ki, számomra csak a teljes megértéshez volt lényeges, mert az önkínzás eme módját hosszan nem szeretem gyakorolni. Így néz ki egy olyan kódrészlet, ami bekapcsolja az alvást:
#include <avr/sleep.h> #include <avr/io.h> ... set_sleep_mode(<mód>); sleep_mode(); // további utasítások, amik az ébredés tán hajtódnak végre
Mint látható, kell a sleep.h amit legelőször #include”-al be kell emelnünk a kódba. Nyilván az „#include…” sorok a forrás legelején helyezkednek el, míg a többi sor valahol a setup(-ban vagy loop()-ban. De remélem ez egyértelmű!
Szükség van az io.h-ra is, mert abban vannak a különböző sleep módok megnevezésének definíciói melyek így néznek ki:
#define SLEEP_MODE_IDLE (0x00<<1)
#define SLEEP_MODE_ADC (0x01<<1)
#define SLEEP_MODE_PWR_DOWN (0x02<<1)
#define SLEEP_MODE_PWR_SAVE (0x03<<1)
#define SLEEP_MODE_STANDBY (0x06<<1)
#define SLEEP_MODE_EXT_STANDBY (0x07<<1)
Ezek a sorok konkréten az „iom328p.h” file legvégén találhatók, amit az „io.h” hoz magával.
A többi sor már a sleep.h-ban előre megírt makró. Első lépésben bekapcsoljuk a kiválasztott sleep módot. A „<mód>” helyére a megfelelő definiciót kell beírni, a fentiek közül pl. „SLEEP_MODE_IDLE”.
Ezt követi az alvás tényleges bekapcsolása a „sleep_mode()” sorral. Ez a makró automatikusan beállítja az alvó állapotot engedélyező bitet, megy alvó állapotba, és törli az alvó állapotot engedélyező bitet. Elriasztásként bemásolom a set_sleep_mode() és a sleep_mode() sorok mögötti makrókat a „sleep.h”-ból. Bár függvénynek látszanak, valójában a fordító ezeket a sorokat az alábbi „szövegre” fogja kicserélni, amit aztán a fordításkor már utasításokként fog értelmezni.
#define set_sleep_mode(mode) do { _SLEEP_CONTROL_REG = ((_SLEEP_CONTROL_REG & ~_BV(SM)) | (mode)); } while(0)
Illetve
#define sleep_mode() do { sleep_enable(); sleep_cpu(); sleep_disable(); } while (0)
Ahol a do{}-ban lévő sorok további makrók pl.:
#define sleep_enable() do { _SLEEP_CONTROL_REG |= (uint8_t)_SLEEP_ENABLE_MASK; } while(0)
illetve a konkrét alvás bekapcsolás:
#define sleep_cpu() do { __asm__ __volatile__ ( "sleep" "\n\t" :: ); } while(0)
A sleep_cpu() definíció egy gépikódú programrészletet illeszt a forrásunkba, ez a konkrét alvás üzemmód bekapcsolás.
Mivel nem követtem végig a teljes #define… láncolatot, a beidézett forrásrészletek minden bizonnyal hiányosak és lehetnek félreértelmezéseim is. Azonban a lényeget vélhetőleg sikerülhet megérteni ebből. Ennyi gyötrődés nekem elég volt, aki ezt szeretné teljesen átlátni, az járjon utána. Bocsi!
Van azonban a sleep mód bekapcsolását végző kódrészletünkkel (ezzel kezdtük pár sorral feljebb) egy nagy hiányosság. Ugyanis nem gondoskodtunk a sleep mód kikapcsolásáról, azaz az ébresztés mikéntjéről. A kód alapján az alvásból csak a reset fogja felébreszteni a vezérlőt. Ha azonban ezen kódrészlet előtt engedélyezzük valamelyik megszakítást pl. a egyik külső megszakítást (INT0 vagy INT1), és meg is történik a megszakítás (pl. INT0 alacsony jelszintet kap), akkor a vezérlő felébred és a sleep_mode() utáni sorral folytatódik a program.
Ennek a kódnak más hiányossága is van. Ugyanis a megszakítások versenyhelyzetet okozhatnak az alvás bekapcsolása közben, amikor a SM bittel engedélyeztük az alvást, és még nem kapcsoltuk be az alvó üzemmódot. Ezért egy kicsit kifinomultabb utasítás sorozatra van szükség:
#include <avr/interrup.h> #include <avr/sleep.h> ... set_sleep_mode(<mód>); cli(); sleep_enable(); SEI(); sleep_cpu(); sleep_disable();
A fenti példában a cli() globálisan tiltja az összes megszakítást. Ezt követően a sleep_enable() engedélyezi az alvást az SM bittel, majd a SEI() engedélyezi a megszakításokat, de következő utasítássl rögtön elaltatja a vezérlőt. Mivel a megszakításokat engedélyeztük, a vezérlő fel fog ébredni, és a sleep_disable() sorral folytatjuk a végrehajtást, ami egyben tiltja is az alvást az SE bit törlésével. Így kerek a történet!
Jelentősen csökkenthetjük az áramfelvételt, ha letiltjuk a BOD áramkört. Ennek oka, hogy a BOD tartalmaz egy feszültség referenciát, ami ugyebár árammal működik. Valamint működnie kell az analóg komparátornak is.
A BOD kikapcsolását (ez a fentebb említett szoftveres kikapcsolás) a „sleep_bod_disable()” sorral lehet kikapcsolni. Mint az alábbi kódrészletből látható, ezt a sort szintén a globális megszakítások tiltása után kell elhelyezni.
#include <avr/interrupt.h> #include <avr/sleep.h> ... set_sleep_mode(<mód>); cli(); sleep_enable(); sleep_bod_disable(); SEI(); sleep_cpu(); sleep_disable();
Bár a fenti kódrészletek elrejtették előlünk a konkrét regiszter műveleteket, a sleep.h és a power.h tanulmányozásával ezek kideríthetők.
LOW-POWER könyvtárak
A többesszám azért indokolt, mert ha a könyvtárkezelőben rákeresünk, több hasonló nevű függvényosztályt is találunk a témakörben . Bár nem néztem át tüzetesen, azt tapasztaltam, hogy ezek működése és szintakszisa azonos. Ezen szeretnék most néhány egyszerű példával átmenni a teljesség igénye nélkül. Én a könyvtárak közül a következőt találtam meg:
name=Low-Power
version=1.81
author=Rocket Scream Electronics
Azt hiszem olvastam valahol egy ismertetőt, és abban volt belinkelve a github elérése. Furcsa módon értelmes leírást nem találtam hozzá, lényegében a kapott forrásokat kell nézegetni és kitalálni, hogyan is kell használni. Persze vannak mintapéldák, és a forrásokban találunk kommenteket, de a megismeréshez bele kell nézni a könyvtárban található LowPower.cpp állományba, ami így nem teljesen komfortos. Főleg egy kezdőnek, aki még nem is igazán látja át, hogy mit és hol keressen. Egyébként úgy tapasztaltam, hogy ez általában jellemző a legtöbb függvénykönyvtárra. De az is lehet, hogy én vagyok béna, és nem találom a leírásokat. Mindegy, kitalálható a működés, és végül is jól használható.
Nem is teszek mást, mint beidézem a függvényekhez készített kommenteket kicsit más csoportosításban. Nyilván csak azokat a kommenteket emeltem ki, melyek az ATMega328 vezérlőhöz készült függvények (illetve metódusok) előtt találhatók. Valamint a komment alá beteszek egy függvény hívási példát a kommentekben használt paraméter megnevezésekkel.

A különböző üzemmódok bekapcsolását az alább részletezett metódusokkal tudjuk használni. Minden egyes metódusnak azonos logikával felépített paraméterei vannak. Mint alább látni fogjuk az első paraméter határozza meg a felébredés módját. Kétféle módon ébreszthetjük fel a vezérlőt a paraméterekkel. Használhatjuk a watchdog megszakítást. Ekkor elég sokféle időértéket adhatunk meg, és a paraméter által meghatározott idő leteltekor a vezérlő magától felébred. Pl a SLEEP_8S paraméter megadásakor kb. 8 másodperc múlva. Ne felejtsük el, hogy a vezérlő ekkor a belső RC oszcillátort használja, tehát a 8 másodperc csak körülbelüli érték. A másik lehetséges ébredést a SLEEP_FOREVER paraméterrel választhatjuk ki. Ekkor nem a wtchdog ébreszt, hanem valamilyen megszakítás. Nyilvánvaló, hogy előre meg kell határoznunk, hogy melyik megszakítás ébreszti a vezérlőt, és azt a megszakítást be is kell kapcsolni. Minden alvási üzemmódan használhatjuk pl. a külső megszakításokat. Ne felejtsük el, hogy csak alacsony szinttel lehet ébreszteni. Egy hosszú ideig tartó alacsony szint viszont folyamatos megszakításokat generál, így a megszakítás rutinban első dolgunk legyen a külső megszakítások kikapcsolása.
Használhatjuk még valamelyik TIMER egységet is az ébresztéshez, de ez már nem áll rendelkezésre minden alvási üzemmódan. Az alábbi metódusok paramétereiből egyértelműen ki fog derülni, hogy hol.
1. Tétlen üzemmód
Tétlen módot kell használnunk, ha szeretnénk az alvó vezérlőt tetszőleges TIMER megszakításaival, ADC átalakítás kész megszakítással, illetve SPI adat érkezés és TWI cím egyezéssel felébreszteni.
Az alvó üzemmód indítása:
LowPower.idle(period, adc, timer2, timer1, timer0, spi, twi)
A paraméterek lehetséges értékei a fenti táblázatban megadottak lehetnek.
2. ADC zajcsökkentési mód
Ez az alvó mód nagyon hasznos az ADC használatakor, ha minél pontosabb mérési eredményt szeretnénk elérni, mert a vezérlő alvása csökkenti a digitálisan keletkező mérési zajt.
Ennél az üzemmódnál a LowPower függvényosztály lehetővé teszi az ADC és a TIMER2 ki és bekapcsolását, de valójában az ADC kikapcsolása értelmetlen, hiszen pont az ADC miatt érdemes használni. Az alvás előtt be kell kapcsolni az ADC átalakítás kész megszakítást, és el kell indítani a mérést. Az ADC átalakítás kész megszakítás fogja felébreszteni a vezérlőt.
Az alvó üzemmód indítása:
LowPower.adcNoiseReduction(period, adc, timer2);
A paraméterek lehetséges értékei a fenti táblázatban megadottak lehetnek. Nyilván a period paraméterben a SLEEP_FOREVER értéket kell megadni, ha az átalakítás végéig szeretnénk az alvó állapotot.
3. Kikapcsolási mód
Ez az üzemmód teszi lehetővé a legalacsonyabb áramfelvételt. Minden egységet ki kell ehhez kapcsolni amit a függvényosztály lehetővé tesz (adc, bod). A period paraméterben adjuk meg a SLEEP_FOREVER értéket, és ekkor az INT0 vagy INT1 külső megszakítási bemenetre kapcsolt alacsony szint ébreszti a vezérlőt.
Az alvó üzemmód indítása:
LowPower.powerDown(period, adc, bod)
A paraméterek lehetséges értékei a fenti táblázatban megadottak lehetnek.
4. Energiatakarékos mód
Ez az üzemmód is lehetővé teszi a lehető legalacsonyabb áramfelvételt, ha minden lehetséges egységet kikapcsolunk. Ha kikapcsoljuk a TIMER2 időzítőt is, (timer2 paraméter értéke TIMER2_OFF), az időzítő még működhet egy külső 32,768 kHz-es kristállyal (8/16 MHz-es külső kristályt el kell távolítani). Ez a külső aszinkron órajelet biztosít a TIMER2 működéséhez. Figyelni kell ekkor azonban arra, hogy a TIMER2-t PWM előállításra is használhatja a vezérlő, ami ekkor nem a megszokottak szerint fog működni. A külső kristály eltávolítása esetén a vezérlőnek a belső RC oszcillátorral kell működnie, ami nem olyan pontos az időkritikus működéshez.
Az alvó üzemmód indítása:
LowPower.powerSave(period, adc, bod, timer2);
A paraméterek lehetséges értékei a fenti táblázatban megadottak lehetnek.
5. Készenléti mód
Ez az üzemmód csak külső kristály esetén használható (fuse bitekkel ez lett kiválasztva). Ez az üzemmód megegyezik a kikapcsolással, azzal a kivétellel, hogy az oszcillátor folyamatosan működik.
Az alvó üzemmód indítása:
LowPower.powerStandby(period, adc, bod);
A paraméterek lehetséges értékei a fenti táblázatban megadottak lehetnek.
6. Meghosszabbított készenléti mód
Ez az üzemmód csak külső kristály esetén használható (fuse bitekkel ez lett kiválasztva). Ez az üzemmód megegyezik az energiatakarékos üzemmóddal, azzal a kivétellel, hogy az oszcillátor folyamatosan működik. Az üzemmód képes a TIMER2 időzítő aszinkron futtatására. Nincs implementálva az Atmega88-on és az Atmega168-on.
Az alvó üzemmód indítása:
LowPower.powerExtStandby(period, adc, bod, timer2);
A paraméterek lehetséges értékei a fenti táblázatban megadottak lehetnek.
És most lássunk egy példa programot. Mint említettem, szeretnék egy vízfogyasztás mérőt készíteni, ami akkumulátorról fog működni, és ezért kell az alvó üzemmód. A vízfogyasztásmérő egy lapátkerék egy csőben, amit a víz áramlása forgásba hoz, és minden fordulatnál egy mágnese HALL érzékelő jelet ad. Az első elképzelésen az volt, hogy a HALL érzékelő jelét az egyik külső megszakítás bemenetre kapcsolom, és a vezérlő csak akkor ébred fel, amikor az alacsony jelszintet ad ki magából, egyébként aluszik. Sajnos a külső megszakítás csak alacsony jelszintre ébreszti a vezérlőt, és amíg a jel alacsony folyamatosan megszakítások generálódnak. Végül más módon fogom megírni a programot, de ez a valós feladat tanulságos példa programot eredményezett, amivel sokat küzdöttem. Így most ezt hoznám fel példának.
A példa program lényege, hogy a vezérlő elalszik „SLEEP_FOREVER paraméterrel, amikor magas jelszintet ad a HALL érzékelő. Amikor lapátkerék forogni kezd (mert kinyitottuk a zuhany csapját) és megérkezik az alacsony jelszint, megszakítás generálódik, ami felébreszti a vezérlőt. Ébredéskor azonnal ki is kapcsoljuk a külső megszakításokat. Ekkor figyelni kell, hogy mikor lesz újra magas jelszint a bemeneten, mert akkor lehet újra engedélyezni a külső megszakítást, és elaltatni a vezérlő „SLEE_FOREVER”-el. De mit csinálunk, amíg a jelszint alacsony? Addig legyen ébren a vezérlő? Nem feltétlenül, mert megoldhatjuk úgy is, hogy elaltatjuk a vezérlőt pl. 15millisec időre, és watchdog megszakítással felébresztjük. Ha ekkor még mindig alacsony a jelszint a bemeneten, akkor újra elaltatjuk a vezérlőt 15millisec időre, és ezt mindaddig megtesszük ciklikusan amíg a bemenet magas jelszintre kerül. Ekkor jöhet a SLEEPFO_REVER és a külső megszakítás engedélyezése.
A példa programhoz kell némi hardver. Egy nyomógomb és egy led. A nyomógombot lehet nyomkodni, és minden ötödik lenyomáskor a program rövid időre felvillantja a led-et. Közben a vezérlő alszik. Csak arra az időre ébred fel, amíg ellenőrzi, hogy nyomva van-e még a nyomógomb, illetve a led felvillanásának ideje alatt is működik. Még itt is lehetne spórolni, hiszen amíg a led világít, a vezérlőt el lehet altatni, de most nem ez volt a lényeg.
// Ez a program megszámolja a nyomógomb megnyomásokat (D2 bemenet), és minden ötödiknél felvillantja a D13-ra kötött led-et 50msec-re. // A vezérlő a köztes időkben alszik, és a lehető legkisebb fogyasztást produkálja. #include "LowPower.h" int i = 0; //gombnyomások száma, ha elérte az 5-öt akkor felvillan a led, és i törlődik void setup() { pinMode(2,INPUT_PULLUP); // A D2 bemenet, és erre kötük a nyomógombot. Megnyomáskor alacsony szint kerül a bemenetre pinMode(13,OUTPUT); // A D13 kimenet, erre kötjük a led-et digitalWrite(13,LOW); // Most a led nem világít } void loop() { attachInterrupt(0, IntD2, LOW); //Interrupt bekapcsolása D2 bemenet alacsony szintje esetén //(csak az alacsony szint képes felébreszteni alvásból a vezérlőt) LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); // Vezérlőt alvásba küldjük, ADC és BOD kikapcsolva // Csak megszakítás képes felébreszteni, jelen esetben csak a D2 // bementre kötött alacsony szint okoz megszakítást, ai fel is tudja // ébreszteni a vezérlőt detachInterrupt(0); //felébredt a vezérlő, kikapcsoljuk a D2 bemenet interruptját, különben sorozatosan //hívja tovább az "IntD2" megszakítás rutint i++; // növeljük a nyomógomb megnyomások számlálóját // Ez a ciklus addig fut, amíg nyomva tartja a nyomógombot. Ha alacsony szint alatt engedélyeznénk a külső // megszakítást, akkor azonnal megszakítás keletkezne, így meg kell várnunk, míg elengedik a nyomógombot // és a D2 bemeneten magas sint lesz. A ciklus addig vár, amíg a magas szint megérkezik. while (digitalRead(2)==LOW) { // A D2 még alacsony, tehát még nyomjuk a nyomógombot, belemegyünk a ciklusba LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF); // Elaltatjuk a vezérlőt 15msec időre. Amikor felébred a vezérlő //a ciklus feltételét fogjuk ellenőrizni, és ha közben már a D2 magas, // akkor nem megyünk bele a ciklusba. } // Mielőtt újra eleltatju a vezérlőt a nyomógomb lenyomásáig, megnézzük, hányszor lett lenyomva a gomb. if (i==5) { // Ez pot az ötödik lenyomás (törölni kell i-t és felvillantani a led-et. i=0; digitalWrite(13,HIGH); delay(50); digitalWrite(13,LOW); } }
Hamarosan áramfelvételi adatokkal is rendelkezni fogok. Éppen gigászi közdelem zajlik, hogy dugdosós panelen egy ATmega328P vezérlőből összerakjam a kapcsolást, legyen külön tápegységem és megfelelő árammérőm. Mindez már megvolt egyszer, de az idő vasfoga megette a az eszközeimet, már semmi nem működik immár. Pár hét és elkészülök, közzéteszem a mérési adatokat is!
Nagy nehezen sikerült értékelhető adatokat összeszednem! Első lépésben írtam egy programot, aminek a loop()-jában nincs semmi. Ez az áramfelvétel jó lesz referenciának. 13.7mA lett az üresjárati áramfelvétel 16Mhz külső kvarc órajellel. Amikor a nyomógomb nincs lenyomott állapotban, azaz a vezérlő „powerdown” (kikapcsolás) módban alszik, és minden lehetséges funkció ki van kapcsolva, az áramfelvétel 57uA. Ez közelében sincs a beidézett weboldalon olvasott 1.7uA áramfelvételnek. Azonban nem teljesen azonosak a körülmények. Vezérlőm 5V feszültségről és 16Mhz órajel frekvenciával ketyeg. Ha megnyomom, és nyomva tartom a nyomógombot, akkor ugyebár 15msec időnként felébred egy pillanatra a vezérlő, kicsit adminisztrál, aztán alszik. Ekkor az áramfelvétel 510uA. Tételezzük fel, hogy egy igen gyenge, 800mAh lítium akkumulátoról üzemel kapcsolásom. Tegyük fel, hogy a vízfogyasztásmérőm hall eleme folyamatosan jelet ad, mert így sikerült megállnia. Tételezzük fel, hogy Hawaii-ra utaztam. Meddig lehetek ott ha nem akarom hogy lemerüljön az akkumulátorom, amit persze elutazáskor teljesen feltöltöttem? Hát számoljuk ki! 800mAh / 0.28mA= 1509óra ami 63nap, ami kb. 2 hónap. Azt hiszem biztonsággal eltölthetek legalább két hónapot, és lesz időn megnézni Pearl Harbor-ban az Arizona csatahajó roncsait!
Rájöttem, hogy a fogyasztás csökkentése érdekében még tehetek valamit! Beállítottam a belső 8Mhz-s oszcillátort az ATmega328 vezérlőmnek. Hogy tuti legyen a dolog ki is vettem a kvarc kristályt, plusz a setup részbe raktam egy kis led villogtatást, hogy lássam tényleg működik. Első lépésben feltöltöttem azt a programot, aminek a loop()-jában nincs semmi. 8.8mA lett az „üresjárati” áramfelvétel 8Mhz belső órajellel. Ezt követően feltöltöttem a fenti példaprogramot. Alvásban (mikor nincs nyomva a nyomógomb) 56uA áramfelvételt jelzett a műszerem. Ha nyomtam a nyomógombot, akkor az áramfelvétel 208uA lett, ami a 15msec időnként történő watchdog felébresztés miatt nekem reálisnak tűnik, tekintve hogy az órajel a felére csökkent.
Még mielőtt szétszedtem a kapcsolást, eszembe jutott, hogy megnézem 1Mhz belső órajelnél is az áramfelvételt. Az üres loop() ciklus alvás nélkül 5.7mA, alváskor (INT0 bemenet ébreszt) 56uA, és végül alváskor ahol 15msec-ként a watchdog ébreszt 240uA. A watchdog ébresztéssel valószínűleg azért nőhetett az áramfelvétel, mert 15sec-kénti ébresztés adminisztrációját jóval hosszabb idő alatt képes megcsinálni a vezérlő, így relatíve többet van ébren, és ezt az órajelfrekvencia csökkenése már nem képes kompenzálni.
Szeretem a táblázatokat, így készítettem egy összefoglalót a mérési eredményekkel:
