Ostatnio opisywałem sposób obsługi tego wyświetlacza przez mikrokontroler Atmega328 na arduino. Tym razem przyszła pora na odpalenie tego samego modułu na STM32. Sposób programowania i inicjalizacji jest bardzo podobny, właściwie jedyną różnicą są tutaj odpowiednie ustawienia i deklaracje poszczególnych portów oraz interfejsu SPI.
Poniżej przedstawiam schemat blokowy wyświetlacza, który został zaczerpnięty z dokumentacji:
Z wyświetlacza są wyprowadzana następujące dane:
Wstęp
Poniżej przedstawiam schemat blokowy wyświetlacza, który został zaczerpnięty z dokumentacji:
Rys. 1. Schemat blokowy wyświetlacza [1]
Z wyświetlacza są wyprowadzana następujące dane:
- R0 - R47 - Sterownik wyświetlacza, określa ilość rzędów, czyli wierszy;
- C0 - C83 - Sterownik wyświetlacza, tym razem kolumn;
- VSS1,2 - Masa;
- VDD1,2 - Zasilanie;
- T1 - Test 1 we;
- T2 - Test 2, wy;
- T3 - Test 3, we/wy;
- T4 - Test 4, we;
- SDIN - Wejście danych
- SCLK - Zegar taktujący
- D/C - linia danych, komend, informuje czy są przesyłanie dane lub komendy.
- SCE - wybranie urządzenia;
- OSC - oscylator.\;
- RES - zewnętrzne wejście sygnału reset;
Zestaw dostępnych komend prezentuje się następująco:
Rys. 2. Zestaw rejestrów [1]
Podłączenie
Mikrokontroler F103RB posiada dwa SPI. Ten interfejs szeregowy został wyprowadzony na następujące piny.
- SPI1
- NSS(CS) - PA4
- MISO - PA6
- MOSI - PA7
- SCK - PA5
- SPI2
- NSS(CS) - PB12
- MISO - PB14
- MOSI - PB15
- SCK - PB13
Wyświetlacz należy podłączyć do następujących wyprowadzeń:
- RST - PC3
- CE - PC2
- DC - PC1
- Din - PA7 (SPI1), PB15 (SPI2)
- Clk - PA5 (SPI1), PB13 (SPI2)
- Vcc - 3,3V
- BL - 3,3V lub jeśli ktoś chciałby sterować podświetleniem wtedy do pinu z PWM
- GND - GND
SPI dla tego wyświetlacza wykorzystuje wyprowadzenia MOSI oraz SCK. Piny RST, CE, DC można podłączyć właściwie dowolnie.
Wyświetlacz wykorzystuje sterownik PCD8544, który jest jego głównym kontrolerem. Daje on możliwość wyświetlenia na ekranie 6 linii po 14 znaków w każdej, jeśli znaki są w rozmiarze 6x8 pikseli. Pamięć sterownika składa się z 6 zestawów po 84 piksele. Dzięki temu wyświetlacz został podzielony na 6 linii wspomnianych wcześniej.
Programowanie odbywa się poprzez przesłanie odpowiednich danych do rejestrów. Ich adresy zostały na stałe przypisane w programie. Wyświetlacz posiada następujące rejestry:
Poniżej przedstawiam opisane poszczególne elementy programu, które są niezbędne do przeprowadzenia zadowalającej komunikacji z wyświetlaczem.
Inicjalizacja portów GPIO:
Programowanie należy rozpocząć od inicjalizacji odpowiednich portów wejścia wyjścia. Należy skonfigurować porty odpowiedzialne za SPI, jak i pozostałe porty, które są wykorzystywane.
Inicjalizacja SPI:
W kolejnym kroku należałoby zainicjalizować odpowiedni interfejs SPI. W następujący sposób:
Inicjalizacja wyświetlacza:
W tym punkcie należy rozpocząć komunikację z wyświetlaczem poprzez wysłanie odpowiednich komend do rejestrów.
Pierwszym krokiem jest wyzerowanie układu poprzez podanie na chwilkę stanu niskiego na pin RST. Kolejnym elementem jest wystawienie stanu wysokiego. Następnie przesyłane są odpowiednie komendy ustawiające sposób pracy wyświetlacza.
Przesłanie komend odbywa się w następujący sposób:
Stanem niskim na linii D/C regulujemy czy wysyłane są dane(wysoki) czy rozkazy(niski). Kiedy jest ona w stanie wysokim przesłane bajty zostają umieszczone w pamięci RAM, zostają one potraktowane jako kolejne fragmenty kodu do wyświetlenia. Jeśli jest stan niski na tej linii, wtedy zostają one automatycznie przyporządkowane jako rozkazy. Pozwalają one na wyświetlanie danych oraz ustawianie trybu pracy wyświetlacza.
Tablica znaków:
Tablica zmiennych znakowych prezentuje się następująco, została ona wykonana przez firmę SpunkFan. Są w niej opisane wszystkie potrzebne znaki, zdeklarowane w kodzie szesnastkowym.
Wystawiane są one poprzez zapalanie odpowiednich pikseli w kolumnie.
Czyszczenie wyświetlacza:
Wyświetlenie znaków:
W celu wyświetlenia danych na wyświetlaczu należy podać numer linii wyświetlacza oraz numer bajtu w wierszu. Po zapisaniu pojedynczego bajtu do pamięci zwiększona zostaje liczba kolumn, tak aby kolejny bajt opisywał kolejną kolumnę.
Program całość:
[1] PCD8544 - dokumentacja
Wyświetlacz wykorzystuje sterownik PCD8544, który jest jego głównym kontrolerem. Daje on możliwość wyświetlenia na ekranie 6 linii po 14 znaków w każdej, jeśli znaki są w rozmiarze 6x8 pikseli. Pamięć sterownika składa się z 6 zestawów po 84 piksele. Dzięki temu wyświetlacz został podzielony na 6 linii wspomnianych wcześniej.
Programowanie odbywa się poprzez przesłanie odpowiednich danych do rejestrów. Ich adresy zostały na stałe przypisane w programie. Wyświetlacz posiada następujące rejestry:
#define NOKIA_FUNCTION_SET 0x20 #define NOKIA_DISP_CONTROL 0x08 #define NOKIA_DISP_NORMAL 0x0c #define NOKIA_SET_Y 0x40 #define NOKIA_SET_X 0x80 #define NOKIA_H_TC 0x04 #define NOKIA_H_BIAS 0x10 #define NOKIA_H_VOP 0x80
Programowanie
Poniżej przedstawiam opisane poszczególne elementy programu, które są niezbędne do przeprowadzenia zadowalającej komunikacji z wyświetlaczem.
Inicjalizacja portów GPIO:
Programowanie należy rozpocząć od inicjalizacji odpowiednich portów wejścia wyjścia. Należy skonfigurować porty odpowiedzialne za SPI, jak i pozostałe porty, które są wykorzystywane.
void GPIOInit_Nokia(void) { GPIO_InitTypeDef GPIOInit; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //Inicjalizacja pinów SCK oraz MOSI GPIO_StructInit(&GPIOInit); GPIOInit.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP; GPIOInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIOInit); //Inicjalizacja MISO GPIOInit.GPIO_Pin = GPIO_Pin_6; GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIOInit.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(GPIOA, &GPIOInit); //Wlaczenie pozostałych linii GPIOInit.GPIO_Pin = LCD_DC|LCD_CE|LCD_RST; GPIOInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &GPIOInit); //Ustawienie stanów wysokich na liniach CE oraz RST. GPIO_SetBits(GPIOC, LCD_CE|LCD_RST); }
Inicjalizacja SPI:
W kolejnym kroku należałoby zainicjalizować odpowiedni interfejs SPI. W następujący sposób:
void SPIInit_Nokia(void) { SPI_InitTypeDef SPIInit; //Włączenie zegara dla SPI RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); SPI_StructInit(&SPIInit); //Transmisja z wykorzystaniem dwóch linii SPIInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //Tryb pracy mikrokontrolera SPIInit.SPI_Mode = SPI_Mode_Master; //Stan sygnału taktującego przy braku transmisji SPIInit.SPI_CPOL = SPI_CPOL_Low; //Aktywne zbocze sygnału taktującego SPIInit.SPI_CPHA = SPI_CPHA_1Edge; //Wylaczenie sprzetowej obslugi linii CS SPIInit.SPI_NSS = SPI_NSS_Soft; //Szybkośc transmisji 64MHz/16 = 4MHz SPIInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //Inicjalizacja SPI SPI_Init(SPI1, &SPIInit); //Włączenie SPI SPI_Cmd(SPI1, ENABLE); }
Inicjalizacja wyświetlacza:
W tym punkcie należy rozpocząć komunikację z wyświetlaczem poprzez wysłanie odpowiednich komend do rejestrów.
Pierwszym krokiem jest wyzerowanie układu poprzez podanie na chwilkę stanu niskiego na pin RST. Kolejnym elementem jest wystawienie stanu wysokiego. Następnie przesyłane są odpowiednie komendy ustawiające sposób pracy wyświetlacza.
void lcd_setup(void) { //Sygnał niski na RST, reset wyświetlacza GPIO_ResetBits(GPIOC, LCD_RST); delay_ms(2); //Stan wysoki na pin reset GPIO_SetBits(GPIOC, LCD_RST); //Wlączenie zasilania wyświetlacza //Function SET 00100001 NOKIACMD(0x21); //Napięcie zasilania matrycy VOP 11001000 NOKIACMD(0xc8); //Wybór krzywej kompensacji temperatury //00001100 NOKIACMD(0x06); //Współczynnik multipleksowania 00010011 NOKIACMD(0x13); //Włączenie zasilania matrycy 00100000 NOKIACMD(0x20); //Standardowy tryb pracy 00001100 //Do wyboru są dwa tryby pracy NOKIACMD(0x0c); //Zresetowanie licznika wierszy NOKIACMD(0x40); //Zresetowanie liczniki kolumn NOKIACMD(0x80); }
Przesłanie komend odbywa się w następujący sposób:
static void NOKIAcmd(uint8_t cmd) { //Ustawienie stanu niskiego na CE i DC GPIO_ResetBits(GPIOC, LCD_CE|LCD_DC); //Przesłanie komendy SPISend(cmd); //Ustawienie stanu wysokiego na ChipSelect GPIO_SetBits(GPIOC, LCD_CE); }
Stanem niskim na linii D/C regulujemy czy wysyłane są dane(wysoki) czy rozkazy(niski). Kiedy jest ona w stanie wysokim przesłane bajty zostają umieszczone w pamięci RAM, zostają one potraktowane jako kolejne fragmenty kodu do wyświetlenia. Jeśli jest stan niski na tej linii, wtedy zostają one automatycznie przyporządkowane jako rozkazy. Pozwalają one na wyświetlanie danych oraz ustawianie trybu pracy wyświetlacza.
Tablica znaków:
Tablica zmiennych znakowych prezentuje się następująco, została ona wykonana przez firmę SpunkFan. Są w niej opisane wszystkie potrzebne znaki, zdeklarowane w kodzie szesnastkowym.
Wystawiane są one poprzez zapalanie odpowiednich pikseli w kolumnie.
const uint8_t font_ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ~ ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f DEL };
Czyszczenie wyświetlacza:
//Czyszczenie wyświetlacza void lcd_clear(void) { //void * memset ( void * buffer, int c, size_t num ); //Wypełnia kolejne bajty w pamięci ustaloną wartością //W tym przypadku jest to zero, czyszczony jest wyświetlacz //Ustawienie stanu niskiego na CE i DC memset(lcd_buffer, 0, (84 * 48 / 8)); }
Wyświetlenie znaków:
W celu wyświetlenia danych na wyświetlaczu należy podać numer linii wyświetlacza oraz numer bajtu w wierszu. Po zapisaniu pojedynczego bajtu do pamięci zwiększona zostaje liczba kolumn, tak aby kolejny bajt opisywał kolejną kolumnę.
void lcd_draw_text(int row, int col, const char* text) { //Deklaracja zmiennych int i; uint8_t* pbuf = &lcd_buffer[row * 84 + col]; //Dopóki będzie jakiś tekst do wyświetlania oraz będzie możliwośc //wpisania danych wtedy funkcja będzie wykonywana while ((*text) && (pbuf < &lcd_buffer[(84 * 48 / 8) - 6])) { //Wskaźnik na wartośc, zostaje ona zwiększona o 1 int ch = *text++; //Deklaracja zmiennej w kodzie ASCII z wskażnikiem na jej adres. //Liczenie od 0 odejmnowanie znaku spacji const uint8_t* font = &font_ASCII[ch - ' '][0]; //Przejście w tablicy po poszczególnych wartościach podanego znaku for (i = 0; i < 5; i++) *pbuf++ = *font++; *pbuf++ = 0; } }
Program całość:
#include <stdio.h> #include <stdint.h> #include "stm32f10x.h" #define LCD_DC GPIO_Pin_1 #define LCD_CE GPIO_Pin_2 #define LCD_RST GPIO_Pin_3 //Deklaracja tablicy znaków const uint8_t font_ASCII[][5] = { {0x00, 0x00, 0x00, 0x00, 0x00} // 20 ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 ! ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 " ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 # ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $ ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 % ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 & ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 ' ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 ( ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 ) ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a * ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b + ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c , ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d - ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e . ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f / ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0 ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1 ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2 ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3 ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4 ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5 ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6 ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7 ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8 ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9 ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a : ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ; ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c < ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d = ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e > ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ? ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @ ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [ ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ] ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^ ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _ ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 ` ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b { ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c | ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d } ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ~ ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f DEL }; uint8_t lcd_buffer[84 * 48 / 8]; volatile static uint32_t timer_ms = 0; void SysTick_Handler(); void delay_ms(int time); //Wysłanie komendy na wyświetlacz static uint8_t SPISend(uint8_t byte); //Funkcja pozwalająca na przesyłąnie danych do układu static void NOKIACMD(uint8_t cmd); //Inicjalicja wyświetlacza void NokiaInit(void); //Czyszczenie wyświetlacza void LCDClear(void); void LCDDraw(int row, int col, const char* text); //Inicjalizacja portów GPIO void GPIOInit_Nokia(void); //Inicjalizacja SPI void SPIInit_Nokia(void); //Inicjalizacja licznika Systick, przerwania co 1ms void SystickInit(void); void LCDCopy(void); int main(void) { GPIOInit_Nokia(); SPIInit_Nokia(); SystickInit(); NokiaInit(); LCDClear(); LCDDraw(1, 1, "Nokia"); LCDDraw(2, 8, "5110"); LCDDraw(3, 1, "STM32"); LCDDraw(4, 8, "Nucleo"); LCDDraw(5, 12, "TEST");
LCDCopy(); while (1) {} } void SysTick_Handler() { if (timer_ms) timer_ms--; } void delay_ms(int time) { timer_ms = time; while (timer_ms); } void SystickInit(void) { SysTick_Config(SystemCoreClock / 1000); } //Wysłanie komendy na wyświetlacz static uint8_t SPISend(uint8_t byte) { //Pętla while czeka aż bufor nadawczy będzie wolny while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); GPIO_ResetBits(GPIOC, LCD_CE); SPI_I2S_SendData(SPI1, byte); // poczekaj na dane w buforze odbiorczym while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); GPIO_SetBits(GPIOC, LCD_CE); return SPI_I2S_ReceiveData(SPI1); } //Funkcja pozwalająca na przesyłąnie danych do układu static void NOKIACMD(uint8_t cmd) { //Ustawienie stanu niskiego na CE i DC GPIO_ResetBits(GPIOC, LCD_CE|LCD_DC); //Przesłanie komendy SPISend(cmd); //Ustawienie stanu wysokiego na ChipSelect GPIO_SetBits(GPIOC, LCD_CE); } //Inicjalicja wyświetlacza void NokiaInit(void) { //Sygnał niski na RST, reset wyświetlacza GPIO_ResetBits(GPIOC, LCD_RST); delay_ms(2); //Stan wysoki na pin reset GPIO_SetBits(GPIOC, LCD_RST); //Wlączenie zasilania wyświetlacza //Function SET 00100001 NOKIACMD(0x21); //Napięcie zasilania matrycy VOP 11001000 NOKIACMD(0xc8); //Wybór krzywej kompensacji temperatury //00001100 NOKIACMD(0x06); //Współczynnik multipleksowania 00010011 NOKIACMD(0x13); //Włączenie zasilania matrycy 00100000 NOKIACMD(0x20); //Standardowy tryb pracy 00001100 //Do wyboru są dwa tryby pracy NOKIACMD(0x0c); //Zresetowanie licznika wierszy NOKIACMD(0x40); //Zresetowanie liczniki kolumn NOKIACMD(0x80); } //Czyszczenie wyświetlacza void LCDClear(void) { //void * memset ( void * buffer, int c, size_t num ); //Wypełnia kolejne bajty w pamięci ustaloną wartością //W tym przypadku jest to zero, czyszczony jest wyświetlacz //Ustawienie stanu niskiego na CE i DC memset(lcd_buffer, 0, (84 * 48 / 8)); } void LCDDraw(int row, int col, const char* text) { //Deklaracja zmiennych int i; uint8_t* pbuf = &lcd_buffer[row * 84 + col]; //Dopóki będzie jakiś tekst do wyświetlania oraz będzie możliwośc //wpisania danych wtedy funkcja będzie wykonywana while ((*text) && (pbuf < &lcd_buffer[(84 * 48 / 8) - 6])) { //Wskaźnik na wartośc, zostaje ona zwiększona o 1 int ch = *text++; //Deklaracja zmiennej w kodzie ASCII z wskażnikiem na jej adres. //Liczenie od 0 odejmnowanie znaku spacji const uint8_t* font = &font_ASCII[ch - ' '][0]; //Przejście w tablicy po poszczególnych wartościach podanego znaku for (i = 0; i < 5; i++) *pbuf++ = *font++; *pbuf++ = 0; } } //Inicjalizacja portów GPIO void GPIOInit_Nokia(void) { GPIO_InitTypeDef GPIOInit; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //Inicjalizacja pinów SCK oraz MOSI GPIO_StructInit(&GPIOInit); GPIOInit.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP; GPIOInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIOInit); //Inicjalizacja MISO GPIOInit.GPIO_Pin = GPIO_Pin_6; GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIOInit.GPIO_Speed = GPIO_Speed_10MHz; GPIO_Init(GPIOA, &GPIOInit); //Wlaczenie pozostałych linii GPIOInit.GPIO_Pin = LCD_DC|LCD_CE|LCD_RST; GPIOInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &GPIOInit); //Ustawienie stanów wysokich na liniach CE oraz RST. GPIO_SetBits(GPIOC, LCD_CE|LCD_RST); } //Inicjalizacja SPI void SPIInit_Nokia(void) { SPI_InitTypeDef SPIInit; //Włączenie zegara dla SPI RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); SPI_StructInit(&SPIInit); //Transmisja z wykorzystaniem dwóch linii SPIInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //Tryb pracy mikrokontrolera SPIInit.SPI_Mode = SPI_Mode_Master; //Stan sygnału taktującego przy braku transmisji SPIInit.SPI_CPOL = SPI_CPOL_Low; //Aktywne zbocze sygnału taktującego SPIInit.SPI_CPHA = SPI_CPHA_1Edge; //Wylaczenie sprzetowej obslugi linii CS SPIInit.SPI_NSS = SPI_NSS_Soft; //Szybkośc transmisji 64MHz/16 = 4MHz SPIInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //Inicjalizacja SPI SPI_Init(SPI1, &SPIInit); //Włączenie SPI SPI_Cmd(SPI1, ENABLE); } void LCDCopy(void) { int i; GPIO_SetBits(GPIOC, LCD_DC); GPIO_ResetBits(GPIOC, LCD_CE); for (i = 0; i < (84 * 48 / 8); i++) SPISend(lcd_buffer[i]); GPIO_SetBits(GPIOC, LCD_CE); }
Bibliografia
[1] PCD8544 - dokumentacja