mirror of
https://github.com/stasenso/rp_pico_display_engine.git
synced 2026-06-26 21:32:41 +03:00
349 lines
11 KiB
Markdown
349 lines
11 KiB
Markdown
# RP Pico Display Engine
|
|
|
|
Лёгкий C-движок вывода для `RP2040/RP2350` и дисплеев `ST7789` / `ILI9341` (SPI + DMA) с явным контрактом кадра `begin/end paint`.
|
|
|
|
## Возможности
|
|
|
|
- Режимы работы: `DISPLAY_MODE_SAFE` и `DISPLAY_MODE_RAW`
|
|
- 1 или 2 кадровых буфера (`buffer_count`)
|
|
- Неблокирующий и блокирующий захват кадра:
|
|
- `display_begin_paint_try()`
|
|
- `display_begin_paint_blocking()`
|
|
- Безопасное завершение кадра через `display_end_paint()`
|
|
- Набор примитивов рендера и шрифт с поддержкой кириллицы
|
|
|
|
## Структура репозитория
|
|
|
|
- `include/display/` - публичные заголовки движка и рендера
|
|
- `src/core/display.c` - ядро дисплейного движка
|
|
- `src/render/` - реализация примитивов
|
|
- `include/Font/`, `src/Font/` - данные шрифта и текстовый рендер
|
|
- `Examples/Thermometr/` - рабочий пример проекта на Pico SDK
|
|
|
|
## Что должно быть установлено заранее
|
|
|
|
- `git`
|
|
- `cmake` версии `3.18.4+`
|
|
- средство сборки, которое поддерживает CMake (`make` по умолчанию на Unix-подобных системах)
|
|
- `Pico SDK` и переменная окружения `PICO_SDK_PATH`, указывающая на него
|
|
- ARM toolchain `arm-none-eabi-gcc`
|
|
|
|
## Поддерживаемые дисплеи и wiring по умолчанию
|
|
|
|
Поддерживаются два backend-контроллера:
|
|
|
|
- `DISPLAY_TYPE_ST7789`
|
|
- `DISPLAY_TYPE_ILI9341`
|
|
|
|
Если не передавать compile definitions для дисплея, библиотека использует значения по умолчанию из [src/core/display_driver.h](/home/smikhai/repo/rp_pico_display_engine/src/core/display_driver.h:1):
|
|
|
|
- Контроллер: `DISPLAY_TYPE_ST7789`
|
|
- SPI порт: `spi0`
|
|
- `MOSI=19`
|
|
- `SCK=18`
|
|
- `CS=17`
|
|
- `DC=22`
|
|
- `RST=13`
|
|
- `BL=12`
|
|
|
|
То есть без переопределения пинов и `DISPLAY_TYPE` прошивка рассчитана на `ST7789`, подключённый к этим GPIO у `RP2040/RP2350`.
|
|
|
|
## Быстрый старт
|
|
|
|
### 1. Подключение как subproject (рекомендуется)
|
|
|
|
Практичный вариант интеграции: добавить этот репозиторий в проект (например, как git submodule) и собрать `display_engine` из вашего основного `CMakeLists.txt`.
|
|
Подключать все примитивы сразу не обязательно: оставьте только нужные и раскомментируйте остальные позже.
|
|
|
|
Создайте новый проект и добавьте библиотеку как submodule:
|
|
|
|
```bash
|
|
mkdir pico_display_app
|
|
cd pico_display_app
|
|
git init
|
|
git submodule add https://github.com/stasenso/rp_pico_display_engine.git external/rp_pico_display_engine
|
|
git submodule update --init --recursive
|
|
mkdir -p src
|
|
```
|
|
|
|
Убедитесь, что переменная `PICO_SDK_PATH` выставлена:
|
|
|
|
```bash
|
|
export PICO_SDK_PATH=/absolute/path/to/pico-sdk
|
|
```
|
|
|
|
Создайте `CMakeLists.txt` в корне проекта:
|
|
|
|
```bash
|
|
cat > CMakeLists.txt <<'EOF'
|
|
cmake_minimum_required(VERSION 3.18.4)
|
|
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
|
|
|
|
project(my_app C CXX ASM)
|
|
set(CMAKE_C_STANDARD 11)
|
|
set(CMAKE_CXX_STANDARD 17)
|
|
|
|
pico_sdk_init()
|
|
|
|
set(DISPLAY_ENGINE_DIR ${CMAKE_CURRENT_LIST_DIR}/external/rp_pico_display_engine)
|
|
|
|
add_library(display_engine STATIC
|
|
${DISPLAY_ENGINE_DIR}/src/Font/font_data.c
|
|
${DISPLAY_ENGINE_DIR}/src/core/display.c
|
|
${DISPLAY_ENGINE_DIR}/src/core/display_transport.c
|
|
${DISPLAY_ENGINE_DIR}/src/core/display_driver.c
|
|
${DISPLAY_ENGINE_DIR}/src/render/context.c
|
|
${DISPLAY_ENGINE_DIR}/src/render/line.c
|
|
# Раскомментируйте при необходимости:
|
|
# ${DISPLAY_ENGINE_DIR}/src/render/grid.c
|
|
# ${DISPLAY_ENGINE_DIR}/src/render/sine_wave.c
|
|
# ${DISPLAY_ENGINE_DIR}/src/render/bezier.c
|
|
)
|
|
|
|
target_include_directories(display_engine PUBLIC
|
|
${DISPLAY_ENGINE_DIR}/include
|
|
${DISPLAY_ENGINE_DIR}/include/display
|
|
)
|
|
|
|
target_link_libraries(display_engine PUBLIC
|
|
pico_stdlib
|
|
hardware_spi
|
|
hardware_dma
|
|
hardware_timer
|
|
pico_multicore
|
|
)
|
|
|
|
target_compile_definitions(display_engine PUBLIC
|
|
DISPLAY_TYPE=DISPLAY_TYPE_ST7789
|
|
DISPLAY_SPI_PORT=spi1
|
|
DISPLAY_PIN_MOSI=15
|
|
DISPLAY_PIN_SCK=14
|
|
DISPLAY_PIN_CS=13
|
|
DISPLAY_PIN_DC=12
|
|
DISPLAY_PIN_RST=11
|
|
DISPLAY_PIN_BL=10
|
|
)
|
|
|
|
add_executable(my_app src/main.c)
|
|
target_link_libraries(my_app PRIVATE display_engine)
|
|
pico_add_extra_outputs(my_app)
|
|
EOF
|
|
```
|
|
|
|
Создайте `src/main.c`:
|
|
|
|
```bash
|
|
cat > src/main.c <<'EOF'
|
|
#include "pico/stdlib.h"
|
|
#include "display/display.h"
|
|
#include "display/render/context.h"
|
|
|
|
int main(void) {
|
|
stdio_init_all();
|
|
|
|
display_config_t cfg = {
|
|
.width = 320,
|
|
.height = 240,
|
|
.buffer_count = 1,
|
|
.mode = DISPLAY_MODE_SAFE
|
|
};
|
|
display_init(&cfg);
|
|
|
|
render_ctx_t rc;
|
|
|
|
while (1) {
|
|
uint16_t *buf = display_begin_paint_blocking();
|
|
render_begin(&rc, buf, 320, 240);
|
|
render_clear(&rc, RGB16(0, 0, 0));
|
|
display_end_paint();
|
|
sleep_ms(16);
|
|
}
|
|
}
|
|
EOF
|
|
```
|
|
|
|
Соберите проект:
|
|
|
|
```bash
|
|
cmake -S . -B build
|
|
cmake --build build
|
|
```
|
|
|
|
### 2. Как менять пины и тип дисплея
|
|
|
|
Задавайте compile definitions на цели `display_engine`:
|
|
|
|
- `DISPLAY_SPI_PORT` (`spi0` или `spi1`)
|
|
- `DISPLAY_PIN_MOSI`, `DISPLAY_PIN_SCK`, `DISPLAY_PIN_CS`, `DISPLAY_PIN_DC`, `DISPLAY_PIN_RST`, `DISPLAY_PIN_BL`
|
|
- `DISPLAY_TYPE` (`DISPLAY_TYPE_ST7789` или `DISPLAY_TYPE_ILI9341`)
|
|
|
|
Если определения не заданы, используются такие значения по умолчанию из [src/core/display_driver.h](/home/smikhai/repo/rp_pico_display_engine/src/core/display_driver.h:1):
|
|
|
|
- `DISPLAY_TYPE=DISPLAY_TYPE_ST7789`
|
|
- `DISPLAY_SPI_PORT=spi0`
|
|
- `DISPLAY_PIN_MOSI=19`
|
|
- `DISPLAY_PIN_SCK=18`
|
|
- `DISPLAY_PIN_CS=17`
|
|
- `DISPLAY_PIN_DC=22`
|
|
- `DISPLAY_PIN_RST=13`
|
|
- `DISPLAY_PIN_BL=12`
|
|
|
|
### 3. Режим SAFE с одним буфером в цикле
|
|
|
|
Подходит для простого цикла, где можно пропустить кадр, если буфер занят.
|
|
|
|
```c
|
|
#include "display/display.h"
|
|
#include "display/render/context.h"
|
|
|
|
#define WIDTH 320
|
|
#define HEIGHT 240
|
|
|
|
void app_loop(void) {
|
|
display_config_t cfg = {
|
|
.width = WIDTH,
|
|
.height = HEIGHT,
|
|
.buffer_count = 1,
|
|
.mode = DISPLAY_MODE_SAFE
|
|
};
|
|
display_init(&cfg);
|
|
|
|
render_ctx_t rc;
|
|
|
|
while (1) {
|
|
uint16_t *buf = display_begin_paint_try();
|
|
if (!buf) {
|
|
continue;
|
|
}
|
|
|
|
render_begin(&rc, buf, WIDTH, HEIGHT);
|
|
render_clear(&rc, RGB16(0, 0, 0));
|
|
// draw...
|
|
|
|
bool ok = display_end_paint();
|
|
hard_assert(ok);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Режим SAFE с двумя буферами и возможным отложенным выводом
|
|
|
|
Режим `SAFE + buffer_count = 2`: пока DMA выводит один буфер, вы рисуете второй.
|
|
Если DMA занят в момент `display_end_paint()`, кадр может быть отложен (очередь на 1 pending-кадр).
|
|
|
|
```c
|
|
#include "display/display.h"
|
|
#include "display/render/context.h"
|
|
|
|
#define WIDTH 320
|
|
#define HEIGHT 240
|
|
|
|
void app_loop(void) {
|
|
display_config_t cfg = {
|
|
.width = WIDTH,
|
|
.height = HEIGHT,
|
|
.buffer_count = 2,
|
|
.mode = DISPLAY_MODE_SAFE
|
|
};
|
|
display_init(&cfg);
|
|
|
|
render_ctx_t rc;
|
|
|
|
while (1) {
|
|
uint16_t *buf = display_begin_paint_try();
|
|
if (!buf) {
|
|
// DMA/очередь заняты, делайте другую логику
|
|
continue;
|
|
}
|
|
|
|
render_begin(&rc, buf, WIDTH, HEIGHT);
|
|
render_clear(&rc, RGB16(8, 16, 8));
|
|
// draw...
|
|
|
|
bool accepted = display_end_paint();
|
|
hard_assert(accepted); // false только при нарушении контракта
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Заголовки примитивов и шрифтов + пример вывода
|
|
|
|
Минимальный набор:
|
|
|
|
```c
|
|
#include "display/display.h"
|
|
#include "display/render/context.h"
|
|
#include "display/render/line.h"
|
|
#include "Font/font_data.h" // draw_string(), draw_char(), get_char_width()
|
|
```
|
|
|
|
Опциональные заголовки (раскомментируйте при необходимости):
|
|
|
|
```c
|
|
// #include "display/render/grid.h"
|
|
// #include "display/render/sine_wave.h"
|
|
// #include "display/render/bezier.h"
|
|
```
|
|
|
|
Пример рендера примитивов и текста:
|
|
|
|
```c
|
|
render_ctx_t rc;
|
|
render_begin(&rc, buf, 320, 240);
|
|
|
|
render_clear(&rc, RGB16(9, 19, 9));
|
|
render_line(&rc, 0, 0, 319, 239, RGB16(255, 0, 0));
|
|
// render_grid(&rc, 20, 20, 40, RGB16(12, 26, 13)); // раскомментируйте, если нужна сетка
|
|
// render_sine_wave(&rc, 960, 100, 2.0f, 0, 120, phase, RGB16(0, 255, 0)); // раскомментируйте при необходимости
|
|
|
|
int px[4] = {20, 80, 140, 220};
|
|
int py[4] = {200, 120, 220, 160};
|
|
// render_bezier(&rc, px, py, 4, RGB16(255, 255, 0)); // раскомментируйте при необходимости
|
|
|
|
draw_string(&rc, 16, 16, L"Проверка кириллицы", RGB565(255, 255, 255));
|
|
```
|
|
|
|
## Сборка примера (Pico SDK)
|
|
|
|
Если хотите собрать готовый пример из этого репозитория без создания собственного проекта:
|
|
|
|
```bash
|
|
cd Examples/Thermometr
|
|
mkdir -p build && cd build
|
|
cmake ..
|
|
cmake --build .
|
|
```
|
|
|
|
Этот пример собирается для стандартной платы Pico SDK, если вы явно не зададите `PICO_BOARD`. Конфигурация дисплея задаётся в [Examples/Thermometr/CMakeLists.txt](/home/smikhai/repo/rp_pico_display_engine/Examples/Thermometr/CMakeLists.txt:1):
|
|
|
|
- Контроллер: `DISPLAY_TYPE_ILI9341`
|
|
- SPI порт: `spi1`
|
|
- `MOSI=15`
|
|
- `SCK=14`
|
|
- `CS=13`
|
|
- `DC=12`
|
|
- `RST=11`
|
|
- `BL=10`
|
|
|
|
Также есть [Examples/EngineDemo/CMakeLists.txt](/home/smikhai/repo/rp_pico_display_engine/Examples/EngineDemo/CMakeLists.txt:1), где по умолчанию выбрано:
|
|
|
|
- Плата: `Pico 2` / `RP2350` (`TARGET_BOARD=pico2_RP2350`)
|
|
- Контроллер: `DISPLAY_TYPE_ST7789`
|
|
- SPI порт: `spi1`
|
|
- `MOSI=15`
|
|
- `SCK=14`
|
|
- `CS=13`
|
|
- `DC=12`
|
|
- `RST=11`
|
|
- `BL=10`
|
|
|
|
То есть получившийся `.uf2` нужно заливать именно в ту плату, которая выбрана в `CMakeLists.txt` конкретного примера, а дисплей подключать к перечисленным там пинам.
|
|
|
|
## Контракт API (важно)
|
|
|
|
- Каждый успешный `display_begin_paint_*()` должен завершаться `display_end_paint()`
|
|
- Нельзя открывать второй `begin`, пока не закрыт первый
|
|
- Не вызывайте `display_submit()` вручную внутри открытой paint-секции
|
|
|
|
Подробные сценарии: `SCENARIOS.ru.md`.
|