Arduino – wyświetlacz znakowy HD44780
W trzecim odcinku kursu Arduino poznajemy wyświetlacz LCD. Oczywiście nie jest to „goły” wyświetlacz, tylko dość skomplikowany moduł zawierający scalony sterownik HD44780 lub jego odpowiednik. Jego obsługa okaże się zaskakująco łatwa. Jest to wyświetlacz znakowy, a wyświetlacze graficzne poznamy później.
Fotografia 1 pokazuje najpopularniejszą odmianę oznaczaną 16×2 (16 znaków, 2 linie).
Fotografia 2 przedstawia niektóre inne wersje. Wszystkie wersje takich wyświetlaczy znakowych są obsługiwane w jednakowy sposób. W Arduino mamy odpowiednią bibliotekę, która znakomicie to ułatwia. Zanim ją wykorzystamy, przypomnijmy kluczowe informacje o tym jakże pożytecznym sprzęcie.
Wyprowadzenia modułu pokazane są na rysunku 3.
Zwykle linii D0…D3 nie wykorzystujemy, a wejście R/W na stałe łączymy do masy (bo nie korzystamy z możliwości odczytywania danych z modułu). Spośród 16 wyprowadzeń modułu zwykle 6 wykorzystujemy do przekazywania danych. W takim oszczędnościowym trybie pracy sześć (dowolnych) pinów płytki Arduino musimy połączyć z wyświetlaczem. Napięcie stałe na nóżce Vo służy do regulacji kontrastu wyświetlacza – podłączamy tam suwak potencjometru montażowego (najczęściej 10kΩ).
Celem tego odcinka kursu jest opanowanie obsługi wyświetlacza LCD. Jeśli go dobrze poznamy, będziemy mieć otwartą drogę do realizacji niezliczonych pomysłów.
Rysunek 4 pokazuje układ połączeń wyświetlacza, jaki wykorzystałem w ćwiczeniach – wyświetlacz jest obsługiwany przez piny 2…7 Arduino, czyli przez piny portu B procesora (PB2…PB7). Wykorzystujemy też podświetlenie, a więc nóżki 15, 16 wyświetlacza. Katodę podświetlającej diody LED dołączamy do masy (GND), natomiast anodę podłączyłem do napięcia 3,3V, co dało wystarczający efekt (upewnij się tylko, czy użyty wyświetlacz ma w obwodzie podświetlenia rezystory ograniczające prąd – fotografia 5).
Aby łatwo dołączyć wyświetlacz do Arduino, polutowałem „interfejs” według rysunku 3, zawierający kawałek listwy goldpin, kawałek listwy „żeńskiej”, potencjometr montażowy oraz przewody – fotografia 6.
W poprzednim odcinku napisaliśmy program termometru „diodowego”, który też wykorzystamy w ćwiczeniach. Ale na razie zajmijmy się tylko obsługą modułu wyświetlacza LCD. W tym celu wykorzystamy bibliotekę LiquidCrystal, która jest zawarta w pakiecie Arduino IDE.
Na początku programu – szkicu umieścimy dyrektywę dla kompilatora (#include), która pozwoli z tej biblioteki skorzystać.
Następnie w programie setup() umieścimy polecenie, dzięki któremu w programie – szkicu niejako pojawi się nasz moduł wyświetlacza pod nazwą wysw. Mówiąc fachowo (cokolwiek by to znaczyło): stworzymy obiekt typu LiquidCrystal o nazwie wysw. Wykorzystujemy tu „minimalistyczny” tryb pracy wyświetlacza, w którym wystarczy określić, które (dowolne) piny Arduino dołączone są do wyprowadzeń: RS, EN D4, D5, D6, D7. W moim przypadku wykorzystane są piny Arduino o numerach 2…7, które są kolejnymi pinami portu D procesora ATmega328P (PD2…PD7).
Biblioteka LiquidCrystal oferuje też tryby pracy wykorzystujące więcej pinów. Od razu warto nadmienić, że istnieją podobne moduły wyświetlaczy LCD ze sterowaniem szeregowym oraz „adaptery szeregowe” do klasycznych wyświetlaczy. Wtedy trzeba wykorzystać inną, „szeregową” bibliotekę, a do sterowania wystarczą dwie linie (piny) Arduino. Jak na razie wyświetlacze LCD z szeregowym interfejsem są mało popularne. Powszechnie wykorzystujemy moduły klasyczne sterowane sześcioma liniami danych według rysunku 3. Z uwagi na różnorodność wersji wyświetlaczy, trzeba poinformować program, ile znaków w linii i ile linii ma użyty wyświetlacz.
Prościutki program wyświetlający napis: Kurs Arduino i sygnalizujący upływ czasu zawarty jest w poniższym szkicu 1 (A0301), dostępnym także tutaj =link do ZIP A03:
#include <LiquidCrystal.h>
const int rs=2, en=3, d4=, d5=5, d6=6, d7=7;
//i tworzymy „wyświetlacz” o nazwie wysw
LiquidCrystal wysw (rs, en, d4, d5, d6, d7);
//tworzymy licznik – deklarujemy zmienną licz
int licz = 0;
void setup() {
wysw.begin(16, 2); //konfiguracja
// jednorazowo wyświetlamy napis:
wysw.print(„Kurs Arduino”); }
void loop() {
// ustawiamy kursor na początku drugiej linii:
wysw.setCursor(0, 1);
//i wyświetlamy stan licznika:
wysw.print(licz);
licz++; //inkrementuj (zwiększ) licznik
delay(1); //opóźnienie 1ms
} //koniec pętli loop()
W funkcji setup() jednorazowo wyświetlamy napis, który umieściliśmy w „podwójnym” cudzysłowie.
Potem w funkcji loop() pracuje prosty licznik, zwiększający co 1ms zawartość zmiennej licz typu int. W każdym obiegu pętli przenosimy kursor na początek dolnej linii i tam wyświetlamy zawartość licznika, pozostawiając niezmieniony napis w linii górnej. Także w wyświetlaczu numeracja linii i znaków zaczyna się od zera, więc instrukcja lcd.setCursor(0, 1); przenosi nas do pierwszego znaku w drugiej linii i tam wyświetlana jest zmieniająca się zawartość licznika.
Jeśli poczekasz trochę po rozpoczęciu pracy programu, to przekonasz się, że zmienna licz typu int może też zawierać liczby ujemne i ucieszysz się, że nasz wyświetlacz we współpracy z biblioteką LiquidCrystal dobrze radzi sobie z wyświetlaniem liczb ujemnych (reprezentowanych w liczniku w kodzie uzupełnienia do dwóch – U2). Jeśli jednak wykażesz jeszcze trochę cierpliwości, przekonasz się, że przy „zmniejszaniu licznika” w dolnej linii pozostają niepotrzebne zera – „śmieci pozostałe po dużej zawartości licznika”, ponieważ aktualnie wpisywana zawartość licznika nadpisuje wcześniejszą treść wyświetlacza, nie czyszcząc wcześniejszej.
Przy okazji ujawnia się też inna kwestia: W programie mamy opóźnienie 1 milisekundy w każdej pętli. Zliczenie od zera do 32768 powinno więc zająć trochę ponad pół minuty. Tymczasem u mnie trwało to ponad półtorej minuty (93 sekundy). A przecież wiadomo, że procesor AVR z taktowaniem 16MHz jest bardzo szybki – wykonuje 16 milionów elementarnych operacji w ciągu sekundy, czyli 16 tysięcy w ciągu 1 milisekundy. Jak z tego widać, w każdej pętli nawet prosta obsługa wyświetlacza i inne pomocnicze procedury są czasochłonne – można szacować, że trwają około 1 milisekundy, co trzeba uwzględniać w niektórych programach. I na pewno typowe procedury Arduino nie powinny służyć do odmierzania czasu. Do tych ważnych kwestii jeszcze będziemy wracać.
A na razie wracamy do wyświetlacza znakowego LCD. Biblioteka LiquidCrystal oferuje szereg przydatnych funkcji – poleceń. Między innymi możemy wyczyścić nasz wyświetlacz instrukcją wysw.clear(); W szkicu A0301.ino masz zakomentowane to polecenie, ale gdy je odkomentujesz, wyczyszczeniu ulegnie całość, w tym „jednorazowo” wyświetlony wcześniej górny napis. Możesz we własnym zakresie poeksperymentować z tym poleceniem.
Dla nas bardzo ważne jest, że biblioteka LiquidCrystal potrafi bez problemu wyświetlić nie tylko dodatnie i ujemne liczby, ale i ułamkowe ze zmiennych typu float. Aby się o tym przekonać, wykorzystajmy prymitywny termometr diodowy z poprzedniego odcinka i fragmenty kodu z wcześniejszego szkicu A0201. Schemat termometru LCD masz na rysunku 7,
a mój model na fotografii 8.
Wykorzystajmy szkic 2 (też zawarty w pliku A0301.ino) w którym nasz wyświetlacz nazywaliśmy lcd.
Szkic 2
#include <LiquidCrystal.h>
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
int zADC; // obsługa termometru
float temp; // obsługa termometru
void setup() {
lcd.begin(16, 2);
//konfiguracja związana z termometrem:
pinMode(A0, INPUT_PULLUP);
analogReference(INTERNAL); }
void loop() {
zADC = 10 * analogRead(A0);
temp = map(zADC, 5170, 4910, 210, 310);
temp = temp / 10; // temperatura
//żeby wyświetlić ujemną wartość
// odejmujemy 30 stopni:
temp = temp – 30;
lcd.clear(); //czyścimy wyświetlacz
lcd.print(„Temperatura”);
lcd.setCursor(0, 1); //w dolnej linii:
lcd.print(„wynosi: „);
//teraz wyświetlamy temperaturę ujemną
// ze zmiennej temp typu float:
lcd.print(temp, 1);
lcd.write(223); //symbol stopnia
lcd.print(’C’); //duża litera C
delay(1000); }
Wynik pomiaru zgodnie z naszymi intencjami jest ujemny (fotografia 9),
termometr działa – obrazuje zmiany temperatury, choć wskazanie niewiele ma wspólnego ze skalą Celsjusza. Jeśli masz kostkę LM35, możesz na podstawie poprzedniego odcinka zrealizować jak najbardziej praktyczny termometr pokojowy (który jednak nie zmierzy temperatur ujemnych). Możesz też z powodzeniem wykorzystać inne czujniki analogowe, niekoniecznie termometry.
Już tu widać, jak genialnie proste jest stworzenie z użyciem Arduino najróżniejszych mierników i przyrządów pomiarowych:
– Bierzemy jakiś czujnik (sensor) i uzyskujemy z niego dane.
– Przetwarzamy te dane do potrzebnej nam postaci i umieszczamy wynik w zmiennej odpowiedniego typu.
– Wyświetlamy wynik, dodając do niego potrzebne napisy uzupełniające.
To naprawdę genialnie proste!
Jeśli są to Twoje pierwsze doświadczenia w zakresie programowania – po prostu ciesz się, że to może być takie łatwe!
A jeżeli wcześniej próbowałeś opanować oprogramowanie wyświetlacza LCD, zapewne dziwisz się, jak to wszystko można uprościć. Jest to możliwe dzięki wykorzystaniu w Arduino inteligentnej biblioteki LiquidCrystal. Wykorzystanie jej rzeczywiście jest łatwe, ale nawet używając tej gotowej, bardzo pożytecznej biblioteki, powinniśmy pamiętać, że dotykamy tu wierzchołka góry lodowej. Stopniowo, w artykułach cyklu „Wokół Arduino” zgłębimy także trudniejsze zagadnienia z tym powiązane, ale na razie omówmy kwestie w miarę proste, a najbardziej potrzebne w praktyce.
I tak w szkicu 2 zmodyfikuj napisy przeznaczone do wyświetlenia. Spróbujmy w górnej linii zamiast: Temperatura, wyświetlić dłuższy napis: Aktualna temperatura. Składa się on z 20 znaków (wraz ze spacją), więc cztery ostatnie zostaną „obcięte” (fotografia 10).
Notujemy, że choć program zna „długość wyświetlacza” (lcd.begin(16, 2);), nie przenosi „nadmiaru” do niższego wiersza. Wiersze wyglądają na niezależne (co, jak się jeszcze okaże, nie do końca jest prawdą).
Zazwyczaj rozumnie skracamy napisy, żeby niczego nie ucinać. Ale biblioteka LiquidCrystal oferuje funkcje (metody), pozwalające przesuwać napisy na wyświetlaczu. Z przesuwaniem górnego napisu nie ma problemu. Możemy nim „kołysać w dwie strony”, na przykład za pomocą dość specyficznego programu – szkicu 3 (zawartego w pliku A0301.ino).
Szkic 2
#include <LiquidCrystal.h>
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
void setup() { lcd.begin(16, 2); }
void loop() {
lcd.clear(); //czyścimy wyświetlacz
lcd.print(„Aktualna temperatura”);
for(int pozycja=0; pozycja<12; pozycja++) {
//dla pozycji 1…4 – przesuwanie w lewo:
if (pozycja>0 && pozycja<5) {lcd.scrollDisplayLeft();}
//dla pozycji 7…10 – przesuwanie w prawo:
else if (pozycja>6 && pozycja<11) {lcd.scrollDisplayRight();}
//a dla pozycji 0, 5, 6, 10 – bez przesuwania
delay(300); //czekaj 300ms
} //koniec pętli for
} //koniec funkcji loop()
Jednak „kołysanie” dotyczy całej zawartości wyświetlacza, a my chcemy mieć wynik wyświetlany nieruchomo.
Łatwiej to zrealizujemy, przesuwając długi górny napis tylko w lewo. Zasadniczo przy okazji przesuniemy też dolną linię, ale będziemy w niej wynik nadpisywać z odpowiednim przesunięciem, żeby wydawał się nieruchomy. W tym celu wykorzystamy pętlę for o czterech stanach dla czterech pozycji górnego napisu. Stan licznika pętli – pozycję wykorzystamy do przesunięcia kursora na pierwsze widoczne miejsce dolnej linii i napiszemy tam wynik, by wydawał się nieruchomy.
Jedno z możliwych rozwiązań zawarte jest w poniższym szkicu 4:
#include <LiquidCrystal.h>
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
int zADC; float temp; //obsługa termometru
void setup() {
lcd.begin(16,2); pinMode(A0,INPUT_PULLUP);
analogReference(INTERNAL); }
void loop() { zADC = 10 * analogRead(A0);
temp = map(zADC, 5170, 4910, 210, 310);
temp = temp / 10 – 30;
lcd.clear(); //czyścimy wyświetlacz
lcd.print(„Aktualna temperatura”);
lcd.setCursor(0, 1); //w dolnej linii:
lcd.print(„wynosi: „); lcd.print(temp, 1);
lcd.write(223); lcd.print(’C’);
delay(2000);
for(int pozycja=0; pozycja<4; pozycja++){
//przesuwamy napis w lewo
lcd.scrollDisplayLeft();
//natomiast w dolnej linii
//wyświetlamy wynik „z przesunięciem”
//zależnym od pozycji górnego napisu
lcd.setCursor(pozycja + 1, 1);
lcd.print(„wynosi: „); lcd.print(temp, 1);
lcd.write(223); lcd.print(’C’);
delay(400); } //koniec pętli for
delay(2000); } //koniec funkcji loop()
Fragment kodu występuje tu dwukrotnie i należałoby umieścić go w oddzielnej funkcji. Jeśli chcesz, zrób to jako samodzielne zadanie domowe.
Mamy satysfakcję, że w tym trzecim odcinku kursu błyskawicznie nauczyliśmy się, jak w prosty sposób wykorzystać wyświetlacz znakowy LCD. Opanowaliśmy najważniejsze umiejętności: umiemy łączyć „stałe napisy” z zawartością zmiennych. Do wyświetlacza będziemy jeszcze wracać, choćby w kwestii wyświetlania „trudniejszych znaków”, w tym polskich liter.
A na razie możemy cieszyć się i eksperymentować, wykorzystując wyświetlacz LCD, łącze szeregowe i konsolę na ekranie komputera. Już teraz możesz zrealizować mnóstwo interesujących własnych układów/projektów, rozmaicie wykorzystując cyfrowe i analogowe wejścia i wyjścia Arduino. Zachęcam do samodzielnych działań, nawet gdyby wiązały się one z kłopotami, trudnościami i błędami. Z czasem będzie ich coraz mniej.
W artykule UR004 zawarty jest kolejny odcinek kursu, natomiast artykuły wątku „Wokół Arduino” dotyczące programowania znajdziesz pod numerami zaczynającymi się od PR001, a te dotyczący budowy i działania sprzętu – pod numerami zaczynającymi się od UR030. Niezależnie od stopnia zaawansowania, nie pomijaj tych dodatkowych materiałów!
Piotr Górecki