From 63fb9ff4bd5ed0b3793a3e82471c7584c7db65b0 Mon Sep 17 00:00:00 2001 From: Stanislav N Mikhailov Date: Sat, 4 Apr 2026 23:58:58 +0300 Subject: [PATCH] Refactor display stack into driver and transport layers --- Examples/Thermometr/CMakeLists.txt | 37 +++-- src/core/display.c | 250 +---------------------------- src/core/display_driver.c | 106 ++++++++++++ src/core/display_driver.h | 65 ++++++++ src/core/display_transport.c | 59 +++++++ src/core/display_transport.h | 14 ++ 6 files changed, 274 insertions(+), 257 deletions(-) create mode 100644 src/core/display_driver.c create mode 100644 src/core/display_driver.h create mode 100644 src/core/display_transport.c create mode 100644 src/core/display_transport.h diff --git a/Examples/Thermometr/CMakeLists.txt b/Examples/Thermometr/CMakeLists.txt index b72bbd0..9134ab6 100644 --- a/Examples/Thermometr/CMakeLists.txt +++ b/Examples/Thermometr/CMakeLists.txt @@ -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}) diff --git a/src/core/display.c b/src/core/display.c index 99b348c..6d8005c 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -1,11 +1,9 @@ #include "display.h" +#include "display_transport.h" #include #include #include #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; diff --git a/src/core/display_driver.c b/src/core/display_driver.c new file mode 100644 index 0000000..4feeda5 --- /dev/null +++ b/src/core/display_driver.c @@ -0,0 +1,106 @@ +#include "display_driver.h" + +#include +#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 diff --git a/src/core/display_driver.h b/src/core/display_driver.h new file mode 100644 index 0000000..563ed88 --- /dev/null +++ b/src/core/display_driver.h @@ -0,0 +1,65 @@ +#pragma once + +#include + +/* 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); diff --git a/src/core/display_transport.c b/src/core/display_transport.c new file mode 100644 index 0000000..02617b6 --- /dev/null +++ b/src/core/display_transport.c @@ -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(); +} diff --git a/src/core/display_transport.h b/src/core/display_transport.h new file mode 100644 index 0000000..25ee90f --- /dev/null +++ b/src/core/display_transport.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#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);