V "arduino" komunitě se název "LCD" vžil pro alfanumerické LCD s driverem, kterým se v tomto článku zabývat nebudeme. Bavit se budeme o LCD bez driveru. Nejprve vás stručně seznámím s obsahem tutoriálu, ať se můžete rozhodnout zda chcete jeho čtení věnovat váš vzácný čas. Napřed si řekneme o LCD něco obecně, pak si uděláme exkurzi do vnitřností LCD driveru na Atmega169. Ukážeme si jak vypadají průběhy, kterými se LCD budí. Postavíme si jednoduchý přípravek a zprovozníme "průměrný" displej. Nakonec uděláme "micro-power" pokus a ukážeme si jak rozjet Atmel i s displejem s minimální spotřebou. Upozorňuji vás, že kvůli velkému objemu informací nemusí být tento tutoriál úplně jednoduché sousto.
Displeje z tekutých krystalů (LCD), které znáte třeba z multimetrů, mají v aplikacích svou nezastupitelnou roli díky svým dvěma klíčovým přednostem. Jednou z nich je kontrast. Zatímco klasické LED nebo TFT displeje ztrácí kontrast když na ně posvítíte, u LCD naopak kontrast roste. Hodí se tedy například pro aplikace kde bude displej na přímém slunci. Druhou předností je spotřeba, která se odvíjí od plochy a pohybuje se běžně v řádu jednotek uA (respektive uW). LCD může ale zaujmout z ryze designových důvodů, cifry bývají typicky velké a k dostání jsou běžně "šestnácti" a "čtvrnácti" segmentové varianty schopné zobrazovat celou abecedu. Mnoho z nich má také různé speciální vzory jako třeba indikaci stavu baterie.
Daní za zmíněné přednosti je jistá náročnost jejich řízení. Displeje je potřeba budit střídavým napětím bez stejnosměrné složky, která by způsobovala elektrolýzu a degradaci. Displeje, které mají pro každý segment samostatný vývod můžete řídit přímo jakýmkoli jednočipem a nebo pro úsporu vývodů třeba přes kaskádu posuvných registrů (i když tím asi přijdete o možnost nízké spotřeby). Tomu způsobu řízení (buzení) se říká "static drive" a setkáte se s ním u displejů s menším počtem segmentů (dejme tomu do 32 - tedy 4 cifry). Budete asi souhlasit že připojovat 32 a více vývodů je značně nepraktické, proto se LCD vyrábí i ve variantách s vícero společnými elektrodami. Jejich buzení už ale není tak triviální a vyžaduje schopnost driveru vytvářet více než jen dvě hodnoty napětí (např 3.3V a 0V). Já se vyhnu výkladu teorie o řízení LCD, neboť to pro praktickou aplikaci není až tak klíčové a odkážu zájemce na seznam literatury na konci článku. My využijeme čip Atmega169A vybavený LCD driverem.
Atmega169 je asi nejznámější zástupcem několika málo "Atmelů" s LCD driverem. Dá se sehnat z číny od 25kč, u nás se jeho cena pohybuje mezi 80-120kč a kvůli počtu vývodů je k dostání pouze v SMD provedení ( 64pinů s "větší roztečí"). Alternativou s větším počtem vývodů je Atmega3290 (100pinů s "menší roztečí"). Mimo běžné periferie, které znáte z jiných "Atmelů" obsahuje LCD driver schopný řídit displeje v konfiguraci až 4x25 segmentů (mega3290 pak až 4x40 segmentů). Převedeme-li tyto počty na "cifry", bavíme se o 12-ti až 14-ti cifrách v případě Megy169 a 20-ti až 22-ti cifrách v případě Megy3290. Bez problémů tedy lze odřídit například dva klasické čtyřmístné displeje. LCD driver na čipu je navržen tak aby byl schopen pracovat i v "low power" režimech, což si v tutoriálu vyzkoušíme. Narazit můžete na čipy Atmega169A, Atmega169PA, případně Atmega169P. Písmeno "P" značí "pico-power" a tyto čipy mají těsnější limity odběru. Zatímco si "A" verze při 2V a 1MHz vezme až 1mA, u "P" verze máte záruku, že to bude pod 0.44mA. Typická hodnota je ale u obou 0.35mA. Význam tyto parametry dostanou až když budete čip trápit například vysokou teplotou.
Než se začneme zabývat hardwarem a připojením displeje k jednočipu, projdeme si trochu teorie abychom měli přehled o funkcích LCD driveru. Driver má dva druhy vývodů. COM0,1,2,3 slouží k buzení společných elektrod a SEG0 až SEG24 k buzení segmentů. Jejich roli si objasníme postupně. Driver pracuje autonomně a o celé zobrazování se stará sám. Krom inicializace interaguje software s driverem jen pokud je potřeba měnit obraz. Jako u všech periferií se driver ovládá skrze skupinu registrů. Projdeme jednotlivé funkce a nakonec si ve stručném přehledu popíšeme ve kterých registrech je lze ovládat. LCD driver lze taktovat buď clockem jádra (takže např 1MHz, 8MHz a pod.) nebo asynchronním clockem z Timeru 2 (typicky 32.768kHz), který znáte z předchozích dílů. Frekvence má vliv na spotřebu a na "kvalitu obrazu". Platí, že čím je nižší tím menší je spotřeba. Proti tomu jde ale požadavek držet obnovovací frekvenci (frame-rate) nad cca 30Hz, jinak displej bliká. Typicky tedy budete hledat nějaký kompromis. K nastavování frekvence vám slouží předdělička (prescaler) a dělička (divider). Frame-rate spočítáte pomocí vztahu
frame_rate = f_clk/(K*N*D)
kde:
N je hodnota předděličky (16,64,128,256,512,1024,2048,4096)
D je hodnota děličky (1,2,3,4,5,6,7,8)
K je 8 pro duty 1/4 a 6 pro duty 1/3,1/2 a u Static drive (o tom více za chvíli)
Další co potřebujete řídit je kontrast displeje. To lze několika mechanismy. První zmíníme dobu buzení, kterou lze nastavit jako fixní s časem 70 až 1150us, případně jako polovinu taktu nebo celý takt. Čím delší je doba buzení, tím větší je kontrast. Bez buzení se displej během periody postupně vybíjí a klesá kontrast. Druhou možností jak lze ovlivnit kontrast je amplitudou budicí waveformy. Její maximum je rovno napětí VLCD, které lze generovat buď vnitřním generátorem nebo ho do čipu přivést z vnějšku (na pin LCDCAP). Vnitřní generátor vám umožňuje nastavit napětí v rozsahu 2.6V až 3.35V bez ohledu na napájecí napětí čipu. Za tento komfort ale platíte zvýšenou spotřebou, zvláště pokud je rozdíl mezi VLCD a napájecím napětím velký. Jinak řečeno pokud například z 1.8V chcete generovat 3.3V. Na několika oscilogramech se pokusím znázornit tvary budicích průběhů pro displej s duty 1/3 a bias 1/3. Bias 1/3 znamená že driver k buzení využívá 4 napěťové úrovně (VLCD, 2/3 VLCD, 1/3 VLCD a GND). To je patrné na žlutém a modrém průběhu. Modrý průběh je napětí na společné elektrodě (COM), žlutý na vybraném segmentu (SEG). To co rozhoduje o kontrastu vybraného segmentu je rozdíl napětí mezi COM a SEG, který je na červeném průběhu (ten nejdůležitější). VLCD je v tomto případě přibližně 2.9V, napěťové úrovně by tedy měly být přibližně 2.9V, 1.9V, 1V a 0V. Všimněte si, že všechny červené průběhy mají nulovou stejnosměrnou složku (což je hlavní důvod proč musí driver vytvářet tak šílené průběhy a nevystačí si se stejnosměrným napětím). Na posledních oscilogramech se můžete podívat jaký vliv má zkracování doby buzení. Komentáře pod obrázky dovysvětlí o co jde.
Vraťme se k ovládání driveru. Podle počtu společných elektrod vašeho displeje musíte rozhodnout o "Duty". K dispozici máte čtyři možnosti:
Než se pustíme do praktických testů podíváme se na hardware. Protože Atmega169A je k dostání jen v SMD provedení, potřebujete na testy nějakou redukci.Můžete ji koupit na Ebay za necelý dolar (hledejte "TQFP adapter") a doosazovat na ni filtrační kondenzátory. Nebo si můžete vlastní improvizovanou desku vyrobit. Já zvolil druhou variantu. Na desce jsem uspořádal všechny vývody vedle sebe. Díky tomu se dá zapojit do kontaktního pole a všechny vývody se dají přehledně popsat. Výrobní podklady ve formátu .sch a .brd (Eagle) lze stáhnout v archivu se zdrojovými kódy.
K ukázce využiju displej DE 161. Je k dostání v TME za cca 100kč. Je konstruovaný pro 3V budicí napětí s parametry 1/3 duty a 1/3 bias. Duty 1/3 nám napovídá, že má tři společné elektordy (COM). Skládá se ze 31 segmentů (čtyři 7-segmentové cifry a tři desetinné tečky). Datasheet čísluje COM od jedničky, což se nám nehodí, neboť Atmel má číslování od nuly. Dovolím si tedy ve zdrojovém kódu používat přečíslování a číslovat i COM na displeji od nuly. Geometricky jsou segmenty přiřazeny COM elektrodám podle následujícího obrázku (což vás vlastně nemusí zajímat). Tato konfigurace nám bude nepěkně komplikovat práci při vytváření znakové sady.
Displej připojíme k testovací desce podle prvních dvou sloupců následující tabulky. Tabulku pro přehlednost najedete i v souboru lcd.h v archivu s projektem. Cifry displeje čísluji od nuly zprava. Zkratka DP znamená desetinnou tečku. DP1 odpovídá tečce za cifrou č.1 atd. Označování segmentů je obvyklé, a,b,c,d,e,f,g ve směru hodinových ručiček (a také ho najdete v datashetu).
Pin Displeje
Pin Atmelu
funkce pinu
segment displeje
COM0 (Atmel) segment displeje
COM1 (Atmel) segment displeje
COM2 (Atmel)
1
2
3
4
PA4
SEG0
3B
3C
3DP
5
PA5
SEG1
2B
2C
2DP
6
PA6
SEG2
1B
1C
1DP
7
PA7
SEG3
0B
0C
8
PA2
COM2
9
10
11
PA0
COM0
12
PG2
SEG4
0A
0G
0D
13
PC7
SEG5
0F
0E
14
PC6
SEG6
1A
1G
1D
15
PC5
SEG7
1F
1E
16
PC4
SEG8
2A
2G
2D
17
PC3
SEG9
2F
2E
18
PC2
SEG10
3A
3G
3D
19
PC1
SEG11
3F
3E
20
PA1
COM1
První příklad jako klasicky věnujeme základům. Zkusíme rozběhnout displej a zobrazit na něm "běžící čas". Nebudeme brát ohled na spotřebu a nic podobného.
Inicializace driveru - lcd_init() - je jednoduchá a přímočará. Nejprve vymažeme LCD memory, abychom měli jistotu, že se nám při startu nezobrazí nějaký nesmysl. V dalším kroku nastavíme kontrast. Protože zatím neřešíme spotřebu, využijeme vnitřní generátor VLCD a nastavíme ho na 3.2V, dobu buzení nastavíme na maximum. Tím si zajistíme dobrý kontrast za všech podmínek. Obnovovací frekvenci (frame-rate) budeme odvozovat od taktu jádra protože obecně nemusíme mít k dispozici jiné zdroje clocku. Jádro běží na 1MHz a pro kvalitní zobrazení naladíme frekvenci do pásma 40-50Hz. Konkrétně 1MHz/512/7/6 = 46.5Hz. Použitý displej vyžaduje konfiguraci 1/3 duty (3 společné elektrody) a 1/3 Bias. Displej je malý a zabírá pouze 12 vývodů SEG, driveru tedy přidělíme pouze SEG0 až SEG12. Poslední SEG12, bude nevyužitý, ale menší konfiguraci Atmel neumožňuje. Po spuštění začne driver generovat budící průběhy. Na displeji se ale ještě nic zobrazovat nebude. Všechny segmenty jsou v LCD memory deaktivované. Pro lepší čitelnost programu jsem v souboru lcd.h připravil sadu maker, sloužících k ovládání displeje. Vyhneme se tak nutnosti listovat datasheetem a hledat jestli kombinace 0b0101 je napětí 3.2V nebo 3.05V ...
// Základní ovládání LCD - "běžící čas" #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> #include "lcd.h" // makra zjednodušující práci s LCD void lcd_init(void); void disp(uint16_t val); void lcd_write(uint8_t *characters, uint8_t dots); uint8_t x[4]={0,0,0,0}; // pole znaků zobrazených na LCD volatile uint16_t tmp=0; // pomocná proměnná int main(void){ lcd_init(); while (1){ tmp++; // tupé počítadlo, abychom viděli že displej pracuje if(tmp>9999){tmp=0;} disp(tmp); // zobraz číselnou hodnotu _delay_ms(100); } } void lcd_init(void){ // vymažu paměť používaným segmentům LCDDR0 = 0; LCDDR1 = 0; LCDDR5 = 0; LCDDR6 = 0; LCDDR10 = 0; LCDDR11 = 0; // doba buzení naplno, napětí VLCD 3.2V - chceme vysoký kontrast LCDCCR = LCD_TIME_FULL | LCD_DRIVE_3V20; // obnovovací frekvence 1MHz/(512*7*6) = 46.5Hz LCDFRR = LCD_PRESC_512 | LCD_DIVIDE_7; // Duty 1/3, využity segmenty SEG0 až SEG12 LCDCRB = LCD_DUTY_3 | LCD_PORTMASK_12; // spustit LCD driver (s interním generátorem VLCD) LCDCRA = (1<<LCDEN); }
Obraz na displeji řídí funkce lcd_write(). Jejím argumentem je pole čtyř znaků a úkolem funkce je podle těchto znaků rozhodnout které segmenty aktivovat. Realizuje tedy znakovou sadu. Obecně je tuto znakovou sadu potřeba napsat pro každou cifru zvlášť. Jistý prostor pro nějaké drobné zobecnění tu je, ale najít a napsat ho by bylo asi pracnější než vytvořit znakové sady pro každou cifru samostatně. Jádro funkce je tedy nutné psát podle tabulky zapojení, kterou jste si prohlédli o pár odstavců výše.
Demonstrujeme si tvorbu znakové sady na příkladu jedné cifry. Dejme tomu, že chceme zobrazit znak "3" na nulté pozici. K tomu musíte aktivovat segmenty 0A,0B,0C,0D a 0G.
// převede číselnou hodnotu na dekadické vyjádření (zjistí cifry) void disp(uint16_t val){ if (val>9999){val=9999;} x[3] = val/1000; // tisíce val = val%1000; x[2] = val/100; // stovky val = val%100; x[1] = val/10; // desítky x[0] = val%10; // jednotky lcd_write(x,0b001); // zapiš cifry do LCD ram podle znakové sady, desetinnou tečku za 1 cifru } // škaredá a důležitá funkce realizující znakovou sadu // argumentem je pole čtyř bytů se znaky (0 až 9) a byte kódující pozici zobrazovaný desetinných teček // znak mimo rozsah 0 až 9 vede ke zhasnutí všech segmentů dané cifry void lcd_write(uint8_t *characters, uint8_t dots){ uint8_t mem0=0,mem1=0,mem5=0,mem6=0,mem10=0,mem11=0;; if(dots & (0b1<<0)){mem10 |= 0b100;} if(dots & (0b1<<1)){mem10 |= 0b010;} if(dots & (0b1<<2)){mem10 |= 0b001;} // digit 0 switch (characters[0]){ case 0: mem0 |= 0b111000; mem5 |= 0b101000; mem10 |= 0b10000; break; case 1: mem0 |= 0b001000; mem5 |= 0b001000; break; case 2: mem0 |= 0b011000; mem5 |= 0b110000; mem10 |= 0b10000; break; case 3: mem0 |= 0b011000; mem5 |= 0b011000; mem10 |= 0b10000; break; case 4: mem0 |= 0b101000; mem5 |= 0b011000; break; case 5: mem0 |= 0b110000; mem5 |= 0b011000; mem10 |= 0b10000; break; case 6: mem0 |= 0b110000; mem5 |= 0b111000; mem10 |= 0b10000; break; case 7: mem0 |= 0b011000; mem5 |= 0b001000; break; case 8: mem0 |= 0b111000; mem5 |= 0b111000; mem10 |= 0b10000; break; case 9: mem0 |= 0b111000; mem5 |= 0b011000; mem10 |= 0b10000; break; } // digit1 ... // .... část funkce vynechána .... // nakopíruj konfiguraci do LCD memory LCDDR0 = mem0; LCDDR1 = mem1; LCDDR5 = mem5; LCDDR6 = mem6; LCDDR10 = mem10; LCDDR11 = mem11; }
Program sice nic smysluplného nedělá, ale můžete si na něm ověřit zda máte vše správně zapojeno. Navíc si můžete vyzkoušet kontrast při různém napětí VLCD, případně dělat testy s ostatními parametry driveru. Otázka kontrastu je zajímavá, takže jsem pro vás připravil obrázkovou závislost kontrastu na budicím napětí.
V tutoriálech o low-power technikách jsme si zavedli modelový příklad, ve kterém má "Atmel" za úkol hlídat napětí lithiového článku, kontrolovat jeho nabíjení a reagovat na vybití pod kritickou mez. Budeme se této šablony držet i při pokusech s LCD. Naše aplikace bude mít stejný úkol, navíc ale bude zobrazovat napětí akumulátoru na displej. Hodnotu bude obnovovat jedenkrát za sekundu. Zbylý čas bude spát, o probouzení se stará Timer 2 s "hodinovým" krystalem. Díky tomu že provozní napětí lithiového článku leží v pásmu 3-4.2V, můžeme vypnout vnitřní generátor VLCD a použít přímo napětí z baterie. Vypnutím generátoru uspoříme energii, ale ztratíme možnost ovládat kontrast displeje napětím. To ale u většiny displejů nevadí, protože při 3V má většina z nich stále ještě slušný kontrast. Při vyšších napětích (dejme tomu od 3.2V) si dokonce můžeme dovolit snižovat kontrast zkracováním doby buzení a spořit tak další energii. Dále díky tomu že nepoužíváme interní generátor VLCD, smíme čip provozovat na minimálním napětí 1.8V, čímž docílíme dalšího snížení odběru (za předpokladu že máte napěťový stabilizátor s malým provozním proudem). My použijeme stabilizátor MCP1700-1.8V s typickým klidovým proudem 1.6uA. Napájecí napětí budeme měřit na ADC0 přes dělič s odporem 3M (viz schema). Výstup stabilizátoru má slušnou stabilitu, takže poslouží také jako reference pro AD převodník.Ze spánku bude náš čip probouzet Timer 2 s hodinovým krystalem, který použijeme také jako clock pro LCD driver. Displej necháme zapojený stejně jako v předchozí ukázce.
Napětí stabilizátoru jsem změřil přesně voltmetrem a vložil do programu jako konstantu VREF. Stejně tak jsem přesně stanovil poměr děliče (makro DELIC). Tím jsem si připravil vše potřebné pro výpočet napájecího napětí. Při pokojové teplotě se zobrazovaná hodnota držela s chybou do +-10mV (vůči multimetru) v rozsahu vstupních napětí 2.6 až 4.3V. Podívejme se ale na funkce, které proti prvnímu příkladu přibyly. Funkce nastav_kontrast() řidí dobu buzení podle napájecího napětí. Skupina maker K0 až K5 rozděluje napětí baterie do sedmi intervalů, makra Kx_KONTRAST pak specifikují pro každý interval dobu buzení. Má-li baterie vyšší napětí než 3.6V, volím nejkratší dobu buzení, protože má minimální spotřebu a kontrast je i tak slušný. Jak napětí klesá, funkce zjišťuje do kterého ze zvolených pásem napětí spadá a postupně zvyšuje dobu buzení. Dorovnává tak pokles kontrastu s cílem udržet displej čitelný (za cenu drobného zvyšování spotřeby). Jak napětí klesá pod 2.9V, klesá kontrast i při plné době buzení. To je daň za to, že nepoužíváme interní regulátor. Napětí lithiového článku by se ale do těchto hodnot nemělo dostat.
Funkce operation() je jen drobně upravená z tutoriálu low-power II. Takže ji popíšu jen ve stručnosti. Nastartuje ADC, zahájí AD převod s využitím režimu spánku. Po čtyřech převodech, AD převodník vypne, zprůměruje výsledky a spočítá napájecí napětí. Poté zkontroluje stav nabití a indikuje ho na pinech PE0 a PE1. Nakonec zavolá funkci na korekci kontrastu. Po jejím dokončení přechází čip opět do hlubokého spánku. Funkce lcd_init() konfiguruje driver. Vybere clock z hodinového krystalu (asynchronní Timer 2 už běží). Nastaví vnější signál jako zdroj pro VLCD, Frame rate konfigurujeme jako 32768/(16*8*6) = 42.7Hz. Ostatní konfigurace je stejná jako v předchozím příkladu. Ze zdrojového kódu jsem si dovolil vypustit lcd_write(), protože se od předchozího příkladu nemění. Archiv s celým projektem si můžete stáhnout.
// B) Napájení čipu přes 1.8V stabilizátor, napětí baterie měřeno přes dělič 1/3 (3Mohm + 100nF), reference pro ADC z VCC (1.8V) // VLCD připojeno přímo na zdroj energie (lithiový akumulátor) - nevyužívá vnitřní generátor VLCD (úspora energie - kolísá kontrast, částečně jej aplikace kompenzuje) #define F_CPU 1000000UL // frekvence v aktivním režimu #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> #include <avr/power.h> // k zapínání/vypínání periferií #include <avr/sleep.h> // funkce režimu spánku #include "lcd.h" // makra pro snadnější ovládání LCD + mapa zapojení LCD #define BLANK 0xff; // prázdný znak na LCD #define VREF 1802 // [mV] napětí stabilizátoru (reference pro ADC) #define DELIC 2.9889 // dělicí poměr 3M děliče z napětí akumulátoru #define VCC_K (uint16_t)(round(VREF*DELIC)) // konstanta pro výpočet napětí akumulátoru #define BEZNY_KONTRAST LCDCCR = LCD_TIME_70 // běžný kontrast #define K0_KONTRAST LCDCCR = LCD_TIME_150 // kontrast v intervalu (K1,K0) #define K1_KONTRAST LCDCCR = LCD_TIME_450 // kontrast v intervalu (K2,K1) #define K2_KONTRAST LCDCCR = LCD_TIME_575 // kontrast v intervalu (K3,K2) #define K3_KONTRAST LCDCCR = LCD_TIME_850 // kontrast v intervalu (K4,K3) #define K4_KONTRAST LCDCCR = LCD_TIME_HALF // kontrast v intervalu (K5,K4) #define K5_KONTRAST LCDCCR = LCD_TIME_FULL // maximální kontrast (pro napětí pod K5) // Makra určující napěťová pásma pro zvolené kontrasty #define K0 3600 #define K1 3400 #define K2 3300 #define K3 3200 #define K4 3100 #define K5 2900 // napětí pro zvýšení kontrastu na maximum #define PREPETI 4200 // napětí nabitého akumulátoru #define PODPETI 3000 // napětí vybitého akumulátoru #define NABITO_ON PORTE |= (1<<PE0) // indikuje plně nabitý akumulátor #define NABITO_OFF PORTE &=~(1<<PE0) #define VYBITO_ON PORTE |= (1<<PE1) // indikuje vybitý akumulátor #define VYBITO_OFF PORTE &=~(1<<PE1) void lcd_init(void); void tim2_init(void); void init_adc(void); void disp_volt(uint16_t val); void operation(void); void nastav_kontrast(uint16_t vcc); void lcd_write(uint8_t *characters, uint8_t dots); uint8_t x[4]={0,0,0,0}; // obsah LCD displeje uint16_t volt; // hodnota napájecího napětí, globální, je k dispozici dalším funkcím int main(void){ ACSR |= (1 << ACD); // vypneme analogový komparátor aby nežral power_all_disable(); // vypneme všechny periferie aby nežraly ADMUX = (1<<REFS0); // připojíme k ADC referenci AVCC (aby se měl čas kondenzátor na AREF nabít) tim2_init(); // spustíme 32.768kHz asynchronní oscilátor a zdroj periodického probouzení power_lcd_enable(); // pustíme šťávu do LCD driveru lcd_init(); // konfigurujeme a spouštíme LCD driver DDRE |= (1<<DDE0) | (1<<DDE1); // nastavujeme indikační výstupy DDRF &=~(1<<DDF0); // PF0 (ADC0) jako vstup DIDR0 = ADC0D; // vypneme na ADC0 vstupní buffer sei(); // povolíme přerušení while (1){ set_sleep_mode(SLEEP_MODE_PWR_SAVE); // chceme spát hluboce sleep_mode(); // dobrou noc ... operation(); // probouzíme se a provádíme "smysluplnou" činnost } } // nastavuje "kontrast" LCD podle napájecího napětí a volených konstant void nastav_kontrast(uint16_t vcc){ if(vcc>K0){BEZNY_KONTRAST;} else if(vcc>K1){K0_KONTRAST;} else if(vcc>K2){K1_KONTRAST;} else if(vcc>K3){K2_KONTRAST;} else if(vcc>K4){K3_KONTRAST;} else if(vcc>K5){K4_KONTRAST;} else{K5_KONTRAST;} } // hlavní činnost naší aplikace void operation(void){ uint16_t tmp=0; // tady budeme ukládat výsledky ADC převodu uint8_t i; // pomocná proměnná power_adc_enable(); // zapnout ADC ADMUX = (1<<REFS0) | 0; // reference AVCC (1.8V), vstup ADC0 // 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); set_sleep_mode(SLEEP_MODE_ADC); // připravíme se na "ADC noise reduction" spánek // 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 } ADCSRA = 0; // vypneme ADC power_adc_disable(); // odpojíme ADC od šťávy tmp=tmp>>2; // dokončíme průměrování (dělení 4mi) volt=((uint32_t)tmp*VCC_K)/1024; // spočítáme napětí akumulátoru (v mV) disp_volt(volt/10); // zobrazíme napětí na LCD (v desítkách mV) // zkontrolujeme stav akumulátoru a signalizujeme ho if(volt>PREPETI){NABITO_ON;}else{NABITO_OFF;} if(volt<PODPETI){VYBITO_ON;}else{VYBITO_OFF;} nastav_kontrast(volt); // nastavíme kontrast podle napětí } // zobrazí napětí na LCD, napětí v jednotkách 10mV void disp_volt(uint16_t val){ if (val>9999){val=9999;} // nesmyslně vysoká čísla saturujeme x[3] = val/1000; // řád tisíců if(x[3]==0){x[3]=BLANK;} // nulu zobrazovat nechceme val = val%1000; x[2] = val/100; // řád stovek val = val%100; x[1] = val/10; // řád desítek x[0] = val%10; // řád jednotek lcd_write(x,0b010); // zapíšeme zjištěné cifry do paměti LCD driveru } // přerušení od Asynchronního timeru 2 (1Hz) ISR(TIMER2_COMP_vect){ asm("nop"); // budíme se z hlubokého spánku, je čas změřit napájecí napětí } // přerušení od dokončeného AD převodu ISR(ADC_vect){ asm("nop"); // jen se probudíme - převod dokončen } // konfigurace asynchronního timeru void tim2_init(void){ ASSR = (1<<AS2); // přepínám časovač 2 do asynchronního režimu OCR2A = 31; // budeme generovat 1Hz (f_timeru/1Hz - 1 = 32/4 - 1 = 31) TCCR2A = (1<<WGM21) | (1<<CS22) | (1<<CS21) | (1<<CS20); // spustit timer s clockem 32.768kHz/1024 = 32Hz // počkat až se konfigurace zapíše do registrů timeru (!!!) while((ASSR & (1<<OCR2UB)) || (ASSR & (1<<TCR2UB))){}; TIFR2 = (1<<OCF2A); // vyčistit vlajku TIMSK2 |= (1<<OCIE2A); // povolit přerušení od compare události (stropu) } void lcd_init(void){ // vymažu paměť používaným segmentům LCDDR0 = 0; LCDDR1 = 0; LCDDR5 = 0; LCDDR6 = 0; LCDDR10 = 0; LCDDR11 = 0; // nastavím kontrast, napětí vnitřního regulátoru nenastavuji LCDCCR = BEZNY_KONTRAST; // clock pro driver 32768Hz/16/8 = 256Hz (frame = 256/6 = 43Hz) LCDFRR = LCD_PRESC_16 | LCD_DIVIDE_8; // Bias 1/3, duty 1/3, využity segmenty 0-12, Asynchronní clock (z TIM2) LCDCRB = LCD_DUTY_3 | LCD_PORTMASK_12 | (1<<LCDCS); // spouštím LCD driver s externím zdrojem VLCD LCDCRA = (1<<LCDEN) | (1<<LCDCCD); }
Spotřebu odhaduji stejnou metodikou jako v tutoriálu Low-power II, tedy z doby kdy se 3.3mF kondenzátor vybije ze 4.2V na 3V (o čemž nás informuje testovaný čip na pinech PE0 a PE1). V tomto případě aplikace vydržela 349s, což odpovídá průměrné spotřebě 11.3uA. Zahrneme-li 20% toleranci kapacity kondenzátoru můžeme říct že by měla ležet v pásmu 9.1-13.6uA. Pro srovnání výdrž takové aplikace z klasického 2Ah lithiového článku (16850) je okolo 20ti let, tedy odběr je nejspíš nižší jak samovybíjení článku. Přirozeně se nabízí několik způsobů jak dále snížit odběr. Lze vyzkoušet různé "low-power" módy driveru. Dále se dá zapracovat na děliči napětí (systematický odběr přes 1uA), lze ho například připojovat tranzistorem až před měřením. V takovém případě se nabízí využít výstup OC2 ovládaný timerem 2. Ten může dělič aktivovat ještě před probuzením čipu a dát mu čas k nabití filtračního kondenzátoru C8. Další prostor se najde v optimalizaci programu.
Obávám se, že se mi nepodařilo všechny informace seřadit v tom nejlepším pořadí, i tak ale doufám že máte představu o možnostech řízení LCD přímo Atmelem. Řekl bych, že potenciál pro využití v praxi je veliký a doufám, že se někdo z vás odhodlá toto řešení nasadit. Těším se u dalších dílů (uf asi si dám pauzu).
Home
V1.01 6.9.2018
By Michal Dudka (m.dudka@seznam.cz)