diff --git a/src/core/display.c b/src/core/display.c new file mode 100644 index 0000000..645bc7f --- /dev/null +++ b/src/core/display.c @@ -0,0 +1,229 @@ +#include "display.h" +#include +#include +#include + + +#ifndef DISPLAY_MAX_BUFFERS +#define DISPLAY_MAX_BUFFERS 2 +#endif + + +typedef struct +{ + uint16_t width; + uint16_t height; + + uint8_t buffer_count; + display_mode_t mode; + + volatile bool dma_busy; + volatile bool frame_done_pending; + + display_frame_done_cb_t frame_done_cb; + + uint16_t* buffers[DISPLAY_MAX_BUFFERS]; + + uint8_t draw_index; + uint8_t scanout_index; + +} display_context_t; + + +static display_context_t ctx; + + +/* ============================================================ + === Hardware abstraction (to be implemented later) + ============================================================ */ + +static void hw_init(uint16_t width, uint16_t height) +{ + /* TODO: + - init SPI + - init DMA + - init Core1 if needed + - register DMA IRQ + */ +} + +static void hw_start_dma(uint16_t* buffer, size_t pixel_count) +{ + /* TODO: + - assert dma not active at HW level + - assert CS low + - configure DMA transfer + - start transfer + */ + + (void)buffer; + (void)pixel_count; +} + +static void hw_raise_cs(void) +{ + /* TODO: raise chip select */ +} + + +/* ============================================================ + === DMA IRQ handler (must be wired to real IRQ later) + ============================================================ */ + +void display_dma_irq_handler(void) +{ + /* TODO: clear DMA interrupt flag */ + + hw_raise_cs(); + + ctx.dma_busy = false; + ctx.frame_done_pending = true; +} + + +/* ============================================================ + === Public API + ============================================================ */ + +void display_init(const display_config_t* cfg) +{ + assert(cfg != NULL); + assert(cfg->buffer_count >= 1); + assert(cfg->buffer_count <= DISPLAY_MAX_BUFFERS); + + memset(&ctx, 0, sizeof(ctx)); + + ctx.width = cfg->width; + ctx.height = cfg->height; + + ctx.buffer_count = cfg->buffer_count; + ctx.mode = cfg->mode; + ctx.frame_done_cb = cfg->frame_done_cb; + + size_t pixels = (size_t)ctx.width * ctx.height; + size_t bytes = pixels * sizeof(uint16_t); + + for (uint8_t i = 0; i < ctx.buffer_count; i++) + { + ctx.buffers[i] = malloc(bytes); + assert(ctx.buffers[i] != NULL); + memset(ctx.buffers[i], 0, bytes); + } + + ctx.draw_index = 0; + ctx.scanout_index = 0; + + ctx.dma_busy = false; + ctx.frame_done_pending = false; + + hw_init(ctx.width, ctx.height); +} + + +/* ------------------------------------------------------------ */ + +uint16_t* display_get_draw_buffer(void) +{ + if (ctx.mode == DISPLAY_MODE_SAFE && + ctx.buffer_count == 1) + { + while (ctx.dma_busy) + { + /* busy wait */ + } + } + + return ctx.buffers[ctx.draw_index]; +} + + +/* ------------------------------------------------------------ */ + +uint16_t* display_get_scanout_buffer(void) +{ + return ctx.buffers[ctx.scanout_index]; +} + + +/* ------------------------------------------------------------ */ + +bool display_swap_buffers(void) +{ + if (ctx.mode != DISPLAY_MODE_RAW) + return false; + + if (ctx.buffer_count < 2) + return false; + + if (ctx.dma_busy) + return false; + + uint8_t tmp = ctx.draw_index; + ctx.draw_index = ctx.scanout_index; + ctx.scanout_index = tmp; + + return true; +} + + +/* ------------------------------------------------------------ */ + +bool display_submit(void) +{ + if (ctx.dma_busy) + return false; + + if (ctx.mode == DISPLAY_MODE_SAFE && + ctx.buffer_count == 2) + { + uint8_t tmp = ctx.draw_index; + ctx.draw_index = ctx.scanout_index; + ctx.scanout_index = tmp; + } + + size_t pixels = (size_t)ctx.width * ctx.height; + + ctx.dma_busy = true; + + hw_start_dma( + ctx.buffers[ctx.scanout_index], + pixels + ); + + return true; +} + + +/* ------------------------------------------------------------ */ + +bool display_ready(void) +{ + return !ctx.dma_busy; +} + + +/* ------------------------------------------------------------ */ + +void display_wait(void) +{ + while (ctx.dma_busy) + { + /* busy wait */ + } +} + + +/* ------------------------------------------------------------ */ + +void display_poll(void) +{ + if (ctx.frame_done_pending) + { + ctx.frame_done_pending = false; + + if (ctx.frame_done_cb) + { + ctx.frame_done_cb(); + } + } +} \ No newline at end of file diff --git a/src/core/include/display.h b/src/core/include/display.h new file mode 100644 index 0000000..bfedc31 --- /dev/null +++ b/src/core/include/display.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* ============================================================ + === Compile-time configuration + ============================================================ */ + +/* + * Максимальное количество буферов, + * доступное в данной сборке. + * + * RP2040: обычно 1 + * RP2350: можно 2 + */ +#ifndef DISPLAY_MAX_BUFFERS +#define DISPLAY_MAX_BUFFERS 2 +#endif + + +/* ============================================================ + === Modes + ============================================================ */ + +typedef enum +{ + /* + * SAFE: + * - swap управляется engine + * - нет tearing при 2 буферах + * - возможны блокировки + */ + DISPLAY_MODE_SAFE = 0, + + /* + * RAW: + * - swap управляется пользователем + * - engine не блокирует + * - tearing возможен + */ + DISPLAY_MODE_RAW + +} display_mode_t; + + +/* ============================================================ + === Callback + ============================================================ */ + +typedef void (*display_frame_done_cb_t)(void); + + +/* ============================================================ + === Configuration + ============================================================ */ + +typedef struct +{ + uint16_t width; + uint16_t height; + + /* + * 1 или 2. + * Должно быть <= DISPLAY_MAX_BUFFERS. + */ + uint8_t buffer_count; + + display_mode_t mode; + + /* + * Вызывается после завершения DMA + * через display_poll(). + * Может быть NULL. + */ + display_frame_done_cb_t frame_done_cb; + +} display_config_t; + + +/* ============================================================ + === Lifecycle + ============================================================ */ + +/* + * Инициализация display engine. + * Выделяет буферы и инициализирует HW. + */ +void display_init(const display_config_t* cfg); + + +/* ============================================================ + === Buffer access + ============================================================ */ + +/* + * Буфер для рисования. + * + * SAFE + 1 buffer: + * может блокировать пока DMA активен. + * + * RAW: + * никогда не блокирует. + */ +uint16_t* display_get_draw_buffer(void); + + +/* + * Буфер, который сейчас передаётся DMA. + */ +uint16_t* display_get_scanout_buffer(void); + + +/* ============================================================ + === Buffer control (RAW mode only) + ============================================================ */ + +/* + * Явная смена ролей draw/scanout. + * + * Возвращает false если: + * - не RAW режим + * - меньше 2 буферов + * - DMA активен + */ +bool display_swap_buffers(void); + + +/* ============================================================ + === Frame control + ============================================================ */ + +/* + * Запуск передачи текущего scanout буфера. + * + * Возвращает false если DMA активен. + */ +bool display_submit(void); + + +/* + * true если DMA не активен. + */ +bool display_ready(void); + + +/* + * Блокирующее ожидание завершения DMA. + */ +void display_wait(void); + + +/* + * Обработка отложенного события завершения кадра. + * Должна вызываться из основного цикла. + */ +void display_poll(void); + + +/* ============================================================ + === IRQ hook + ============================================================ */ + +/* + * Должен вызываться из реального DMA IRQ handler. + */ +void display_dma_irq_handler(void); + + +#ifdef __cplusplus +} +#endif \ No newline at end of file