niedziela, 13 grudnia 2015

[4] STM32 M4F - Discovery - Wyświetlacz ze sterownikiem HD44780

W tym poście opiszę sposób obsługi wyświetlacza ze sterownikiem HD44780. Program został wykonany i przetestowany na płytce STM32 Discovery. 

Wstęp


Wyświetlacz alfanumeryczny LCD ze sterownikiem Hitachi HD44780 pozwala na obsługę dwóch wielkości ekrany 2x16 albo 4x20. Każdy ze znaków jest w rozmiarze 5x8 pikseli. 

Komunikacja może odbywać się w trybie 4 bądź 8 bitowym. Przeważnie wykorzystuje się komunikację 4 bitową, ponieważ oszczędza ona linie wejścia/wyjścia a pozwala na zrealizowanie tych samych funkcji.

Wyświetlacz posiada zapamiętane wartości znaków w kodzie ASCII w pamięci ROM. Dzięki temu w celu wyświetlenia konkretnego znaku wystarczy przesłać jego kod, a nie sam znak. Jeśli potrzebny jest znak z poza znaków ASCII, istnieje możliwość zdefiniowana własnego ciągu, maksymalnie ośmiu wzorców.

Rys. 1. Wygląd zewnętrzny wyświetlacza


Podłączenie


Wyświetlacz należy podłączyć do następujących wyprowadzeń:
  • VSS - GND
  • VDD - 5V
  • V0 - Kontrast do potencjometru
  • RS - Liniar Register Select - GPIOB2
  • RW - Read/Write - GND, wymuszenie cały czas stanu niskiego, wpisujemy tylko dane na wyświetlacz 
  • E - Enable - GPIOB7
  • D0-D3 - linie danych nie podłączone
  • D4 - linia danych - GPIOC12
  • D5 - linia danych - GPIOC13
  • D6 - linia danych - GPIOC14
  • D7 - linia danych - GPIOC15
  • A - podświetlenie wyświetlacza, można podłączyć do potencjometru, na stałe do 3V, bądź np. sterować wypełnieniem sygnału 
  • K - GND podświetlenia

Opis poszczególnych wyprowadzeń wyświetlacza:

Rys. 2. Pinout wyświetlacza [radzio.dxp.pl]

Poniżej przedstawię sposób inicjalizacji wyświetlacza oraz ustawienia wszystkich niezbędnych parametrów oraz funkcji.

Programowanie


Przed przystąpieniem do programowania należy zapoznać się z wartościami oraz funkcjami poszczególnych rejestrów sterownika wyświetlacza:


//Komendy
#define HD44780_CLEARDISPLAY  0x01
#define HD44780_RETURNHOME  0x02
#define HD44780_ENTRYMODESET  0x04
#define HD44780_DISPLAYCONTROL  0x08
#define HD44780_CURSORSHIFT  0x10
#define HD44780_FUNCTIONSET  0x20
#define HD44780_SETCGRAMADDR  0x40
#define HD44780_SETDDRAMADDR  0x80
 
//Flagi dla wyświetlania danych przychodzących
#define HD44780_ENTRYRIGHT  0x00
#define HD44780_ENTRYLEFT         0x02
#define HD44780_ENTRYSHIFTINCREMENT     0x01
#define HD44780_ENTRYSHIFTDECREMENT     0x00
 
//Flagi do wyświetlania włączenia/wyłączenia wyświetlacza
#define HD44780_DISPLAYON  0x04
#define HD44780_CURSORON  0x02
#define HD44780_BLINKON   0x01
 
//Flagi dla przesunięc kursora
#define HD44780_DISPLAYMOVE  0x08
#define HD44780_CURSORMOVE  0x00
#define HD44780_MOVERIGHT  0x04
#define HD44780_MOVELEFT  0x00
 
//Flagi dla ustawienia poszczegolnych trybów pracy wyświetlacza
#define HD44780_8BITMODE  0x10
#define HD44780_4BITMODE  0x00
#define HD44780_2LINE   0x08
#define HD44780_1LINE   0x00
#define HD44780_5x10DOTS  0x04
#define HD44780_5x8DOTS   0x00
 


Inicjalizacja


Rozpoczęcia programowania całego wyświetlacza należy rozpocząć od aktywacji jego pinów.

//Inicjalizacja pinów wyswietlacza
void HD44780_InitPins(void) 
{
 GPIO_InitTypeDef GPIOINIT;
 
 //Wlaczenie zegarów dla poszczególnych pinów
 RCC_AHB1PeriphClockCmd(HD44780_RS_RCC | HD44780_E_RCC, ENABLE);
 RCC_AHB1PeriphClockCmd(HD44780_D4_RCC | HD44780_D5_RCC, ENABLE);
 RCC_AHB1PeriphClockCmd(HD44780_D6_RCC | HD44780_D7_RCC, ENABLE);
 
 //RS
 GPIOINIT.GPIO_Pin = HD44780_RS_PIN;
 //Pin jako wyjście
 GPIOINIT.GPIO_Mode = GPIO_Mode_OUT;
 //Typ pushPull
 GPIOINIT.GPIO_OType = GPIO_OType_PP;
 //bez podciągnięcia
 GPIOINIT.GPIO_PuPd = GPIO_PuPd_NOPULL;
 //100MHz taktowanie
 GPIOINIT.GPIO_Speed = GPIO_Speed_100MHz;
 GPIO_Init(HD44780_RS_PORT, &GPIOINIT);
 //Po inicjalizacji stan niski na wszystkie linie w celu zresetowania układu 
 GPIO_WriteBit(HD44780_RS_PORT, HD44780_RS_PIN, 0x00);
 
 //E
 GPIOINIT.GPIO_Pin = HD44780_E_PIN;
 GPIOINIT.GPIO_Mode = GPIO_Mode_OUT;
 GPIOINIT.GPIO_OType = GPIO_OType_PP;
 GPIOINIT.GPIO_PuPd = GPIO_PuPd_NOPULL;
 GPIOINIT.GPIO_Speed = GPIO_Speed_100MHz;
 GPIO_Init(HD44780_E_PORT, &GPIOINIT);
 GPIO_WriteBit(HD44780_E_PORT, HD44780_E_PIN, 0x00);
 
 //D4
 GPIOINIT.GPIO_Pin = HD44780_D4_PIN;
 GPIOINIT.GPIO_Mode = GPIO_Mode_OUT;
 GPIOINIT.GPIO_OType = GPIO_OType_PP;
 GPIOINIT.GPIO_PuPd = GPIO_PuPd_NOPULL;
 GPIOINIT.GPIO_Speed = GPIO_Speed_100MHz;
 GPIO_Init(HD44780_D4_PORT, &GPIOINIT);
 GPIO_WriteBit(HD44780_D4_PORT, HD44780_D4_PIN, 0x00);
 
 //D5
 GPIOINIT.GPIO_Pin = HD44780_D5_PIN;
 GPIOINIT.GPIO_Mode = GPIO_Mode_OUT;
 GPIOINIT.GPIO_OType = GPIO_OType_PP;
 GPIOINIT.GPIO_PuPd = GPIO_PuPd_NOPULL;
 GPIOINIT.GPIO_Speed = GPIO_Speed_100MHz;
 GPIO_Init(HD44780_D5_PORT, &GPIOINIT);
 GPIO_WriteBit(HD44780_D5_PORT, HD44780_D5_PIN, 0x00);
 
 //D6
 GPIOINIT.GPIO_Pin = HD44780_D6_PIN;
 GPIOINIT.GPIO_Mode = GPIO_Mode_OUT;
 GPIOINIT.GPIO_OType = GPIO_OType_PP;
 GPIOINIT.GPIO_PuPd = GPIO_PuPd_NOPULL;
 GPIOINIT.GPIO_Speed = GPIO_Speed_100MHz;
 GPIO_Init(HD44780_D6_PORT, &GPIOINIT);
 GPIO_WriteBit(HD44780_D6_PORT, HD44780_D6_PIN, 0x00);
 
 //D7
 GPIOINIT.GPIO_Pin = HD44780_D7_PIN;
 GPIOINIT.GPIO_Mode = GPIO_Mode_OUT;
 GPIOINIT.GPIO_OType = GPIO_OType_PP;
 GPIOINIT.GPIO_PuPd = GPIO_PuPd_NOPULL;
 GPIOINIT.GPIO_Speed = GPIO_Speed_100MHz;
 GPIO_Init(HD44780_D7_PORT, &GPIOINIT);
 GPIO_WriteBit(HD44780_D7_PORT, HD44780_D7_PIN, 0x00);
}

Kolejnym elementem jest włączenie wyświetlacza. Wykonuje się to w następujący sposób. Po inicjalizacji pinów należy podać stan 0 na wszystkie linie. Pin R/W jest na stałe ustawiony do masy, oznacza to, że do wyświetlacza można dokonywać tylko zapis. Stan 1 na tej linii pozwoli na odczytywanie danych przez mikrokontroler z jego sterownika.

Po skonfigurowaniu wszystkich linii należy odczekać co najmniej 40 ms zanim rozpocznie się wysyłanie danych do sterownika. Następnie należy wysłać komendę 0011 czyli 0x03, po jej wysłaniu trzeba odczekać co najmniej 4,1ms. Kolejnym krokiem jest wysłanie 0x03 i odczekanie 100us. Na samym końcu należy wysłać ostatni raz wartość 0x03. W dalszej części można się już komunikować ze sterownikiem. Wobec tego w kolejnym kroku wybiera się tryb transmisji, w moim przypadku 4 bitowy. Następnie wybiera się sposób wyświetlania, włącza się wyświetlacz, czyści się go, oraz ustawia parametry domyślne czcionek.

//Funkcja inicjalizacja wyswietlacza
void HD44780_Init(uint8_t cols, uint8_t rows) 
{
 //Inicjalizacja opoznienia
 DELAY_Init();
 
 //Inicjalizacja pinow, wyprowadzen mikrokontrolera
 HD44780_InitPins();
 
 //Opóznienie minimum 40ms
 Delay(45000);
 
 //Przypisanie zmiennej HD44780_Opts.Rows zmiennej rows czyli rzedy
 HD44780_Opts.Rows = rows;
 //Przypisanie zmiennej HD44780_Opts.Cols zmiennej cols czyli kolumny
 HD44780_Opts.Cols = cols;
 
 //Przypisanie startowej pozycji x na 0
 HD44780_Opts.currentX = 0;
 //Przypisanie startowej pozycji y na 0
 HD44780_Opts.currentY = 0;
 
 //Ustawienie funkcji wyswietlacza
 //Tryb 4bitowy 0x00 | 5x8 0x00 | 1Line 0x00
 HD44780_Opts.DisplayFunction = HD44780_4BITMODE | HD44780_5x8DOTS | HD44780_1LINE;
 if (rows > 1) 
 {
  //Wybranie dwóch linii 0x08
  HD44780_Opts.DisplayFunction |= HD44780_2LINE;
 }
 
 //Wyslanie komendy 0011, po niej nalezy odczekac przynajmniej 4,1 ms
 HD44780_Cmd4bit(0x03);
 Delay(4500);
 
 //Druga próba
 HD44780_Cmd4bit(0x03);
 Delay(200);
 
 //Trzecia próba
 HD44780_Cmd4bit(0x03);
 
 //Wybieramy 4 bitowy interfejs, wyslanie komendy 0010
 //Ustawienie 4 bitowego interfejsu
 HD44780_Cmd4bit(0x02);
 Delay(100);
 
 //Ustawienie lini, sposobu wyswietlania
 HD44780_Cmd(HD44780_FUNCTIONSET | HD44780_Opts.DisplayFunction);
 
 //Wlacz wyswietlacz 0x04
 HD44780_Opts.DisplayControl = HD44780_DISPLAYON;
 HD44780_DisplayOn();
 
 //Czyszczenie wyswietlacza
 HD44780_Clear();
 
 //Ustawienie domyslnej czcionki
 HD44780_Opts.DisplayMode = HD44780_ENTRYLEFT | HD44780_ENTRYSHIFTDECREMENT;
 HD44780_Cmd(HD44780_ENTRYMODESET | HD44780_Opts.DisplayMode);
 
 Delay(4500);
}

Wykorzystywanie 4 bitowego trybu transmisji wymusza wysyłanie znaku w dwóch częściach. Najpierw wysyłana jest starsza połówka słowa danych, określana jako nibble, potem zaś wysyłana jest młodsza.

Wobec tego funkcja wysyłająca dane do wyświetlacza wygląda następująco:

//Wysyła komendy do wyświetlacza
void HD44780_Cmd(uint8_t cmd) 
{
 //Sygnał niski na linię RS
 GPIO_WriteBit(HD44780_RS_PORT, HD44780_RS_PIN, 0x00);
 
 //Wysłanie starszej części danych
 HD44780_Cmd4bit(cmd >> 4);
 //Wysłanie młodszej części
 HD44780_Cmd4bit(cmd & 0x0F);
}
//Wysłanie danych do wyświetlacza
void HD44780_Data(uint8_t data) 
{
 GPIO_WriteBit(HD44780_RS_PORT, HD44780_RS_PIN, Bit_SET);
 
 //Wysłanie starszej części danych
 HD44780_Cmd4bit(data >> 4);
 //Wysłanie młodszej części
 HD44780_Cmd4bit(data & 0x0F);
}
/* Wysyla 4 bitowe komendy do wyswietlacza */
void HD44780_Cmd4bit(uint8_t cmd) 
{
 //Wpisanie danych do wyświetlacza, jeśli wpisywane dane są różne od zera wtedy stan wysoki
 //Jeśli nie są wtedy stan niski
 GPIO_WriteBit(HD44780_D7_PORT, HD44780_D7_PIN, (cmd & 0x08) != 0 ? Bit_SET : Bit_RESET);
 GPIO_WriteBit(HD44780_D6_PORT, HD44780_D6_PIN, (cmd & 0x04) != 0 ? Bit_SET : Bit_RESET);
 GPIO_WriteBit(HD44780_D5_PORT, HD44780_D5_PIN, (cmd & 0x02) != 0 ? Bit_SET : Bit_RESET);
 GPIO_WriteBit(HD44780_D4_PORT, HD44780_D4_PIN, (cmd & 0x01) != 0 ? Bit_SET : Bit_RESET);
 //Wystawienie sygnału wysokiego na pin E 
 GPIO_WriteBit(HD44780_E_PORT, HD44780_E_PIN, Bit_SET);
 //Odczekanie 20us
 Delay(20);
 //Wystawienie stanu niskiego na pin E
 GPIO_WriteBit(HD44780_E_PORT, HD44780_E_PIN, Bit_RESET);
 //Odczekanie 20us
 Delay(20);
}

Do podawania odpowiednich wartości tzn set oraz reset wykorzystano odpowiednią strukturę z biblioteki stm32f4xx_gpio.h.

typedef enum
{ 
  Bit_RESET = 0,
  Bit_SET
}BitAction;
#define IS_GPIO_BIT_ACTION(ACTION) (((ACTION) == Bit_RESET) || ((ACTION) == Bit_SET))

Poniżej przedstawiam funckję wprowadzającą dane na wyświetlacz. W argumentach podaje się miejsce tzn. wartość położenia tekstu w miejscu x oraz y.

void HD44780_Puts(uint8_t x, uint8_t y, char* str) 
{
 HD44780_CursorSet(x, y);
 while (*str) 
 {
  if (HD44780_Opts.currentX >= HD44780_Opts.Cols) 
  {
   HD44780_Opts.currentX = 0;
   HD44780_Opts.currentY++;
   HD44780_CursorSet(HD44780_Opts.currentX, HD44780_Opts.currentY);
  }
  if (*str == '\n') 
  {
   HD44780_Opts.currentY++;
   HD44780_CursorSet(HD44780_Opts.currentX, HD44780_Opts.currentY);
  } 
  else if (*str == '\r') 
  {
   HD44780_CursorSet(0, HD44780_Opts.currentY);
  } 
  else 
  {
   HD44780_Data(*str);
   HD44780_Opts.currentX++;
  }
  str++;
 }
}

Główna funkcja prezentująca funkcje wyświetlacza wygląda następująco:

#include "stm32f4xx.h"
#include "stm32f4_discovery.h"
#include "itoa.h"
#include "stm32f4_delay.h"
#include "stm32f4_HD44780.h"
 
int main(void)
{
 int f = 3;
 char buf[20];
 char *buf1[10];
 
 //Inicjalizacja wyswietlacza, podajemy wartosc wierszy i kolumn
 HD44780_Init(16, 2);
 
 //Wypisanie stringu na wyswietlaczu
 HD44780_Puts(0, 0, "STM32F4 Discover");
 
 itoa(f, buf1, 10);
 HD44780_Puts(0, 1, buf1);
 
 Delayms(4000);
 
 HD44780_Clear();
 
 //Zapala kursor
 HD44780_CursorOn();
 HD44780_BlinkOn();
 
 //Wpisanie tekstu
 HD44780_Puts(0, 1, "16x2 HD44780 LCD");
 
 itoa(34243, buf1, 10);
 HD44780_Puts(0, 0, buf1);
 
 while (1)
 {
 }
}

Biblioteki dodatkowe do projektu można dodawać poprzez umieszczenie ich w folderach dołączonych do niego, lub poprzez podanie pełnej ścieżki dostępu w cudzysłowie.

Bibliografia:


[1]       Dokumentacja sterownika Hitachi HD44780
[2]       Galewski M. STM32 Aplikacje i ćwiczenia w języku C
[3]       http://stm32f4-discovery.com