diff --git a/Examples/EngineDemo/CMakeLists.txt b/Examples/EngineDemo/CMakeLists.txt new file mode 100644 index 0000000..615bee1 --- /dev/null +++ b/Examples/EngineDemo/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.18.4) +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) + +project(engine_demo_example C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL "Export compile_commands.json" FORCE) + +pico_sdk_init() + +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 + ../../src/render/sine_wave.c + ../../src/render/bezier.c +) + +target_include_directories(display_engine PUBLIC + ../../include + ../../include/display +) + +target_link_libraries(display_engine PUBLIC + pico_stdlib + hardware_spi + hardware_dma + hardware_timer + pico_multicore +) + +# Display pin mapping (single source of truth for this example). +set(DISPLAY_SPI_PORT spi1) +set(DISPLAY_PIN_MOSI 15) +set(DISPLAY_PIN_SCK 14) +set(DISPLAY_PIN_CS 13) +set(DISPLAY_PIN_DC 12) +set(DISPLAY_PIN_RST 11) +set(DISPLAY_PIN_BL 10) +# Available values: DISPLAY_TYPE_ST7789, DISPLAY_TYPE_ILI9341 +set(DISPLAY_TYPE "DISPLAY_TYPE_ILI9341" CACHE STRING "Display controller backend") +set_property(CACHE DISPLAY_TYPE PROPERTY STRINGS DISPLAY_TYPE_ST7789 DISPLAY_TYPE_ILI9341) + +# 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/Examples/EngineDemo/src/main.c b/Examples/EngineDemo/src/main.c new file mode 100644 index 0000000..f244307 --- /dev/null +++ b/Examples/EngineDemo/src/main.c @@ -0,0 +1,135 @@ +#include "pico/stdlib.h" // Подключаем базовые функции SDK Pico (время, таймеры, sleep, assert). +#include "display/display.h" // Подключаем API дисплейного движка (init + begin/end paint). +#include "display/render/context.h" // Подключаем контекст рисования для примитивов. +#include "display/render/grid.h" // Подключаем функцию рисования сетки. +#include "display/render/sine_wave.h" // Подключаем функцию рисования синусоиды. +#include "Font/font_data.h" // Подключаем шрифты и draw_string. +#include // Подключаем swprintf для форматирования строки FPS. + + +#define WIDTH 320 // Ширина экрана в пикселях. +#define HEIGHT 240 // Высота экрана в пикселях. +#define TEXT_H 22 // Высота глифа шрифта для расчета нижней границы текста. + +static inline void wait_for_irq(void) // Вспомогательная функция для энергосберегающего ожидания прерываний. +{ + __asm volatile ("wfi"); // Инструкция ARM Wait For Interrupt: CPU спит до любого IRQ. +} + +static uint16_t text_width_px(const wchar_t* str) // Считаем фактическую ширину строки в пикселях через таблицу ширин глифов. +{ + uint16_t width = 0; // Накапливаем итоговую ширину строки. + while (*str) // Пока не дошли до нулевого терминатора строки. + { + width = (uint16_t)(width + get_char_width(*str)); // Добавляем ширину текущего символа. + str++; // Переходим к следующему символу. + } + return width; // Возвращаем итоговую ширину строки. +} + +static void update_axis_reflect(int* pos, int* dir, int min_pos, int max_pos) // Обновляем одну координату с зеркальным отражением от границ. +{ + if (max_pos <= min_pos) // Если объект больше доступного диапазона или диапазон вырожден. + { + *pos = min_pos; // Фиксируем позицию в единственной доступной точке. + *dir = 0; // Останавливаем движение по этой оси. + return; // Завершаем обработку оси. + } + + *pos += *dir; // Двигаем объект на 1 пиксель по текущему направлению. + if (*pos < min_pos) // Проверяем удар о минимальную границу (верх/лево). + { + *pos = min_pos + (min_pos - *pos); // Зеркально отражаем координату внутрь диапазона. + *dir = -*dir; // Инвертируем направление движения. + } + else if (*pos > max_pos) // Проверяем удар о максимальную границу (низ/право). + { + *pos = max_pos - (*pos - max_pos); // Зеркально отражаем координату внутрь диапазона. + *dir = -*dir; // Инвертируем направление движения. + } +} + +int main() +{ + stdio_init_all(); // Инициализируем стандартный ввод/вывод (UART/USB stdio при необходимости). + + display_config_t cfg = { // Формируем конфигурацию дисплейного движка. + .width = WIDTH, // Передаем ширину кадра. + .height = HEIGHT, // Передаем высоту кадра. + .buffer_count = 1, // Используем один буфер кадра (SAFE режим блокирует доступ при DMA). + .mode = DISPLAY_MODE_SAFE, // Выбираем безопасный режим работы буфера. + }; + + display_init(&cfg); // Инициализируем дисплей и внутренний контекст по заданной конфигурации. + float phase = 0.0f; // Фаза синусоиды для анимации волны. + const wchar_t* text1 = L"Проверка кириллицы"; // Текст первой строки. + const wchar_t* text2 = L"Latin character check"; // Текст второй строки. + const wchar_t* text3 = L"1234567890!@#$%^&*()"; // Текст третьей строки. + const int text1_w = text_width_px(text1); // Фактическая ширина первой строки в пикселях. + const int text2_w = text_width_px(text2); // Фактическая ширина второй строки в пикселях. + const int text3_w = text_width_px(text3); // Фактическая ширина третьей строки в пикселях. + int text1_x = 50; // Начальная X-координата первой строки. + int text1_y = 90; // Начальная Y-координата первой строки. + int text2_x = 45; // Начальная X-координата второй строки. + int text2_y = 110; // Начальная Y-координата второй строки. + int text3_x = 35; // Начальная X-координата третьей строки. + int text3_y = 130; // Начальная Y-координата третьей строки. + int text1_dx = 1; // Горизонтальное направление первой строки: +1 вправо, -1 влево. + int text1_dy = 1; // Вертикальное направление первой строки: +1 вниз, -1 вверх. + int text2_dx = -1; // Горизонтальное направление второй строки. + int text2_dy = 1; // Вертикальное направление второй строки. + int text3_dx = -1; // Горизонтальное направление третьей строки. + int text3_dy = -1; // Вертикальное направление третьей строки. + uint32_t fps_value = 0; // Текущее вычисленное значение FPS. + uint32_t fps_frame_count = 0; // Количество завершенных кадров в текущем окне измерения. + uint64_t fps_window_start_us = time_us_64(); // Время старта текущего окна измерения в микросекундах. + wchar_t fps_text[16] = L"FPS: 0"; // Буфер строки FPS для вывода в угол экрана. + + render_ctx_t rc; // Контекст рендера, который будет привязываться к текущему буферу кадра. + + while (1) // Бесконечный основной цикл приложения. + { + uint16_t* buf = display_begin_paint_try(); // Пытаемся начать новый кадр и получить буфер рисования. + if (buf == NULL) // Если кадр ещё выводится или paint уже начат, пропускаем этот тик. + { + wait_for_irq(); // Спим до прерывания, чтобы не крутить пустой busy-loop. + continue; + } + render_begin(&rc, buf, WIDTH, HEIGHT); // Привязываем контекст рендера к буферу и размерам экрана. + + render_clear(&rc, RGB16(9,19,9)); // Очищаем кадр темно-зеленым фоном. + render_grid(&rc, 10, 10, 20, RGB16(40,40,40)); // Рисуем фон-сетку с шагом и цветом. + render_sine_wave(&rc, WIDTH*3, 100, 2.0f, 0, HEIGHT / 2, phase, RGB16(0,255,0)); // Рисуем анимированную синусоиду. + draw_string(&rc, (uint16_t)text1_x, (uint16_t)text1_y, text1, RGB565(255, 255, 255)); // Рисуем первую строку по текущим X/Y. + draw_string(&rc, (uint16_t)text2_x, (uint16_t)text2_y, text2, RGB565(0, 0, 255)); // Рисуем вторую строку по текущим X/Y. + draw_string(&rc, (uint16_t)text3_x, (uint16_t)text3_y, text3, RGB565(255, 0, 0)); // Рисуем третью строку по текущим X/Y. + draw_string(&rc, 0, 0, fps_text, RGB565(255, 255, 0)); // Выводим текущее значение FPS в левый верхний угол. + + update_axis_reflect(&text1_x, &text1_dx, 0, WIDTH - text1_w); // Двигаем первую строку по X с отражением от левой/правой грани. + update_axis_reflect(&text1_y, &text1_dy, 0, HEIGHT - TEXT_H); // Двигаем первую строку по Y с отражением от верхней/нижней грани. + update_axis_reflect(&text2_x, &text2_dx, 0, WIDTH - text2_w); // Двигаем вторую строку по X с отражением от левой/правой грани. + update_axis_reflect(&text2_y, &text2_dy, 0, HEIGHT - TEXT_H); // Двигаем вторую строку по Y с отражением от верхней/нижней грани. + update_axis_reflect(&text3_x, &text3_dx, 0, WIDTH - text3_w); // Двигаем третью строку по X с отражением от левой/правой грани. + update_axis_reflect(&text3_y, &text3_dy, 0, HEIGHT - TEXT_H); // Двигаем третью строку по Y с отражением от верхней/нижней грани. + + phase += 0.16f; // Продвигаем фазу синусоиды для плавной анимации. + if (phase > 6.2831853f) // Если фаза превысила 2*pi. + { + phase -= 6.2831853f; // Возвращаем фазу в базовый диапазон без скачка формы. + } + + bool submitted = display_end_paint(); // Завершаем paint-секцию и отправляем кадр. + hard_assert(submitted); // В этом сценарии отправка обязана проходить успешно. + + fps_frame_count++; // Учитываем успешно отправленный кадр в статистике FPS. + uint64_t now_us = time_us_64(); // Фиксируем текущее время для проверки окна измерения. + uint64_t elapsed_us = now_us - fps_window_start_us; // Сколько прошло с начала окна измерения. + if (elapsed_us >= 1000000ULL) // Пересчитываем FPS примерно раз в секунду. + { + fps_value = (uint32_t)((fps_frame_count * 1000000ULL) / elapsed_us); // Средний FPS за окно. + (void)swprintf(fps_text, sizeof(fps_text) / sizeof(fps_text[0]), L"FPS: %lu", (unsigned long)fps_value); // Обновляем строку для отрисовки. + fps_frame_count = 0; // Начинаем новое окно измерения. + fps_window_start_us = now_us; // Сдвигаем старт окна на текущее время. + } + } +}