Wstęp
Cześć! 👋
Dzisiaj chcę Ci pokazać mój ostatni poboczny projekt: połączenie płytki STM32 Nucleo G474RE z 3,7-calowym ekranem e-paper od Waveshare. W skrócie – chciałem zrozumieć, jak te dwa kawałki sprzętu dogadują się ze sobą, jak skonfigurować CubeMX i jak faktycznie wyświetlić coś sensownego na ekranie.
Zanim przejdziemy do kabli i kodu, ustalmy kilka rzeczy:
- Czym jest płytka Nucleo?
STM32 Nucleo to rodzina płytek rozwojowych od STMicroelectronics. Oparte są na mikrokontrolerach STM32 i często używa się ich w profesjonalnych projektach embedded. W porównaniu do Arduino dają Ci znacznie większą kontrolę nad peryferiami i funkcjami niskopoziomowymi. W porównaniu do ESP32, STM32 nie skupia się na Wi-Fi/Bluetooth, tylko na czystej wydajności, deterministycznym czasie działania i niezawodności przemysłowej 🚀. - Co jest wyjątkowego w e-paperze?
Waveshare 3,7’’ to ekran podobny do tego z Kindle’a. Odbija światło zamiast je emitować, więc jest bardzo przyjazny dla oczu. Raz wyrenderowany obraz zostaje na ekranie bez poboru energii, co sprawia, że świetnie nadaje się do projektów zasilanych bateryjnie albo takich, które mają być stale włączone (stacje pogodowe, dashboardy, elektroniczne metki cenowe itd.).
Część teoretyczna
Zanim odpalimy CubeMX, krótko o teorii.
- LUT (Look-Up Table)
Definiuje przebieg sygnałów sterujących cząsteczkami czerni/bieli wewnątrz e-papera. Bez właściwego LUT-u pojawiają się artefakty albo ghosting. Taka lista instrukcji dla wyświetlacza mówiąca jak odbierać nasze dane. - Tryby wyświetlania e-paper:
- GC (Global Clear) – pełne odświeżenie z „mrugnięciem”, bardzo czyste, ale wolne.
- DU (Direct Update) – bez mrugnięcia, dużo szybsze, ale zostawia ghosting.
- A2 – tylko czerń i biel, najszybsze, ale z wyraźnym ghostingiem. Idealne do edytorów tekstu czy terminali.
- Komunikacja: SPI
Ekran komunikuje się przez SPI (Serial Peripheral Interface). Jest proste, szybkie i powszechnie obsługiwane. - Piny GPIO:
- DC – informuje ekran, czy wysyłane są komendy, czy dane.
- BUSY – w stanie wysokim oznacza, że wyświetlacz jest zajęty i nie można mu jeszcze wysyłać nowych danych.
Tworzymy nowy projekt STM32
Odpalam STM32CubeMX, czyli graficzne narzędzie od ST do konfigurowania mikrokontrolerów. Dzięki niemu nie musimy pisać tony boilerplate’u.
- Tworzę nowy projekt i wybieram płytkę Nucleo G474RE.
- Zmieniam toolchain na CMake/GCC (można wtedy pracować z VSCode zamiast przestarzałego Eclipse).
- W ustawieniach generowania zaznaczam „Generate peripheral initialization as a pair of
.c/.h
files per peripheral” – dzięki temu kod jest bardziej modularny.
Po wygenerowaniu kodu otwieram projekt w VSCode, importuję go przez wtyczkę STM32, wciskam F5 i… debugowanie działa od strzału ✅.
Podłączanie ekranu
Darmowe bonusowe treści
⚡ Chcesz od razu dostać jeszcze lepszy sterownik za darmo?
Zapisz się do mojego newslettera!
Dołączając do mojego newslettera, odblokujesz dodatkowe materiały do tego poradnika:
– Pełny, zrefactorowany plik main.c
z dodatkowymi komentarzami i integracją LVGL (rysowanie, czcionki itd)
– Dostęp do całego repozytorium z kompletnym kodem integracji STM32 + Waveshare e-paper
👉 Zapisz się i pobierz gotowe pliki projektu.
E-paper potrzebuje 8 połączeń:
- Zasilanie:
VCC
,GND
,RST
(reset). Nucleo daje 3,3V, które w zupełności wystarcza. - SPI:
CS
,MOSI
,SCK
. - Sterowanie:
DC
(komenda/dane) iBUSY
(status).
RST jest aktywne w stanie niskim, czyli trzeba go ściągnąć do masy, żeby zresetować ekran. BUSY to pin tylko do odczytu, informujący, że ekran nadal się odświeża.
Konfiguracja SPI i GPIO
SPI
SPI to po prostu szybka magistrala szeregowa z linią zegara i danych. W CubeMX włączyłem SPI2 jako „Transmit Only Master” z rozmiarem danych 8 bitów.
CubeMX zaproponował:
PB15
→ MOSIPB13
→ SCK
Preskaler ustawiłem na 64, co dało mi około 2,65 Mbit/s. W zupełności wystarczy do e-papera.
Profesjonalniej byłoby dostroić zegary całego mikrokontrolera, zamiast walić taki duży preskaler, ale w tym krótkim poradniku nie będziemy komplikować 😉.
GPIO
Dla pinów GPIO zrobiłem tak:
PB0
,PB1
,PB14
→ Wyjścia (DC, RST, CS).PB2
→ Wejście (BUSY).
Moja przygoda ze sterownikiem
Tutaj zaczęło się robić ciekawie.
Jak zwykle w hobbystycznych projektach, postanowiłem „zrobić to po trudniejszej drodze” i napisać własny sterownik zamiast kopiować gotowy. Logika była prosta: inicjalizacja → załadowanie LUT → ustawienie okna → wysłanie bufora.
Pierwszy test się udał: wyrenderowałem szachownicę. Kolejny krok: częściowe odświeżanie. I tu klops.
Normalnie partial update działa tak: ustawiasz okno rysowania (x, y, szerokość, wysokość), ładujesz dane do bufora, ewentualnie zmieniasz LUT i wysyłasz. Ale w tym ekranie… nie działało. Pomyślałem, że mój kod jest zły, więc sprawdziłem oficjalny sterownik Waveshare. Wynik? To samo – popsute.
Po kilku godzinach grzebania znalazłem taki komentarz w kodzie oficjalnego sterownika:
/******************************************************************************
function : Sends part the image buffer in RAM to e-Paper and displays
notes:
* You can send a part of data to e-Paper,But this function is not recommended
* 1. Xsize must be as big as EPD_3IN7_WIDTH
* 2. Ypointer must be start at 0
******************************************************************************/
int EPD_3IN7_1Gray_Display_Part(const UBYTE *Image, UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend)
No i super… partial update jest „udawany”. Działa tylko wtedy, gdy szerokość X = pełny ekran, a Y zaczyna się od 0. To wyjaśniało wszystko 🤦.
W końcu udało mi się to obejść, ale tylko jeśli poprzednia komenda nie była GC i jeśli wcześniej zrobiłem DU całego ekranu. Strasznie toporne, ale jakoś działa.
Na dodatek w oficjalnym sterowniku znalazłem bugi: pełne odświeżenie nie resetuje okna rysowania, więc po partial update kolejne pełne odświeżenie wyświetlało śmieci. To też poprawiłem w swojej wersji.
Sterownik
Zaadaptowałem sterownik od Waveshare, trochę go posprzątałem i dodałem poprawki. Nie będę tu wklejał całego pliku (jest długi), ale idea jest taka:
epd_3in7.h
→ sekcja konfiguracji z definicjami pinów.epd_3in7.c
→ integracja ze STM32 + obsługa LUT.
Musisz tylko zmienić sekcję konfiguracyjną.
Program
W main.c
napisałem małe demo:
- Czyszczenie ekranu w trybie GC.
- Narysowanie szachownicy w trybach GC i DU.
- Rysowanie na zmianę czarnego/białego kwadratu na środku przy użyciu „partial update”.
EPD_3IN7_1Gray_Init();
EPD_3IN7_1Gray_Clear(1); // 1 = GC
UBYTE black = 1; // starting with black square
UBYTE partial_since_full = 0; // count partial updates since last full refresh
UBYTE toggles = 0; // total square color toggles
// One-time "top full" push so partials won't blank the rest
draw_checker_full();
EPD_3IN7_1Gray_Display(frame_bw, 1); // GC
EPD_3IN7_1Gray_Display(frame_bw, 2); // DU
while (1)
{
// (...)
Dzięki temu widzimy:
- pełne odświeżanie działa,
- DU zostawia ghosting,
- pseudo-partial update działa tak, jak można się tego spodziewać, ale jest szybciej
Oficjalny sterownik obsługuje też 4-poziomową skalę szarości, ale jej nie testowałem, więc nic nie obiecuję.
Podsumowanie
Ten mały projekt nauczył mnie więcej, niż się spodziewałem. Udało mi się zmusić Nucleo do gadania z e-paperem, odkryłem dziwactwa partial update i poprawiłem kilka bugów w oficjalnym sterowniku.
Jeśli chcesz samemu poeksperymentować, weź mój poprawiony sterownik, podłącz ekran i spróbuj wyrenderować własne rzeczy.
Jeśli potrzebujesz czegoś więcej, polecam zajrzeć do pełnej wersji mojego sterownika (newsletter). W oficjalnym sterowniku LUT-y są wysyłane niepotrzebnie, pozycje rysowania nie są zbyt niskopoziomowe, a abstrakcja jest trochę dziurawa. Najlepiej byłoby mieć rdzeń niskopoziomowy, a dopiero na nim warstwę wyższego poziomu do prostszego rysowania.