Přesné měření spotřeby v řádu jednotek uA domácími prostředky není úplně triviální. Ručičkové měřicí přístroje sice díky mechanické setrvačnosti umí "průměrovat" divokou časovou závislost spotřeby jednočipu, ale jen v případě že se ona divokost odehrává řádově několikrát během sekundy. Situaci kdy odběr rapidně vzroste třeba jednou za několik sekund už nezaznamenají. A i kdyby ji zaznamenali, vy ji nebudete moct ze stupnice odečíst, protože ručička jen na okamžik "cukne" a to bude vše. Běžné digitální multimetry na tom budou ještě hůř. Jak tedy alespoň porovnávat naše micropower řešení aniž bychom museli stavět měřicí aparaturu ? Stačí na to jít z druhého konce. Zajímá nás přece "výdrž" z baterie. Stačí tedy najít baterii s dostatečně malou kapacitou a měřit za jak dlouho ji naše aplikace vybije. A co je taková malá baterka ? No přece kondenzátor. Vzhledem k velmi malým proudům, které naše aplikace vyžadují nám na testy stačí běžné elektrolytické kondenzátory. Jak nakonec uvidíte, i malé superkondenzátory mají tak velkou kapacitu, že by naše zařízení mohli živit klidně celý den. Pojďme si tedy hrát.
Situace vybízí k tomu abychom náš příklad postavili jako šablonu pro budoucí aplikace. Mnoha micropower aplikacím stačí k provozu malý solární článek a lithiový akumulátor. Solární články s provozním napětím mezi 5-6V jsou v číně k dostání za pár korun. Lithiové články se dají také koupit za rozumné peníze ale z číny bych je asi nesháněl. Snadno tedy sestavíte systém Solár-Akumulátor-Atmel, ve kterém je potřeba nějak řídit nabíjení. Můžete si opatřit "nabíjecí" modulky z číny (například s čipem TP4056), ale jejich spotřeba (i když odpájíte LEDky) převyšuje spotřebu jednočipu. Pro úsporu energie je tedy vhodnější ponechat řízení nabíjení na jednočipu. Jak ale řídit malý solární panel když jeho napětí ve vypnutém stavu překračuje napětí jednočipu ? Jako první se nabízí tradiční cesta pomocí P-FETu, pullup rezistoru a spínacího NPN nebo N-FETu (rámcově popsáno v tutoriálu o GPIO). V našem specifickém případě (solární článek s malým výkonem) si můžeme dovolit udělat jednoduchou fintu a vše vyřídit jediným N-FETem. Stačí solární článek k baterce připojit přes diodu a pomocí tranzistoru ho zkratovat v době kdy nechceme nabíjet. Podívejte se na schema.
Výstupem PB0 můžeme sepnout tranzistor Q2 a zkratovat tím solární panel. Tranzistor 2N7000 není úplně ideální pro tuto roli, ale je levný a dobře k dostání. Naše aplikace bude otevírat tranzistor (a zkratovat solárko) jen v případě vyššího napětí. To nám hraje do karet, protože vyšší napětí potřebujeme na spolehlivé otevření FETu. Otevřený 2N7000 má dle datasheetu při 4.5V napětí na gate a zatěžovacím proudu 75mA odpor kanálu pod 6Ohm. To je velmi blízko situaci z naší aplikace. My ho plánujeme otevírat přibližně při 4.2V (nabitý lithiový článek). Naše solárko bude mít sice menší proudy (řádově do 50mA), ale i tak bude ztrátový výkon na tranzistoru v nejhorším případě okolo 6*0,075^2 = 35mW. Solární článek na prázdno dává napětí pod 7V, takže ani napětím bychom neměli tranzistor poškodit. Dále si ve schematu všimněte kondenzátoru C4, ten zaujímá roli akumulátoru. Pro testovací účely si osciloskop připojíme na oba výstupy jednočipu (PB0 i PB1) a ještě navíc na napájecí napětí. I když naše aplikace nevyžaduje přesný čas, nechám ji vybavenou hodinovým krystalem (Q1) a využiji Timer 2 k pravidelnému probouzení. Kdo by měl nouzi o vývody, může krystal vynechat a ukázku si upravit s pomocí watchdogu (viz předchozí díl). A teď pojďme zformulovat co má naše aplikace přesně dělat.
Jednočipu dáme za úkol monitorovat 4x za vteřinu napájecí napětí. Při překročení horní meze (4.2V) vypneme nabíjení (otevřeme Q2). Pokud napětí klesne pod dolní mez (3.0V), budeme tento stav signalizovat log.1 na PB1. Reálná aplikace by pak měla v tomto bodě vypnout všechny zbytečné funkce a maximálně šetřit energií. V naší aplikaci nám tato signalizace umožní spočítat průměrný odběr. Asynchronní Timer2 s krystalem 32.768kHz bude vytvářet časovou základnu (4Hz) a periodicky nás bude budit ze hlubokého spánku (Power-Save). Po probuzení zapneme AD převodník a nastavíme mu referenci AVCC (tedy napájecí napětí čipu - napětí kondenzátoru) a jako vstup interní Bandgap referenci (přibližně 1.1V), které dáme jistý čas na stabilizaci. Z výsledku měření vnitřní reference je možné dopočítat napájecí napětí (více o tom v tutoriálu o ADC). Pro jistotu AD převod provedeme 4krát a zprůměrujeme. Protože během převodu nemá čip nic na práci, uspíme ho. Jako režim spánku zvolíme "ADC noise reduction", protože spoří energii stejně jako Idle režim a navíc ještě snižuje rušení a zlepšuje přesnost (teoreticky, nikdy jsem žádný pozitivní dopad nepozoroval). Když práci dokončíme, zkontrolujeme zda se napájecí napětí nachází v povoleném pásmu, povypínáme periferie a usneme opět hlubokým spánkem.
// Low-power prakticky - hlídáme nabíjení (Atmega328P, fuses H:0xD1 L:0x62) #define F_CPU 1000000 #include <avr/io.h> #include <util/delay.h> // protože používáme delay #include <avr/interrupt.h> // kvůli přerušením #include <avr/power.h> // k zapínání/vypínání periferií #include <avr/sleep.h> // funkce režimu spánku #define VREF 1.099 // Volt - napětí interní reference (změřeno) #define PREPETI 4.2 // Volt - napětí při kterém chceme ukončit nabíjení #define PODPETI 3.0 // Volt - kritické napětí při kterém musíme vypnout zbytné spotřebiče #define HLIMIT (uint16_t)(round(1024*VREF/PODPETI)) // překročení této hodnoty značí podpětí baterie #define LLIMIT (uint16_t)(round(1024*VREF/PREPETI)) // pokles pod tuto hodnotu značí přepětí baterie // řidí nabíjení kondenzátoru/akumulátoru (zkratuje solární panel) #define DOBIJENI_ON PORTB &=~(1<<PORTB0) #define DOBIJENI_OFF PORTB |= (1<<PORTB0) // indikuje kritické vybití #define KRITICKY_STAV_ON PORTB |= (1<<PORTB1) #define KRITICKY_STAV_OFF PORTB &=~(1<<PORTB1) void live_check(void); void init_timer2(void); int main(void){ ACSR |= (1 << ACD); DDRB |= (1<<DDB0) | (1<<DDB1); DOBIJENI_OFF; power_all_disable(); // vypneme všechny periferie power_timer2_enable(); // krom timeru 2 ACSR |= (1<<ACD); // vypnout komparátor init_timer2(); // asynchronní timer2 nás bude budit z hlubokého spánku sei(); // povolit přerušení while (1) { set_sleep_mode(SLEEP_MODE_PWR_SAVE); // chceme spát hluboce sleep_mode(); // uspí čip live_check(); // změří napájecí napětí a vyhodnotí jeho stav } } ISR(TIMER2_COMPA_vect){ asm("nop"); // budíme se z hlubokého spánku, je čas změřit napájecí napětí } ISR(ADC_vect){ asm("nop"); // jen se probudíme - převod dokončen } void init_timer2(void){ ASSR = (1<<AS2); // přepínám časovač 2 do asynchronního režimu OCR2A = 63; // budeme generovat 4Hz (f_timeru/4Hz - 1 = 256/4 - 1 = 63) TCCR2A = (1<<WGM21); // CTC režim se stropem OCR1A TCCR2B = (1<<CS20) | (1<<CS22); // spustit timer 2 s clockem 32.768kHz/128 = 256Hz // počkat až se konfigurace zapíše do registrů timeru (!!!) while((ASSR & (1<<OCR2AUB)) || (ASSR & (1<<TCR2AUB)) || (ASSR & (1<<TCR2BUB))){}; TIFR2 = (1<<OCF2A); // vyčistit vlajku TIMSK2 |= (1<<OCIE2A); // povolit přerušení od compare události (stropu) } void live_check(void){ uint16_t tmp=0; // tady budeme sčítat výsledky převodu uint8_t i; power_adc_enable(); // zapnout ADC // konfigurace ADC, reference AVCC, vstup 1.1V bandgap ADMUX = (1<<REFS0) | 0b1110; // Prescaler /4 (1MHz/4 = 250kHz pro ADC), povolit přerušení, vyčisti vlajku, spustit ADC ADCSRA = (1<<ADPS1) | (1<<ADIE) | (1<<ADIF) | (1<<ADEN); // připravíme se na "ADC noise reduction" spánek set_sleep_mode(SLEEP_MODE_ADC); _delay_us(15); // první rozběh interní reference vyžaduje nějaký čas na stabilizaci // průměrujeme výsledek ze 4 měření for(i=0;i<4;i++){ sleep_mode(); // uspáním se automaticky spustí AD převod tmp = tmp+ADC; //.. jakmile se probudíme akumulujeme výsledek převodu } tmp=tmp>>2; // dokončíme průměrování (dělení 4mi) // zkontrolujeme zda je napájecí napětí v kritickém pásmu if(tmp>HLIMIT){ KRITICKY_STAV_ON; // máme podpětí, vypneme všechny nezbytné funkce (já žádné nemám) } else{ KRITICKY_STAV_OFF; // nemáme podpětí, funkce mohou běžet } if(tmp<LLIMIT){ DOBIJENI_OFF; // máme přepětí, vypneme nabíjení ze solárka } else{ DOBIJENI_ON; // nemáme přepětí, můžeme nabíjet } ADCSRA = 0; // vypneme ADC power_adc_disable(); // odpojíme ho od šťávy }
První test naší aplikace provedeme se zakrytým solárním panelem. Otevřením tranzistoru Q2 nám dá aplikace vědět, že napětí kleslo pod 4.2V a začneme měřit čas. Na PB1 se pak dozvím o poklesu napětí na 3V a měření času zastavíme. Z poklesu napětí dU=1.2V, kapacity kondenzátoru 3300uF+-20% a času dt se dá snadno dopočítat odběr jako I=C*dU/dt. Naše aplikace vydržela 19min 18s, její průměrný odběr je tedy I=3300e-6*1.2/1158=3.4uA (2.7 ~ 4.1uA se započtením tolerance kondenzátoru). Doba je to bohužel tak dlouhá že jsem ji celou nebyl schopen zachytit ani na pomaloběžný režim osciloskopu. Pro ilustraci si tedy můžete začátek a konec děje prohlédnou na oscilogramech níže.
Průběh nabíjení solárním panelem (za zavřenými žaluziemi) si můžete prohlédnou na posledním oscilogramu. Vidíte, že aplikace indikuje kritické vybití na pinu PB1, jakmile napětí dosáhne 3V, kritická podmínka mizí a nabíjení probíhá až ke 4.2V, tam aplikace střídavě otevírá Q2 a zastavuje tak nabíjení. Poté se ustaví rovnováha. Aplikace vždy na jeden cyklus (čtvrtina sekundy) zahájí nabíjení (log.0 na PB0), za tuto čtvrtinu sekundy napětí dostatečně přeroste 4.2V a několik cyklů pak aplikace nenabíjí (log.1 na PB0). Dynamika tohoto děje závisí na kapacitě kondenzátoru a intenzity světla na solárním článku. Při intenzivním osvětlení se za čtvrtinu sekundy kondenzátor nabije vysoko nad 4.2V (třeba na 5V) a pak trvá několik minut než napětí klesne aby se nabíjení zahájilo znovu. Tímto problémem přirozeně nebude trpět baterie. Ta bude mít řádově větší kapacitu a solární článek s jejím napětím během zlomku vteřiny pohne jen minimálně. Takto sestavený pokus tedy skýtá jisté riziko, nastavíme-li čas mezi měřením dlouhý, kapacita kondenzátoru bude dost nízká a proud dodávaný solárkem dost vysoký, může se stát, že se kondenzátor během intervalu spánku přebije nad 6V, což Atmel nemusí přežít. Ale protože jde jen o pokus, byl jsem ochoten toto riziko podstoupit :) A můžu vás ujistit že Atmel přežil bez úhony. K aplikaci bych rád dodal ještě jednu poznámku. V systémech které běží bez zdroje energie (solárka a pod.), tedy takových které běží pouze z akumulátoru by možná stálo za to dodat ještě jednu funkci. Detekovat podpětí například 2.8V, tedy stav kdy už jde o život akumulátoru a po dosažení tohoto stavu vypnout Atmel definitivně. Tedy vypnout Timer2 a všechny periferie a poslat čip do hlubokého spánku ze kterého už se neprobudí (až vnějším zásahem jako třeba tlačítkem na externím přerušení nebo resetem). Tím by měl odběr spadnout na zlomek uA a dát akumulátoru větší šanci na přežití. Než vše uzavřeme, nezapomeňte na to, že přesnost měření napětí se odvíjí od přesnosti interní reference a je otázkou jak bude kolísat třeba se změnou vnější teploty. Jen rychle nastíním, že vzroste-li o 0.5% (55mV) zvýší se naše hladina 4.2V o 1.9% na 4.28V (viz tutoriál o ADC) !
Doufám, že jste získali přehled jak řešit jednoduché "micro-power" aplikace a doufám, že tento příklad některým z vás poslouží třeba jako šablona do dalších aplikací. Já jsem byl celkem mile překvapen jak nízkého odběru se nakonec podařilo dosáhnout. Konec konců z ideálního akumulátoru s kapacitou 2000mAh by naše nic nedělající aplikace běžela pouhých 57 let ...
Home
V1.0 28.8.2018
By Michal Dudka (m.dudka@seznam.cz)