Refactor display stack into driver and transport layers

This commit is contained in:
Stanislav N Mikhailov
2026-04-04 23:58:58 +03:00
parent 6e96e41b69
commit 63fb9ff4bd
6 changed files with 274 additions and 257 deletions
+24 -13
View File
@@ -8,10 +8,11 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Export compile_commands.json" F
pico_sdk_init()
add_executable(${PROJECT_NAME}
src/main.c
add_library(display_engine STATIC
../../src/Font/font_data.c
../../src/core/display.c
../../src/core/display_transport.c
../../src/core/display_driver.c
../../src/render/context.c
../../src/render/line.c
../../src/render/grid.c
@@ -19,12 +20,12 @@ add_executable(${PROJECT_NAME}
../../src/render/bezier.c
)
target_include_directories(${PROJECT_NAME} PRIVATE
target_include_directories(display_engine PUBLIC
../../include
../../include/display
)
target_link_libraries(${PROJECT_NAME}
target_link_libraries(display_engine PUBLIC
pico_stdlib
hardware_spi
hardware_dma
@@ -40,16 +41,26 @@ set(DISPLAY_PIN_CS 13)
set(DISPLAY_PIN_DC 12)
set(DISPLAY_PIN_RST 11)
set(DISPLAY_PIN_BL 10)
set(DISPLAY_TYPE DISPLAY_TYPE_ST7789)
# Pass board pin mapping into display.c without editing engine sources.
target_compile_definitions(${PROJECT_NAME} PRIVATE
SPI_PORT=${DISPLAY_SPI_PORT}
PIN_MOSI=${DISPLAY_PIN_MOSI}
PIN_SCK=${DISPLAY_PIN_SCK}
PIN_CS=${DISPLAY_PIN_CS}
PIN_DC=${DISPLAY_PIN_DC}
PIN_RST=${DISPLAY_PIN_RST}
PIN_BL=${DISPLAY_PIN_BL}
# Pass display mapping into display backend without editing engine sources.
target_compile_definitions(display_engine PUBLIC
DISPLAY_TYPE=${DISPLAY_TYPE}
DISPLAY_SPI_PORT=${DISPLAY_SPI_PORT}
DISPLAY_PIN_MOSI=${DISPLAY_PIN_MOSI}
DISPLAY_PIN_SCK=${DISPLAY_PIN_SCK}
DISPLAY_PIN_CS=${DISPLAY_PIN_CS}
DISPLAY_PIN_DC=${DISPLAY_PIN_DC}
DISPLAY_PIN_RST=${DISPLAY_PIN_RST}
DISPLAY_PIN_BL=${DISPLAY_PIN_BL}
)
add_executable(${PROJECT_NAME}
src/main.c
)
target_link_libraries(${PROJECT_NAME}
display_engine
)
pico_add_extra_outputs(${PROJECT_NAME})
+6 -244
View File
@@ -1,11 +1,9 @@
#include "display.h"
#include "display_transport.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#ifndef DISPLAY_MAX_BUFFERS
@@ -35,80 +33,6 @@ typedef struct
static display_context_t ctx;
static int dma_chan = -1;
#ifndef SPI_PORT
#define SPI_PORT spi0
#endif
#ifndef PIN_MOSI
#define PIN_MOSI 19
#endif
#ifndef PIN_SCK
#define PIN_SCK 18
#endif
#ifndef PIN_CS
#define PIN_CS 17
#endif
#ifndef PIN_DC
#define PIN_DC 22
#endif
#ifndef PIN_RST
#define PIN_RST 13
#endif
#ifndef PIN_BL
#define PIN_BL 12
#endif
/*
* Назначение:
* Отправляет дисплею служебную команду (например, сброс или смену режима).
*
* Параметры:
* cmd (uint8_t) - код команды ST7789, диапазон 0..255.
*/
static inline void st7789_send_command(uint8_t cmd)
{
/* DC=0: передаём байт команды контроллеру дисплея */
gpio_put(PIN_DC, 0);
gpio_put(PIN_CS, 0);
spi_write_blocking(SPI_PORT, &cmd, 1);
gpio_put(PIN_CS, 1);
}
/*
* Назначение:
* Отправляет дисплею набор данных, относящихся к последней команде.
*
* Параметры:
* data (const uint8_t*) - указатель на буфер данных; должен быть валиден при len > 0.
* len (size_t) - количество передаваемых байтов, диапазон 0..SIZE_MAX.
*/
static inline void st7789_send_data_bytes(const uint8_t* data, size_t len)
{
/* DC=1: передаём полезные данные команды */
gpio_put(PIN_DC, 1);
gpio_put(PIN_CS, 0);
spi_write_blocking(SPI_PORT, data, len);
gpio_put(PIN_CS, 1);
}
/*
* Назначение:
* Отправляет дисплею один байт данных.
*
* Параметры:
* data (uint8_t) - байт данных, диапазон 0..255.
*/
static inline void st7789_send_data_u8(uint8_t data)
{
st7789_send_data_bytes(&data, 1);
}
/*
* Назначение:
@@ -120,157 +44,6 @@ static void display_dma_irq_trampoline(void)
}
/* ============================================================
=== Абстракция оборудования (реализовать позже)
============================================================ */
/*
* Назначение:
* Полностью подготавливает железо для работы экрана: линии, SPI, контроллер и DMA.
*
* Параметры:
* width (uint16_t) - ширина кадра в пикселях, диапазон 0..65535.
* height (uint16_t) - высота кадра в пикселях, диапазон 0..65535.
*/
static void hw_init(uint16_t width, uint16_t height)
{
/* Максимально быстрый SPI для вывода кадров на ST7789 */
spi_init(SPI_PORT, 1000 * 100 * 625); /* 62.5 MHz */
spi_set_format(
SPI_PORT,
8,
SPI_CPOL_0,
SPI_CPHA_0,
SPI_MSB_FIRST
);
/* Линии данных SPI */
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
/* Управляющие пины дисплея */
gpio_init(PIN_CS);
gpio_init(PIN_DC);
gpio_init(PIN_RST);
gpio_init(PIN_BL);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_set_dir(PIN_DC, GPIO_OUT);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_set_dir(PIN_BL, GPIO_OUT);
gpio_put(PIN_CS, 1);
gpio_put(PIN_DC, 1);
gpio_put(PIN_BL, 0);
/* Аппаратный reset дисплея */
gpio_put(PIN_RST, 0);
sleep_ms(50);
gpio_put(PIN_RST, 1);
sleep_ms(50);
st7789_send_command(0x01); /* SWRESET: программный сброс */
sleep_ms(150);
st7789_send_command(0x11); /* SLPOUT: выход из sleep mode */
sleep_ms(150);
st7789_send_command(0x36); /* MADCTL: ориентация/порядок осей и RGB/BGR */
st7789_send_data_u8(0b10100000); /* Параметр из рабочей версии Thread.c */
st7789_send_command(0x3A); /* COLMOD: формат пикселя */
st7789_send_data_u8(0x55); /* 16 бит на пиксель (RGB565) */
{
uint16_t x_end = (width > 0) ? (uint16_t)(width - 1u) : 0u;
uint16_t y_end = (height > 0) ? (uint16_t)(height - 1u) : 0u;
uint8_t window[4];
/* Окно вывода по X: [0 .. width-1] */
st7789_send_command(0x2A); /* CASET: Column Address Set */
window[0] = 0x00;
window[1] = 0x00;
window[2] = (uint8_t)(x_end >> 8);
window[3] = (uint8_t)x_end;
st7789_send_data_bytes(window, sizeof(window));
/* Окно вывода по Y: [0 .. height-1] */
st7789_send_command(0x2B); /* RASET: Row Address Set */
window[2] = (uint8_t)(y_end >> 8);
window[3] = (uint8_t)y_end;
st7789_send_data_bytes(window, sizeof(window));
}
/* DMA -> SPI TX, чтобы выгружать кадр без участия CPU */
dma_chan = dma_claim_unused_channel(true);
dma_channel_config c = dma_channel_get_default_config((uint)dma_chan);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8); /* SPI в режиме 8 бит */
channel_config_set_read_increment(&c, true); /* читать массив буфера */
channel_config_set_write_increment(&c, false); /* писать в один регистр SPI DR */
channel_config_set_dreq(&c, spi_get_dreq(SPI_PORT, true)); /* Тактирование от готовности SPI TX */
dma_channel_configure(
(uint)dma_chan,
&c,
&spi_get_hw(SPI_PORT)->dr, /* куда пишем: SPI data register */
NULL,
0,
false
);
/* Прерывание по окончанию DMA-передачи кадра */
dma_channel_set_irq0_enabled((uint)dma_chan, true);
irq_set_exclusive_handler(DMA_IRQ_0, display_dma_irq_trampoline);
irq_set_enabled(DMA_IRQ_0, true);
st7789_send_command(0x21); /* INVON: включить инверсию (как в старом коде) */
st7789_send_command(0x29); /* DISPON: включить дисплей */
gpio_put(PIN_BL, 1);
}
/*
* Назначение:
* Готовит экран к приёму кадра и запускает быструю отправку пикселей через DMA.
*
* Параметры:
* buffer (uint16_t*) - указатель на буфер пикселей RGB565; должен быть валиден.
* pixel_count (size_t) - число пикселей для отправки, диапазон 0..SIZE_MAX.
*/
static void hw_start_dma(uint16_t* buffer, size_t pixel_count)
{
uint16_t x_end = (ctx.width > 0) ? (uint16_t)(ctx.width - 1u) : 0u;
uint16_t y_end = (ctx.height > 0) ? (uint16_t)(ctx.height - 1u) : 0u;
uint8_t window[4];
st7789_send_command(0x2A); /* CASET: Column Address Set */
window[0] = 0x00;
window[1] = 0x00;
window[2] = (uint8_t)(x_end >> 8);
window[3] = (uint8_t)x_end;
st7789_send_data_bytes(window, sizeof(window));
st7789_send_command(0x2B); /* RASET: Row Address Set */
window[2] = (uint8_t)(y_end >> 8);
window[3] = (uint8_t)y_end;
st7789_send_data_bytes(window, sizeof(window));
st7789_send_command(0x2C); /* RAMWR: Memory Write */
/* DC=1 и CS=0 перед непрерывной DMA-передачей буфера */
gpio_put(PIN_DC, 1);
gpio_put(PIN_CS, 0);
dma_channel_set_read_addr((uint)dma_chan, buffer, false);
dma_channel_set_trans_count((uint)dma_chan, pixel_count * sizeof(uint16_t), true);
}
/*
* Назначение:
* Завершает текущую передачу в экран, поднимая линию выбора устройства (CS).
*/
static void hw_raise_cs(void)
{
gpio_put(PIN_CS, 1);
}
/*
* Назначение:
* Пытается запустить отложенный submit, если DMA уже освободился.
@@ -292,7 +65,7 @@ static void display_kick_pending_if_possible(void)
size_t pixels = (size_t)ctx.width * ctx.height;
ctx.dma_busy = true;
hw_start_dma(ctx.buffers[ctx.scanout_index], pixels);
display_transport_start_frame(ctx.width, ctx.height, ctx.buffers[ctx.scanout_index], pixels);
}
@@ -307,15 +80,7 @@ static void display_kick_pending_if_possible(void)
*/
void display_dma_irq_handler(void)
{
/* Проверяем, что DMA-канал действительно был выделен. */
if (dma_chan >= 0)
{
/* Сбрасываем флаг IRQ у текущего DMA-канала */
dma_hw->ints0 = 1u << (uint)dma_chan;
}
/* Поднимаем CS, завершая SPI-транзакцию кадра. */
hw_raise_cs();
display_transport_complete_irq();
/* Отмечаем, что DMA завершил передачу и шина свободна. */
ctx.dma_busy = false;
@@ -382,8 +147,8 @@ void display_init(const display_config_t* cfg)
ctx.dma_busy = false;
ctx.paint_in_progress = false;
/* Инициализируем аппаратный слой дисплея. */
hw_init(ctx.width, ctx.height);
/* Инициализируем транспортный слой дисплея и backend контроллера. */
display_transport_init(ctx.width, ctx.height, display_dma_irq_trampoline);
}
@@ -577,10 +342,7 @@ bool display_submit(void)
ctx.dma_busy = true;
/* Запускаем передачу scanout-буфера в дисплей. */
hw_start_dma(
ctx.buffers[ctx.scanout_index],
pixels
);
display_transport_start_frame(ctx.width, ctx.height, ctx.buffers[ctx.scanout_index], pixels);
/* Запуск принят. */
return true;
+106
View File
@@ -0,0 +1,106 @@
#include "display_driver.h"
#include <stddef.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#if DISPLAY_TYPE == DISPLAY_TYPE_ST7789
static inline void display_send_command(uint8_t cmd)
{
gpio_put(DISPLAY_PIN_DC, 0);
gpio_put(DISPLAY_PIN_CS, 0);
spi_write_blocking(DISPLAY_SPI_PORT, &cmd, 1);
gpio_put(DISPLAY_PIN_CS, 1);
}
static inline void display_send_data_bytes(const uint8_t* data, size_t len)
{
gpio_put(DISPLAY_PIN_DC, 1);
gpio_put(DISPLAY_PIN_CS, 0);
spi_write_blocking(DISPLAY_SPI_PORT, data, len);
gpio_put(DISPLAY_PIN_CS, 1);
}
static inline void display_send_data_u8(uint8_t data)
{
display_send_data_bytes(&data, 1);
}
static inline void st7789_set_window(uint16_t width, uint16_t height)
{
uint16_t x_end = (width > 0) ? (uint16_t)(width - 1u) : 0u;
uint16_t y_end = (height > 0) ? (uint16_t)(height - 1u) : 0u;
uint8_t window[4];
display_send_command(0x2A); /* CASET */
window[0] = 0x00;
window[1] = 0x00;
window[2] = (uint8_t)(x_end >> 8);
window[3] = (uint8_t)x_end;
display_send_data_bytes(window, sizeof(window));
display_send_command(0x2B); /* RASET */
window[2] = (uint8_t)(y_end >> 8);
window[3] = (uint8_t)y_end;
display_send_data_bytes(window, sizeof(window));
}
void display_driver_panel_init(uint16_t width, uint16_t height)
{
gpio_set_function(DISPLAY_PIN_MOSI, GPIO_FUNC_SPI);
gpio_set_function(DISPLAY_PIN_SCK, GPIO_FUNC_SPI);
gpio_init(DISPLAY_PIN_CS);
gpio_init(DISPLAY_PIN_DC);
gpio_init(DISPLAY_PIN_RST);
gpio_init(DISPLAY_PIN_BL);
gpio_set_dir(DISPLAY_PIN_CS, GPIO_OUT);
gpio_set_dir(DISPLAY_PIN_DC, GPIO_OUT);
gpio_set_dir(DISPLAY_PIN_RST, GPIO_OUT);
gpio_set_dir(DISPLAY_PIN_BL, GPIO_OUT);
gpio_put(DISPLAY_PIN_CS, 1);
gpio_put(DISPLAY_PIN_DC, 1);
gpio_put(DISPLAY_PIN_BL, 0);
gpio_put(DISPLAY_PIN_RST, 0);
sleep_ms(50);
gpio_put(DISPLAY_PIN_RST, 1);
sleep_ms(50);
display_send_command(0x01); /* SWRESET */
sleep_ms(150);
display_send_command(0x11); /* SLPOUT */
sleep_ms(150);
display_send_command(0x36); /* MADCTL */
display_send_data_u8(0b10100000);
display_send_command(0x3A); /* COLMOD */
display_send_data_u8(0x55); /* RGB565 */
st7789_set_window(width, height);
display_send_command(0x21); /* INVON */
display_send_command(0x29); /* DISPON */
gpio_put(DISPLAY_PIN_BL, 1);
}
void display_driver_begin_frame_transfer(uint16_t width, uint16_t height)
{
st7789_set_window(width, height);
display_send_command(0x2C); /* RAMWR */
gpio_put(DISPLAY_PIN_DC, 1);
gpio_put(DISPLAY_PIN_CS, 0);
}
void display_driver_end_frame_transfer(void)
{
gpio_put(DISPLAY_PIN_CS, 1);
}
#else
#error "Unsupported DISPLAY_TYPE. Add implementation in src/core/display_driver.c."
#endif
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <stdint.h>
/* Display type identifiers used by DISPLAY_TYPE. */
#define DISPLAY_TYPE_ST7789 1
#ifndef DISPLAY_TYPE
#define DISPLAY_TYPE DISPLAY_TYPE_ST7789
#endif
/* Backward compatibility with old compile definitions from CMake. */
#if !defined(DISPLAY_SPI_PORT) && defined(SPI_PORT)
#define DISPLAY_SPI_PORT SPI_PORT
#endif
#ifndef DISPLAY_SPI_PORT
#define DISPLAY_SPI_PORT spi0
#endif
#if !defined(DISPLAY_PIN_MOSI) && defined(PIN_MOSI)
#define DISPLAY_PIN_MOSI PIN_MOSI
#endif
#ifndef DISPLAY_PIN_MOSI
#define DISPLAY_PIN_MOSI 19
#endif
#if !defined(DISPLAY_PIN_SCK) && defined(PIN_SCK)
#define DISPLAY_PIN_SCK PIN_SCK
#endif
#ifndef DISPLAY_PIN_SCK
#define DISPLAY_PIN_SCK 18
#endif
#if !defined(DISPLAY_PIN_CS) && defined(PIN_CS)
#define DISPLAY_PIN_CS PIN_CS
#endif
#ifndef DISPLAY_PIN_CS
#define DISPLAY_PIN_CS 17
#endif
#if !defined(DISPLAY_PIN_DC) && defined(PIN_DC)
#define DISPLAY_PIN_DC PIN_DC
#endif
#ifndef DISPLAY_PIN_DC
#define DISPLAY_PIN_DC 22
#endif
#if !defined(DISPLAY_PIN_RST) && defined(PIN_RST)
#define DISPLAY_PIN_RST PIN_RST
#endif
#ifndef DISPLAY_PIN_RST
#define DISPLAY_PIN_RST 13
#endif
#if !defined(DISPLAY_PIN_BL) && defined(PIN_BL)
#define DISPLAY_PIN_BL PIN_BL
#endif
#ifndef DISPLAY_PIN_BL
#define DISPLAY_PIN_BL 12
#endif
/* Display controller abstraction, selected by DISPLAY_TYPE define. */
void display_driver_panel_init(uint16_t width, uint16_t height);
void display_driver_begin_frame_transfer(uint16_t width, uint16_t height);
void display_driver_end_frame_transfer(void);
+59
View File
@@ -0,0 +1,59 @@
#include "display_transport.h"
#include "display_driver.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
static int dma_chan = -1;
void display_transport_init(uint16_t width, uint16_t height, irq_handler_t dma_irq_handler)
{
spi_init(DISPLAY_SPI_PORT, 1000 * 100 * 625); /* 62.5 MHz */
spi_set_format(
DISPLAY_SPI_PORT,
8,
SPI_CPOL_0,
SPI_CPHA_0,
SPI_MSB_FIRST
);
display_driver_panel_init(width, height);
dma_chan = dma_claim_unused_channel(true);
dma_channel_config cfg = dma_channel_get_default_config((uint)dma_chan);
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_8);
channel_config_set_read_increment(&cfg, true);
channel_config_set_write_increment(&cfg, false);
channel_config_set_dreq(&cfg, spi_get_dreq(DISPLAY_SPI_PORT, true));
dma_channel_configure(
(uint)dma_chan,
&cfg,
&spi_get_hw(DISPLAY_SPI_PORT)->dr,
NULL,
0,
false
);
dma_channel_set_irq0_enabled((uint)dma_chan, true);
irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
irq_set_enabled(DMA_IRQ_0, true);
}
void display_transport_start_frame(uint16_t width, uint16_t height, uint16_t* buffer, size_t pixel_count)
{
display_driver_begin_frame_transfer(width, height);
dma_channel_set_read_addr((uint)dma_chan, buffer, false);
dma_channel_set_trans_count((uint)dma_chan, pixel_count * sizeof(uint16_t), true);
}
void display_transport_complete_irq(void)
{
if (dma_chan >= 0)
{
dma_hw->ints0 = 1u << (uint)dma_chan;
}
display_driver_end_frame_transfer();
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "hardware/irq.h"
/* Initializes transport and panel backend, and registers DMA IRQ callback. */
void display_transport_init(uint16_t width, uint16_t height, irq_handler_t dma_irq_handler);
/* Starts a single frame transfer via panel backend + DMA. */
void display_transport_start_frame(uint16_t width, uint16_t height, uint16_t* buffer, size_t pixel_count);
/* Acknowledges DMA IRQ and finalizes frame SPI transaction. */
void display_transport_complete_irq(void);