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.

LCD displej ST9720

IMG 20251018 143236

Zapojení

Piny na displeji

ST7920 zapojeni pinu

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.

Výsledek na videu (třese se mi ruka - promiňte)
Sestavení projektu
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
Celý projekt ke stažení

pico-u8g2-st7920.tar.gz

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

část 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:

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

Šablona CMakeLists.txt
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