ła funkcja pomocnicza Icd LineStart, która przyjmuje numer linii, a zwraca adres jej początku w wyświetlaczu. 7 funkcji tej będziemy jeszcze korzystać. Zauważ, żc została ona stworzona w taki sposób, aby zmieniała się w zależności od zdefiniowanej liczby linii wyświetlacza Jeśli liczba linii nie będzie odpowiadała żadnej ze znanych - zostanie zgłoszony błąd. Przyjrzyj się algorytmowi przeliczającemu pozycję na adres w pamięci modułu alfanumerycznego. Przypominam, ze znak ‘%' oznacza resztę z dzielenia.
Listing 76
Deklaracja i definicja funkcji pat wyświetlacza
static int lcd put(char c, file* f)
{
// zabezpieczenie przed przepełnieniem
if(.lcd_curpo.«? >= F.T.F.MS(lcd_buffer)) lcd curpos = 0;
// Zapfs do bufora
lcc_buf fer [Lcd_curpoa++] =* ej
return 0;
Zmienna przechowująca „ogonki”
Otwórz plik led.h. Zanim przejdziemy dalej, wprowadzimy tutaj definicję struktury LCD_LOCAL. Umieścimy ją w pliku nagłówkowym, ponieważ sama deklaracja wyglądu znaków specjalnych pojawi się poza modułem lcd. Listing 81 przedstawia deklarację potrzebnej nam struktury. Pamiętaj, żc analogicznie do pliku harddcf.h, deklarację tę, razem z dołączeniem plików nagłówkowych, należy umieścić między odpowiednimi komendami #ifndef oraz #endif. Nadawana jest nazwa
pomocne przy ustawianiu kursora. Funkcja IcdJdoToAdr ustawia kursor wewnątrz procesora, ale nie ma wpływu na jego pozycję bezpośrednio na wyświetlaczu. Dopiero funkcja IcdJJpdateCurPos odwzorowuje pozycję kursora w module LCD. Wprowadzona zosta-
Usting 75
Funkcje niskiego poziomu obsługi wyświetlacza
#define lcd_epulse() \
{fokt(lco ctkl?ort) |= 1«lcd_e; \ _delay_us(0.2 5); \
PGR?(LCD CTRLPORT) ~(1«1CD_E);>
/ j odbiór danej - funkcja wewnętrzna static inline .iłntf r l rd nat(void)
uint8_t dana;
// ustaw-enie portu lcd jako wejścia
dfr(lcd_datapcrt) - OxOC;
// Aktywacja odczytu PCRT(LCD_CTRLEORT) |* 1«LC3_EJ deiay us(0.5);
7/ Skopiowanie danych z wyj. modułu
dana ■ PIN(LCDJ)ATAPORr);
// Deaktywacja wyjęcia FNARl F por?(lcd_ctrlport) &*= ~(1«lcd_e); return dana;
ł
uintti t lcd Get3F(void)
( ”
// Wyprowadzenia do odczytu statusu PORT Clcd_ctrl port) |- 1«lcd_rw; port(lcd ctrlport) &= ~(1«lcd_rs):
// Odczekanie wymaganego czasu _dcLay_ue(0.25);
7/ Odczyt danej return lcd c-cl();
ł
// Czeka ra wyzerowanie flagi zajętości
void 1cd_Wa i t B F(voi d)
// Rit zajętości to bit najstarszy whileCO I- (0<80 A Lcd_GetRFO ) ) 1};
// wysłanie danej - funkcja wewnętrzna
static void l~d_send(uint3 t dana)
// Ustawienie portu lcd jako wyjęcia
DDR(LCD_DATAPORr) - 0>FF;
// Przesłanie na port danej
PORT(l,CD_nATAPORT) ■ dana;
// Przesłame do łcd lcd_epul3e();
ł
// wysłanie darej
V0id lcd Send0ata(uint8 t dana)
lcd_Wait3FO;
// Wyprowadzenia dla zapisu danej P0RT(LCD_CTPI,30RT) Arn ~(1«LCD_RW);
port(lcd_ctrl?ort) I* 1«lcd_rs;
// Odczekanie oraz wysłanie _delcy_us(0.25); lcd_Send(dana); i
// wysłanie instrukcji sterującej
V0id lcd Sendlr.str(uint8 t dana)
lcd_Wa-tBF();
// Wyprowadzenia dla zapisu instrukcji port(lcd_ctrlport) A~
~(l«LCD PW I 1«LCC_RS);
// OdczekanTe oraz wysłanie _aelay_us(0.25); lcd_Senć(dana);
nowemu typowi zmiennej LCD LOCAL PGM, która jest naszą strukturą umieszczoną w pamięci programu. Aby możliwe było wykorzystanie makra PROGMEM, dodajemy plik nagłówkowy <avr/pgmspace>. W tej chwili są to wszystkie dane. jakie wprowadzamy do pliku led.h.
Listing 77 - Inicjacja wyświetlacza.
void lcd Init(void)
{
lcd_cis();
fortClc^ctrlport) &=
_del<ły_ts (0. 25); lcd_senć(LCOC func | delay_ms(4.35; lcd SencCLCDC FUNC |
_delay_us(200); lrd_Senć(LCDC_FUNC |
// Już nożna sprawdzać RF #if LCD_SY == 1
lcd_3endlnstr(LCDC_FUNC | LCDC_FUNC8b | LCDC_FUNC1L); #else
lcd_sendinstr(LCUC_FUNC
Jendif
lcdSendlnstr(LCDC ON); lcd_3endInstr(LC0C_CLS); lcd”spru1Tn.sr r(LCDC_NODE | LCDC_MODER) \ lcd WaitBF();
■ ~(1«LCC_RW | _«LCD_RS) LCDC_FUNC8b)J LCDC_FUNC8b);
LCDC FUNC8b);
LCDC FUNC8b | LCDC FLNC2L);
ABC... C
Propozycja na opóźnienia: delay.ms oraz delay us
W bibliotece avr- ibc, w pliku <util/delay h> znaj-dąją się dwie tytułowe funkcje służące do wygodnego generowania opóźnień. Ich wykorzystanie nie daje nam takiej kontroli nad czasem opóźnienia, jak w przypadku samodzielnego napisania pętli, jednak tam gdzie nic jest ważne bardzo dokładne obliczenie czasu, są one wyjątkowo wygodne w zastosowaniu.
Obie funkcje przyjmują jako argumen: wartość typa double (zmiennoprzecinkową!!, która oznacza żądane opóźnienie w mili- lub mikrosekundach. Tutaj niektórych może ogarnąć słuszna zgroza: wykorzystanie arytmetyki zmiennoprzecinkowej po to, aby wygenerować opóźnienie, wydaje się dużą przesadą. Jednak funkcje tc zostały napisane jako statyczne funkcje typu Inllne. Oznacza to, że działają one trochę jak makra - są rozwijane w miejscu wywołana. Teraz, jeśli argument będzie wartością stalą (!), jako wartość stała pojawi się w rozwinięciu. Ponieważ reszta wartości w chlic/eniach jest wartościami stałymi, m całe
ohliczenie zostanie wykonane laetap.e kompilacji programu - procesor nie będzie wykonywał żadnej operacji zmiennoprzecinkowej.
Opóźnienia są obliczane z uwzględnieniem prędkości oscylatora, klotu została zdefiniowana w pliku makefile. Ważne jest jednak, aby pamiętać o tym, że obie funkcje mają pewną maksymalna wartość opóźnienia, ja<ą są w stanie wygenerować.
Funkcja rielayim jest w stanie wytworzyć opóźnienie o maksymalnej wartości równe 2ó2.14ms / F_CPU, gdzie F CPU oznacza częstotliwość zegara w megahercach. Funkcja delayus może zająć procesor na nic dłużej liż 768us / K_CPU, gdzie F CPU jest także liczone w' megahercach.
Funkcje zostały napisane w taki sposób, żc możemy mieć pewność, iż wygenerowane zostanie opóźnienie nie krótsze niż zadane Wyjątkiem jest przekroczenie maksymalnego zakresu - wtedy wygenerowane zesłanie maksymalne możliwe opóźnienie.
Dzięki temu, że funkcje opóźnień do icj pory pisaliśmy samemu, powinieneś być w stanic zrozumieć kody zawarte w pliku <util/deiay.łi>. Zachęcam Cię do ich przejrzenia
Przykłady: hs/uig 75 oraz 77
Elektronika dla Wszystkich Maj 2006 45