Podíváme se, jak používat monochromatický LCD displej 128x64 bodů s čipem ST7920. Displej je poměrně velký a dobře čitelný, dá se používat v grafickém režimu a máme k dispozici pro něj velké množství fontů včetně psaní znaků v UTF-8. Nemusíme tedy řešit doplňování českých znaků. Vše je to založeno na knihovně pana Olivera Krause u8g2. U8g2 je universální knihovna pro velké množství monochromatických OLED a LCD displejů a pro velké množství architektur (Arduino, STM32, RPi Pico).
Displej umí komunikovat po paralelní sběrnici i po SPI sběrnici, my se budeme zabývat připojením pomocí SPI. Pomocí PWM jsem udělal řízení jasu podsvícení displeje.

Zapojení

Na displeji musíme mít propájenou propojku S/P na S (sériová sběrnice) — na obrázku ještě propájená neni. Potenciometr VR1 je na regulaci kontrastu displeje, musel jsem ho vytočit na maximum, aby bylo něco vidět. Kontrast by se dal ovládat i externím potenciometrem 10K, pokud by se odpájela propojka JP6. Externí potenciometr se zapojí na krajními nožičkami na +3.3V a zem, prostřední nožička k pinu V0 (toto jsem však nezkoušel).
| pin na dislpeji ST7920 | barva drátu | RPi Pico pin |
|---|---|---|
GND |
černá |
GND 0V zem |
VCC |
červená |
VSYS (+5V pin 39) (na 3.3V nefunguje) |
V0 |
nezapojeno |
|
RS |
žlutá |
GPIO5 SPI0 CSn (chip select) |
P/W |
bílá |
GPIO3 SPI0 TX (MOSI) |
E |
zelená |
GPIO2 SPI0 SCK (hodiny) |
DB0 až DB7 (paralelní sběrnice) |
nezapojeno |
nepoužíváme |
PSB |
černá |
GND zem |
NC |
nezapojeno |
|
RST |
modrý |
GPIO13 (reset dipleje) |
VOUT |
nezapojeno |
|
BLA (anoda LED podsvitu) |
oranžová |
GPIO22 (řízení jasu podsvitu - možno připojit na 3.3V a jas bude naplno) |
BLK (katoda LED podsvitu) |
černá |
GND zem |
Testovací program
pico-u8g2-st7920/main.cpp
/* Testování LCD displeje 128x64 bodů v SPI zapojení
* main.cpp
* (c) Jirka Chráska 2025; <jirka@lixis.cz
*/
#include <stdio.h>
#include <tusb.h>
#include <u8g2.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/pio.h"
#include "hardware/pwm.h"
#include "hardware/irq.h"
#include "math.h"
// nastavení parametrů SPI je zde
#include "st7920_spi_u8g2_hal.h"
u8g2_t u8g2;
// test displeje
void draw_test_display()
{
char hey[] = "Ahoj Jirko!";
u8g2_ClearBuffer(&u8g2);
u8g2_ClearDisplay(&u8g2);
u8g2_SetDrawColor(&u8g2, 1);
u8g2_SetFont(&u8g2, u8g2_font_lubBI14_tr);
u8g2_DrawStr(&u8g2, 0, 25, hey);
u8g2_UpdateDisplay(&u8g2);
}
// nastavení displeje ST7920
void display_sequence()
{
u8g2_Setup_st7920_s_128x64_f(&u8g2, U8G2_R0, u8x8_byte_pico_hw_spi, u8x8_gpio_and_delay_pico);
u8g2_InitDisplay(&u8g2);
}
// PWM řízení podsvitu
uint8_t pin = 22; // pin, se kterým budeme řídit podsvit
uint slice_num;
uint chan;
uint state = 0;
// výpočet střídy
uint32_t pwm_set_freq_duty( uint slice_num, uint chan, uint32_t f, int d)
{
uint32_t clock = 125000000;
uint32_t divider16 = clock / f / 4096 + (clock % (f * 4096) != 0);
if( divider16 / 16 == 0) {
divider16 = 16;
}
uint32_t wrap = clock * 16 / divider16 / f - 1;
pwm_set_clkdiv_int_frac( slice_num, divider16/16, divider16 & 0xf );
pwm_set_wrap( slice_num, wrap );
pwm_set_chan_level( slice_num, chan, wrap * d/100 );
return wrap;
}
// výpočet hodnoty wrap
uint32_t pwm_get_wrap( uint slice_num )
{
valid_params_if(HARDWARE_PWM, slice_num >= 0 && slice_num < NUM_PWM_SLICES );
return pwm_hw->slice[slice_num].top;
}
// nastavení střídy
void pwm_set_duty( uint slice_num, uint chan, int d)
{
pwm_set_chan_level(slice_num, chan, pwm_get_wrap( slice_num) * d/100 );
}
#define LED_PIN 25
int main()
{
bool led_stav = true;
char buf[128];
stdio_init_all();
// LED na GPIO25 -- jenom test
gpio_init( LED_PIN );
gpio_set_function( LED_PIN, GPIO_FUNC_SIO );
gpio_set_dir( LED_PIN, true );
gpio_put(LED_PIN, led_stav);
// nastavení řízení podsvitu displeje pomocí PWM
gpio_set_function( pin, GPIO_FUNC_PWM );
slice_num = pwm_gpio_to_slice_num ( pin );
chan = pwm_gpio_to_channel( pin );
uint wrap = pwm_set_freq_duty(slice_num, chan, 500, 0); // 200 Hz
pwm_set_enabled (slice_num, true);
// čekáme na připojení USB sérového rozhraní
// minicom -b 115200 -D /dev/ttyACM0
stdio_filter_driver(&stdio_usb);
while (!tud_cdc_connected())
sleep_ms(100);
printf("\nUSB Serial connected!\n");
puts("Jdeme na to.");
display_sequence();
draw_test_display();
int d = 0;
int b = 60;
bool zvysujeme = true;
while(1)
{
// kubické stmívání
d = (b*b*b)/10000;
pwm_set_duty(slice_num, chan, d);
// výmaz displeje
u8g2_ClearDisplay(&u8g2);
u8g2_SetDrawColor(&u8g2, 1);
// nastavení velkého fontu
u8g2_SetFont(&u8g2, u8g2_font_spleen12x24_me);
sprintf(buf,"Ahoj Jirko!",b);
u8g2_DrawStr(&u8g2, 0, 18, buf);
// nastavení menšího fontu
u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_me);
// Maličký ježeček žere jablíčka je v UTF-8
u8g2_DrawUTF8(&u8g2, 0, 36, "Maličký ježeček");
u8g2_DrawUTF8(&u8g2, 0, 50, "žere jablíčka.");
sprintf(buf,"Podsvit: %d %%",b);
u8g2_DrawStr(&u8g2, 0, 64, buf);
u8g2_UpdateDisplay(&u8g2);
zvysujeme ? b++ : b-- ;
if ( b > 100 ) {
zvysujeme = false; b--;
}
if ( b < 60 ) {
zvysujeme = true; b++;
}
sleep_ms(500);
led_stav = led_stav ? false : true;
gpio_put( LED_PIN, led_stav );
}
return 0;
}
Hardware abstraction layer pico-u8g2-st7920/st7920_spi_u8g2_hal.h
#ifndef ST7920_SPI_U8G2_HAL_H
#define ST7920_SPI_U8G2_HAL_H
// Reference: https://github.com/olikraus/u8g2/issues/2159
#include <u8g2.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
// zapojení displeje a parametry DPI sběrnice
#define SPI_PORT spi0
#define PIN_CS 5
#define PIN_SCK 2
#define PIN_MOSI 3
#define SPI_SPEED 1000 * 1000
#define PIN_RST 13
void st7920_writeReg_SPI(uint8_t aByte);
void st7920_writeData_SPI(uint8_t aByte);
uint8_t u8x8_gpio_and_delay_pico(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8x8_byte_pico_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
#endif
Hardware abstraction layer pico-u8g2-st7920/st7920_spi_u8g2_hal.cpp
#include "st7920_spi_u8g2_hal.h"
void st7920_writeReg_SPI(uint8_t aByte)
{
uint8_t cmdBuf[3];
cmdBuf[0] = 0b11111000;
cmdBuf[1] = aByte & 0xf0;
cmdBuf[2] = (aByte & 0x0f) << 0x04;
spi_write_blocking(SPI_PORT, cmdBuf, sizeof(cmdBuf));
}
void st7920_writeData_SPI(uint8_t aByte)
{
uint8_t cmdBuf[3];
cmdBuf[0] = 0b11111010;
cmdBuf[1] = aByte & 0xf0;
cmdBuf[2] = (aByte & 0x0f) << 0x04;
spi_write_blocking(SPI_PORT, cmdBuf, sizeof(cmdBuf));
}
uint8_t u8x8_gpio_and_delay_pico(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
switch (msg)
{
case U8X8_MSG_GPIO_AND_DELAY_INIT:
gpio_init(PIN_RST);
gpio_init(PIN_CS);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
spi_init(SPI_PORT, SPI_SPEED);
gpio_put(PIN_RST, 0);
sleep_ms(100);
gpio_put(PIN_RST, 1);
gpio_put(PIN_CS, 1);
break;
case U8X8_MSG_DELAY_NANO: // delay arg_int * 1 nano second
sleep_us(arg_int); // 1000 times slower, though generally fine in practice given rp2040 has no `sleep_ns()`
break;
case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
sleep_us(arg_int);
break;
case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
sleep_us(arg_int * 10);
break;
case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
sleep_ms(arg_int);
break;
case U8X8_MSG_GPIO_CS: // CS (chip select) pin: Output level in arg_int
gpio_put(PIN_CS, arg_int);
break;
case U8X8_MSG_GPIO_DC: // DC (data/cmd, A0, register select) pin: Output level
break;
case U8X8_MSG_GPIO_RESET: // Reset pin: Output level in arg_int
gpio_put(PIN_RST, arg_int); // printf("U8X8_MSG_GPIO_RESET %d\n", arg_int);
break;
default:
u8x8_SetGPIOResult(u8x8, 1); // default return value
break;
}
return 1;
}
uint8_t u8x8_byte_pico_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
uint8_t *data;
switch (msg)
{
case U8X8_MSG_BYTE_SEND:
spi_write_blocking(SPI_PORT, (uint8_t*)arg_ptr, arg_int);
break;
case U8X8_MSG_BYTE_INIT:
break;
case U8X8_MSG_BYTE_SET_DC:
break;
case U8X8_MSG_BYTE_START_TRANSFER:
gpio_put(PIN_CS, 1);
break;
case U8X8_MSG_BYTE_END_TRANSFER:
gpio_put(PIN_CS, 0);
break;
default:
return 0;
}
return 1;
}
pico-u8g2-st7920/CMakeLists.txt
# Generated Cmake Pico project file
# u8g2 on pico-sdk reference: https://github.com/olikraus/u8g2/issues/2159
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections -Wl,--gc-sections")
# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
#set(PICO_SDK_PATH "E:/pico-sdk")
set(PICO_BOARD pico CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()
project(pico-u8g2-st7920 C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(pico-u8g2-st7920 main.cpp
st7920_spi_u8g2_hal.cpp )
pico_set_program_name(pico-u8g2-st7920 "pico-u8g2-st7920")
pico_set_program_version(pico-u8g2-st7920 "0.1")
pico_enable_stdio_uart(pico-u8g2-st7920 0)
pico_enable_stdio_usb(pico-u8g2-st7920 1)
# Add the standard library to the build
target_link_libraries(pico-u8g2-st7920
pico_stdlib)
# Add the standard include files to the build
target_include_directories(pico-u8g2-st7920 PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
u8g2/csrc
)
file(GLOB U8G2_SRC u8g2/csrc/*.c)
add_library(u8g2 ${U8G2_SRC})
target_link_options(pico-u8g2-st7920 PRIVATE -Xlinker --print-memory-usage)
# Add any user requested libraries
target_link_libraries(pico-u8g2-st7920
hardware_pio
hardware_spi
hardware_pwm
u8g2
)
pico_add_extra_outputs(pico-u8g2-st7920)
Jas podsvitu nemá cenu snižovat pod 50%, na displeji pak toho moc vidět není. Optimální se mi jeví jas na 80%. Přitom se snižuje spotřeba displeje o 10 mA. Na plný jas z 3.3V bez řízení má displej spotřebu 40 mA, což je poměrně dost.
mkdir pico-u8g2-st7920
cp CMakeLists.txt pico-u8g2-st7920/
cp main.cpp pico-u8g2-st7920/
cp st7920_spi_u8g2_hal.cpp pico-u8g2-st7920/
cp st7920_spi_u8g2_hal.h pico-u8g2-st7920/
cd pico-u8g2-st7920
mkdir u8g2
git clone https://github.com/olikraus/u8g2
mkdir build
cd build
cmake ..
make
# kvůli zatím neznáme chybě ještě jednou
make
Jak zařadit monochromatický displej s řadičem ST7920 do svého projektu
1. Vytvořte si adresář pro svůj projekt, dejme tomu projekt_s_displejem_st7920, v něm si vytvořte podadrsář u8g2 a naklonujte z githubu knihovnu u8g2.
$ mkdir projekt_s_displejem_st7920
$ cd projekt_s_displejem_st7920
$ mkdir u8g2
$ git clone https://github.com/olikraus/u8g2
2. Do adresáře projekt_s_displejem_st7920 nakopírujte soubory st7920_spi_u8g2_hal.cpp a st7920_spi_u8g2_hal.h.
Soubor st7920_spi_u8g2_hal.cpp můžeme klidně přejmenovat na st7920_spi_u8g2_hal.c, protože obsahuje výhradně kód v jazyce C.
Pokud potřebujete zapojit displej jinak, upravte Pico GPIO piny v souboru st7920_spi_u8g2_hal.h
// zapojení displeje a parametry DPI sběrnice
// SPI_PORT může být buď spi0 nebo spi1
#define SPI_PORT spi0
// pin chip select (na displeji RS)
#define PIN_CS 5
// pin pro hodiny (na displeji E)
#define PIN_SCK 2
// pin pro výstup (na dislpeji P/W
#define PIN_MOSI 3
#define SPI_SPEED 1000 * 1000
// pin pro reset displeje (na displeji RST)
#define PIN_RST 13
3. Vezměte si níže uvedenou šablonu CMakeLists.txt, upravte a doplňte ji podle svých potřeb.
Nakopírujte ji do adresáře projekt_s_displejem_st7920.
Je potřeba mít nastavenou proměnnou prostředí PICO_SDK_PATH.
(Já ji mám nastavenou v souboru /home/jirka/.bashrc; takto:
export PICO_SDK_PATH=/home/jirka/pico/pico-sdk
V šabloně si upravíte jméno projektu a EXE, případně doplňte další knihovny, které ve vašem projektu potřebujete.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections -Wl,--gc-sections")
# místo pico můžete napsat picow (Pico s Wifi) nebo pico2 (Pico 2 s procesorem RP3520)
set(PICO_BOARD pico CACHE STRING "Board type")
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
# nastaveni jmena projektu
project(muj_projekt C CXX ASM)
# nastaveni jmena spustitelneho souboru (abychom to nemuseli stále opisovat)
set(EXE muj_projekt)
#inicializace Raspberry Pi Pico SDK
pico_sdk_init()
add_executable(${EXE}
st7920_spi_u8g2_hal.c
main.c
)
pico_set_program_name(${EXE} "muj_projekt")
pico_set_program_version(${EXE} "0.1")
# nastavení standardního výstupu 0 znamená nepoužito, 1 znamená použito
pico_enable_stdio_uart(${EXE} 0)
pico_enable_stdio_usb(${EXE} 1)
# přidání standardní knihovny do projektu
target_link_libraries(${EXE}
pico_stdlib
)
# include adresáře
target_include_directories( ${EXE} PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/.. # pokud potřebujeme třeba pro lwipopts
u8g2/csrc
)
file(GLOB U8G2_SRC u8g2/csrc/*.c)
add_library(u8g2 ${U8G2_SRC})
# volby pro linker
target_link_options( ${EXE} PRIVATE -Xlinker --print-memory-usage)
# další knihovny, které budeme v projektu potřebovat
target_link_libraries( ${EXE}
hardware_spi # hardware_spi potřebujeme pro displej
hardware_pwm # hardware_pwm je pro řízení jasu displeje
u8g2 # u8g2 potřebujeme pro displej
# dále doplňte knihovny, které potřebujete
hardware_rtc
)
# toto je potřeba pro generování uf2 souboru, který nahráváme do Pica
pico_add_extra_outputs(${EXE})
4. Nyní vytvoříme adresář build a v něm sestavíme celý projekt
$ mkdir build
$ cd build
$ cmake ..
$ make
# make musíme spustit ješě jednou, kvůli zatím nezjištěné chybě
$ make
Pokud se nic nepokazilo, tak máme v adresáři build soubor muj_projekt.uf2, který po stisknutí tlačítka BOOTSEL na Picu a připojení USB kabelu nakopírujeme na Pico.
Používání knihovny u8g2
Knihovna u8g2 funguje i na jiných platformách (třeba Arduino) proto pokud budeme studovat referenční manuál, budeme si vybírat vždy C prototypy funkcí.
V kódu, kde budeme používat tuto knihovnu musíme přidat na začátku řádek:
#include <u8g2.h>
Zdroje a odkazy
datasheet ST7920 Datový list čipu ST7920
https://github.com/olikraus/u8g2 knihovna u8g2 Olivera Krause
pico-u8g2-ST7920 tady jsem se inspiroval
Дисплей 128x64 на контроллере ST7920 zapojení displeje ST7920
Kde se to dá koupit: