Obsługa LCD (HD44780) #2: komunikacja jednokierunkowa

Obsługa LCD HD44780 bez odczytu busy flag

W tej części zajmiemy się tworzeniem mini biblioteki do obsługi wyświetlacza o wielkości 2×16 pracującego w trybie 4 bitowym, bez odczytu flagi zajętości. Jeśli nie czytałeś poprzedniego artykułu to zdecydowanie warto zacząć od niego, ponieważ zawarłem tam wiele istotnych informacji, do których będę się tutaj odnosił.

Kod z tego przykładu powinieneś bez większego problemu przenieść na dowolny mikrokontroler, jakiego używasz. Ja wykorzystam tutaj biblioteki SPL dla mikrokontrolerów STM32. Całkowite kody źródłowe dla mikrokontrolera STM32F103 znajdziesz na moim githubie.

Połączenia

Sterownik i podświetlenie wyświetlacza, w zależności od modelu, zasilamy napięciem 5v lub 3.3v. Dokładniej opisałem to w punkcie 5v vs 3.3v. Przy transmisji jednokierunkowej będziemy wyłącznie zapisywać dane do sterownika, więc pin RW należy na stałe podłączyć do masy.
Wyświetlacz może pracować w dwóch trybach: 8 lub 4-bitowym. Zawsze korzystam z trybu 4-bitowego i ten sposób opiszę. Implementacja dla trybu 8-bitowego jest prostsza, więc w razie potrzeby, wszelkie modyfikacje nie powinny stanowić dużego problemu. W trybie 4-bitowym wykorzystuje się tylko 4 linie danych (D4-D7) zamiast 8, co jest jego ogromną zaletą. Możemy je podłączyć do dowolnych niezajętych GPIO mikrokontrolera, a pozostałe (D0-D3) pozostają niepodłączone. W takim trybie najpierw wysyłamy starszą (bardziej znaczącą), a potem młodszą połowę bajta. Zakończenie transmisji zachodzi dopiero po wysłaniu obu części. Wynikające z tego spowolnienie jest mało znaczące. Zostały jeszcze piny E i RS, które podobnie jak D4-D7 podłączamy do dowolnych niezajętych GPIO.

Makra do wyboru pinów i portów

Aby ułatwić sobie konfigurację wyświetlacza w różnych projektach, warto na początku utworzyć makra dla pinów i portów mikrokontrolera, do których połączone będą poszczególne piny wyświetlacza. Dzięki temu, jeśli będziemy chcieli zmienić któreś połączenie wystarczy, że wprowadzimy zmianę tylko w tym jednym miejscu.

Aby późniejszy kod był bardziej przejrzysty można też pokusić się o stworzenie makr dla funkcji ustawiających stan niski lub wysoki na pinach E i RS.

Makra komend

Poniżej przedstawiam tabelę komend prosto z dokumentacji sterownika HD44780:
komendy hd44780

Będziemy z nich korzystali, więc aby nie musieć więcej razy zaglądać do tej tabeli także warto stworzyć makra dla poszczególnych komend:

No i dodajmy jeszcze makra dla adresów pamięci DDRAM odpowiadające pierwszym polom wyświetlacza z każdego wiersza. Adresy te były przedstawione w pierwszym artykule i dla wyświetlacza 2×16 miały następujące wartości:

W zasadzie to najgorsze za nami. Pozostały do napisania funkcje potrzebne do obsługi wyświetlacza, ale zapewniam, że są one banalnie proste.

Wysyłanie danych

Jak już wspominałem wykorzystamy tryb 4-bitowy i wysyłanie bajta danych będzie zachodziło w dwóch turach po 4 bity. Wysyłanie sprowadza się do ustawienia odpowiednich stanów na poszczególnych liniach danych DB4-DB7. Stany na tych liniach oczywiście odpowiadają wartościom bitów połowy przesyłanego bajta. Informację o tym, że już ustawiliśmy stany na liniach danych przekazujemy dla kontrolera LCD poprzez zbocze opadające na pinie E. Dlatego na początku funkcji ustawimy stan wysoki na tej linii, a na końcu zmienimy na niski:

Funkcja GPIO_WriteBit() jest funkcją z biblioteki SPL i służy do ustawiania stanów logicznych na określonych pinach. Jako argument przyjmuje numer portu i pinu oraz wartość, jaka ma zostać przypisana: 1 odpowiada stanowi wysokiemu, 0 niskiemu. Stosując kolejno maski 0x01, 0x02, 0x04, 0x08 uzyskujemy odpowiedni stan na odpowiednich liniach danych, odpowiadający bitom przesyłanej danej.

Gdy mamy już funkcję do przesłania połowy bajta, możemy teraz napisać taką, która umożliwi nam przesłanie całości:

Jak już wspominałem, na początku przesyłamy starszą, a potem młodszą połowę. Funkcja lcd_sendHalf() została napisana tak, że przesyła młodszą połowę bajta, który jej przekazujemy jako argument, dlatego przy pierwszym wywołaniu funkcji lcd_write_byte() dokonujemy przesunięcia bitowego w prawo o 4 miejsca, aby na pozycjach młodszego półbajtu znajdowały się wartości odpowiadające starszemu półbajtowi przesyłanej wartości. Jeśli nie do końca to widzisz, to „wywołaj” sobie na kartce funkcję lcd_write_byte() z jakimś argumentem i przeanalizuj wszystko krok po kroku. Na końcu, ze względu na to, że nie możemy odczytać stanu Busy Flag, bo korzystamy z transmisji jednokierunkowej musimy odczekać chwilę, w czasie, której sterownik „przetrawi” poprzednią porcję informacji. Ja ustawiłem 60µs, ale gdyby pojawiały się jakieś problemy to warto tę wartość zwiększyć.

Jak wiesz, przesłany bajt danych może być interpretowany przez sterownik albo jako komenda, albo znak do wyświetlenia. Wszystko zależy od stanu na linii RS. Napiszmy więc dwie funkcję, które nam to umożliwią:

Nie ma w nich nic co wymagałoby dodatkowego opisu. Dodam tylko, że na wykonanie poszczególnych komend sterownik potrzebuje różną ilość czasu. Poszczególne czasy znajdziesz w tabeli komend z pierwszego artykułu.

Inicjalizacja

Mając te funkcje możemy już zrobić wszystko. Przejdźmy więc do inicjalizacji LCD. Cały proces jest przedstawiony w nocie katalogowej i dla trybu 4-bitowego wygląda następująco:

Wystarczy, że wszystko wykonamy zgodnie z powyższą instrukcją. No to do dzieła 🙂

Jak już wiesz z komentarza w kodzie, musimy jeszcze napisać funkcję, która będzie odpowiadała za inicjalizacje GPIO mikrokontrolera – musimy ustawić je jako wyjścia, a w przypadku STM32 włączyć jeszcze taktowanie portów.

Ustawianie kursora

Napiszmy jeszcze funkcję, która umożliwi nam ustawienie kursora w dowolnej pozycji na wyświetlaczu.

Jak widać funkcja przyjmuje dwa argumenty: x i y. Są to odpowiednio numery kolumny i wiersza wyświetlacza indeksowane od zera, w których chcemy ustawić kursor i zaczynać wpisywać znaki. Jak już wiesz, poszczególnym polom wyświetlacza odpowiadają określone adresy pamięci DDRAM i to właśnie je będziemy ustawiać. Przypomnę, że dla wyświetlacza 2×16 są one przyporządkowane w następujący sposób:

Adres DDRAM dla LCD 2x16

Komenda LCDC_SET_DDRAM służy do ustawiania adresu pamięci DDRAM. W instrukcji switch, w zależności od wybranego wiersza, ustawiamy odpowiedni adres. W przypadku wyświetlacza np. 4 wierszowego, należy dopisać jeszcze case 2 i case 3 oraz stworzyć makra LCD_LINE3 i LCD_LINE4.

Podsumowanie

No i dochodzimy do końca tej części. Mam nadzieję, że po przeczytaniu wszystko stało się w miarę jasne 🙂 Mamy podstawowe funkcje, na podstawie których możemy już bez problemu tworzyć kolejne: np. funkcja do wyświetlania stringów:

Poniżej przedstawiam jeszcze główną funkcję programu:

Jak widać, na początku musimy pamiętać jedynie o inicjalizacji LCD, a potem już dowolnie korzystamy z opisywanych tu funkcji ? Ja inicjalizuję tu także timery na potrzeby funkcji delay_ms() i delay_us(). Opóźnienia można zrealizować na wiele sposobów, ale gdybyś miał z tym problem to możesz skorzystać z mojego kodu – całkowite kody źródłowe z tego projektu wraz z ciałami funkcji opóźniających znajdziesz na moim githubie

Jak już wspominałem, ten sposób jest trochę prostszy, ale niestety spowalnia komunikację, więc jeśli zależy Ci na szybkości to warto korzystać z pinu RW i odczytu busy flag, co opisuję w kolejnym artykule:

Obsługa LCD (HD44780) #3: komunikacja dwukierunkowa