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.

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 w cale. Jest najprostszy do zaimplementowania oraz “kosztuje” nas niewiele jeśli chodzi o zasoby MCU. 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// Write byte to LCD
//
void LCD_WriteByte(uint8_t data)
{
SET_LCD_E;
LCD_SetDataPort(data &gt;&gt; 4);
RESET_LCD_E;

SET_LCD_E;
LCD_SetDataPort(data);
RESET_LCD_E;

HAL_Delay(1); // &lt;&lt;--- wait for data processing
}

Kod testowy na którym będę bazował wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
12
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. 

1
2
3
4
5
6
void Delay_us(uint16_t us)
{
htim3.Instance-&gt;CNT = 0;
while(htim3.Instance-&gt;CNT &lt;= us);
}
<p style="text-align: justify;">

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//
// Write byte to LCD
//
void LCD_WriteByte(uint8_t data)
{
SET_LCD_E;
LCD_SetDataPort(data &gt;&gt; 4);
RESET_LCD_E;

SET_LCD_E;
LCD_SetDataPort(data);
RESET_LCD_E;

// HAL_Delay(1);
Delay_us(120); //&lt;&lt;--- wait for data processing
}

//
// Write command to LCD
//
void LCD_WriteCmd(uint8_t cmd)
{
RESET_LCD_RS;
LCD_WriteByte(cmd);
Delay_us(1000);//&lt;&lt;--- wait for command processing
}
<p style="text-align: justify;">

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<<.

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.


Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Serwis wykorzystuje pliki cookies. Korzystając ze strony wyrażasz zgodę na wykorzystywanie plików cookies. więcej informacji

Wrażenie zgody na pliki Cookies jest konieczne, aby uzyskać najlepsze wrażenia z przeglądania strony. Jeżeli nadal nie wyraziłeś zgody na używanie plików Cookies, zaakceptuj poniżej klikając w przycisk "Akceptuj" na banerze.

Close