news 2026/10/7 2:10:11

U8g2 在 STM32L031 上的 DMA SPI 驱动性能实测:SW/HW/DMA 三种传输方式 FPS 与内存对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
U8g2 在 STM32L031 上的 DMA SPI 驱动性能实测:SW/HW/DMA 三种传输方式 FPS 与内存对比
  • 嵌入式
  • 嵌入式GUI
  • 驱动开发

【免费下载链接】u8g2

U8glib library for monochrome displays, version 2

项目地址:https://gitcode.com/gh_mirrors/u8/u8g2
点击查看免费下载

导读

本文基于 U8g2 仓库中的官方 DMA 性能测试工程(sys/arm/stm32l031x4/u8g2_dma_spi_2mhz),完整还原 STM32L031 驱动 UC1609 SLG19264 单色屏时,软件模拟 SPI(SW SPI)、硬件 SPI(HW SPI)与 DMA SPI 三种数据传输方式的实测帧率(FPS)与静态内存(BSS)占用,并深入解析 u8x8cb.c 中三个 byte 回调函数的底层实现差异。读完本文,你将掌握:如何在 Cortex-M0+ 上为 U8g2 接入 DMA SPI 传输、三种方式在 2MHz 与 32MHz 主频下的真实性能差距,以及全缓冲(full buffer)与页缓冲(page buffer)模式各自的内存代价,可直接复用到自己的 STM32 裸机工程中。

一、测试工程概览

该工程位于sys/arm/stm32l031x4/u8g2_dma_spi_2mhz/,是一个以gnu-arm-none-eabi工具链构建的裸机 C 工程,由以下几部分组成:

文件作用
main.c主程序:时钟切换、显示初始化、FPS 计测与循环刷屏
u8x8cb.cU8x8 回调层:GPIO/延时回调 + HW SPI / DMA SPI 字节回调
delay.c基于 SysTick 的微秒级延时实现
MakefileGNU ARM 构建脚本(含upload/clean目标)
stm32l031x4.ld链接脚本

测试对象是UC1609 SLG19264(192×64 点阵)显示屏,其驱动定义与三种 Setup 函数(页缓冲_1、_2与全缓冲_f)位于 csrc/u8g2_d_setup.c。

二、实测性能数据:2MHz 与 32MHz 主频

工程 README(README.md)给出了两组权威实测数据。测试中 MCU 系统时钟分别运行在2MHz与32MHz,同一屏幕分别使用全缓冲构造器uc1609_slg19264_f与页缓冲构造器uc1609_slg19264_1,并切换三种 SPI 传输方式。

2.1 系统时钟 2MHz(HSI 4MHz 分频场景)

ConstructorSysClkTransferFPSBSS (RAM)
uc1609_slg19264_f2MHzSW SPI0.31692
uc1609_slg19264_f2MHzHW SPI4.71692
uc1609_slg19264_f2MHzDMA SPI5.01948
uc1609_slg19264_12MHzSW SPI0.3348
uc1609_slg19264_12MHzHW SPI2.6348
uc1609_slg19264_12MHzDMA SPI2.6604

2.2 系统时钟 32MHz(PLL 场景)

ConstructorSysClkTransferFPSBSS (RAM)
uc1609_slg19264_f32MHzSW SPI6.01692
uc1609_slg19264_f32MHzHW SPI73.81692
uc1609_slg19264_f32MHzDMA SPI76.71948
uc1609_slg19264_132MHzSW SPI5.6348
uc1609_slg19264_132MHzHW SPI39.6348
uc1609_slg19264_132MHzDMA SPI40.7604

说明:数据来源为工程 README 及 main.c 中的注释,二者完全一致。

2.3 数据解读

  • SW SPI 是性能瓶颈:32MHz 主频下全缓冲模式仅 6.0 FPS,因为每个比特都要由 CPU 通过 GPIO 翻转产生,且 u8x8cb.c 中时钟/数据引脚翻转均依赖 CPU 逐位执行;
  • HW SPI 带来数量级提升:32MHz 下全缓冲从 6.0 FPS 跃升至 73.8 FPS(约 12 倍),数据传输交给 SPI 外设硬件完成,CPU 仅负责逐字节写数据寄存器;
  • DMA SPI 进一步榨干外设:32MHz 下全缓冲达到 76.7 FPS,比 HW SPI 再提升约 4%;页缓冲模式达到 40.7 FPS,比 HW SPI 的 39.6 略高;
  • 页缓冲内存优势明显:_1构造器 BSS 仅 348 字节(DMA 版 604 字节),而全缓冲_f需 1692 字节(DMA 版 1948 字节),多出的约 256 字节正是 u8x8cb.c 中 DMA 中转缓冲区dma_buffer[256]的占用;
  • 低主频下差异被 SPI 时序掩盖:2MHz 主频时 HW SPI 与 DMA SPI 差距极小(4.7 vs 5.0),此时瓶颈已不在传输本身,而在于整机执行帧绘制逻辑的速度。

三、硬件接线:STM32L031 与 UC1609 SLG19264

工程提供了手绘接线示意图与实物连线照片,二者相互印证:

实物照片中屏幕正显示STM32L031 02 MHz 069 FPS:005.1,即 2MHz 主频下 FPS 计测画面。SPI 四线接法(不含 I2C 的 SDA/SCL)对应关系如下:

GPIODisplayColor
PA14CDbrown(棕)
PA13CSyellow(黄)
PA7MOSIgreen(绿)
PA6Resetwhite(白)
PA5SCKpurple(紫)

该映射在 u8x8cb.c 的注释中与代码一一对应:PA5 作 SCK、PA7 作 MOSI(数据)、PA13 作 CS、PA14 作 CD(数据/命令选择)、PA6 作 Reset。需要留意的是,该工程未使用硬件片选,SPI1->CR1中开启了SSM | SSI(软件管理 NSS),CS 引脚完全由 GPIO 回调手动控制。

四、U8g2 回调机制与三种传输的实现剖析

U8g2 的显示驱动层通过两个回调把上层绘图指令最终落到硬件:

  • byte 回调(u8x8_byte_hw_spi、u8x8_byte_dma_spi等):负责U8X8_MSG_BYTE_SEND、U8X8_MSG_BYTE_START_TRANSFER、U8X8_MSG_BYTE_END_TRANSFER、U8X8_MSG_BYTE_SET_DC等消息(消息定义见 csrc/u8x8.h);
  • GPIO/延时回调:负责U8X8_MSG_GPIO_SPI_CLOCK、U8X8_MSG_GPIO_SPI_DATA、U8X8_MSG_GPIO_CS、U8X8_MSG_GPIO_DC、U8X8_MSG_GPIO_RESET及各延时消息。

在 main.c 的initDisplay()中,通过切换u8g2_Setup_uc1609_slg19264_f的第三个参数即可在三者间切换:

// 软件模拟 SPI(注释状态) // u8g2_Setup_uc1609_slg19264_f(&u8g2, U8G2_R2, u8x8_byte_4wire_sw_spi, u8x8_gpio_and_delay_stm32l0_spi); // 硬件 SPI u8g2_Setup_uc1609_slg19264_f(&u8g2, U8G2_R2, u8x8_byte_stm32l0_hw_spi, u8x8_gpio_and_delay_stm32l0_spi); // DMA SPI(注释状态) // u8g2_Setup_uc1609_slg19264_f(&u8g2, U8G2_R2, u8x8_byte_stm32l0_dma_spi, u8x8_gpio_and_delay_stm32l0_spi);

u8x8_byte_4wire_sw_spi是 U8g2 内置的软件 SPI 字节回调,直接调用U8X8_MSG_GPIO_SPI_CLOCK/U8X8_MSG_GPIO_SPI_DATA消息驱动 GPIO 翻转,对应 u8x8cb.c 中 PA5/PA7 的 BSRR 原子操作。

4.1 硬件 SPI:CPU 逐字节搬运

u8x8_byte_stm32l0_hw_spi(u8x8cb.c)的核心流程:

case U8X8_MSG_BYTE_SEND: data = (uint8_t *)arg_ptr; while( arg_int > 0 ) { while ( (SPI1->SR & SPI_SR_BSY) || (DMA1_Channel3->CNDTR != 0) ) // 等待上次传输完成 ; *(uint8_t *)&(SPI1->DR) = *data; // 逐字节写入数据寄存器 data++; arg_int--; } break;

在U8X8_MSG_BYTE_INIT中完成 SPI1 时钟使能、PA5/PA7 复用模式配置(AF0)、SPI1->CR1主模式配置(MSTR | SSM | SSI,CPHA/CPOL 均为 0,即 SPI Mode 0),并明确 SPI 时钟分频由主频决定、未强制预分频(2MHz 场景无需分频)。

4.2 DMA SPI:外设自动搬运

u8x8_byte_stm32l0_dma_spi(u8x8cb.c)在 HW SPI 基础上引入 DMA1 通道 3:

  1. U8X8_MSG_BYTE_INIT:除 SPI1 外还使能RCC_AHBENR_DMA1EN;通过DMA1_CSELR将通道 3 请求源选择为SPI_TX;配置CMAR(内存地址指向dma_buffer)与CPAR(外设地址指向SPI1->DR);并使能SPI1->CR2 = SPI_CR2_TXDMAEN(发送 DMA 使能);
  2. U8X8_MSG_BYTE_SEND:由于回调返回后arg_ptr指向的数据区会被覆盖,先手动拷贝到全局dma_buffer[256](注释明确说明memcpy反而更慢,因此使用循环赋值);随后重置通道:CCR = 0,CNDTR = arg_int(传输字节数),再置MINC(内存地址递增)、DIR(内存到外设)、EN(使能通道)启动传输;
  3. 同步等待:在U8X8_MSG_BYTE_SET_DC与U8X8_MSG_BYTE_END_TRANSFER处,均通过(SPI1->SR & SPI_SR_BSY) || (DMA1_Channel3->CNDTR != 0)等待 DMA 传输真正完成,随后才切换 DC 或拉高 CS。

关键结论(由源码结构推断):DMA SPI 的加速本质是"CPU 只负责一次内存拷贝 + 启动 DMA,剩余的逐字节 SPI 发送完全由 DMA 与 SPI 外设流水完成"。在本测试中 DMA 拷贝 256 字节的开销被外设级并发所抵消,因此在 32MHz 下获得约 4% 的额外提升;但代价是 256 字节的全局 DMA 缓冲区常驻 BSS——这正是第二节表格中 DMA 列 BSS 比 HW 列多出约 256 字节的直接原因。

4.3 页缓冲与全缓冲的内存差异

从 csrc/u8g2_d_setup.c 可以看到三个构造器的缓冲差异:

  • uc1609_slg19264_1:调用u8g2_m_24_8_1,仅分配 1 页缓冲(页高 8 像素);
  • uc1609_slg19264_2:调用u8g2_m_24_8_2,2 页缓冲;
  • uc1609_slg19264_f:调用u8g2_m_24_8_f,全缓冲,一次性容纳整幅 192×64 画面。

三者的刷新流程也不同:页缓冲用u8g2_FirstPage()/u8g2_NextPage()分页绘制(main.c 即采用此模式),全缓冲则用u8g2_ClearBuffer()/u8g2_SendBuffer()。实测数据显示:帧率上全缓冲明显优于页缓冲(76.7 vs 40.7),但代价是 BSS 从 604 字节涨到 1948 字节,在 RAM 紧张的 MCU 上需要权衡。

五、FPS 计测方法:SysTick 计时

main.c 的测速逻辑非常简洁,可复用到任何工程:

  1. startUp()中配置 SysTick 每 10ms 触发一次中断(main.c),SysTick_Handler累加全局计数SysTickCount;
  2. 主循环每完成一次完整刷屏frame_cnt++,同时计算fps = frame_cnt*1000 / diff(diff为 10ms 刻度计数),再拆出整数与小数部分;
  3. 屏幕上实时显示STM32L031、系统主频(MHz)、计数器值与FPS:x.x(main.c)。

同时PA9被配置为输出并随每帧翻转,可用示波器/逻辑分析仪直接观测帧周期(main.c)。

六、系统时钟切换:HSI 与 PLL

测试覆盖 2MHz 与 32MHz 两个主频点,切换逻辑在setHSIClock()(main.c):

  • 2MHz 场景:直接使用 HSI 4MHz 时钟(setHSIClock()被注释,代码注释表明"2MHz uC Clock");
  • 32MHz 场景:使能 HSI 并开启RCC_CR_HSIDIVEN(HSI 4 分频 → 4MHz),随后配置 PLL:PLLMUL16 | PLLDIV2,即 4MHz × 16 ÷ 2 =32MHz,切换时钟源后调用SystemCoreClockUpdate()同步全局变量。

延时函数delay_micro_seconds()(delay.c)依赖SystemCoreClock换算系统 tick,因此 README 与代码均强调:必须先调用SystemCoreClockUpdate()再使用延时。

七、构建、烧录与复现实验

该工程使用 GNU ARM 工具链构建(Makefile),关键配置如下:

  • 架构:-mcpu=cortex-m0plus -mthumb,定义STM32L031xx,优化等级-Os;
  • 源码组织:CSRC := $(wildcard *.c)收集本目录源文件,U8G2SRC := $(wildcard ../../../../csrc/*.c)全量编译 U8g2 的 csrc 源码,系统库取自../../stm32l031x6/stm32l0xx/(该目录同时存放 STM32L0 标准外设库的头文件与源文件);
  • 链接:使用 stm32l031x4.ld,并开启--gc-sections裁剪未引用段。

常用命令:

make # 生成 u8g2_test.hex / .elf / .dis,并打印 size make clean # 清理所有生成物 make upload # 通过 stm32flash 以 115200 波特率写入串口设备(脚本内为 /dev/ttyUSB0)

复现性能对比的完整步骤:

  1. 保持u8g2_Setup_uc1609_slg19264_f(&u8g2, U8G2_R2, u8x8_byte_stm32l0_dma_spi, u8x8_gpio_and_delay_stm32l0_spi);为有效行,注释掉其他两个 Setup 调用;
  2. 默认setHSIClock()被注释时测 2MHz 场景;取消注释后测 32MHz 场景;
  3. 将构造器换成uc1609_slg19264_1并保持u8g2_FirstPage/NextPage循环,即可对比页缓冲数据;
  4. 编译烧录后读取屏幕上的FPS:x.x,并与第二节表格对照。

八、工程延伸与参考

  • 同系列还有 sys/arm/stm32l031x4/u8g2_dma_spi_32mhz 与 sys/arm/stm32l031x4/u8g2_test 工程,可对比不同主频与不同显示驱动下的表现;
  • UC1609 页/全缓冲 Setup 的定义集中在 csrc/u8g2_d_setup.c,DMA 用到的 SPI_TX 请求映射与 SPI 外设寄存器操作见 u8x8cb.c;
  • 该工程最初针对 U8g2 issue #2564(DMA 性能讨论)而建立,实测结论即本文第二节的两组数据;
  • 若需在 Linux 上快速验证 U8g2 的 SW/HW 传输差异,可参考 sys/sdl 下的 SDL 仿真示例,本工程则适用于真实硬件时序验证。

总结:对 Cortex-M0+ 这类资源受限平台,DMA SPI 是"用 256 字节 BSS 换约 4% 帧率提升"的划算选择,而 HW SPI 已是性价比最高的方案;若 RAM 紧张,_1页缓冲构造器配合 HW/DMA SPI 可在 348/604 字节 BSS 下获得接近全缓冲 50% 的帧率。实际选型时,请结合自身主频、可用 RAM 与显示刷新需求,参考本文第二节的完整数据表决策。

  • 嵌入式
  • 嵌入式GUI
  • 驱动开发

【免费下载链接】u8g2

U8glib library for monochrome displays, version 2

项目地址:https://gitcode.com/gh_mirrors/u8/u8g2
点击查看免费下载
上一篇:逆向工程师工具集:构建Windows恶意PDF分析的完整解决方案
下一篇:qm 会话分享(Session Sharing)机制全解析:只读快照、受众控制与安全边界

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 2:04:54

Scaffolt 生成器实战:为 Brunch with Chaplin 骨架批量生成 MVC 代码

构建工具前端 【免费下载链接】brunch 🍴 Web applications made easy. Since 2011. 项目地址: https://gitcode.com/gh_mirrors/br/brunch 点击查看 免费下载 导读 本指南围绕 brunch-with-chaplin 骨架内置的生成器集合展开,说明如何借助…

作者头像 李华