czwartek, 24 grudnia 2015

[12] STM32 M3 - Nucleo - F103RB - Wyświetlacz Nokia 5110 SPI

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.

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:

#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