STM32H5E5ZJ有4MB的片上Flash,1.5MB的SRAM,GPDMA支持RAM到RAM,Flash到RAM的数据搬运。本文介绍STM32H5E5ZJ片上RAM、Flash的读写测试,以及开启DMA数据传输的性能。
在 STM32H5E5ZJ 上量化对比以下三类操作的耗时与吞吐:
- 片上 RAM 的读 / 写性能(CPU 不同位宽访问、调用
memcpy实现);
- 使用 GPDMA 进行数据搬运的耗时(RAM↔RAM、Flash→RAM),与 CPU 方式直接对比;
- 片上 Flash 的擦除 / 编程 / 读取性能(含写后读回校验)。
所有时间测量统一使用调试组件的 DWT 周期计数器 (CYCCNT),精度为 1 个 CPU 周期(250 MHz 下 4 ns),消除 HAL_GetTick 毫秒级粒度带来的误差。每组非破坏性测试固定搬运 1 MB 数据,块大小取 4/16/64/256 KB,直接对比 CPU 与 DMA:
| 测试项 |
说明 |
| RAM write 8/16/32-bit (CPU) |
不同位宽写 SRAM 的吞吐 |
| RAM read 32-bit (CPU) |
CPU 顺序读 SRAM |
| RAM memcpy (CPU libc) |
newlib 库搬运 |
| RAM→RAM DMA (GPDMA) |
GPDMA1 存储-存储搬运,与上面 memcpy 对比 |
| Flash read 32-bit (CPU) |
CPU 顺序读片上 Flash |
| Flash→RAM DMA (GPDMA) |
DMA 从 Flash 搬到 RAM,与上面对比 |
| Flash erase / program |
擦除 32 个 8KB 扇区 + quad-word(16B) 编程 256KB(破坏性,仅上电跑一次),含读回校验 |
1、CubeMX设置
设置MCU使用外部晶振,MCU主频调整为250MHz。

使能GPDMA1的Ch0,用于测试RAM↔RAM、Flash→RAM的数据传输

保存并生成代码
2、测试程序
片上RAM和Falsh的测试程序的框架如下
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ main() ── 调度:Banner → Flash写(一次) → RAM/DMA/读(循环) │
└───────────────┬─────────────────────────────────────────────┘
│ 调用
┌───────────────▼─────────────────────────────────────────────┐
│ 基准层 (Benchmark) benchmark.c │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ RAM 测试 │ │ DMA 测试 │ │ Flash 测试 │ │
│ │ 写(8/16/32b) │ │ RAM→RAM │ │ 擦除/编程/校验 │ │
│ │ 读(32b) │ │ Flash→RAM │ │ 读(CPU) │ │
│ │ memcpy │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ 计时 │ 完成回调 │ HAL │
│ ┌──────▼─────────────────▼────────────────────▼─────────┐ │
│ │ DWT->CYCCNT 计时子系统 + report() 串口格式化输出 │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│ 依赖
┌───────────────────────────▼─────────────────────────────────┐
│ HAL / CMSIS 驱动层 (来自 STM32Cube_FW_H5) │
│ GPIO · RCC · PWR · UART · DMA(GPDMA) · FLASH · ICACHE │
└─────────────────────────────────────────────────────────────┘
测试过程的流程图如下。

2.1 执行时间计算
使能 Cortex-M33 的 DWT 周期计数器,在250MHz主频下,计数器单个计数对应4ns。BENCH_Init中初始化DWT 周期计数器
/* ---- DWT cycle counter helpers ---- */
void BENCH_Init(void)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0U;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
static inline uint32_t cycles(void)
{
return DWT->CYCCNT;
}
2.2 Flash 测试区规划
应用程序的存测试相关的宏定义、测试函数的声明如下。
#include "stm32h5xx_hal.h"
/* ---- Platform constants (derived from STM32H5E5ZJ) ---- */
#define BENCH_CORE_HZ (250000000UL) /* SYSCLK = 250 MHz */
/* STM32H5 embedded Flash: 2 banks x 2 MB, 256 sectors/bank, 8 KB/sector,
programmed by 16-byte quad-words. */
#define BENCH_FLASH_SECTOR_SIZE (0x2000U) /* 8 KB */
#define BENCH_FLASH_QUADWORD (16U) /* 16 bytes per program */
#define BENCH_FLASH_BASE (0x08000000U)
#define BENCH_FLASH_TOTAL (0x400000U) /* 4 MB */
#define BENCH_FLASH_BANK2_BASE (0x08200000U)
/* Non-destructive test region: last 256 KB of the embedded Flash (Bank2).
The application image lives at the very beginning of Flash, so this region
is untouched by the program. */
#define BENCH_FLASH_ADDR (BENCH_FLASH_BASE + BENCH_FLASH_TOTAL - 0x40000U)
#define BENCH_FLASH_SIZE (0x40000U) /* 256 KB */
/* RAM working buffers (placed in .bss of internal SRAM, 1.5 MB available). */
#define BENCH_MAX_SIZE (0x40000U) /* 256 KB */
/* Block sizes exercised by the benchmark (bytes). All are multiples of 16 so
that Flash quad-word programming stays naturally aligned. */
#define BENCH_BLOCK_COUNT (4U)
/* ---- Public API ---- */
void BENCH_Init(void); /* enable DWT cycle counter */
void BENCH_PrintBanner(void); /* print platform info */
void BENCH_RunRAM_DMARead(void); /* RAM + DMA + Flash-read, non-destructive */
void BENCH_RunFlashWrite(void
| 参数 |
值 |
说明 |
| Flash 总大小 |
4 MB (0x400000) |
双 Bank,每 Bank 2 MB |
| Bank2 基址 |
0x08200000 |
测试区位于 Bank2 末尾 |
| 测试区起始 |
0x083C0000 |
片上 Flash 最后 256 KB |
| 测试区大小 |
256 KB (0x40000) |
不覆盖程序本体(程序在 Flash 起始处,仅 ~38 KB) |
| 扇区大小 |
8 KB (0x2000) |
H5 Flash 按扇区擦除 |
| 起始扇区号 |
224 |
(0x083C0000 - 0x08200000) / 0x2000 |
| 擦除扇区数 |
32 |
256 KB / 8 KB,覆盖扇区 224~255 |
| 编程粒度 |
16 字节 (quad-word) |
H5 仅支持 FLASH_TYPEPROGRAM_QUADWORD |
测试区放在 Flash 末尾而非起始,确保擦写不会破坏应用程序向量表与代码段。
2.3 基准测试模块
工作缓冲区为两个 256 KB 的全局数组(32 字节对齐,位于内部 SRAM 的 .bss 段):
/* ---- RAM working buffers (internal SRAM, 32-byte aligned) ---- */
static uint8_t src_buf[BENCH_MAX_SIZE] __attribute__((aligned(32)));
static uint8_t dst_buf[BENCH_MAX_SIZE] __attribute__((aligned(32)));
#define FLASH_WIN ((const uint8_t *)BENCH_FLASH_ADDR)
/* Block sizes exercised (bytes), all multiples of 16 (Flash quad-word). */
static const uint32_t block_sizes[BENCH_BLOCK_COUNT] =
{
4U * 1024U, 16U * 1024U, 64U * 1024U, 256U * 1024U
};
/* Total bytes moved per non-destructive test (1 MiB) for fair comparison. */
#define BENCH_TOTAL_BYTES (1024U * 1024U)
/* ---- DMA transfer synchronization ---- */
static volatile uint8_t dma_done;
static volatile uint8_t dma_error;
void dma_tc_callback(DMA_HandleTypeDef *hdma)
{
(void)hdma;
dma_done = 1U;
}
void dma_err_callback(DMA_HandleTypeDef *hdma)
{
(void)hdma;
dma_error = 1U;
dma_done = 1U;
}
每个测试函数遵循统一模式:
- 预热 (warmup):先执行一次传输,使 Cache / 总线进入稳态;
- 计时:
t0 = cycles() → 循环 iters 次 → cycles() - t0;
- 返回周期数(错误返回 0)。
2.4 破坏性测试(仅上电执行一次)
| 测试函数 |
操作 |
bench_flash_erase |
HAL_FLASHEx_Erase 擦除 Bank2 的 32 个扇区 (256 KB),测擦除耗时 |
bench_flash_program |
循环 HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, ...) 写满 256 KB(16384 次 quad-word 编程),测编程耗时 |
flash_verify |
memcmp 读回 Flash 与 src_buf 比对,验证写入正确性 |
/* ========================================================================== */
/* Flash erase + program (CPU, destructive, measured once over 256 KiB) */
/* ========================================================================== */
static uint32_t bench_flash_erase(void)
{
FLASH_EraseInitTypeDef erase = {0};
uint32_t sector_error = 0xFFFFFFFFU;
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
erase.Banks = FLASH_BANK_2;
erase.Sector = (BENCH_FLASH_ADDR - BENCH_FLASH_BANK2_BASE) / BENCH_FLASH_SECTOR_SIZE;
erase.NbSectors = BENCH_FLASH_SIZE / BENCH_FLASH_SECTOR_SIZE;
HAL_FLASH_Unlock();
uint32_t t0 = cycles();
HAL_StatusTypeDef st = HAL_FLASHEx_Erase(&erase, §or_error);
uint32_t cyc = cycles() - t0;
HAL_FLASH_Lock();
if (st != HAL_OK || sector_error != 0xFFFFFFFFU)
{
return 0U; /* signal error */
}
return cyc;
}
static uint32_t bench_flash_program(void)
{
const uint32_t n = BENCH_FLASH_SIZE / BENCH_FLASH_QUADWORD;
uint32_t flash_addr = BENCH_FLASH_ADDR;
uint8_t *psrc = src_buf;
HAL_FLASH_Unlock();
uint32_t t0 = cycles();
for (uint32_t i = 0; i < n; i++)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, flash_addr, (uint32_t)psrc) != HAL_OK)
{
HAL_FLASH_Lock();
return 0U;
}
flash_addr += BENCH_FLASH_QUADWORD;
psrc += BENCH_FLASH_QUADWORD;
}
uint32_t cyc = cycles() - t0;
HAL_FLASH_Lock();
return cyc;
}
static int flash_verify(void)
{
return (memcmp((const void *)BENCH_FLASH_ADDR, src_buf, BENCH_FLASH_SIZE) == 0) ? 1 : 0;
}
2.5 非破坏性测试(每轮固定搬运 1 MB)
测试块大小集合:{4 KB, 16 KB, 64 KB, 256 KB},迭代次数 iters = 1 MB / block,保证不同块大小下总数据量相同,结果可直接对比。
| 测试函数 |
操作 |
对比意义 |
bench_ram_write_wide (width=4/2/1) |
CPU 以 32/16/8-bit 顺序写 dst_buf |
位宽对 SRAM 写吞吐的影响 |
bench_ram_read32 |
CPU 以 32-bit 顺序读 src_buf 并累加(防编译器优化) |
SRAM 读吞吐 |
bench_memcpy |
newlib 库 memcpy(dst, src, bytes) |
CPU 搬运基线 |
bench_dma_m2m |
GPDMA src_buf → dst_buf |
DMA vs CPU memcpy 的加速比 |
bench_flash_read32 |
CPU 以 32-bit 顺序读 Flash 测试区 |
Flash 读吞吐(CPU) |
bench_dma_flash2ram |
GPDMA Flash → dst_buf |
DMA vs CPU 读 Flash 的加速比 |
bench_ram_write_wide 、bench_ram_read32以及 bench_memcpy用于CPU访问RAM的读写速度测试,代码如下
/* ========================================================================== */
/* RAM access (CPU) */
/* ========================================================================== */
static uint32_t bench_ram_write_wide(uint32_t bytes, uint32_t iters, uint32_t width)
{
/* warmup */
if (width == 4U)
{
volatile uint32_t *p = (volatile uint32_t *)dst_buf;
uint32_t n = bytes / 4U;
for (uint32_t i = 0; i < n; i++) { p[i] = i; }
}
else if (width == 2U)
{
volatile uint16_t *p = (volatile uint16_t *)dst_buf;
uint32_t n = bytes / 2U;
for (uint32_t i = 0; i < n; i++) { p[i] = (uint16_t)i; }
}
else
{
volatile uint8_t *p = (volatile uint8_t *)dst_buf;
for (uint32_t i = 0; i < bytes; i++) { p[i] = (uint8_t)i; }
}
uint32_t t0 = cycles();
if (width == 4U)
{
volatile uint32_t *p = (volatile uint32_t *)dst_buf;
uint32_t n = bytes / 4U;
for (uint32_t k = 0; k < iters; k++)
for (uint32_t i = 0; i < n; i++) { p[i] = i; }
}
else if (width == 2U)
{
volatile uint16_t *p = (volatile uint16_t *)dst_buf;
uint32_t n = bytes / 2U;
for (uint32_t k = 0; k < iters; k++)
for (uint32_t i = 0; i < n; i++) { p[i] = (uint16_t)i; }
}
else
{
volatile uint8_t *p = (volatile uint8_t *)dst_buf;
for (uint32_t k = 0; k < iters; k++)
for (uint32_t i = 0; i < bytes; i++) { p[i] = (uint8_t)i; }
}
return cycles() - t0;
}
static uint32_t bench_ram_read32(uint32_t bytes, uint32_t iters)
{
volatile uint32_t sink = 0;
const volatile uint32_t *p = (const volatile uint32_t *)src_buf;
uint32_t n = bytes / 4U;
(void)sink;
uint32_t t0 = cycles();
for (uint32_t k = 0; k < iters; k++)
for (uint32_t i = 0; i < n; i++) { sink += p[i]; }
return cycles() - t0;
}
/* ---- CPU memcpy (newlib) ---- */
static uint32_t bench_memcpy(uint32_t bytes, uint32_t iters)
{
memcpy(dst_buf, src_buf, bytes); /* warmup */
uint32_t t0 = cycles();
for (uint32_t k = 0; k < iters; k++)
{
memcpy(dst_buf, src_buf, bytes);
}
return cycles() - t0;
}
bench_dma_m2m用于测试使用GPDMA在RAM之间进行数据搬运的效果
/* ========================================================================== */
/* GPDMA memory-to-memory (src_buf -> dst_buf) */
/* ========================================================================== */
/* GPDMA single-block transfer is limited to 65535 bytes: the block-length
* field CBR1.BNDT is only 16 bits. Any length >= 65536 gets truncated to 0
* and the channel reports an error. Chunk big transfers into sub-blocks that
* stay <= 65520 bytes (largest multiple of 16 below 65536, keeping burst=4
* word alignment). The whole block (all chunks) is timed end-to-end. */
#define DMA_MAX_CHUNK (65520U) /* 0xFFF0 */
static int dma_xfer(uint32_t src, uint32_t dst, uint32_t bytes)
{
uint32_t off = 0U;
while (off < bytes)
{
uint32_t chunk = bytes - off;
if (chunk > DMA_MAX_CHUNK) { chunk = DMA_MAX_CHUNK; }
dma_done = 0U;
dma_error = 0U;
if (HAL_DMA_Start_IT(&handle_GPDMA1_Channel0, src + off, dst + off, chunk) != HAL_OK)
{
return 0;
}
while (!dma_done) { }
if (dma_error) { return 0; }
off += chunk;
}
return 1;
}
static uint32_t bench_dma_m2m(uint32_t bytes, uint32_t iters)
{
/* warmup */
if (!dma_xfer((uint32_t)src_buf, (uint32_t)dst_buf, bytes)) { return 0U; }
uint32_t t0 = cycles();
for (uint32_t k = 0; k < iters; k++)
{
if (!dma_xfer((uint32_t)src_buf, (uint32_t)dst_buf, bytes)) { return 0U; }
}
return cycles() - t0;
}
bench_flash_read32、bench_dma_flash2ram测试Flash在CPU读取和DMA读取是的速度。
/* ========================================================================== */
/* Flash read (CPU) */
/* ========================================================================== */
static uint32_t bench_flash_read32(uint32_t bytes, uint32_t iters)
{
volatile uint32_t sink = 0;
const volatile uint32_t *p = (const volatile uint32_t *)FLASH_WIN;
uint32_t n = bytes / 4U;
(void)sink;
uint32_t t0 = cycles();
for (uint32_t k = 0; k < iters; k++)
for (uint32_t i = 0; i < n; i++) { sink += p[i]; }
return cycles() - t0;
}
/* ---- GPDMA Flash -> RAM ---- */
static uint32_t bench_dma_flash2ram(uint32_t bytes, uint32_t iters)
{
/* warmup */
if (!dma_xfer((uint32_t)FLASH_WIN, (uint32_t)dst_buf, bytes)) { return 0U; }
uint32_t t0 = cycles();
for (uint32_t k = 0; k < iters; k++)
{
if (!dma_xfer((uint32_t)FLASH_WIN, (uint32_t)dst_buf, bytes)) { return 0U; }
}
return cycles() - t0;
}
2.6 测试信息输出
通过调用测试函数,将测试结果通过串口发送到终端显示,以下为函数调用的具体实现。
void BENCH_PrintBanner(void)
{
printf("\r\n");
printf("==============================================================\r\n");
printf(" STM32H5E5ZJ Flash / RAM / DMA Performance Benchmark\r\n");
printf(" Core: Cortex-M33 @ %lu MHz | 4 MB Flash 1.5 MB SRAM\r\n",
(unsigned long)(BENCH_CORE_HZ / 1000000UL));
printf(" Timer: DWT->CYCCNT (1 tick = 4 ns)\r\n");
printf(" UART: USART3 PD8/PD9 115200 8N1 (ST-LINK VCP)\r\n");
printf(" Flash test window: 0x%08lX - 0x%08lX (%lu KiB, Bank2)\r\n",
(unsigned long)BENCH_FLASH_ADDR,
(unsigned long)(BENCH_FLASH_ADDR + BENCH_FLASH_SIZE - 1U),
(unsigned long)(BENCH_FLASH_SIZE / 1024U));
printf(" Non-destructive tests move 1 MiB per row.\r\n");
printf("==============================================================\r\n");
}
void BENCH_RunFlashWrite(void)
{
printf("\r\n----- Flash write test (DESTRUCTIVE, runs once) -----\r\n");
fill_src(BENCH_FLASH_SIZE);
uint32_t cyc_e = bench_flash_erase();
if (cyc_e == 0U)
{
report_error("Flash erase (32x8K sector)");
}
else
{
report("Flash erase (32x8K sector)", BENCH_FLASH_SIZE, 1U, cyc_e);
}
uint32_t cyc_p = bench_flash_program();
if (cyc_p == 0U)
{
report_error("Flash program (quad-word)");
}
else
{
report("Flash program (quad-word)", BENCH_FLASH_SIZE, 1U, cyc_p);
}
if (flash_verify())
{
printf("Flash read-back verify: PASSED (%lu KiB)\r\n",
(unsigned long)(BENCH_FLASH_SIZE / 1024U));
}
else
{
printf("Flash read-back verify: FAILED\r\n");
}
}
void BENCH_RunRAM_DMARead(void)
{
printf("\r\n----- RAM / DMA / Flash-read test (non-destructive) -----\r\n");
printf("%-26s | %7s x%3s = %9s | %10s | %9s | %7s\r\n",
"Test", "Block", "It", "Total B", "cycles", "us", "MB/s");
printf("---------------------------+--------------------------+-----------------+--------------+---------\r\n");
fill_src(BENCH_MAX_SIZE);
for (uint32_t b = 0; b < BENCH_BLOCK_COUNT; b++)
{
uint32_t bytes = block_sizes[b];
uint32_t iters = BENCH_TOTAL_BYTES / bytes;
uint32_t cyc;
cyc = bench_ram_write_wide(bytes, iters, 4U);
if (cyc) report("RAM write 32-bit (CPU)", bytes, iters, cyc); else report_error("RAM write 32-bit (CPU)");
cyc = bench_ram_write_wide(bytes, iters, 2U);
if (cyc) report("RAM write 16-bit (CPU)", bytes, iters, cyc); else report_error("RAM write 16-bit (CPU)");
cyc = bench_ram_write_wide(bytes, iters, 1U);
if (cyc) report("RAM write 8-bit (CPU)", bytes, iters, cyc); else report_error("RAM write 8-bit (CPU)");
cyc = bench_ram_read32(bytes, iters);
if (cyc) report("RAM read 32-bit (CPU)", bytes, iters, cyc); else report_error("RAM read 32-bit (CPU)");
cyc = bench_memcpy(bytes, iters);
if (cyc) report("RAM memcpy (CPU libc)", bytes, iters, cyc); else report_error("RAM memcpy (CPU libc)");
cyc = bench_dma_m2m(bytes, iters);
if (cyc) report("RAM->RAM DMA (GPDMA)", bytes, iters, cyc); else report_error("RAM->RAM DMA (GPDMA)");
cyc = bench_flash_read32(bytes, iters);
if (cyc) report("Flash read 32-bit (CPU)", bytes, iters, cyc); else report_error("Flash read 32-bit (CPU)");
cyc = bench_dma_flash2ram(bytes, iters);
if (cyc) report("Flash->RAM DMA (GPDMA)", bytes, iters, cyc); else report_error("Flash->RAM DMA (GPDMA)");
printf("\r\n");
}
}
主函数中调用接口函数,进行RAM和Flash的Benchmark测试。
BENCH_Init();
BENCH_PrintBanner();
/* Run the destructive Flash write test once after reset. */
BENCH_RunFlashWrite();
BENCH_RunRAM_DMARead();
编译烧录程序到开发板,测试结果如下
