Kurs AVR-GCC Wyświetlacz LCD od nokii 3310
Jak mówią, jeden obraz wart jest tysiąca słów, dlatego warto zainteresować się wyświetlaczami graficznymi. W tym artykule zajmiemy się wykorzystaniem wyświetlaczy LCD od popularnych niegdyś telefonów komórkowych Nokia 3310/3410, na allegro można taki kupić w cenie 5..7zł.
Wyświetlacz LCD od telefonu Nokia 3310 posiada monochromatyczny ekran o rozdzielczość 84x48 (wymiary czynnego pola ekranu 30x20mm). Wyposażony jest w sterownik PCD8544(lub podobny) z szeregowym interfejsem SPI. Dostępny jest wyłącznie tryb graficzny, ale można łatwo programowo zaaranżować tryb tekstowy; przykładowo można pokazać sześć linii tekstu, każda po 14 znaków wielkości 6x8 pikseli.
Wyświetlacz od Nokii3410 wygląda identycznie jak wyświetlacz od Nokii3310, ten sam układ wyprowadzeń złącza, ale ma nieco większą rozdzielczość: 96x65 (można pokazać 8 linii tekstu, każda po 16 znaków 6x8). Wyświetlacz od Nokii3410 wyposażony jest w kontroler "Philips OM6206", który programuje się prawie w ten sam sposób jak kontroler PCD8544 z Nokii3310. Do obsługi obu typów wyświetlaczy będziemy wykorzystywać te same funkcje napisane w języku C.
LCD Nokia 3310 | LCD Nokia 3410 | |
---|---|---|
Rozdzielczość | 84x48 | 96x65 |
kontroler | PCD8544 | OM6206 |
Podłączenie wyświetlacza do mikrokontrolera AVR
Wyświetlacz jest delikatnym elementem, dlatego lepiej jest pozostawić go w jego oryginalnej ramce. Ramkę można odpowiednio przyciąć i wraz z wyświetlaczem przykręcić wkrętami do jakieś płytki, albo przykleić.
Na tylnej stronie ramki wyświetlacza dostępne jest złącze; w telefonie miedziane styki złącza przylegają do ścieżek na płytce drukowanej.
Nr styku | Sygnał | kierunek | Opis |
---|---|---|---|
1 | VDD | zasilanie | Zasilanie 2.7 .. 3.3 V |
2 | SCLK | wejście | Serial clock. Sygnał zegarowy taktujący dane na linii SDIN |
3 | SDIN | wejście | Wejście danych synchronizowane sygnałem SCLK |
4 | D/C | wejście | Wejście wyboru rodzaju danych wprowadzanych do sterownika (wyświetlane-1,sterujące -0) |
5 | SCE | wejście | Wejście aktywujące interfejs szeregowy(aktywny stan niski) |
6 | GND | zasilanie | Masa zasilania |
7 | VOUT | zasilanie | Ouptut voltage. Kondensator elektrolityczny 1-10 uF między VOUT i GND. |
8 | /RES | input | Reset LCD (aktywny stan niski) |
Najprostszym sposobem przyłączenia wyświetlacza jest przylutować przewody bezpośrednio do miedzianych styków złącza, bez rozbierania ramki. Ale trzeba lutować szybko i zdecydowanie, inaczej plastykowa listwa, na której osadzone są miedziane styki złącza może się pod wpływem ciepła roztopić i odkształcić.
Wyświetlacz wymaga napięcia zasilania 2.7-3.3 VDC, zatem najprościej jest zasilać całość: mikrokontroler i wyświetlacz napięciem 3.3V. W takim przypadku wejścia sygnałowe wyświetlacza można podłączyć bezpośrednio do wyprowadzeń portów we/wy mikrokontrolera. Na schematach wejścia sygnałowe wyświetlacza połączone są do wyprowadzeń mikrokontrolera skojarzonych ze sprzętowym interfejsem SPI AVRa atmega. W przykładowych programach, które będziemy dalej uruchamiać, można wybrać sprzętowe lub programowe SPI; w przypadku wyboru programowego SPI, sygnały sterujące wyświetlaczem można przyłączyć do dowolnych porów we/wy AVRa.
Nie wszystkie wersje AVR mogą być zasilane napięciem 3.3V, schemat nr 2 pokazuje, jak można podłączyć wyświetlacz zasilany napięciem 3.3V do mikrokontrolera zasilanego napięciem 5V. Wyświetlacz od Nokii 3310 zużywa minimalne ilości prądu, napięcie 3.3V do jego zasilania można uzyskać stosując dzielnik napięcia: rezystor i dioda zenera 3.3V. Na schemacie wejścia sterujące wyświetlacza połączono z portami we/wy AVRa poprzez bufor 74ls07. Scalony układ 74LS07 zawiera sześć cyfrowych buforów z wyjściami typu otwarty kolektor, wyjścia te zostały podciągnięte rezystorami 4k7 do napięcia 3.3V.
Komunikacja z wyświetlaczem
Wyświetlacz od Nokii 3310 wyposażony jest kontroler PCD8544 z szeregowym interfejsem SPI(Serial Peripheral Interface). Interfejs posiada cztery wejścia:
- SDIN - szeregowe wejście danych,
- SCLK - sygnał zegarowy taktujący dane na linii, SDIN
- /SCE - wejście aktywujące interfejs szeregowy,
- D/C - wejście wyboru rodzaju danych (wyświetlane lub sterujące).
Komunikacja przebiega tylko w jednym kierunku, od mikrokontrolera do wyświetlacza. Zależnie od stanu linii D/C, bajty danych wysyłane do wyświetlacza, mogą być interpretowane przez kontroler jako komendy do wykonania albo dane zapisywane do pamięci RAM obrazu; stan wysoki na linii D/C sygnalizuje daną, stan niski komendę.
Rys.1 pokazuje przebieg transmisji jednego bajtu danych od mikrokntrolera do wyświetlacza Komunikację rozpoczyna się od ustawienia linii /SCE w stan niski, co aktywuje interfejs SPI. Jeśli wysyłany bajt jest komendą, linię D/C ustawia się w stan niski, a jeśli zwykłą daną - w stan wysoki Następnie, linią SDIN, szeregowo(bit po bicie) przesyła się 8 bitów danej, zaczynając od bitu najbardziej znaczącego. Transmisja szeregowa jednego bitu przebiega w następujący sposób: Wpierw na linii danych SDIN ustawia się stan niski lub wysoki, zależnie od wartości przesyłanego bitu; następnie na linii SCLK podaje się impuls: 0-1-0. Zmiana na linii /SCE stanu niskiego na wysoki sygnalizuje zakończenie transmisji.
A oto funkcja "lcd_write_byte" realizująca w sposób programowy przesłanie jednego bajtu z mikrokontrolera do wyświetlacza:
void lcd_write_byte(unsigned char c_d, unsigned char data ) { unsigned char m; LCD_CE_CLR if(c_d) LCD_DC_SET else LCD_DC_CLR for(m=0x80; m; m>>=1) { if(data & m) LCD_DATA_SET else LCD_DATA_CLR LCD_CLK_SET LCD_NOP LCD_CLK_CLR } LCD_CE_SET }
Pierwszy argument funkcji "lcd_write_byte" wskazuje czy wysyłana jest komenda, czy bajt danych (0-komenda, 1-bajt danych); drugi argument to kod komendy lub bajt danych. Użyte w funkcji makrodefinicje: LCD_x_SET, LCD_x_CLR ustawiają na liniach sygnałowych stan wysoki lub niski, a makro LCD_NOP to krótkie opóźnienie w programie.
Mikrokontrolery atmega wyposażone są w sprzętowy interfejs SPI, który możemy wykorzystać do sterowania naszym wyświetlaczem. Poniżej znajduje się listing drugiej wersja funkcji "lcd_write_byte", która wysyła do wyświetlacza jeden bajt z wykorzystaniem sprzętowego interfejsu SPI.
/**/ SPCR =(1<<SPE)|(1<<MSTR)|(1<<SPR0); /**/ void lcd_write_byte(unsigned char c_d, unsigned char data ) { LCD_CE_CLR if(c_d) LCD_DC_SET else LCD_DC_CLR SPDR = data; while(!(SPSR & (1<<SPIF))); LCD_CE_SET }
Do kontroli sprzętowego interfejsu SPI AVRa atmeaga wykorzystuje się trzy rejestry IO :
- SPDR -SPI Data Register,
- SPCR -SPI Control Register,
- SPSR -SPI Status Register.
Sprzętowy interfejs SPI mikrokontrolera atmega ma szerokie możliwości konfiguracji, ale do sterowanie naszym wyświetlaczem pasują ustawienia domyślne. Pozostaje tylko wybrać szybkość działania interfejsu SPI, tzn. częstotliwość sygnału taktującego na linii SCLK. Służą do tego celu bity SPR0, SPR1 rejestru SPCR(SPI Control Register) oraz bit SPI2X rejestru SPSR(SPI -Status Register), tabela poniżej:
SPI2X | SPR1 | SPR0 | SCK Frequency |
---|---|---|---|
0 | 0 | 0 | fosc/4 |
0 | 0 | 1 | fosc/16 |
0 | 1 | 0 | fosc/64 |
0 | 1 | 1 | fosc/128 |
1 | 0 | 0 | fosc/2 |
1 | 0 | 1 | fosc/8 |
1 | 1 | 0 | fosc/32 |
1 | 1 | 1 | fosc/64 |
W naszych przykładach transmisja przez sprzętowy interfejs SPI będzie przebiegać z częstotliwością fosc/16, czyli przy częstotliwości pracy mikrokontrolera 16MHz sygnał taktujący na wyjściu SCLK będzie mieć częstotliwość 1MHz.
Ustawienie bitów SPE i MSTR w rejestrze SPCR(SPI Control Register) włącza interfejs SPI AVRa w trybie MASTER. Wysłanie bajtu następuje po zapisaniu danej do rejestru SPDR(SPI Data register), a ustawiony bit SPIF w rejestrze SPSR(SPI Status Register) sygnalizuje zakończenie transmisji.
Inicjalizacja wyświetlacza
Poniżej znajduje się listing funkcji inicjującej wyświetlacz. Na początku funkcji program resetuje wyświetlacz ustawiając linie /RES w stan niski na ok 15ms, po resecie funkcja wysyła kilka instrukcji inicjujących wyświetlacz.
/* Inicjuje wyświetlacz */ void lcd_init(void) { LCD_RST_CLR; // < 30ms _delay_ms(15); LCD_RST_SET LCD_CE_SET lcd_write_byte(LCD_CMD, 0x21); // Function set - extended instruction set lcd_write_byte(LCD_CMD, 0x13); // Bias - 1:48 lcd_write_byte(LCD_CMD, 0x06); // Temperature Control lcd_write_byte(LCD_CMD, 0xa5); // Set Vop lcd_write_byte(LCD_CMD, 0x20); // Function set - basic instruction set, horizontal addressing lcd_write_byte(LCD_CMD, 0x0C); // Display control - normal mode }
Pamięć RAM obrazu wyświetlacza
Każdemu pikselowi na ekranie wyświetlacza odpowiada jeden bit w pamięci RAM obrazu. Ekran wyświetlacza od Nokii 3310 ma rozdzielczość 84x48, zatem, aby zapamiętać cały obraz, kontroler wyświetlacza potrzebuje mieć 4032 bity pamięci RAM obrazu. Bity w pamięci obrazu pogrupowane są w komórki po 8 bitów(bajty), zapisując dane do pamięci obrazu zmieniamy od razu cały bajt(osiem pikseli na ekranie wyświetlacza). Niestety nie ma możliwości odczytu danych z pamięci RAM obrazu :( Komórki pamięci RAM obrazu są ponumerowane, numer komórki w pamięci nazywany jest jej adresem. Kolejnym ośmiu bitom każdej komórki pamięci RAM obrazu, odpowiada osiem kolejnych pikseli na ekranie LCD, ALE w kierunku pionowym, LSB na górze, MSB na dole ,patrz rys. 2. Przykładowo, jeśli gdzieś w pamięci RAM obrazu zapisana zostanie bajt o wartości 0xff, to w odpowiednim miejscu na ekranie wyświetlacza pojawi się pionowa kreska | o wysokości 8 pikseli, patrz rys.2.
Dodatkowo, obok pamięci RAM obrazu, kontroler wyświetlacza posiada rejestr: licznik_adresu. Liczniku_adresu zawiera adres wskazujący na komórkę w pamięci RAM obrazu, gdzie zostanie zapisany następny bajt danych wysyłany do wyświetlacza, gdy na linii D/C występuje stan wysoki. Każdorazowo po zapisaniu bajtu danych w pamięci obrazu, licznik adresu zwiększa się automatycznie o jeden i wskazuje na następną komórkę w pamięci RAM obrazu. Wysyłając do wyświetlacza komendy: Set_Y_address_of_RAM i Set_X_address_of_RAM można ustawić zawartość licznika_adresu tak, aby skazywał na dowolną komórkę w pamięci RAM obrazu.
Komendy kontrolera
Jak pisałem wcześniej, bajty danych wysyłane do wyświetlacza mogą być interpretowane przez kontroler wyświetlacza jako komendy do wykonania albo jako dane kopiowane do pamięci RAM obrazu - zależnie od stanu linii sygnału D/C. Bajty danych i komendy będziemy wysyłać do LCD omawianą wcześniej funkcją:
Parametr "c_d" to stan linii D/C, parametr "byte" to wysyłany bajt danych albo kod komendy.
Zbiór wszystkich komend wyświetlacza można znaleźć w dokumentacji kontrolera PCD8544. W naszych przykładach będziemy najczęściej wysyłać do LCD komendy Set_Y_address_of_RAM i Set_X_address_of_RAM", żeby ustawić zawartość licznika_adresu.
Wszystkie komendy kontrolera mają rozmiar jednego bajta(osiem bitów). Na bitach 6..0 kodu komendy Set_X_address_of_RAM kodowana jest współrzędna X, która może przyjmować wartości od 0 do 83, patrz rys.2.
1 X X X X X X X
Na bitach 2..0 kodu komendy Set_Y_address_of_RAM kodowana jest współrzędna Y, która może przyjmować wartości od 0 do 5, patrz rys.2
0 1 0 0 0 Y Y Y
W przypadku wyświetlacza od Nokii3410 z kontrolerem OM6206 można ustawić współrzędną Y na wartości 0..7, a współrzędną X na wartości 0..96
I to jest wszystko, co potrzebujemy wiedzieć, aby wykorzystać wyświetlacz od Nokii_3310. W dalszej części artykułu uruchomimy kilka przykładowych programików. Wszystkie przykłady są maksymalnie uproszczone, bo jak wiadomo, dobry przykład, to prosty przykład:)
Przykład 1. Jak wyświetlić obrazek
W programie MicroLCD narysowałem przykładowy obrazek, który zostanie pokazany na ekranie naszego wyświetlacza. Obrazek ma wymiary 84x48 - zajmie cały ekran wyświetlacza.
Dane obrazka wyeksportowałem do pliku tekstowego "hello_img.c", wybierając w menu programu MicroLCD opcję:
File->Export (.C hex file)
Plik z danymi obrazka zostaje dołączony do programu instrukcją preprocesora #include "hello_img.c", dane obrazka trafią do tablicy typu char.
unsigned char hello_img[] PROGMEM = { #include "hello_img.c" };
Słówko PROGMEM w deklaracji tablicy decyduje, że tablica zostanie utworzona w pamięci programu (we FLASHu AVRa).
Teraz, aby pokazać obrazek na ekranie, wystarczy przekopiować dane z tablicy we FLASHu AVRa do pamięci RAM obrazu wyświetlacza, robi to funkcja "lcd_image".
/* */ void lcd_image(unsigned char img[],char x0,char y0,char w,char h) { unsigned int i,j,k; for(i=0,j=0,k=0; i<h; i++) { /* Komenda LCD "Set Y address of RAM" */ lcd_write_byte(LCD_CMD, 0x40|(i+y0)); /* Komenda "Set X address of RAM"*/ lcd_write_byte(LCD_CMD, 0x80|(x0)); /* Kopiowanie z FLASH do pamięci obrazu LCD */ for(j=0; j<w ; j++,k++) lcd_write_byte(LCD_DATA, pgm_read_byte(&img[k])); } }
Pierwszy argument funkcji "lcd_image" to tablica we FLASHu z danymi obrazka; kolejne dwa argumenty (x0,y0), to położenie górnego lewego rogu obrazka na ekranie LCD; argument czwarty i piąty to szerokość i wysokość obrazka. Parametry: y0 i wysokość obrazka trzeba podać w bajtach (osiem pikseli pionowo, patrz rys.2); na przykład, jeśli obrazek ma wysokość 48 pikseli, to należy wstawić 6.
A oto główny pliku pierwszego przykładu. Najpierw funkcja "lcd_init" inicjuje wyświetlacz, następnie funkcja "lcd_image" kopiuje dane obrazka z tablicy we FLASHu do pamięci RAM obrazu wyświetlacza.
/* Plik "main.c" */ #include <avr/io.h> #include <avr/pgmspace.h> #include "lcd.h" /* Dołącza dane z obrazkiem, obrazek zostanie umieszczony w pamięci programu, we FLASHu */ unsigned char hello_img[] PROGMEM = { #include "hello_img.c" }; int main(void) { /* Inicjuje wyświetlacz */ lcd_init(); /* Wyswietla obrazek */ lcd_image(hello_img,0,0,84,6); /* Zatrzymanie programu */ while(1); return 0; }
Katalog z plikami źródłowymi przykładu pierwszego można pobrać klikają w link: 001.zip; katalog zawiera także plik Makefile, który należy dostosować do "sprzętu", tzn. wybrać typ AVRa, częstotliwość pracy mikrokontrolera, programator -przykłady były testowane na atmega88 8MHz. Plik Makefile wygodnie jest edytować z pomocą programu MFile dostarczonego razem z pakietem WinAVR. Jeśli nie wiesz jak skompilować przykłady, to polecam artykuł "Szybki start z WinAVR"
Przykład 2. Tekst na ekranie LCD
Nasz wyświetlacz nie udostępnia trybu tekstowego, zatem aby napisać na ekranie tekst, trzeba samemu rysować litery. W programie MicroLCD przygotowałem obrazek ze wzorami liter, cyfr i innych znaków z tablicy kodów ASCII, każdy znak narysowany jest na polu o wymiarach 6x8 punktów. Dane obrazka ze wzorami znaków wyeksportowałem do pliku tekstowego i dołączyłem do programu komendą preprocesora #include "font6x8p.c". Wzory znaków trafią do tablicy o nazwie "font" umieszczonej w pamięci programu (we FLASHu)
Aby tekst pojawił się na ekranie wyświetlacza, program będzie kopiował wzory znaków z tablicy "font" do odpowiedniego miejsca w pamięci RAM obrazu wyświetlacza. Ekran wyświetlacza podzielony jest na linie tekstowe o wysokości ośmiu punktów, taka jest organizacja pamięci RAM obrazu naszego wyświetlacza, patrz rys.2. Żeby narysować jeden znak o wymiarach 6x8, trzeba skopiować sześć kolejnych bajtów; każdy bajt to osiem ułożonych pionowo punków na ekranie wyświetlacza. Wyświetlacz Noki_3310 ma rozdzielczość 84x48, zatem można na nim pokazać 6 linii teksu, po 14 znaków 6x8. A to jest funkcja pisząca na ekranie wyświetlacza tekst:
/* Wyświetla tekst - znaki 6x8 s - ciąg znaków zakończony zerem x - pierwsza kolumna 0..84(96) y - wiersz 0..5(7) */ void lcd_text(char s[], unsigned char x, unsigned char y) { unsigned int c,j; unsigned char i,k; /* Kody polskich literek z ogonkami */ char pl[] = {'ą','ć','ę','ł','ń','ó','ś','ź','ż','Ą','Ć','Ę','Ł','Ń','Ó','Ś','Ź','Ż'}; /* Ustawia położenia pierwszej litery tekstu na ekranie LCD */ lcd_write_byte(LCD_CMD, LCD_SETY|(y)); lcd_write_byte(LCD_CMD, LCD_SETX|(x)); /* Rysuje znak po znaku */ for(k=0; (c = s[k]); k++) { /* Dopasowuje kody znaków z ogonkami */ for(i=0; (i<18) && (pl[i]!=c); i++) ; if(i<18) c= 0x80+i; /* Kopiuje jeden znak(6x8) z FLASH do pamięci obrazu LCD */ for(i=0, j=(c-32)*6; i<6; i++,j++) lcd_write_byte(LCD_DATA, pgm_read_byte(&font[j])); } }
Pierwszym argumentem funkcji lcd_text jest ciąg znaków zakończony zerem, tekst może zawierać polskie znaki z ogonkami. Drugi i trzeci argument to położenie tekstu na ekranie LCD; x może przyjmować wartości 0..84, y-wartości 0..5.
A to jest główny plik drugiego przykładu:
/* Plik "main.c" LCD od nokia3310 przykład 2 KURS AVR-GCC www.abxyz.bplaced.net testowane na atmega8 (16MHz) */ #include <avr/io.h> #include <avr/pgmspace.h> #include "lcd.h" /* */ int main(void) { /* Inicjuje wyświetlacz */ lcd_init(); /* Czyści ekran */ lcd_clear(); /* Wyświetla tekst */ lcd_text("Kurs AVR-GCC", 1*6, 0); lcd_text("ABXYZ :)", 4*6, 1); lcd_text("Wyświetlacz", 1*6, 3); lcd_text("LCD", 5*6, 4); lcd_text("od Nokii 3310", 0*6, 5); /* Zatrzymanie programu */ while(1); return 0; }
Katalog z plikami źródłowymi przykładu drugiego można pobrać klikają w link 002.zip; katalog zawiera także plik Makefile, który należy dostosować do "sprzętu", tzn. wybrać typ AVRa, częstotliwość pracy mikrokontrolera, programator -przykłady były testowane na atmega88 8MHz. Plik Makefile wygodnie jest edytować z pomocą programu MFile dostarczonego razem z pakietem WinAVR. Jeśli nie wiesz jak skompilować przykłady, to polecam artykuł "Szybki start z WinAVR"
Przykład 3. Płynący napis.
W tym przykładzie, na górze ekranu LCD, pokazany jest obrazek, z użyciem funkcji "lcd_image", podobnie jak w przykładzie 1. W dolnej części ekranu, w linii nr 5, wyświetlany jest tekst, ale, żeby było nieco ciekawiej, tekst "płynący", robi to funkcja "scroll".
/* Plik "main.c" LCD od Nokii_3310 przykład 3 KURS AVR-GCC www.abxyz.bplaced.net testowane na atmega8 16(MHz) */ #include <avr/io.h> #include <avr/pgmspace.h> #include <util/delay.h> #include <stdio.h> #include "lcd.h" // Dane obrazka "avr_gcc" unsigned char screen[] PROGMEM = { #include "avr_gcc.c" }; // Wzory znaków 6x8 dla funkcji "scroll" extern unsigned char font[] PROGMEM; // Tekst płynącego napisu unsigned char tekst[] PROGMEM = " Internetowy kurs programowania mikrokontrolerów AVR w języku C "; // Funkcja tworzy na ekranie LCD płynący napis void scroll(unsigned char txt[], unsigned char line) { unsigned int j,l,k,n; unsigned char c,i,m; // Kody polskich literek z ogonkami unsigned char pl[] = {'ą','ć','ę','ł','ń','ó','ś','ź','ż','Ą','Ć','Ę','Ł','Ń','Ó','Ś','Ź','Ż'}; // liczenie znaków w tekście for(n=0;pgm_read_byte(&txt[n]);n++); for(j=0; j<(n-(LCD_X/6))*6 ;j++) { LCD_GOTO(0, line) for(i=0,l=j; i<LCD_X ;i++,l++) { c = pgm_read_byte(&txt[ (l/6) ]); // Dopasowuje kody polskich znaków z ogonkami for(m=0; (m<18) && (pl[m]!=c); m++) ; if(m<18) c= 0x80+m; k = (c-32)*6+(l%6); lcd_write_byte(LCD_DATA, pgm_read_byte(&font[k])); } _delay_ms(70); } } /* MAIN */ int main(void) { // Inicjuje LCD lcd_init(); // Obrazek w górnej części ekranu lcd_image(screen,0,0,84,5); // Płynący napis w linii nr 5 while(1) scroll(tekst,5); return 0; }
Katalog z plikami źródłowymi przykładu trzeciego można pobrać klikają w link 003.zip; katalog zawiera także plik Makefile, który należy dostosować do "sprzętu", tzn. wybrać typ AVRa, częstotliwość pracy mikrokontrolera, programator -przykłady były testowane na atmega88 8MHz. Plik Makefile wygodnie jest edytować z pomocą programu MFile dostarczonego razem z pakietem WinAVR. Jeśli nie wiesz jak skompilować przykłady, to polecam artykuł "Szybki start z WinAVR"
Przykład 4. Termometr z układem ds18b20
A teraz bardziej użyteczny przykład, termometr z układem ds18b20. Na środku ekranu wyświetlacza pokazywany jest wskaźnik cyfrowy, a po prawej wskaźnik analogowy - słupek cieczy. Podobny kity(zestaw do samodzielnego montażu) z mikrokontrolerem PIC dostępny jest w handlu.
Na schemacie czujnik ds18b20 przyłączony jest do wyprowadzenia PD7 mikrokontrolera atmega, ale można wykorzystać dowolny port AVRa, w tym celu należy zmodyfikować makrodefinicje w pliku ds18b20.h
W programie MicroLCD, przygotowałem tło, które będzie ładowane na ekran wyświetlacza jednorazowo, na początku programu funkcją "lcd_image"- podobnie jak w przykładzie pierwszym.
Przygotowałem również obrazek ze wzorami cyfr dla wskaźnika cyfrowego. Każdy znak został narysowany na polu o wymiarach 8x16, czyli znaki cyferek mają wysokość dwóch linii tekstowych w pamięci RAM obrazu wyświetlacza.
A poniżej znajduje się główny plik przykładu. W skrócie program działa następująco: Najpierw inicjowany jest wyświetlacz i na ekran ładowany jest obrazek z tłem; następnie, w pętli, odczytywana jest wartość temp z czujnika ds18b20, po czym na ekranie LCD rysowane są cyferki i słupek cieczy.
/* Plik "lcd.c" LCD od nokia3310 przykład 4 KURS AVR-GCC www.abxyz.bplaced.net testowane na atmega8 16(MHz) */ #include <avr/io.h> #include <avr/pgmspace.h> #include <util/delay.h> #include <util/crc16.h> #include <stdio.h> #include "lcd.h" #include "ds18b20.h" // Obrazek z cyferkami dla wskaźnika cyfrowego unsigned char digits8x16[] PROGMEM = { #include "dig8x16.c" }; // Obrazek z tłem unsigned char screen[] PROGMEM = { #include "screen.c" }; // unsigned char tab[8]; // 9 bajtów odczytanych z ds18b20 unsigned char ds18b20_pad[9]; // Funkcja tworzy z liczby ciąg cyfr w kodzie BCD // jeden bajt - jedna cyfra void i2dig(int t, unsigned char tab[]) { // Część ułamkowa liczby unsigned char frac[] = { '0','1','1','2','2','3','4','4','5','6','6','7','8','8','9','9' }; signed char i, s = 0; if(t < 0) { t = -t; s = 1; } for(i=0; i < 4 ; i++) tab[i] = '9'+5; // spacja tab[7] = 0; tab[6] = '9'+4; // Znak 'C' tab[5] = '9'+3; // Znak '°' tab[4] = frac[t & 0x0f]; tab[3] = '9'+1; // znak kropka tab[2] = '0'; tab[1] = '9'+5; // spacja tab[0] = '9'+5; t >>= 4; for(i=2; t; i--, t/=10) tab[i] = t%10+'0'; if(i == 2) i-- ; if(s) tab[i] = '9'+2; // znak minus } // Funkcja wyświetla wskaźnik cyfrowy void lcd_d_t(unsigned char *s, unsigned char x, unsigned char y) { unsigned int i,j; unsigned char k; LCD_GOTO(x, y) for(k=0; s[k]; k++) { lcd_write_byte(LCD_DATA, 0); for(i=0, j=(s[k]-'0')*8; i<8; i++, j++) lcd_write_byte(LCD_DATA, pgm_read_byte(&digits8x16[j])); } LCD_GOTO(x, y+1) for(k=0; s[k]; k++) { lcd_write_byte(LCD_DATA, 0); for(i=0, j=(s[k]-'0')*8; i<8; i++, j++) lcd_write_byte(LCD_DATA, pgm_read_byte(&digits8x16[j+15*8])); } } // Funkcja rysuje słupek cieczy void lcd_a_t(char v, unsigned char x) { char y,t,b; v+=7; for(y=5,t=0; y>=0; y--,t+=8) { if(v>=t+8) b = 0xff; else if(v>t) b= 0xff00>>(v%8); else b = 0; LCD_GOTO(x, y) lcd_write_byte(LCD_DATA, b); lcd_write_byte(LCD_DATA, b); lcd_write_byte(LCD_DATA, b); } } // MAIN int main(void) { unsigned int crc = 0; unsigned char i; int t; // wartość temperatury * 16 // Inicjuje wyświetlacz lcd_init(); // lcd_contrast(0x25); // lcd_clear(); // Pokazuje obrazek z tłem lcd_image(screen,0,0,84,6); while(1) { // Polecenie pomiaru if(ds18b20_ConvertT()) { // Zatrzymuje do czasu zakończenia pomiaru while(!OneWireReadTimeSlot()); // Odczyt danych z ds18b20 ds18b20_Read(ds18b20_pad); // Oblicza sumę kontrolną CRC danych z ds18b20 for (i = 0, crc =0; i < 9; i++) crc = _crc_ibutton_update(crc, ds18b20_pad[i]); // Jeśli suma kontrolna się zgadza, // pokazuje nowe wyniki pomiaru if(!crc) { // Zmienna "t" zawiera wartość temperatury * 16 t = (ds18b20_pad[1] << 8) | ds18b20_pad[0] ; i2dig(t,tab); // tworzy z liczby ciąg cyfr lcd_d_t(tab,0,2); // rysuje wskaźnik cyfrowy lcd_a_t(t>>4, 68); // rysuje słupek cieczy } } } return 0; }
Katalog z plikami źródłowymi przykładu czwartego można pobrać klikają w link 004.zip; katalog zawiera także plik Makefile, który należy dostosować do "sprzętu", tzn. wybrać typ AVRa, częstotliwość pracy mikrokontrolera, programator -przykłady były testowane na atmega88 8MHz. Najlepiej skompilować program otwierając projekt w edytorzy "Programers's Notepad" dostarczonym wraz z pakietem "WinAVR". Jeśli nie wiesz jak skompilować przykłady, to polecam artykuł "Szybki start z WinAVR"
Poza tym cały kurs oceniam jako 10/10.