Někdy potřebujete na alfanumerickém LCD řídit kontrast softwarově. Jedním z důvodů může být velký rozsah teplot v nichž displej pracuje. Druhým důvodem může být velký rozsah provozních napětí. Například napájíte-li aplikaci z baterií nebo akumulátoru. Jistě existuje řada řešení využívajících DA převodník, digitální potenciometr či operační zesilovač. Já jsem vyzkoušel jedno řešení jen se základními prostředky MCU a o něm bude tento příspěvek.
Dovolím si čtenáře odkázat na předchozí díl, který obsahuje většinu informací z nichž budu dále vycházet. Protože na VLCD bude až na výjimky (5V napájení) záporný potenciál, musíme jeho napětí snímat nepřímo. K tomu slouží dělič z rezistorů R8 a R7. Kondenzátory C1,C2 a diody D1,D2 tvoří nábojovou pumpu, která je buzena PWM signálem z mikrokontroléru. Odpor R6 "změkčuje" nábojovou pumpu a je v zapojení vlastně úplně na nic. Případně je ho možné zvětšit aby sloužil jako omezení špičkového proudu do C2. Nábojová pumpa je zatížena odporem 5*4k7 (na displeji) paralelně s děličem 120k+22k (v našem zapojení). Tedy za běžných podmínek kdy je rozdíl mezi VCC a VLCD okolo 4.2V odebírá z pumpy proud (4.2/19.7k=213uA). Celý systém pracuje jednoduše. Mikrokontrolér snímá napájecí napětí a napětí na ADC, dopočítává z něj rozdíl mezi VCC a VLCD (kontrast). Pokud zjistí, že je nižší než požadované (např. zvolených 4.2V) zvětší střídu PWM signálu a tím vzroste proud dodávaný nábojovou pumpou. Naopak pokud zjistí vyšší napětí, tak střídu sníží.
Mikrokontrolér nejprve změří napětí VCC, poté napětí na ADC1. Z jejich rozdílu spočítá napětí na R7. Z nej lze vypočítat proud a z něj pak napětí na R8. A to je vše co potřebuje, neboť součet napětí na R7 a R8 je to co hledáme - kontrast (rozdíl potenciálů mezi VCC a VLCD). Stanovit napětí na R7 lze ale i přímo. Připojíme-li na další vstup AD převodníku (například na ADC0) napájecí napětí, můžeme provést diferenciální převod a změřit napětí na R7 v jednom kroku. A navíc může být výsledek převodu vztažen k libovolné referenci (například vnitřní 2.56V nebo 1.1V). Nevýhoda této varianty je v tom, že diferenciální měření na většině starších AVR funguje jen pokud je napájecí napětí nad 2.7V. Aby bylo možné zkoušet i tuto variantu, zvolil jsem hodnotu R7 menší jak R8 (aby úbytek na R7 spadal do rozsahu některé vnitřní reference). Jinak by bylo vhodnější volit v děliči R7 větší pro lepší rozlišení.
Velikost VLCD spočteme jako:
VLCD=(VCC-AIN1)/R7*(R7+R8)
Nábojová pumpa nedělá nic jiného než, že s každým impulzem přenese nějaké množství náboje do kondenzátoru C1. Množství roste s kapacitou C2, roste také s šířkou impulzu po kterou se C2 nabíjí, klesá s odporem R6 (a vnitřním odporem výstupu MCU) a zdá se že je také závislé na napájecím napětí. První co člověka napadne je zafixovat frekvenci PWM a řídit střídu. To se ale ukázalo jako velmi nepraktické. Ve většině pracovního rozsahu (od 2.7V do 5V) je totiž střída velmi malá. A pro dobrou stabilizaci je ji potřeba jemně regulovat. Uvažujme například následující hodnoty. Perioda je 5ms (frekvence 200Hz) a časovač je taktován 1MHz. Střída potřebná pro udržení VLCD=4.2V při VCC=4V je přibližně 0.62%. Tedy šířka impulzu je přibližně 30us. Tak úzký impulz ale nelze jemně ladit. Krok 1us je už příliš velký. Hrubá regulace pak často vede k oscilacím a nestabilitě napětí na VLCD. Jako lepší strategie se ukazuje zafixovat šířku impulzu (v naší ukázce 30us) a měnit místo toho periodu (střída pak jde ladit mnohem jemněji).
Ukázku jsem zapojil a odladil na Atmega164A. Tento mikrokontrolér jsem zvolil proto, že je schopen pracovat od 1.8V do 5.5V, tedy spolehlivě pokrývá celý pracovní rozsah na kterém jsem LCD chtěl testovat. Dále umožňoval vyzkoušet i variantu s "diferenciálním" AD převodníkem. Taktoval jsem jej 1MHz, ke generování PWM jsem využil výstup OC1A (PD5) 16bitového timeru 1. Napětí jsem snímal na vstupu AIN1 (PA1). K měření vlastního napájecího napětí jsem využil vnitřní 1.1V referenci a s přesností jsem si hlavu moc nelámal, neboť není v aplikaci klíčová. Samotná regulace frekvence(střídy) probíhá "dvourychlostně". Při větších odchylkách kontrastu od požadované hodnoty se frekvence mění s krokem přibližně 2%. Při menších odchylkách probíhá změna pomalu s minimálním krokem který timer umožňuje. Taktovací frekvence pro AD převodník je volena na spodní hranici jeho možností, protože pak roste vzorkovací čas, který je vhodný pro přesnější měření děliče (má totiž vysoký odpor). Timer běží v režimu "Phase and correct PWM" protože jakékoli "glitche" na výstupu by měly velmi nepříjemný dopad. Regulace se provádí přibližně 10x za sekundu, což zajišťuje tupý 100ms delay (pro účely demonstrace jsem si dovolil tuto nežádoucí funkci vypustit do světa).
/* Ukázka regulace kontrastu LCD pomocí nábojové pumpy Timer1 generuje na OC1A (PD5) pulzy o fixní šířce 30us s proměnnou frekvencí (a tedy PWM s proměnnou střídou) AD převodník měří napájecí napětí VCC a napětí z děliče z VLCD (AIN1, PA1) Regulace frekvence probíhá dvourychlostně. Při větších odchylkách napětí VLCD se mění s velkým krokem */ #include <avr/io.h> #include <util/delay.h> #include "avr_hd44780.h" // knihovna pro práci s LCD #define VREF_1V1_VALUE 1100UL // napětí interní 1.1V reference v mV #define TOP 2300 // počáteční frekvence ~217Hz #define PULSE_WIDTH 15 // 30us šířka pulzu nábojové pumpy #define RTOP 22UL // horní odpor děliče snímajícího VLCD #define RBOT 100UL // dolní odpor děliče snímajícího VLCD #define TARGET_VLCD 4200 // cílová hodnota kontrastu (napětí VDD-VLCD) #define ADC_REF_SEL_VDD (1<<REFS0) // pomocné makro, VDD jako reference void init_system(void); void process_lcd_contrast(void); uint16_t avdd; // napájecí napětí int16_t vlcd; // napětí mezi AVDD a VLCD displeje - "kontrast" uint16_t period=TOP; int main(void){ init_system(); lcd_init(); lcd_puts("Ahoj"); // aby na displeji něco bylo a aby byl kontrast patrný while (1){ _delay_ms(100); // neohrabané řešení, ale jde jen o demonstraci process_lcd_contrast(); // obsluhujeme řízení kontrastu } } void process_lcd_contrast(void){ uint16_t tmp; uint8_t i; // MĚŘÍME NAPÁJECÍ NAPĚTÍ ADMUX = ADC_REF_SEL_VDD | 0b11110; // reference VDD, vstup interní 1.1V reference _delay_us(250); // nějaký zvláštní podraz na Atmega164A (viz text) tmp=0; for(i=0;i<4;i++){ // průměr ze čtyř AD převodů ADCSRA |= (1<<ADSC); // spustíme převod while(ADCSRA & (1<<ADSC)); // počkáme na dokončení převodu tmp += ADC; // přičteme výsledek } tmp = tmp >> 2; // vyčíslíme průměr avdd = (VREF_1V1_VALUE*1024UL)/(uint32_t)tmp; // vyčíslíme přibližnou hodnotu AVDD v mV // MĚŘÍME VÝSTUP DĚLIČE Z VLCD ADMUX = ADC_REF_SEL_VDD | 0b00001; // výstup děliče na ADC1 (PA1) tmp=0; for(i=0;i<4;i++){ ADCSRA |= (1<<ADSC); while(ADCSRA & (1<<ADSC)); tmp += (ADC); } tmp = tmp>>2; // vyčíslíme napětí na vstupu ADC1 v mV tmp = ((uint32_t)tmp * (uint32_t)avdd + 512)/1024; // spočítáme napětí mezi VDD a VLCD (kontrast) vlcd = ((RBOT+RTOP)*((int32_t)avdd-tmp))/RTOP; // UPRAVUJEME PARAMETRY NÁBOJOVÉ PUMPY if(vlcd < (TARGET_VLCD-50)){ // pokud je kontrast hodně nízký if(period > 50){period = (98*(uint32_t)period)/100;} // zvyšujeme frekvenci (proud pumpy) velkým krokem }else if(vlcd > (TARGET_VLCD+50)){ // pokud je kontrast hodně vysoký if(period < (uint16_t)(0.97*0xFFFF)){period = (102*(uint32_t)period)/100;} // snížíme proud pumpy velkým krokem } if(vlcd < TARGET_VLCD){ // vše ještě jednou ale s jemným krokem if(period > 3*PULSE_WIDTH){period--;} }if(vlcd > TARGET_VLCD){ if(period<0xFFFF){period++;} } ICR1 = period; // zapíšeme vypočtenou periodu do timeru } void init_system(void){ // Timer1 - zdroj signálu pro nábojovou pumpu DDRD |= (1<<5); // PD5 výstup (OC1A) ICR1 = TOP; // zapíšeme počáteční strop timeru (periodu) OCR1A = PULSE_WIDTH; // zapíšeme fixní šířku pulzu TCCR1A = (1<<COM1A1); // volíme PWM režim // volíme režim "PWM phase and freq. correct" a clock = F_CPU (1MHz) TCCR1B = (1<<WGM13) | (1<<CS10); // inicializace ADC DIDR0 |= 1<<ADC1D; // vypínáme vstupní buffer na AIN1 (PA1) ADCSRA = (1<<ADEN) | (1<<ADPS2); // volíme clock 62.5kHz (1MHz/16) ADMUX = ADC_REF_SEL_VDD | 0b00001; // počáteční konfigurace vstupu na ADC1 }
Možná se pozastavujete nad tím proč po přepnutí multiplexeru volám funkci _delay_us(250). Také mě to udivuje, neboť u řady jiných AVR není nic takového potřeba. Ale u této Atmegy164A dochází k chybnému měření, pokud aplikace neposkytne dostatečnou prodlevu mezi nastavením multiplexeru na měření vnitřní 1.1V reference a samotným převodem. Po přepnutí na vstup AIN1 nic takového potřeba není. Jestli někdo přijdete na to co jsem udělal blbě, dejte mi vědět třeba do mailu.
Ze zajímavosti jsem změřil závislost mezi udržovaným "VLCD" a napájecím napětím (a zároveň frekvencí nábojové pumpy). Z grafu je vidět, že při vyšším napětí roste potřebná perioda nad rozsah timeru a aplikace není dále schopná stabilizovat. To by mělo být možné do jisté míry ošetřit změnou šířky pulzu. Opticky ale kontrast 4.6V ještě není přepálený, takže to není potřeba a s vyšším napájecím napětím než 5V se nepočítá. Minimální napájecí napětí při kterém se dal text ještě považovat za čitelný bylo 2.3V. Tam už aplikace narážela na limity nábojové pumpy. Pokud by měla pracovat i s nižším napětím, bylo by třeba pumpu sestavit jako ztrojovač napětí. Je ale otázkou zda by při nižším napětí pracoval driver v LCD správně (minimální provozní napětí je 2.7V).
Na následujících oscilogramech je vidět rychlost reakce při startu do napájecího napětí 2.7V, 3.75V a 5V. Červená stopa je napájecí napětí, modrá stopa je výstup nábojové pumpy, fialová stopa je rozdílové napětí VCC-VLCD, tedy kontrast. Obecně může být reakce na skokovou změnu mnohem delší (třeba při velkém skoku, řekněme z 5V na 2.5V a opačně). Za běžných okolností se ale počítá s tím, že by změna napájecího napětí (napětí akumulátoru nebo baterií) měla být velmi pozvolná.
Home
| V1.00 10.7.2021 /
| By Michal Dudka (m.dudka@seznam.cz) /