Wyświetlacz znakowy 16×2 jest chyba najpopularniejszą formą komunikacji świata mikrokontrolera ze światem człowieka. Postanowiłem zrobić mini cykl trzech wpisów, które przybliżą Tobie różnice w sposobach sterowania LCD 16×2 na STM32. Na ogół w internecie można spotkać się z jednym, który często jest wystarczający. Więc po co inne? Zapraszam do lektury.
Wyświetlacz LCD 2×16
LCD alfanumeryczny czy jak kto woli, znakowy skupia w sobie wiele zalet jak chociażby prostotę obsługi, niską cenę, czy transfleksyjność w pozytywowej wersji. Wadą jest niewątpliwie słaby kontrast oraz prędkość odświeżania.
Problemem też może okazać się napięcie zasilania. Na rynku przeważają wyświetlacze z poziomem zasilania 5V, zwłaszcza od majfriendów. Czy jest to problem? W __tym__ wpisie wskazałem jedną z zalet STM32 jaką jest tolerancja IO na 5V. Poziom napięcia wychodzącego z LCD mamy z głowy. Co z wejściem? Na ogół tolerancja poziomu wysokiego przez kontroler wyświetlacza jest na tyle szeroka, że 3.3V powinno wystarczyć do wysterowania. Mój testowy egzemplarz jest przystosowany do 5V, natomiast z powodzeniem udało mi się go wysterować z STM32. Można też dorwać wersję 3.3V ponieważ coraz częściej można takie spotkać, ale za “extra” można “extra” dopłacić.
Do rozmowy z klasycznym LCD zgodnym z HD44780 mamy dostępne dwa międzymordzia – 4-bitowe i 8-bitowe. Baardzo rzadko widuję, że ktoś korzysta z 8-bitowego. W końcu zabiera aż 4 cenne piny MCU więcej niż popularna wersja 4-bitowa. W każdym z tych trybów możemy z koleji wysyłać dane na dwa sposoby. Jeden to ciągły tryb write z uziemionym sygnałem RW wyświetlacza. Najprostszy oraz często dostępny w internetowych opisach. Drugim sposobem jest obsługa z czytaniem flagi zajętości. Wymaga to podłączenia pinu RW do mikrokontrolera i sterowania kierunkiem przesyłu danych na linii MCU<->LCD. Koszt: jeden pin MCU więcej.
Chciałbym sprawdzić wszystkie te metody i porównać je ze sobą pod kątem ilości wymaganego czasu przesłania całej ramki 2×16 znaków do wyświetlacza. Jesteś ciekawy efektów?
Platformą testową będzie Nucleo-64 z STM32F401RE z zegarem HCLK ustawionym na 84 MHz.
Magistrala 4-bitowa bez sprawdzania flagi zajętości
Ten sposób jest chyba najpopularniejszy wśród użytkowników wyświetlaczy alfanumerycznych. Nie dziwię się temu wcale. Jest najprostszy do zaimplementowania oraz “kosztuje” nas niewiele jeśli chodzi o zasoby MCU.
Do wysterowania wyświetlacze użyję Nucleo F401RE.
Schemat połączenia wygląda następująco:
oraz konfiguracja STM32CubeMX
Z punktu widzenia pomiaru czasu odświeżania interesuje nas czas przesłania wszystkich komend i danych do wyświetlacza, aby ukazał się nam napis testowy. Spinając pin RW do masy, wprowadzamy wyświetlacz w ciągły tryb zapisu. W trybie tym należy dostosować czas oczekiwania na przesłanie kolejnych danych tak, aby był on dłuższy niż trwa proces przetwarzania poprzedniego bajtu. Najpierw wykonam to na najprostszym HAL_Delay(), który oferuje biblioteka od ST.
// // Write byte to LCD // void LCD_WriteByte(uint8_t data) { SET_LCD_E; LCD_SetDataPort(data >> 4); RESET_LCD_E; SET_LCD_E; LCD_SetDataPort(data); RESET_LCD_E; HAL_Delay(1); // <<--- wait for data processing }
Kod testowy na którym będę bazował wygląda następująco:
while (1) { // Measurement start LCD_Cls(); LCD_Locate(0,0); LCD_String(" STM32 + HD44780"); LCD_Locate(0,1); LCD_String("www.msalamon.pl "); // Measurement end HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_Delay(500); }
Niestety funkcja HAL_Delay() działa z minimalną rozdzielczością 1ms, która jest podstawą czasu dla całej biblioteki STM32HAL. Dla wyświetlacza jest to stosunkowo długi czas stąd efekt jego działania jest mizerny. Na ekranie wyraźnie widać odświeżanie bo jest ono po prostu zbyt wolne. Cała procedura odświeżenia testowego trwa AŻ 67,29 ms! Jest to ponad trzykrotnie dłuższy czas niż potrzebuje ludzkie oko, aby zostało oszukane i nie zauważyło odświeżania. Przez to też można zauważyć jak druga linia znaków brzydko mruga.
Trzeba to przyśpieszyć! Biblioteka od ST nie posiada niestety żadnej funkcji opóźniającej o mniejszej rozdzielczości. Co teraz? Kończę post. Na STM32 się nie da – Arduino jest lepsze 🙁
ŻARTOWAŁEM!
MCU użyty w przykładzie(jak każdy STM32) na mnóstwo Timerów sprzętowych. Po co mają leżeć i się kurzyć? Wykorzystam jeden do delaya. Który? Na razie nie będę zagłębiał się w tajniki Timerów i przelecę szybko temat konfiguracji. Dzisiaj w maszynie losującej padło na TIM3 z wewnętrznym taktowaniem. Delay jakiego potrzebujemy powinien działać z rozdzielczością 1 us, więc używając magicznego wzoru wychodzi wymagane taktowanie na poziomie 1 MHz. Używając kolejnego magicznego wzoru wychodzi, że przy taktowaniu 84 Mhz, aby dostarczyć 1 MHz na TIM3 potrzebuję ustawić prescaler na wartość 83. Kierunek zliczania ustawię na Up, a CounterPeriod na wartość maksymalną czyli 65535(16-bitowy timer). Inne ustawienia pozostawię przy wartościach domyślnych. Nie włączam dla Timera żadnych przerwań ani innych cudów. Po ustawieniu regeneruję kod programu.
Dla przejrzystości kod delaya napisałem oczywiście w osobnych plikach – delays.c i delays.h.
void Delay_us(uint16_t us) { htim3.Instance->CNT = 0; while(htim3.Instance->CNT <= us); }
Po ustawieniu opóźnienia między wysyłaniem bajtami na 120 us pojawił się problem w postaci ucinania początku ramki. Problem leży w przetwarzaniu wcześniej wpisywanych komend cls i locate. Skracając przesył danych musiałem jednocześnie przedłużyć oczekiwanie po komendach, gdyż ich wykonanie trwa trochę dłużej niż przyjęcie danych. Testowałem różne ilości mikrosekund, ale szybko zbliżyłem się do wartości 1 ms.
// // Write byte to LCD // void LCD_WriteByte(uint8_t data) { SET_LCD_E; LCD_SetDataPort(data >> 4); RESET_LCD_E; SET_LCD_E; LCD_SetDataPort(data); RESET_LCD_E; // HAL_Delay(1); Delay_us(120); //<<--- wait for data processing } // // Write command to LCD // void LCD_WriteCmd(uint8_t cmd) { RESET_LCD_RS; LCD_WriteByte(cmd); Delay_us(1000);//<<--- wait for command processing }
Wynik? Jest zdecydowanie lepiej. Nie widać “pisania” po ekranie, więc jest to akceptowalne. Czas wykonania? 7,14 ms czyli możemy maksymalnie wycisnąć ok 140 Hz. Sukces!
W kolejnej wpisie zajmę się obsługą flagi zajętości. Zobaczę na ile jeszcze uda mi się skrócić wysyłanie ramki 😉
Druga część wpisu znajduje się >>tutaj<<.
Jeżeli wyświetlacz Ci się spodobał, możesz nabyć go w różnych wariantach u mnie w sklepie.
Kompletny kod z tego cyklu wpisów znajduje się na moim GitHubie: link
Na koniec tego wpisu zachęcam do dyskusji w komentarzach na temat obsługi wyświetlacza LCD na STM32. Uważasz, że popełniłem gdzieś błąd? Masz jakiś ciekawy pomysł co można ulepszyć? Podziel się tym w komentarzu! Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.
7 komentarzy
cd · 13/09/2024 o 15:17
Np na moim stm32F303RET6 kompilator nie rozumie komend:
SET_LCD_E;
LCD_SetDataPort(data >> 4);
RESET_LCD_E;
No i co mam zrobić?
komendy skąd · 13/09/2024 o 14:25
A skąd wziąłeś te instrukcje:
SET_LCD_E;
LCD_SetDataPort(data >> 4);
RESET_LCD_E;
SET_LCD_E;
LCD_SetDataPort(data);
RESET_LCD_E;
Z jakiegoś datasheet, czy skądś tam? Czy nie można takich informacji podawać od razu?
SaS · 03/02/2022 o 15:41
Właśnie skończyłem pisać soft obsługi wyświetlacza na przerwaniach. W przerwaniu wystawiane są dane (starszy półbajt) z bufora (wirtualny wyświetlacz) do wyświetlacza. ARR ustawiam na 15us. Kolejne przerwanie ustawia linię E wyświetlacza. Po następnych 15us E=0, kolejne 15us wystawienie młodszego półbajtu, 15us E=1, 15us E=0, ARR = 1000us. Po ms sekwencja się powtarza. Po wysłaniu jednej linii rozkaz 0x80+0x40 (kursor w drugiej linii). Po wypełnieniu drugiej linii rozkaz 0x80 (pozycja home).
Dzięki obsłudze wyświetlacza na przerwaniach program główny nie “wisi” na delay po transmisji danych czy rozkazów. Nie używam rozkazu CLS więc nic nie miga.
Mario · 05/11/2021 o 18:05
Jak zaimportować Twój projekt do LCD HD44780 na płytkę Nucleo f303re. Jestem początkujący (tzn.zaczynający). Skopiowałem pliki biblioteki LCD h i c ale dostałem kilkadziesiąt błędów. Poprawiłem nazwy w plikach np: zstm32f4xx_hal_conf na stm32f3xx_hal_conf. Mimo wszystkich zmian z plikami zostaje mi jeszcze ok 9 błedów i kilka warningów. Możesz mi napisać maila co mam zrobić. Co skopiować a co zmienić, żeby Twoja biblioteka zadziałała na mojej płytce? mail to:mc1973@o2.pl . Pozdrawiam.
Mateusz Salamon · 17/11/2021 o 15:35
Oj trzeba by dostosować operacje na pinach. To jedna ze starszych moich bibliotek, gdzie założyłem, że odpowiednimi pinami steruję LCD, zamiast zrobić to uniwersalnie 🙁
Przerwania · 04/04/2019 o 23:04
Witam. W końcu zabrałem się za podłączenie pod Nucleo LCD z HD44780. Mam pewien problem, ponieważ moje nucleo ma zegar 72MHz więc zmieniłem z twojego prescalera z wartość 83 na 71, a CounterPeriod pozostawiam wartość maksymalną (16 bit) czyli 65535. Niestety wyświetla losowe znaki na wyświetlaczu. Pytanie dlaczego należy ustawić CounterPeriod, przecież przerwanie ma z rozdzielczością 1MHz więc w twoim przypadku wystarczy ustawić prescalera na 83
Mateusz Salamon · 05/04/2019 o 07:33
Jak wystawia losowe znako to znak, że jakaś komunikacja jest. Może coś z połączeniem albo konfiguracją pinów masz nie tak. Gorzej jeśli Twój LCD nie toleruje 3,3 V na komunikacji…
Co do CounterPeriod to tam wpisujesz maksymalną wartość jaką może osiągnąć. Wartość którą wpisujesz do funkcji to taka, którą ten licznik ma osiągnąć przy czekaniu. Jak będziesz miał w CointerPeriod np. 10, a będziesz chciał doliczyć do 20 to nigdy się nie doliczy.