STM32 移植

本文档介绍 EmbeddedGUI 在 STM32G0 系列 MCU 上的移植实现,当前基于 SPI 接口驱动 ST7789 LCD 屏幕,并通过 FT6336 提供触摸输入。

硬件配置

参考平台:STM32G0B0RE

组件

型号/接口

说明

MCU

STM32G0B0RE

Cortex-M0+, 64MHz, 128KB Flash, 144KB RAM

LCD

ST7789

240x320, RGB565, SPI 接口

触摸

FT6336

电容触摸,I2C 接口

文件结构

porting/stm32g0/
├── Porting/
│   ├── egui_port_mcu.c    # Platform / LCD / Touch 初始化与注册
│   ├── port_main.c        # 主循环入口
│   └── port_main.h        # 主循环头文件
├── GCC/
│   └── Makefile.base      # GCC 构建规则
├── MDK-ARM/
│   └── proj_stm32g0.uvprojx
├── STM32G0B0RETX_FLASH.ld # 链接脚本
├── STM32G0B0RETX_RAM.ld   # RAM 链接脚本
└── build.mk               # 构建模块定义

主流程概览

当前 stm32g0 port 的主流程位于 port_main.c,典型顺序如下:

EGUI_CONFIG_PFB_BUFFER_DECLARE(egui_pfb);
static egui_core_t core;

void port_main(void)
{
    egui_init(&core, egui_pfb);
    egui_port_init(&core);
    egui_display_driver_register(&core, egui_port_get_display_driver());
#if EGUI_CONFIG_FUNCTION_SUPPORT_TOUCH
    egui_port_register_touch_driver(&core);
#endif
    uicode_disp0_init(&core);
    egui_screen_on(&core);

    while (1)
    {
        egui_polling_work(&core);
    }
}

如果只是单屏场景,上述流程已经足够。若后续需要多屏、异构分辨率或统一化初始化流程,建议改用 egui_display_setup_t + egui_setup_display()

Display Driver 注册

egui_port_mcu.c 中主要完成三件事:

  1. 注册 platform

  2. 初始化 LCD / Touch HAL 驱动

  3. 准备并导出 egui_display_driver_t

当前 stm32g0 已不再手写一层 draw_area(core, ...) 转发。像素输出桥接由 egui_hal_lcd_register() 自动完成,板级代码主要补充亮度和旋转能力。

static void port_display_set_brightness(egui_core_t *core, uint8_t level)
{
    EGUI_UNUSED(core);
    egui_hal_stm32g0_set_backlight_level(level);
}

static void port_display_set_rotation(egui_core_t *core, egui_display_rotation_t rotation)
{
    egui_hal_lcd_driver_t *lcd = egui_hal_lcd_get(core);
    if (lcd == NULL)
    {
        return;
    }

    switch (rotation)
    {
    case EGUI_DISPLAY_ROTATION_0:
        if (lcd->mirror) lcd->mirror(lcd, 0, 0);
        if (lcd->swap_xy) lcd->swap_xy(lcd, 0);
        break;
    case EGUI_DISPLAY_ROTATION_90:
        if (lcd->mirror) lcd->mirror(lcd, 1, 0);
        if (lcd->swap_xy) lcd->swap_xy(lcd, 1);
        break;
    case EGUI_DISPLAY_ROTATION_180:
        if (lcd->mirror) lcd->mirror(lcd, 1, 1);
        if (lcd->swap_xy) lcd->swap_xy(lcd, 0);
        break;
    case EGUI_DISPLAY_ROTATION_270:
        if (lcd->mirror) lcd->mirror(lcd, 0, 1);
        if (lcd->swap_xy) lcd->swap_xy(lcd, 1);
        break;
    }
}

这套结构同时支持同步和异步(DMA)两种传输模式。

SPI 屏幕通信

典型的 SPI LCD 写入流程如下:

  1. 设置列地址范围(CASET

  2. 设置行地址范围(RASET

  3. 发送写内存命令(RAMWR

  4. 通过 SPI 持续发送像素数据

ST7789 的驱动初始化由 egui_lcd_st7789_init() 完成,底层 SPI IO 通过 egui_panel_io_spi_t 适配。

SysTick 定时器

平台时间基准来自 HAL 的 SysTick:

static uint32_t mcu_get_tick_ms(void)
{
    return HAL_GetTick();
}

static void mcu_delay(uint32_t ms)
{
    HAL_Delay(ms);
}

HAL_GetTick()1ms 递增一次,用于动画、定时器和输入超时等逻辑。

DMA 异步传输

基本原理

启用 SPI DMA 后,CPU 可以在数据发送期间继续渲染下一个 PFB 分块,从而提高整体吞吐。

PFB 缓冲区清零加速

当配置 EGUI_CONFIG_PLATFORM_CUSTOM_MEMORY_OP=1 时,可以注册平台自定义 memset_fast。当前 stm32g0 支持在清零 PFB 时改走 DMA:

#if APP_EGUI_CONFIG_USE_DMA_TO_RESET_PFB_BUFFER
extern DMA_HandleTypeDef hdma_memtomem_dma2_channel5;
const egui_color_int_t fixed_0_buffer[EGUI_CONFIG_PFB_WIDTH * EGUI_CONFIG_PFB_HEIGHT] = {0};

static void mcu_memset_fast(void *s, int c, int n)
{
    if (c == 0)
    {
        HAL_DMA_Start(&hdma_memtomem_dma2_channel5, (uint32_t)fixed_0_buffer, (uint32_t)s, n >> 2);
        HAL_DMA_PollForTransfer(&hdma_memtomem_dma2_channel5, HAL_DMA_FULL_TRANSFER, 1000);
        return;
    }

    memset(s, c, n);
}
#endif

这样可以保证:

  • c == 0 时使用 DMA 清零,收益最大

  • 非零填充值时回退到标准 memset,语义不变

多缓冲 + DMA

当前多缓冲已经由 core 内部的 egui_pfb_manager_t 统一管理,不再由应用层手动切换“主 / 备份 PFB 指针”。主流程只需要把编译期声明的 PFB 数组交给 egui_init()

EGUI_CONFIG_PFB_BUFFER_DECLARE(egui_pfb);
static egui_core_t core;

void port_main(void)
{
    egui_init(&core, egui_pfb);
}

DMA 发送完成后,需要在中断回调中通知 core 推进 PFB 队列:

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == ST7789_SPI_PORT.Instance)
    {
        egui_pfb_notify_flush_complete(&core);
    }
}

触摸输入

FT6336 触摸驱动

当前移植不再由应用层手写触摸轮询转 motion event,而是通过 HAL touch driver 统一注册到指定 core

static egui_hal_touch_driver_t s_touch_driver;

void egui_port_register_touch_driver(egui_core_t *core)
{
    egui_hal_touch_config_t touch_config = {
        .width = EGUI_CONFIG_SCREEN_WIDTH,
        .height = EGUI_CONFIG_SCREEN_HEIGHT,
        .swap_xy = 0,
        .mirror_x = 0,
        .mirror_y = 0,
    };

    egui_hal_touch_register(core, &s_touch_driver, &touch_config);
}

FT6336 的 INT / RST 管脚仍由底层 port 提供,但触摸数据上报已经统一封装在 HAL touch driver 内部,应用层无需再自己转换成 motion event。

典型资源占用

基于 STM32G0B0RE + RGB565 + PFB 30x40 的典型占用大致如下:

项目

大小

核心框架代码

~5-8KB Flash

PFB 缓冲区

2,400B RAM(单缓冲)

每个控件实例

~50-200B RAM

字体资源(16px, 4-bit)

~2-10KB Flash(取决于字符数)

图片资源

取决于尺寸和格式

构建方式

# 使用 GCC 工具链构建
make all APP=HelloSimple PORT=stm32g0

# 使用 Keil 工程
# porting/stm32g0/MDK-ARM/proj_stm32g0.uvprojx

如果只是做尺寸分析或纯编译检查,优先使用仓库统一的脚本和空平台方案,不建议直接依赖 STM32 硬件工程。

中断优先级建议

中断

优先级

说明

SysTick

保证时间基准准确

SPI DMA

DMA 传输完成回调

GPIO EXTI(触摸)

触摸中断通常不要求极低延迟

小结

当前 stm32g0 移植的关键特点是:

  1. 使用 egui_hal_lcd_register() 自动桥接显示驱动

  2. 使用 egui_hal_touch_register() 统一绑定触摸驱动

  3. 主流程采用 egui_init(&core, egui_pfb) + 手动注册 display / touch 的单屏模式

  4. 多缓冲和 DMA flush 由 core 内部 PFB 管理器统一协调

如果后续扩展到多屏或更复杂平台,建议以仓库根目录下的 porting/PORTING_GUIDE.md 为准,逐步迁移到 egui_setup_display() 方案。