你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【NUCLEO-H5E5ZJ评测】CoreMark 移植实录:从 CubeMX 到 700.89 分

[复制链接]
xusiwei1236 发布时间:2026-11-1 01:11

【NUCLEO-H5E5ZJ评测】CoreMark 移植实录:从 CubeMX 到 700.89 分

[TOC]

本文记录我在 NUCLEO-H5E5ZJ 开发板上移植 CoreMark 的完整过程:从查阅数据手册(DS)和原理图确认关键参数,到创建 CubeMX 工程、命令行 CMake 构建、printf 串口重定向,再到 CoreMark 的移植与多轮优化,最终在 250MHz 主频下跑出 700.89 CoreMark(2.80 CoreMark/MHz)的成绩。

过程中踩了不少坑——包括板卡 HSE 默认频率与实际晶振不符导致首次时钟方案翻车、首次构建大面积缺文件(链接脚本 MEMORY 为空、缺 startup 文件——根因是 H5 支持包未安装,正确解法是先安装支持包、再创建项目,见 2.1)等,都一并在文中记录。

本文全程使用命令行工具链(STM32CubeCLT 1.22 + CMake + Ninja + GCC 14.3.1),配合 STM32CubeMX 6.18.1 完成配置与代码生成。完整工程代码见文末链接。

一、准备工作

1.1 CoreMark 简介

来自 CoreMark 官方页面的解释:

CoreMark is a simple, yet sophisticated benchmark that is designed specifically to test the functionality of a processor core. Running CoreMark produces a single-number score allowing users to make quick comparisons between processors.

翻译一下:

CoreMark 是一个简单而又精密的基准测试程序,专门为测试处理器核功能而设计。运行 CoreMark 会产生一个"单个数字"的分数,允许用户在不同 CPU 之间进行快速比较。

STM32H5E5ZJ 的数据手册首页就标注了官方跑分:1023 CoreMark(4.092 CoreMark/MHz)。这就是我们要挑战的目标。

1.2 主控MCU最高主频

查阅数据手册(DS):

  • Arm Cortex-M33 内核,最高频率 250 MHz(VOS0 电压档,PWR_REGULATOR_VOLTAGE_SCALE0)
  • 4 MB Flash、1.5 MB RAM(SRAM1 256K + SRAM2 128K + SRAM3 384K + SRAM4 384K + SRAM5 384K)
  • 官方 CoreMark 成绩:1023 分(4.092 CoreMark/MHz)
  • VCO 输出范围:宽范围 128~560 MHz(来自参考手册 RM0517,配置 PLL 时会用到)

1.3 ST-LINK虚拟串口

要跑 CoreMark 并输出结果,先得解决"串口输出到 PC"的问题。NUCLEO-H5E5ZJ 板载 ST-LINK-V3EC 调试器,自带虚拟串口(VCP)。查阅原理图 MCU IOs 页:

原理图:DBGIN 网络与 MCU 引脚

可以确认 ST-LINK 与主控 MCU 的连接关系为:

ST-LINK 侧信号 MCU 引脚 MCU 复用功能
DBGIN.VCP_TX PD8 USART3_TX(boot 串口)
DBGIN.VCP_RX PD9 USART3_RX
DBGIN.SWDIO PA13 SWDIO
DBGIN.SWCLK PA14 SWCLK
DBGIN.SWO PB3 SWO
DBGIN.JTDI PA15 JTDI

再查数据手册确认 PD8 的复用功能表,USART3_TX(boot) 明确在列——这说明 PD8/PD9 除了是 VCP 串口,还是 ST 出厂的 bootloader 串口,选它做 printf 输出最合适。

调试方面,PA13(SWDIO)/PA14(SWCLK) 是 SWD 两线调试的最小集合,CubeMX 默认 SYS 调试使能后即可用。

1.4 CubeMX 与 CubeCLT

本文用到两个 ST 官方工具,分工明确:CubeMX 负责"生成工程",CubeCLT 负责"编译烧录"。开工前先装好,后面各章直接用。

STM32CubeMX(图形化配置与代码生成器)

  • 是什么 / 为什么要装:ST 官方的图形化配置工具,用于选型 MCU 或开发板、配置引脚外设与时钟树、选择中间件,最后一键生成工程骨架(初始化代码 + 构建系统)。没有它,外设初始化、时钟树计算、链接脚本、CMake 工程都得手写。
  • 本文用它干什么:第二章创建 NUCLEO-H5E5ZJ 板级工程(ioc 文件)、把时钟树配到 250MHz、配置 USART3、生成 CMake 工程;后续每次调整配置(如 ICACHE 改 2-way)也都在 ioc 里改再重新生成。
  • 安装提醒:从 ST 官网下载安装包(自带 Java 运行环境,无需另装 JRE),本文使用 6.18.1。装完第一件事:打开 Package Manager 安装对应 MCU 系列的固件支持包——本文是 STM32Cube FW_H5 V1.7.0。支持包是 CubeMX 生成代码的"原料库"(CMSIS 设备文件、HAL 驱动、板级 BSP 全在里面),它的完整性直接决定生成质量:不先安装就建工程,生成会缺 startup、缺 HAL 文件、链接脚本存储器映射为空——这正是本文踩过的最大的坑,详见 2.1。

STM32CubeCLT(命令行工具链全家桶)

  • 是什么 / 为什么要装:ST 官方打包的命令行开发工具集(Cross-compiler Toolchain),一次安装配齐:Arm GNU Toolchain for STM32(arm-none-eabi-gcc,本文 14.3.Rel1)、CMake、Ninja、STM32CubeProgrammer 命令行版(STM32_Programmer_CLI,烧录)、ST-LINK GDB Server,外加 ST Arm Clang(st-arm-clang,LLVM 系编译器,第 5.6 节会用到)。装它就不用分别下载 GCC、CMake、Ninja、烧录器再逐个配 PATH。
  • 本文用它干什么:cmake --preset Release / cmake --build 命令行构建(2.4 节)、STM32_Programmer_CLI SWD 烧录(3.3 节)、st-arm-clang 构建(5.6 节);第二章排查时钟问题时还用它从 SWD 读 RCC 寄存器。
  • 安装提醒:ST 官网下载,默认安装到 D:\ST\STM32CubeCLT_<版本>,本文 1.22。安装时建议全选组件;装完后确认各工具的 bin 目录已在 PATH(安装器可自动添加,或使用开始菜单里的 "STM32CubeCLT Environment" 命令行入口),命令行能直接跑 cmake、ninja、STM32_Programmer_CLI 即可。

二、项目骨架

这一章把工程从零建起来:CubeMX 创建板级工程(ioc 配置)→ 生成 CMake 代码 → 命令行构建通过。跑通这三步,开发环境就算验证完毕。

2.1 CubeMX配置要点

在 CubeMX 中创建 NUCLEO-H5E5ZJ 板级工程(Board Selector 搜索 H5E5ZJ),需要配置的核心内容如下:

  1. 调试口:SYS → Serial Wire(PA13/PA14 被占用为 SWDIO/SWCLK);
  2. USART3:PD8=USART3_TX、PD9=USART3_RX,异步模式,**115200-8-N-1**;
  3. 时钟:HSE 48MHz → PLL1(M=12, N=125, P=2)→ SYSCLK 250MHz。注意板卡 ioc 的 HSE 默认频率是 25MHz、与板上实际晶振不符,直接用会翻车——后果见下方坑点总结,配置步骤见 2.2;
  4. ICACHE:直接映射模式(后文优化时改为 2-way);
  5. Project Manager:Toolchain/IDE 选 CMake。

配置坑点总结——创建工程前先核对两项:

  • 必须先安装 H5 支持包:CubeMX → Package Manager 安装 STM32Cube FW_H5 V1.7.0(若用 GitHub 克隆固件包,记得补齐全部 git 子模块)。未安装的后果:生成的工程缺 startup、缺 HAL 文件、链接脚本 MEMORY 为空,编译直接失败;
  • HSE 输入频率默认 25MHz,板上实际晶振是 48MHz(X2 @ PH0/PH1):照默认值配 PLL 会实际跑在 480MHz 超频档,芯片僵死、串口无输出。按 2.2 的 48÷12×125÷2 配置即可。

2.2 时钟源配置

正确配置分两步,都在 CubeMX 里完成。

第一步:启用 HSE 晶振。 Pinout & Configuration 页左侧 System Core → RCC,把 High Speed Clock (HSE) 从 Disable 改为 Crystal/Ceramic Resonator——PH0/PH1 会自动分配为 RCC_OSC_IN/RCC_OSC_OUT:

CubeMX System Core → RCC:HSE 设为 Crystal/Ceramic Resonator

第二步:配时钟树。 切到 Clock Configuration 页,按下面顺序设置(关键:输入频率框默认是 25,必须手动改成 48):

  1. HSE 输入频率框:48 MHz;
  2. PLL Source Mux 单选 HSE;
  3. PLLM = 12(48÷12 = 4MHz,满足 VCO 输入 2~16MHz 宽范围要求);
  4. PLLN = 125(4×125 = 500MHz,满足 VCO 输出 128~560MHz 宽范围要求);
  5. PLLP = 2(500÷2 = 250MHz);
  6. System Clock Mux 选 PLLCLK,SYSCLK 显示 250MHz 即成功。

CubeMX Clock Configuration:HSE 48MHz → PLL(÷12 ×125 ÷2)→ SYSCLK 250MHz

时钟树全部变蓝(无红色冲突)后生成代码,SystemClock_Config()(Core/Src/main.c)就是我们要的样子:

__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);  /* VOS0,250MHz 的前提 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLL1_SOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 12;
RCC_OscInitStruct.PLL.PLLN = 125;
RCC_OscInitStruct.PLL.PLLP = 2;
...
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
...
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);

VOS0 电压档与 FLASH_LATENCY_5 都由 CubeMX 按 250MHz 自动生成,无需手工干预。

2.3 代码生成配置

CubeMX 的 Project Manager 中把 Toolchain/IDE 切换为 CMake 后生成。生成的工程结构:

NUCLEO-H5E5ZJ-CoreMark/
├── CMakeLists.txt          # 顶层 CMake(用户可自由修改)
├── CMakePresets.json       # Debug/Release 预设(Ninja 生成器)
├── STM32H5E5xx.ld          # GCC 链接脚本
├── cmake/
│   ├── gcc-arm-none-eabi.cmake
│   └── stm32cubemx/CMakeLists.txt   # CubeMX 生成的源文件清单
├── Core/
│   ├── Inc/                # main.h / usart.h / gpio.h / icache.h ...
│   └── Src/                # main.c / usart.c / gpio.c / icache.c / syscalls.c ...
└── Drivers/                # CMSIS + HAL(固件包链接)

注意 CMakePresets.json 已经定义好了 Debug/Release 两个预设,直接用 cmake --preset Release 配置即可。

2.4 CMake构建验证

构建命令两行(构建成功的截图,注意最后打印的内存占用):

cmake --preset Release
cmake --build build\Release

命令行构建成功,RAM 0.11%、FLASH 0.52%

工具链信息:CMake 3.26.4 + Ninja 1.11.1 + Arm GNU Toolchain 14.3.Rel1(GCC 14.3.1)。

至此项目骨架就绪、构建链路验证完毕。

三、串口链路

这一章打通固件 printf 到 PC 的整条数据链路。数据从 MCU 固件到 pyserial 窗口,实际经过了这样一长串环节:

固件 printf()
  → C 库(newlib-nano)_write 系统调用          [MCU 内,syscalls.c 弱符号]
  → __io_putchar(逐字符)                      [MCU 内,用户实现,USER CODE 区]
  → HAL_UART_Transmit() 写 USART3 发送寄存器    [MCU 内,HAL]
  → PD8 引脚输出 UART 电平(115200-8N1)        [PCB 走线]
  → 板载 ST-LINK-V3EC 的 VCP_RX 引脚            [板载调试器 MCU 接收]
  → ST-LINK 固件把字节打包成 USB CDC 数据       [ST-LINK MCU]
  → USB Type-C 线                               [物理连接]
  → Windows 枚举出虚拟串口 "STLink Virtual COM Port"(本机 COM29)
  → pyserial 打开 COM29 读取                    [PC 侧]

反方向(PC → MCU)同理:pyserial 写 COM29 → USB CDC → ST-LINK → PD9(USART3_RX)→ 固件读 USART3。注意这条链路上有两个"串口":MCU 侧是真实的 UART(波特率 115200 是它的限制),PC 侧是 USB 虚拟串口(USB 带宽远高于 115200,瓶颈永远在 UART 段)。理解了这条链路,串口没输出时就能分段定位:固件卡死?PD8 没波形?ST-LINK 固件没桥接?驱动没枚举?

3.1 printf 重定向

CubeMX 生成的 syscalls.c 已提供 _write 弱符号,只需在 Core/Src/usart.c 末尾的 USER CODE 区添加 __io_putchar:

/* USER CODE BEGIN 1 */
#ifdef __GNUC__
/* GCC 工具链 printf 重定向:syscalls.c 中弱定义的 _write 会逐字符调用 __io_putchar */
int __io_putchar(int ch)
{
  if (HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, HAL_MAX_DELAY) != HAL_OK)
  {
    return -1;
  }
  return ch;
}
#endif
/* USER CODE END 1 */

所有改动都放在 USER CODE BEGIN/END 区域内,之后无论怎么重新生成代码都不会丢失。

3.2 printf 测试

main.c 的 USER CODE 区:

/* USER CODE BEGIN 2 */
printf("\r\n[NUCLEO-H5E5ZJ] System Clock: %lu Hz\r\n", SystemCoreClock);
printf("[NUCLEO-H5E5ZJ] CoreMark build (USART3 VCP 115200-8N1)\r\n");
/* USER CODE END 2 */

/* USER CODE BEGIN WHILE */
int count = 0;
while (1)
{
  printf("[NUCLEO-H5E5ZJ] hello %d\r\n", ++count);
  HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
  HAL_Delay(1000);
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
}

主循环之前先打印当前系统时钟频率(SystemCoreClock),便于确认时钟配置真实生效。

3.3 命令行烧录

STM32_Programmer_CLI.exe -c port=SWD mode=Normal -w build\Release\NUCLEO-H5E5ZJ-CoreMark.elf -hardRst

输出确认连接正常:

ST-LINK SN  : 002400333335511035383531
Board       : NUCLEO-H5E5ZJ
Voltage     : 3.28V
SWD freq    : 8000 KHz
Device ID   : 0x47A
Device name : STM32H5F4xJ/H5F5xJ/H5E4xJ/H5E5xJ
Device CPU  : Cortex-M33

3.4 pyserial 验证

先 pip 安装:python -m pip install pyserial。读取脚本(完整见仓库 tools/serial_read.py):

import serial, serial.tools.list_ports, time

def find_stlink_port():
    for p in serial.tools.list_ports.comports():
        if p.description and "STLink" in p.description:
            return p.device

with serial.Serial(port, 115200, timeout=1) as ser:
    ser.reset_input_buffer()
    end = time.time() + dur
    while time.time() < end:
        line = ser.readline().decode(errors="replace").rstrip()
        if line:
            print(line)

先开监听、再复位(否则会错过开机横幅),成功看到:

[NUCLEO-H5E5ZJ] System Clock: 250000000 Hz
[NUCLEO-H5E5ZJ] CoreMark build (USART3 VCP 115200-8N1)
[NUCLEO-H5E5ZJ] hello 1
[NUCLEO-H5E5ZJ] hello 2
...

pyserial 读到 250MHz 主频与 hello 循环输出

串口每秒打印一行,绿灯同步闪烁——MCU 时钟、串口、GPIO 全部正常,数据链路全通。

四、CoreMark 移植

4.1 添加源码

CoreMark 代码仓:https://github.com/eembc/coremark

把核心的 7 个文件复制到 Core/Src/:

core_list_join.c  core_main.c  core_matrix.c  core_state.c
core_util.c  coremark.h  core_portme.c  core_portme.h

并在 CMakeLists.txt 把它们加入构建(顺带加上浮点 printf 支持):

target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user sources here
    Core/Src/core_list_join.c
    Core/Src/core_main.c
    Core/Src/core_matrix.c
    Core/Src/core_state.c
    Core/Src/core_util.c
    Core/Src/core_portme.c
)

# 使 newlib-nano 的 printf 支持浮点数输出(CoreMark 结果为浮点数)
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
    -u _printf_float
)

4.2 修改 core_portme.c

替换计时实现为 HAL 的 Tick(1ms 分辨率):

#include "stm32h5xx_hal.h"
#define CORETIMETYPE               uint32_t
#define GETMYTIME(_t)              (*_t = HAL_GetTick())
#define EE_TICKS_PER_SEC           (1000)

4.3 修改 core_portme.h

#define ITERATIONS 12000    /* 保证运行至少 10 秒(规则要求) */
#define FLAGS_STR "-Ofast"  /* 与实际编译选项一致,输出时原样打印 */
#define MAIN_HAS_NOARGC 1
#define MAIN_HAS_NORETURN 1
#define MEM_METHOD MEM_STATIC   /* 关键:默认的 MEM_STACK 会爆栈 */

这里有个隐蔽的坑:core_portme.h 默认的 MEM_METHOD 是 MEM_STACK——CoreMark 会在栈上分配数据结构。而 CubeMX 默认工程栈只有 0x400(1KB),直接爆栈 HardFault、串口静默卡死。改成 MEM_STATIC(静态数组,RAM 1.5MB 空间充裕)即可。如果坚持用 MEM_STACK,记得把 ioc 里的 StackSize 调大到 0x1000 以上。

4.4 修改 main.c

core_main.c 里自带 main(),与 CubeMX 生成的 main() 冲突,参考官方移植说明把它的 main 改名为 core_main(),然后在我们的 main 里调用:

/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "coremark.h"
/* USER CODE END Includes */
...
  printf("[NUCLEO-H5E5ZJ] CoreMark build (USART3 VCP 115200-8N1)\r\n");
  core_main();

五、性能优化

5.1 首跑成绩

Release 默认 -Os,首跑成绩:

CoreMark 1.0 : 537.778973 / GCC14.3.1 20250623 -Os / STACK

(输出里的 STACK 是 core_portme.h 里 MEM_LOCATION 的显示标签,改用 MEM_STATIC 后我把它同步改成了 STATIC。)

对照官方 1023 分差距明显,开始优化。

5.2 编译选项

Release 的 -Os 对性能敏感代码太保守。在 CMakeLists.txt 覆盖:

# CoreMark 跑分优化:-Ofast 覆盖 Release 默认的 -Os
target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
    -Ofast
)

同时同步 FLAGS_STR "-Ofast"。成绩:700.89(+30.3%)。

5.3 cache 实验

STM32H5 的 cache 是独立的 IP(不像 M7 集成在核内):

  • ICACHE(16KB 指令缓存):CubeMX 默认生成 1-way 直接映射,改为 2 路组相联(Core/Src/icache.c 中 ICACHE_1WAY → ICACHE_2WAYS);
  • DCACHE(8KB 数据缓存):CubeMX 未生成,在 main.c 的 USER CODE 区直接操作寄存器使能(注意先开外设时钟!):
/* USER CODE BEGIN 2 */
/* 启用 DCACHE(H5 系列 DCACHE 为独立 IP,CubeMX 本工程未生成其初始化,这里直接操作寄存器) */
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DCACHE1EN);        /* 打开 DCACHE 外设时钟 */
(void)READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DCACHE1EN); /* 时钟使能后的读回延迟 */
DCACHE1->CR |= DCACHE_CR_CACHEINV;                   /* 失效整个缓存 */
DCACHE1->CR |= DCACHE_CR_EN;                         /* 使能 DCACHE */
while (DCACHE1->SR & DCACHE_SR_BUSYF) { /* 等待失效完成 */ }

/* 开启 Flash 预取,降低 250MHz 下取指等待造成的流水停顿 */
SET_BIT(FLASH->ACR, FLASH_ACR_PRFTEN);

第一次加 DCACHE 时忘了开 RCC_AHB1ENR_DCACHE1EN 时钟,寄存器写不进去直接 HardFault,串口无输出——用 CubeProgrammer 读回寄存器才定位到。

实测结果:以上三项加起来对分数几乎没有影响(700.89 纹丝不动)。 说明对 CoreMark 这种紧凑循环,ICACHE 命中率已经很高,数据集(666 字节)也远小于 DCACHE,瓶颈不在取指/数据缓存上。

5.4 负优化实验

实验一:CoreMark 代码搬进 RAM 执行。 通过链接脚本的文件名匹配把 core_*.c 的代码段重定向到 RAM,main() 里先 memcpy 再跑分:

.ramfunc :
{
  __ramfunc_start__ = .;
  *core_list_join.c.obj*(.text .text*)
  *core_main.c.obj*(.text .text*)
  ...
} > RAM AT> FLASH
__ramfunc_load__ = LOADADDR(.ramfunc);
__ramfunc_size__ = SIZEOF(.ramfunc);

结果 675.98 分,不升反降(-3.6%)。结论:这颗芯片的 Flash 读取加速器(ART)+ ICACHE 供指已经没有等待,RAM 执行省下的取指时间抵不过复制与访问路径的差异。这个结论反过来说明:在 H5E5 上不值得为跑分做"代码搬运 RAM"这类高成本改造。

实验二:LTO 链接时优化。 -flto 全量重编:698.45 分,与 700.89 在噪声范围内,无收益。

5.5 最终成绩

配置 分数 CoreMark/MHz
-Os + ICACHE 1-way 537.78 2.15
-Ofast + ICACHE 2-way + DCACHE + 预取(最终版) 700.89 2.80
上述 + RAM 执行 675.98 2.70
上述 + LTO 698.45 2.79

最终串口输出:

CoreMark 最终成绩 700.893639

2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 17121
Total time (secs): 17.121000
Iterations/Sec   : 700.893639
Iterations       : 12000
Compiler version : GCC14.3.1 20250623
Compiler flags   : -Ofast
Memory location  : STATIC
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0xd340
Correct operation validated. See README.md for run and reporting rules.
CoreMark 1.0 : 700.893639 / GCC14.3.1 20250623 -Ofast / STATIC

CRC 全部校验通过,结果有效。

5.6 换编译器

CubeMX 生成的 CMake 工程里其实自带了两套工具链文件:gcc-arm-none-eabi.cmake 和 starm-clang.cmake(ST Arm Clang,基于 LLVM)。前面所有成绩都是 GCC 的,这一节用 st-arm-clang 构建同一工程做对比。

它随 STM32CubeCLT 1.22 安装(见 1.4),版本为 ST Arm Clang 21.1.1(LLVM 21),默认搭配 picolibc C 库(工具链文件里 STARM_TOOLCHAIN_CONFIG 可选 HYBRID/NEWLIB/PICOLIBC 三种配置,默认 PICOLIBC)。CMake 预设里没有 clang 预设,手动指定工具链文件配置一个独立构建目录(不影响 GCC 的 build/Release):

cmake -S . -B build/clang -G Ninja -DCMAKE_BUILD_TYPE=Release ^
      -DCMAKE_TOOLCHAIN_FILE=cmake/starm-clang.cmake
cmake --build build/clang

构建一遍通过,产物 33544 B,比 GCC 的 40984 B 小了约 18%。

代码修改(三处,均做成 clang/GCC 自适应,GCC 构建行为不变):

  1. main.c 主循环前打印编译器名称和版本:
/* USER CODE BEGIN WHILE */
printf("\r\n[NUCLEO-H5E5ZJ] System Clock: %lu Hz\r\n", SystemCoreClock);
#if defined(__clang__)
printf("[NUCLEO-H5E5ZJ] Compiler: st-arm-clang %s\r\n", __clang_version__);
#elif defined(__GNUC__)
printf("[NUCLEO-H5E5ZJ] Compiler: arm-none-eabi-gcc %s\r\n", __VERSION__);
#endif
  1. core_portme.h 里 CoreMark 自报的编译器版本/编译选项原来硬编码了 "GCC" 前缀(clang 也会定义 __GNUC__,会输出错误的编译器版本串),按编译器分流:
#ifndef COMPILER_VERSION
#if defined(__clang__)
#define COMPILER_VERSION "st-arm-clang "__clang_version__
#elif defined(__GNUC__)
#define COMPILER_VERSION "GCC"__VERSION__
#else
#define COMPILER_VERSION "Please put compiler version here (e.g. gcc 4.1)"
#endif
#endif

#ifndef COMPILER_FLAGS
#if defined(__clang__)
#define COMPILER_FLAGS "-O3 -ffast-math" /* st-arm-clang 21 已移除 -Ofast */
#else
#define COMPILER_FLAGS \
    FLAGS_STR
#endif
#endif
  1. CMakeLists.txt 按编译器分流浮点 printf 与优化选项:
# newlib-nano(GCC)的 printf 浮点支持需拉入 _printf_float 符号;
# st-arm-clang 默认用 picolibc,浮点 printf 内建,无该符号
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
    target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
        -u _printf_float
    )
endif()

# CoreMark 跑分优化:覆盖各工具链 Release 默认优化(GCC -Os / clang -Oz)
# clang 21 已移除 -Ofast,等价改用 -O3 -ffast-math
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
    target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
        -Ofast
    )
else()
    target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
        -O3 -ffast-math
    )
endif()

三个适配点的原因:

  • -u _printf_float 是 newlib-nano 的浮点 printf 开关(拉入 _printf_float 符号);picolibc 的浮点 printf 是内建的,库中根本没有这个符号,不加分流直接链接报 undefined reference;
  • -Ofast 在 clang 21 中已被移除,改用等价的 -O3 -ffast-math;CoreMark 的结果校验是整数 CRC,浮点只是计时换算,fast-math 不影响结果有效性(本次 CRC 全部通过);
  • st-arm-clang 的 Release 默认优化是 -Oz(clang 的工具链文件),上面的 -O3 -ffast-math 追加在其后覆盖之,与 GCC 侧 -Ofast 覆盖 -Os 同理。

串口输出(st-arm-clang 版):

[NUCLEO-H5E5ZJ] System Clock: 250000000 Hz
[NUCLEO-H5E5ZJ] Compiler: st-arm-clang 21.1.1
[NUCLEO-H5E5ZJ] CoreMark starting...
2K performance run parameters for coremark.
CoreMark Size    : 666
Total ticks      : 16959
Total time (secs): 16.959000
Iterations/Sec   : 707.588891
Iterations       : 12000
Compiler version : st-arm-clang 21.1.1
Compiler flags   : -O3 -ffast-math
Memory location  : STATIC
seedcrc          : 0xe9f5
[0]crclist       : 0xe714
[0]crcmatrix     : 0x1fd7
[0]crcstate      : 0x8e3a
[0]crcfinal      : 0xd340
Correct operation validated. See README.md for run and reporting rules.
CoreMark 1.0 : 707.588891 / st-arm-clang 21.1.1  -O3 -ffast-math / STATIC

与 GCC 对比(同为 250MHz、MEM_STATIC、12000 次迭代,CRC 全部校验通过):

GCC 14.3.1 st-arm-clang 21.1.1
优化选项 -Ofast -O3 -ffast-math
C 库 newlib-nano picolibc
CoreMark 700.89 707.59
CoreMark/MHz 2.80 2.83
FLASH 占用 40984 B 33544 B(-18%)

结论:两者基本持平(+0.96%,在跑分波动范围内),但 st-arm-clang 的固件体积明显更小(一部分功劳属于 picolibc)。免费工具链里的红利就到这里了——离官方 IAR 的 1023 分仍差约 31%,这条鸿沟只能用 IAR 来填。

六、结果讨论

700.89 分(2.80 CoreMark/MHz)是什么水平?

  • 对照官方数据手册标注的 1023 分(4.092/MHz):约为官方的 68.5%。需要注意官方成绩通常用 IAR 商用编译器测得,GCC 与 IAR 在 CoreMark 上普遍有 20%~40% 的差距,这一点在我上一篇 STM32H7S78-DK 的 CoreMark 测评里也一样;
  • 对照 250MHz Cortex-M33 + GCC 的公开跑分:2.8~3.0 CoreMark/MHz 属于正常发挥水平;
  • 优化过程中确认了一个重要事实:H5E5 的 Flash 访问加速(ART/预取)+ ICACHE 已经把取指等待压到零,否则 RAM 执行实验不可能不涨反跌。芯片的存储子系统设计是称职的。

如果追求极限分数,剩下的路只有换 IAR 了——免费的 ArmClang 这条路本文已经替你踩过:CubeMX 生成的工程里预留了 starm-clang.cmake(ST Arm Clang 工具链文件),实测 707.59 分,与 GCC 基本持平,适配过程与对比数据见 5.6。

本文完成了 NUCLEO-H5E5ZJ 的 CoreMark 从零移植到跑分优化的全过程,要点回顾:

  1. 资料先行:DS 确认 250MHz 上限与官方跑分基准;原理图确认 ST-LINK VCP 接的是 USART3(PD8/PD9),SWD 为 PA13/PA14;
  2. 板卡 HSE 默认值是陷阱:板上实际有 48MHz HSE 晶振(X2 @ PH0/PH1),但板卡 ioc 默认 HSE_VALUE=25MHz 与之不符,按默认值配 PLL 会实际跑到 480MHz 超频僵死;正确方案 HSE 48MHz → PLL(M=12,N=125,P=2)→ 250MHz(注意点见 2.1,配置见 2.2);读 RCC 寄存器排查时记得用 CubeProgrammer 的 HotPlug 模式(默认模式读到的是复位值);
  3. 首次构建"空 MEMORY 链接脚本 + 缺 startup"的根因是 H5 支持包不完整(固件包子模块没拉全),不是 CubeMX 的缺陷——先安装支持包、再创建项目即可一遍跑通(见 2.1);
  4. printf 重定向只需在 usart.c 的 USER CODE 区加 __io_putchar,浮点输出加 -u _printf_float;
  5. CoreMark 移植三件套:HAL_GetTick 计时、MEM_METHOD 改 STATIC(默认 STACK 会爆 1KB 栈)、main() 改名 core_main();
  6. 优化收益主要来自编译选项(-Os → -Ofast,+30%),cache 配置在 CoreMark 这种负载下已不是瓶颈,RAM 执行与 LTO 均无收益;换 st-arm-clang 21.1.1(LLVM + picolibc)为 707.59,与 GCC 基本持平但固件小 18%(见 5.6);
  7. 最终 700.89 分(2.80 CoreMark/MHz),为官方 IAR 成绩的 68.5%,符合 GCC 的正常水平。

七、参考资料

本文完整工程代码(ioc + CubeMX 生成的 CMake 工程 + CoreMark 移植):

https://gitcode.com/xusiwei1236/STM32H5E5-CoreMark

仓库以 git 管理,本文各阶段——初始工程、时钟方案修正(HSI→HSE)、CoreMark 移植、-Ofast 优化、st-arm-clang 支持——的完整变更历史都在仓库提交记录中,可逐一回溯。

参考链接:

  1. CoreMark 官方页面:https://www.eembc.org/coremark/
  2. CoreMark 源代码仓:https://github.com/eembc/coremark
  3. STM32CubeH5 固件包(GitHub):https://github.com/STMicroelectronics/STM32CubeH5
  4. STM32H5E5ZJ 数据手册(DS)
  5. RM0517 参考手册(PLL 约束:输入 216MHz,宽范围 VCO 128560MHz)
  6. NUCLEO-H5E5ZJ 原理图(ST-LINK VCP 与 USART3 连接关系)
赞 收藏 评论0 发布时间:2026-11-1 01:11

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版