Przedstawiona poniżej procedura odpowiedzialna jest za inicjalizację wyświetlacza po uruchomieniu urządzenia. Wykonuje ona sekwencję resetującą wewnętrzny procesor wyświetlacza oraz ustawia wstępne parametry pracy (tabela nr 3).
| Interfejs | 4-ro bitowy |
| Ilość linii | 2 |
| Rozmiar czcionki | 5 x 7 pikseli |
| Wyświetlacz włączony | Tak |
| Wyświetlacz wyczyszczony | Tak |
| Auto inkrementacja nadchodzących danych | Tak |
Tabela 3 - parametry wstępnej inicjalizacji wyświetlacza
void LCDinit(void) //inicjalizacja LCD
{
RSDDR|=(1<
CLR_E;
waitms(20);
CLR_D7;
CLR_D6;
SET_D4;
SET_D5;
unsigned char i;
for(i=0;i<3;i++) {SET_E;CLR_E;waitms(5);}
CLR_D4;
SET_E;
CLR_E;
LCDcmd(0x28); //function set - interface=4 bit,line=2,font=5x7
LCDcmd(ONDISP); //display ctrl - display on
LCDcmd(0x06); //entry mode set - increment
LCDcmd(0x01); //clear display
}
Podstawowe procedury obsługi wyświetlacza zaczerpnięte zostały z materiałów dostępnych na stronie www.avrfreaks.net. Jednak podczas pisania programu uległy tak poważnym zmianom, że obecnie tworzą bibliotekę stanowiącą własność autora niniejszej pracy.
LCDinit() wykorzystuje jedną z dwóch funkcji:
void LCDcmd(char d) //Wyślij komendę do LCD
{
CLR_RS;
LCDwrite(d);
}
void LCDchr(char d) //Wyślij 1 znak do LCD
{
SET_RS;
LCDwrite(d);
}
Wyświetlacz rozróżnia nadsyłane dane (znak lub komenda) poprzez stan linii RS w momencie nadawania danej. Aby w ogóle możliwe było wysłanie danych do wyświetlacza, potrzebna jest funkcja, która podzieli wysyłany bajt na dwie części po 4 bity, po czym kolejno je nada. Do tego celu służy LCDwrite(...):
void LCDresetdata(void) //wyzeruj szynę danych LCD
{
CLR_D4;
CLR_D5;
CLR_D6;
CLR_D7;
}
void LCDwrite(char d) //zapis bajtu do LCD
{
LCDresetdata();
if(d & 0x10){SET_D4;}
if(d & 0x20){SET_D5;}
if(d & 0x40){SET_D6;}
if(d & 0x80){SET_D7;}
SET_E;CLR_E;
LCDresetdata();
if(d & 0x01){SET_D4;}
if(d & 0x02){SET_D5;}
if(d & 0x04){SET_D6;}
if(d & 0x08){SET_D7;}
SET_E;CLR_E;
waitms(3); }
Kolejna z funkcji umożliwia ustawienie kursora w wybranej pozycji na ekranie:
void
LCDxy(char x,char y)
{
unsigned char com=0x80;
void LCDxy(char x,char y)
{
unsigned char com=0x80;
com|=(x|(y<<6));
LCDcmd(com);
}
Współpracuje ona z funkcją wyświetlającą tekst:
void LCDtext(char *txt)
{
while(*txt) {LCDchr(*txt++);}
}
Pozostałe funkcje służące do obsługi wyświetlacza są modyfikacją komendy LCDcmd(...).W pliku programu zdefiniowane są stałe (tabela nr 4), po których wysłaniu za pomocą LCDcmd() następuje żądana przez nas reakcja wyświetlacza.
| CLRDISP 0x01 | //czysc wyswietlacz |
| HOMEDISP 0x02 | //kursor na pozycje 0,0 |
| OFFDISP 0x08 | //wylacz ekran |
| ONDISP 0x0c | //wlacz ekran |
| SHIFT 0x9f | // przesuwanie kursora |
Tabela 4 - pomocnicze stałe do obsługi wyświetlacza
Funkcje typu LCD_CURSOR, LCD_BLINK, LCD_SHIFT to jedynie uproszczenie dla programisty. Równie dobrze w pętli programu można np. zamiast komendy LCD_BLINK(0) użyć komendy LCDcmd(dana), tylko że przy wielokrotnym używaniu tego typu zapisów program w końcu staje się całkowicie nieczytelny nawet dla jego twórcy.
//wlączanie kurosra
//a=1 wł
//a=0 wył
void LCD_CURSOR(char a)
{
if (a) {LCDcmd(ONDISP | 0x02);}
else {LCDcmd(ONDISP);}
}
//******************************************************************************
//miganie kursora
//a=1 wł
//a=0 wył
void LCD_BLINK(char a)
{
if (a) {LCDcmd(ONDISP | 0x01);}
else {LCDcmd(ONDISP);}
}
//******************************************************************************
//przesuwanie tekstu
//a=1 w prawo
//a=2 w lewo
//a=0 wył
void LCD_SHIFT(char a)
{
if (a == 1) {LCDcmd(SHIFT | 0x04);} //w prawo
else if (a == 2){LCDcmd(SHIFT);} // w lewo
else {LCDcmd(ONDISP);} // wyłączenie przesuwania
}
//******************************************************************************
Sterownik „SPA – 1” zawiera w sobie oprogramowanie dwóch niezależnych urządzeń korzystających z przetwornika ciśnienia, dlatego zdecydowano się na wyodrębnienie głównej funkcji odczytującej zmierzoną wartość:
//***********************************************************
//odczytanie wartosci zmierzonej przez A/C
unsigned int getADC(void)
{
ADCSRA|=0b01000000;
zwrot=ADC;
return zwrot;
}
Następnie otrzymane dane są przetwarzane w zależności od trybu pracy urządzenia. Dla timera pneumatycznego jest to funkcja:
//***********************************************************
//timer pneumatyczny - operacja różniczkowania odczytu z A/C
unsigned int liczADC(void)
{
getADC();
zwrot=zwrot* 2;
por=(zwrot/ 8);
//...................
if(por>= cisnienie+czulosc)
{
ustaw=3;
TIMSK=0b00000100;
cisnienie=por;
//...................
}
else
{
cisnienie=por;
//...................
}
}
Zaś dla wskaźnika wysokości słupa wody:
void wyswADC(void)
{
getADC();
zwrot-=48;
value=zwrot/8+poziom;
..............
.............
}
Obliczenia w trybie timera pneumatycznego polegają na cyklicznym zapamiętywaniu zmierzonej wartości i porównywaniu jej z wartością poprzednią. Jeżeli wartość ta jest większa od poprzedniej o ustawioną przez użytkownika czułość, to układ zostaje wyzwolony. W przeciwnym wypadku zmiana zmierzonej wartości pozostaje zignorowana. W ten sposób uzyskuje się niezależność działania układu od zmian ciśnienia w pomieszczeniu maszynowni basenu.
Timer 0 wykorzystywany jest w 2 przypadkach: użytkownik wybrał opcję ustawienia trybu pracy urządzenia lub sterownik pracuje w trybie wskaźnika wysokości słupa wody. W tych sytuacjach timer wykorzystywany jest do cyklicznej zmiany wyświetlanego komunikatu. Timer ten jest jedynie 8-io bitowy i nie umożliwia bezpośredniego uzyskania odpowiednio dużego czasu opóźnienia (nawet z zastosowaniem preskalera). Dlatego potrzebne było zadeklarowanie dodatkowej zmiennej „plansza”, poprzez którą określany jest aktualny tekst do wyświetlenia. Listing przerwania od timera 0 znajduje się poniżej:
SIGNAL(SIG_OVERFLOW0)
{sei();
plansza++;
if(plansza==255){plansza=0;}
TCNT0=0b00000000;
}
Ze względu na dwubajtową pojemność licznika 1 możliwe jest uzyskanie za jego pomocą przerwania co 1 sekundę. Uzyskana dokładność odmierzania czasu jest całkowicie wystarczająca dla prezentowanego urządzenia, jednak przeprowadzone próby dowiodły, że stabilność długoterminowa takiego rozwiązania jest mało zadowalająca. Tak skonstruowany zegar może przekłamywać nawet o kilkadziesiąt sekund na dobę. Z tego właśnie powodu konstruktorzy Atmela umożliwili jednemu z timerów pracę z typowym nisko częstotliwościowym kwarcem zegarkowym. W moim przypadku zastosowanie dwóch kwarców było z ekonomicznego i funkcjonalnego punktu widzenia całkowicie nieuzasadnione.
SIGNAL(SIG_OVERFLOW1)
{
sekunda--;
if ((sekunda ==0) && (minuta ==0))
{
TIMSK=0b00000000;
sekunda = eeprom_read_byte(0x65);
minuta = eeprom_read_byte(0x66);
}
if (sekunda == 255)
{
sekunda=59;
if ((minuta ==0) && (sekunda < 60))
{minuta=0;}
else {minuta--;}
}
TCNT1=0x573F; //dla F=11059200, 43200 cykle zegara
//TCNT1=0x85ED; //dla F=8000000, 31250 cykle zegara
//TCNT1=0x0BDB; //dla F=16000000,62500 cykle zegara
}
Jak wynika z analizy listingu, możliwa jest praca z kilkoma różnymi częstotliwościami kwarcu. Wybrane zostały te najbardziej typowe, dla innych należałoby przeliczyć wartość jaką należy wpisać do rejestru TCNT1.
Obsługa watchdoga to jedna z wielu możliwości zaimplementowanych bibliotecznie w AVR Studio. Zasadniczo sprowadza się do uruchomienia układu nadzorcy z jednym z zadanych czasów oczekiwania, a następnie do ciągłego resetowania jego pamięci. W przypadku mojego programu wygląda to tak:
wdt_enable(WDTO_2S); //start watchdoga – 2 sek. czasu oczekiwania
wdt_reset(); //cykliczne resetowanie nadzorcy
Obsługa wejść / wyjść możliwa jest po ich zainicjowaniu poprzez wpisanie odpowiednich wartości do rejestrów PORTx i DDRx. Aby odczytać stan wejścia lub wyjścia, należy zbadać stan odpowiedniego bitu w rejestrze PINx. Przykład poniżej przedstawia w jaki sposób można to zrealizować najprościej (bez użycia aliasów wejść).
// USTAWIENIE PORTOW KLAWISZY JAKO WEJŚCIA
PORTC = (1<
Tym sposobem porty zostały skonfigurowane jako wejściowe z rezystorami podciągającymi do plusa zasilania. Poniższą procedurą można odczytać stan wejść:
if(bit_is_clear(PINC,6))
{
...........
}
Podczas pisania dużych programów warto jest przypisać wszystkim używanym portom aliasy. Poprawia to w znaczący sposób czytelność programu:
//*************************
#define KLAW_UP_pin PINC
#define KLAW_UP_bit 6
#define KLAW_UP_port PORTC
#define KLAW_UP_ddrb DDRC
Obsługa UART wykorzystywana w urządzeniu SPA umożliwia przesłanie wszelkich parametrów konfigurowalnych pomiędzy komputerem a sterownikiem za pomocą zwykłego portu RS232. Ponieważ magistrala RS485, wykorzystana w celu zwiększenia długości RS232, jest typu HALF – DUPLEX, należało stale czuwać nad kierunkiem transmisji układu MAX485. Zrealizowano to w przerwaniu od nadania znaku: układ w czasie normalnej pracy stale pozostaje w nasłuchu. Po odebraniu żądania wysłania danych, przełącza się na nadawanie, wysyła daną a następnie powraca do normalnej pracy. Magistrala zostaje przełączona znów do trybu nasłuchu dopiero w momencie wysłania ostatniego bitu, o czym informuje przerwanie od zakończenia transmisji.
Aby możliwa była praca USART’u należy go zainicjalizować. Służy do tego funkcja UART_init(MYUBRR):
//***********************************************************
#define BAUD 57600 // prędkość transmisji po RS232
#define MYUBRR F_CPU/16/BAUD-1
void UART_init(unsigned int ubrr) // inicjalizacja
{
UBRRH = (unsigned char) (ubrr>>8); // ustawienie prędkości
UBRRL = (unsigned char) ubrr;
UCSRB = (1<
UCSRC = (1<
Do wysłania pojedynczego znaku przez USART wystarczy funkcja USART_out(...), która jako argument przyjmuje znak do nadania:
void USART_out (unsigned char data) // wysłanie znaku
{
while ( !( UCSRA & (1<<UDRE)) );
UDR = data;
}
Aby w łatwy sposób wysłać ciąg znaków, np. napis, należy skorzystać z jednej z poniższych funkcji:
void rs_text(char *txt)
{
while(*txt)
{
USART_out(*txt++);
}
}
void rs_text_mem(const char *s)
{
char c;
while ((c = pgm_read_byte(s++)))
{
USART_out(c);
}
}
Pierwsza z nich wysyła na USART ciąg znaków podany jako jej argument, zaś druga ciąg znaków umieszczony w pamięci programu.
Procedura obsługi zegara RTC nie została jeszcze zaimplementowana ani w opro-gramowaniu mikrokontrolera, ani w programie „SPA – Soft”. Jednakże umieszczenie układu PCF na płytce modułu „SPA – 1” daje taką możliwość.
Copyright © 2008-2010 EPrace oraz autorzy prac.