Słowo wstępu
Przez jedno z amatorskich for dyskusyjnych poświęconych elektronice przetoczyła się dyskusja na temat wydajności procedur dla wyświetlacza HD44780 przedstawionych na mojej stronie. Pod wątpliwość poddano, sensowność oczekiwania na zwolnienie flagi zajętości wyświetlacza LCD. Przedstawiam więc rozwiązanie wykorzystujące bufor w pamięci RAM mikrokontrolera, będące bardziej optymalnym rozwiązaniem obsługi wyświetlacza HD44780.
Ogólna idea buforowania
O co wogóle tyle hałasu? Problem polega na tym, iż sterownik HD44780 jest bardzo wolny. Każda operacja (zapis znaku, ustawienie współrzędnych zapisu itp) zajmuje mniej więcej określoną ilosć czasu - od kilkudziesięciu mikrosekund do kilku milisekund. Czasy te jak na obecnie dostępne moce obliczeniowe mikrokontrolerów są bardzo długie. Przedstawione przezemnie dwa podejścia, a więc albo oczekiwanie czasu maksymalnego dla danej operacji albo cykliczne odczytywanie flagi zajętości i czekanie na jej zwolnienie w znacznym stopniu absorbują procesor. Ponadto, w obydwu przypadkach operujemy bezpośrednio na zawartości pamięci DDRAM sterownika HD44780 no i oczywiście "blokujemy" procesor w oczekiwaniu na zwolnienie sterownika. Wprowadzenie bufora pośredniczącego pomiędzy "programem użytkownika" a pamięcią DDRAM sterownika pozwoli na zapis danych przeznaczonych do wyświetlenia w zasadzie w dowolnym momencie - bez względu na stan zajętośći sterownika - nawet w miejscach tak krytycznych jak procedury obsługi przerwań. Będzie to możliwe ze względu na bardzo krótki czas zapisu do bufora wyświetlacza - równy czasowi dostępu do wewnętrznej pamięci RAM mikrokontrolera. Kopiowanie danych z bufora do pamięci DDRAM sterownika HD44780 odbywać się będzie w "wolnych chwilach" mikrokontrolera. W pętli głównej programu cyklicznie wywoływana będzie funkcja sprawdzająco-zapisująca. Funkcja ta ma za zadnie sprawdzić czy zawartość pamięci DDRAM wyświetlacza wymaga aktualizacji oraz czy sterownik wyświetlacza jest wolny. W przypadku spełnienia obydwu warunków nastąpi zapis jednego znaku z bufora do pamięci sterownika i opuszczenie funkcji. W wyniku tego każdy obieg pętli głównej może zapisać do pamięci wyświetlacza co najwyżej jeden znak. W przypadku braku gotowści sterownika do zapisu funkcja oczywiście zakończy swoje działanie, dzięki czemu program nie będzie tracił czasu czekając na zwolnienie sterownika. Oczywiście przepisanie całej zawartości bufora wymaga tylu obiegów pętli głównej, ile znaków ma wyświetlacz + 1 obieg na każą linię (zapis współrzędnych).
Przykład pętli głównej obrazujący powyższy opis :
int main(void){
// inicjalizacja sprzętu
do{
LCDUpdateTask();
... // inne zadania
... // wykonywane przez
... // program
}while(1);
} |
Wykorzystywane zmienne
Buforowanie zawartości wyświetlacza LCD w pamięci RAM mikrokontrolera pociaga za sobą oczywiście koniecznosć utworzenia bufora, oraz kilku zmiennych pomocniczych. Konfiguracja drivera sprowadza się do określenia ilości linii i znaków wykorzytywanego wyświetlacza LCD :
#define LCD_LINES 4
#define LCD_CHARSPERLINE 16 |
Deklaracje zmiennych wykorzystywanych przez driver :
unsigned char LCDBuffer[LCD_LINES][LCD_CHARSPERLINE];
unsigned char LCDNeedUpdate[LCD_LINES];
signed char LCDCharIndex[LCD_LINES];
unsigned char LCDLineIndex;
unsigned char LCDLineAddress[4] = {0x00, 0x40, 0x14, 0x54}; |
Zmienna LCDBuffer to oczywiście bufor danych wyświetlacza. Rozmiar tego bufora jest równy ilości znaków zastosowanego wyświetlacza LCD. Dla lepszego odwzorowania relacji pomiędzy buforem a organizacja wyświetlacza bufor stanowi tablicę dwuwymiarową.
Zmienna LCDNeedUpdate to tablica o ilości elementów równej ilości linii wyświetlacza. Element tablicy o wartości różnej od zera wskazuje iż linia wyświetlacza o podanym indeksie wymaga odświeżenia.
Zmienna LCDCharIndex to tablica o ilości elementów równiej ilości linii wyświetlacza. Przechowywany jest w niej numer aktuanej pozycji w danej linii wyświeltacza.
Zmienna LCDLineIndex wykorzystywana będzie do iteracji po liniach wyświetlacza, tudzież elementach odpowiednich tablic.
Zmienna LCDLineAddress przechowuje adresy (w pamięci DDRAM wyświetlacza) poszczególnych linii.
Kod funkcji kopiującej dane z bufora do wyświetlacza :
int LCDUpdateTask(void)
{
if(LCDNeedUpdate[LCDLineIndex]) // Czy dana linia wymaga odświeżenia?
{
if(LCD_NotBusy()) // czy sterownik nie jest zajęty ?
{
if(LCDCharIndex[LCDLineIndex] == -1) // czy to początek linii?
{
LCD_JustWriteCommand(0x80 | LCDLineAddress[LCDLineIndex]);
LCDCharIndex[LCDLineIndex]++;
return 0;
}
LCD_JustWriteData(LCDBuffer[LCDLineIndex][LCDCharIndex[LCDLineIndex]++]);
if(LCDCharIndex[LCDLineIndex] == (LCD_CHARSPERLINE - 1)) // czy był to ostatni znak?
{
LCDCharIndex[LCDLineIndex] = -1;
LCDNeedUpdate[LCDLineIndex] = 0;
}
}
return 0;
}
LCDLineIndex++;
if(LCDLineIndex == LCD_LINES)
LCDLineIndex = 0;
} |
Jak już wcześniej pisałem, funkcja ta wywoływana jest przy każdym obiegu pętli głównej programu. W pierwszej kolejności sprawdzamy, czy aktualna linia wyświetlacza (określona zmienną LCDLineIndex) wymaga odświeżenia. Jeśli tak jest, to następnie sprawdzamy, czy sterownik wyświetlacza nie jest przypadkiem zajęty. Jeśli zapis do sterownika jest możliwy, to sprawdzamy czy jesteśmy na początku linii - indeks znaku jest równy -1. W tym przypadku zapisujemy współrzędne aktualnej linii, po czym opuszczamy funkcję. Jeśli jesteśmy "w trakcie" linii (indeks znaku różny od -1) to zapisujemy kolejny znak z bufora. Jeśli osiągnięto ostatni znak w linii, to kasujemy flagę LCDNeedUpdate danej linii, przez co możliwa będzie aktualizacja linii kolejnej.
Zapis danych do bufora wyświetlacza
Funkcja zapisu do bufora poza zapisem danych właściwych, tzn. tekstu modyfikuje również odpowiedni element tablicy LCDNeedUpdate, wskazujący na konieczność odświeżenia danej linii wyświetlacza :
int LCDWriteToBuffer(unsigned char x, unsigned char y, char * str)
{
int cnt = 0;
while(*str != 0)
{
LCDBuffer[y][x + cnt] = *str;
str++;
cnt++;
}
LCDNeedUpdate[y] = 1;
return cnt;
} |
Czyszczenie bufora
Po uruchomieniu programu bufor wyświetlacza należy wypełnić znakami "spacji". Dokonuje tego funkcja LCDClearBuffer().
void LCDClearBuffer(void)
{
int i,j;
for(j = 0; j < LCD_LINES; j++)
{
LCDCharIndex[j] = -1;
for(i = 0; i < LCD_CHARSPERLINE; i++)
{
LCDBuffer[j][i] = 32;
}
}
} |
Przykład zapisu do bufora :
LCDWriteToBuffer(0,0,"http://radzio.dxp.pl");
LCDWriteToBuffer(0,1,"WYswietlacz HD44780");
LCDWriteToBuffer(0,2,"Zapis buforowany");
LCDWriteToBuffer(0,3,"********************"); |
Do pobrania :
Driver z buforowaniem
|