Programowanie ■
W C parametry do funkcji przekazywane są przez wartość. Oznacza to, że wpisanie wartości do zmiennej będącej parametrem, nie będzie miało wpływu na zmienną w miejscu wywołania. Doskonale obrazuje to listing 24. Gdyby wewnątrz funkcji LCDsend wartość w zmiennej data została zmieniona przez funkcję LCDsendHalf wysłanie młodszej części komendy nic przebiegałoby prawidłowo.
Zasadę działania funkcji LCDsendHalf najlepiej przedstawi rysunek 26. Interesujące informacje znajdziesz także w ramce ABC... C omawiającej optymalizację przy dostępie do portów oraz zmiennych.
Funkcja LCDsend zajmuje się przesłaniem dalej starszej połowy a następnie młodszej wysyłanej danej/instrukcji. Przed zakończeniem oczekiwane jest 120ps. Jest to czas wykonywania większości komend Jednocześnie nic istnieją komendy, które wykonywałyby się szybciej.
Kolejne potrzebne nam funkcje przedstawia listing 25. Zgodnie z dokumentacją wysłanie komendy od wysłania danej różni się wyzerowaniem lub ustawieniem linii RS wyświetlacza. W najprostszej wersji program właściwy może korzystać już z przedstawionych dwóch funkcji. Dla wygody dodane zostały oddzielne funkcje powodujące czyszczenie wyświetlacza oraz powrót kursora na pozycję zerową. Są to jedyne dwie komendy wymagające dłuższego niż I20ps czasu oczekiwania.
D7 D6 D5 D4
k k i k
Dane wyświetlacza jflaast* PORTB
PORT(LCD DPORT) -f (pchtClcu upurt) &
Taki zapis uniezależnia nas od wartości L(JD_L)4 ▼
(0xDF«LCD_U4))
lammiDinmiu
Na lisitngu 26 znajduje się funkcja inicjująca wyświetlacz do pracy Jest to bezpośrednie przeniesienie na C grafu znajdującego się w „Elektronice dla Wszystkich” 1/98. W tym miejscu po raz ostatni używana jest instrukcja wysyłająca tylko połowę danych, co jest konieczne do czasu, aż interfejs nie zostanie ustawiony w tryb czterobitowy.
Zauważ, jak wygodne jest wydawanie komend przy korzystaniu ze zdefiniowanych wcześniej stałych. Ostatnia linia inicjacji może być w praktyce usunięta lub zmieniona na przykład tak, aby kursor pozostawał niewidoczny.
MITOWI
UUiKOtoririn
blglfleldlcłblB
(data & OxOF) « LCD D4; k
Listing 25 LCD: wysyłanie komend i danych
// Funkcje interfejsu
V0id LCDcommand(uint3 t command)
PORT(LCD_R5PORT) &= ~(1-«1CD_RS) \ LCUsenć(command);
VOid LCDdata(uint8_t data)
PORT(lcd_rsfort) |= 1«lcd_rs ; LCDocnć(data);
vo1d LCDcis(vo1d)
LCDcommand(LCDCCLS)| delaylC0us3(48);
void LCDhome(voi d)
LCDcomrr;and(LCDC_HOME); delaylC0us3(48);
▼
Przesunięcie o 4 powoduje, że i tak najstarsze bity zostaną stracone. Jednak zakładamy, że LCD D4 może mieć także ima wartość (ze względu na przenośność kodu inne połączenie wyświetlacza) Możemy nie przejmować *ię oszczędzaniem pamięci Kmrpilntor san zauważy, że jeśli LCD_D4 ma war.ość co najmniej 4, wykonywanie maskowania nie ma znaczenia i ta część obi czeń zostanie pominięta.
Rys. 26 przebieg obliczeń w funkcji LCDsendHalf.
Listing 26 LCD: inicjacja wyświetlacza
void LCDinit(void)
deLaylOOus8(150);
rORT(LCDJ*SrORT) 4- ~(i«lcc_rs);
deLaylOOus8(41);
LCDser.dHaLf (LCDC_FUtJC | LCDC_FUNC8b) ,* delayl00u38(2);
LCDser.dHaLf(LCDC_FUNCl LCDC_FUNC4b); delayl00us8(2);
}/ Teraz jest już 4h. Koniec korzystania 7 senriHalf LCDcoirjnand(LCDC_FUNC | LCDC_FUNC4b | LCDC_FUNC2L | LCDC_FUNC5x7) ; LCDcotrjnond(LCDC_Oh') J LCDclsO;
LCDcorcmand(LCDC_MOiJL | LCDC_MOD£k) ;
LCDcorrjnand(LCDC ON | LCDC ONDISFLAY | LCDC ONCURSOR) ;
}
ABC... C
Przyjrzyjmy się jeszcze raz listingowi 24. Spróbuj zamienić zapis:
ća -a = (data & OxOF) « LCD_D4; na odpowiednik zapisany w oddzielnych działaniach:
data &= 0x0 f; data «= LCD_D4;
Jeśli teraz skompilujesz program, okaże się, że nie zmienił on swojego rozmiaru. Jak to jest możliwe? Pamiętaj, żc zmienne w czasie obliczeń (za wyjątkiem yolatile) przechowywane są w rejestrach. Zmienne lokalne w zasadzie zupełnie poza rejestrami nie istnieją! Tak więc, rozpisując kod, zrobiliśmy dokładnie to samo.
co wcześniej zrobił kompilator. Pójdźmy jednak dalej:
data |= 0xF0; data &= OxOF; data «- LCD_D4 ;
Dodaliśmy linię, tworzącą nadmiarowe obliczenia, które jednak nie mogą wpłynąć na wynik (ustawiamy bity, które zaraz zosianą wyzerowane). Po kompilacji znowu przekonasz się, że rozmiar kodu nie uległ zmianie! Kompilator zauwaza, że pierwsza instrukcja nigdy nie zmieni wyniku i pomija ją. Okazuje się więc, żc przeprowadzając obliczenia za pomocą zwyczajnych zmiennych, mamy POi dość dużą swobodę zapisu obliczeń. Kompilator zwykle postara się, aby identyczny wynik uzyskać najprostszą drogą.
Może więc domyślasz się. co się stanie, jeśli pozostawimy samą linię:
data «= LCD_D4 \
Odpowiedź znajdziesz na rysunku 26.
Inaczej sprawa ma się z portami. Tutaj kompilator nie może założyć, ze ważny jest dla nas tylko końcowy wynik. W takim przypadku niemożliwe stałoby się generowanie sekwencji na portach, ponieważ kompilator, wypisałby tylko ostatnią z wartości. Pod względem dos:ępu porty można traktować w zasadzie jako zmienną volatile. Tak więc zamiana naszego zapisu do portu na pojedyncze działania:
(lcd_dfort) &= ~(OxOf«lcd C4); (LCD_DFORT) 1= ddLdJ
spowoduje zwiększenie rozmiaru wygenerowanego kodu. Na wyjściach procesora rzeczywiście najpierw wyprowadzenia D4-D7 zostaną wyzerowane, a następnie pojawi się na nich właściwa wartość.
Elektronika dla Wszystkich Październik 2005 39