Cortex-M85 + GCC 13.3 + CMSIS-DSP/NN 接入笔记
适用场景:Cortex-M85 内核(Alif Ensemble E1/E3/E5、瑞萨 RA8D1 等),
工具链固定使用
arm-none-eabi-gcc 13.3.Rel1,使用 CMSIS-DSP / CMSIS-NN 的Helium (MVE) 内核库,不使用 GCC 自动向量化。
一句话:13.3 + 手写 intrinsics / 调库 是 OK 组合,坑不在工具链版本。
0. TL;DR
| 项 | 值 |
|---|---|
| 工具链 | arm-none-eabi-gcc 13.3.Rel1(GCC 13.3.1 + binutils 2.41) |
| 编译器参数 | -mcpu=cortex-m85 -mfloat-abi=hard -mthumb(FPU 由 -mcpu 隐含,不要加 -mfpu=fpv5-d16) |
| DSP 库 | libarm_cortexM85lfd_mve_math.a(MVE-F + FP-Armv8 DP;带 _mve_ 后缀;默认统一用 lfd) |
| NN 库 | 厂方提供或 CMSIS-NN 8.0+ 的 M85 Helium 变体(命名不统一) |
| 必开宏 | ARM_MATH_CM85 + ARM_MATH_HELIUM |
| 必关宏 | ARM_MATH_AUTOVECTORIZE(别开!) |
| 决策性宏 | ARM_NN_TRUNCATE_INPUT(默认不定义,按模型训练配置决定;不是性能开关) |
| 栈对齐 | __initial_sp 必须是 8 的倍数(末位十六进制为 0 或 8);调用 Helium 函数前 SP 同样需 8-byte 对齐 |
| CMSIS-DSP 版本 | ≥ 6.0.0 (CMake 源码构建,Helium 变体带 _mve_ 后缀);5.9.0 是 prebuilt pack 老格式 |
| CMSIS-NN 版本 | ≥ 8.0.0(首版含 M85 Helium) |
1. 工具链安装(Windows)
1.1 下载
- 主入口:https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
- 选
Arm GNU Toolchain (arm-none-eabi)→13.3.Rel1 - Windows 二选一:
arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi.exe(安装版,带 gdb)arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi.zip(解压即用)
1.2 安装后验证
bash
arm-none-eabi-gcc --version
# 期望: arm-none-eabi-gcc (Arm GNU Toolchain 13.3.Rel1 ...) 13.3.1 ...
arm-none-eabi-gcc -mcpu=cortex-m85 -mfloat-abi=hard -mthumb \
-E -dM -x c /dev/null | grep -E "__ARM_ARCH|__ARM_FP|__ARM_FEATURE"
# 应包含:
# __ARM_ARCH_8_1M_MAIN__
# __ARM_FEATURE_MVE
# __ARM_FEATURE_FP_DP
不出现 __ARM_FEATURE_MVE = 编译选项不对。
1.3 路径建议
D:\tools\arm-gnu-toolchain\13.3\
D:\tools\cmake\
D:\tools\mingw64\ # 拿 make
D:\tools\openocd\ # 烧录 / 调试
四个 bin 目录都加进 PATH,路径里不要有中文或空格。
2. 编译选项(Makefile 模板)
makefile
TARGET = firmware
CPU = -mcpu=cortex-m85
# M85 自带 FPU(Armv8.1-M / FP-ARMv8),由 -mcpu 隐含声明
# ❌ 不要写 -mfpu=fpv5-d16 ------ 那是 Cortex-M7 的命名,跟 M85 冲突
FABI = -mfloat-abi=hard
THUMB = -mthumb
# 关键宏:开 Helium,关自动向量化
DEFS += -DARM_MATH_CM85
DEFS += -DARM_MATH_HELIUM
# ❌ 绝对不要 -DARM_MATH_AUTOVECTORIZE
# 这个宏会触发 GCC 对 CMSIS 标量代码自动向量化
# 13.3 auto-vec 偶发错码 / 漏向量化,要避开
# ❌ 绝对不要 -ffast-math(见下方 2.1 解释)
# 包括它隐式打开的 -ffp-contract=fast / -ffinite-math-only / -funsafe-math-optimizations
# 对 CMSIS-DSP / CMSIS-NN 这种依赖 IEEE 754 严格语义的库,会引发难复现的数值漂移
CFLAGS = $(CPU) $(FABI) $(THUMB) $(DEFS) -O2 -g3 -ffunction-sections -fdata-sections
LDFLAGS = $(CPU) $(FABI) $(THUMB) -Wl,--gc-sections -Wl,--print-memory-usage
# 链接库(按 Helium + 双精度 FP 选)
# 关键: Helium 变体带 _mve_ 后缀,没有这个后缀的是标量版
LIBS += -larm_cortexM85lfd_mve_math # CMSIS-DSP (Helium + DP)
LIBS += -larm_nn_M85 # CMSIS-NN,按原厂 pack 名称(命名不统一)
LIBS += -lm -lc -lnosys
cortex-m85在 GCC 13.3 里隐含armv8.1m.main+mve.fp+fp.dp,不需要手写
-march。如果手写,请保持一致。
2.1 -ffast-math 为什么在 M85 + Helium 项目里是雷
-ffast-math 不是一个 flag,它是一组 flag 的合集,会改写 GCC 的 FP 语义假设:
| 隐式开启 | 行为 | 对 CMSIS 的影响 |
|---|---|---|
-ffp-contract=fast |
自动把 a*b + c 折叠成 FMA |
CMSIS-DSP 部分函数依赖 mul / add 的中间舍入,折叠后行为变 |
-ffinite-math-only |
假设输入无 NaN/Inf,删除相关检查 | 滤波 / ML 边界态穿过去,结果突变 |
-funsafe-math-optimizations |
改 FP 运算顺序、提常量 | CMSIS-NN 多层累加误差逐层放大 |
-fno-math-errno |
假设 math.h 函数不写 errno |
CMSIS 用到 sqrt / pow 时行为可能不一致 |
-fno-signaling-nans |
关闭 sNaN 触发 FP exception | 调试期用 NaN 做"未初始化"哨兵的代码失效 |
经验法则 :CMSIS-DSP / CMSIS-NN 永远 -O2 不开 fast-math。如果你确认 自己的某段代码(不调 CMSIS)想榨 FMA / 改运算顺序,做成函数级 attribute:
c
/* 例:某个独立的非 DSP 工具函数,知道自己在干什么 */
__attribute__((optimize("ffast-math", "tree-loop-vectorize")))
static inline float my_fast_rms(const float *x, int n) {
float s = 0.f;
for (int i = 0; i < n; i++) s += x[i] * x[i];
return sqrtf(s / n);
}
或者 #pragma GCC optimize("ffast-math") 限定一个小编译单元,不要全局开。
3. CMSIS-DSP 接入
3.1 库选型(极容易选错)
Reminder :CMSIS-DSP 6.0+(CMake 源码构建)后,Helium 变体的库文件名带
_mve_中缀 。老版本(5.9 prebuilt pack)里 Helium 和标量混在同一个
.a里,靠宏选。你要构建的是源码版本,所以下面的命名规则以 6.0+ CMake 输出为准。
CMSIS-DSP 6.0+ CMake 默认产出(双 FPU 字符 = 字节序+FPU 类型,_mve_ 中缀 = Helium):
| 文件名 | 标量/Helium | FPU | M85 项目使用 |
|---|---|---|---|
libarm_cortexM85l_math.a |
标量 | 无 FPU | ❌ |
libarm_cortexM85lf_math.a |
标量 | 单精度 | ❌ |
libarm_cortexM85lfd_math.a |
标量 | 双精度 | ❌(是标量版,不是 Helium 版!) |
libarm_cortexM85l_mve_math.a |
MVE-I(整数 Helium) | 无 FPU | ⚠️(仅整数 DSP 路径可用,浮点 DSP 不可用) |
libarm_cortexM85lf_mve_math.a |
MVE-F | 单精度 | ⚠️(不推荐;见下方 lf vs lfd 建议) |
libarm_cortexM85lfd_mve_math.a |
MVE-F | 双精度 | ✅ 默认用这个 |
libarm_cortexM85b*_math.a |
大端(一般不用) | --- | ❌ |
链错最常见的现象:函数体里全是标量指令、vldrwq 一条都没有,profile 速度跟纯 C 一样。
FPU 命名澄清 :M85 是 Armv8.1-M 架构,FPU 是 Armv8.1-M Floating Point Extension(FP-Armv8) ------属于 Armv8.1-M 自己的 FP 扩展,不是 Armv7E-M 时代的 FPv5-D16(M4/M7 那个)。
GCC 在 M85 上不会定义
__ARM_FPV5__,而是__ARM_FEATURE_FP_DP。文档里之前写的"FPv5-D16"是历史命名残留,按 M85 实际硬件是错的。
lf vs lfd 选型建议 :默认统一用 lfd。
CMSIS-DSP 绝大多数函数是 float32,用 lfd 跟用 lf 性能一样
lfd ABI 完整,跟
-mfloat-abi=hard+-mcpu=cortex-m85默认 ABI 一致未来代码里若不小心调了
*_f64,有定义可用,不会出链接错 / fault仅在明确知道没 DP 硬件、且需要最小库尺寸时,才用 lf
lf 调 f64 的实际后果(不是一定 hardfault):链接时:
undefined reference to arm_xxx_f64(最常见)运行时:函数实现可能 trap、可能软模拟、可能直接走 UsageFault / HardFault,取决于具体 CMSIS 版本和实现
编译器:若 ABI 不一致(hard vs softfp),可能编译期就被拒
3.2 头文件包含
c
#include "arm_math.h" // 主头
// 或更细的拆分
#include "arm_math_types.h"
#include "arm_math_memory.h"
#include "arm_math_statistics.h"
3.3 第一次调用前的 sanity check
c
#include "arm_math.h"
void cmsis_dsp_selftest(void) {
// 1. 验证编译期 ABI 选对
assert(__ARM_FEATURE_MVE != 0); // Helium 编译进来了
assert(__ARM_FEATURE_FP_DP != 0); // DP FPU 编译进来了
assert(__ARM_ARCH_8_1M_MAIN__ != 0); // 架构对了
// 2. 跑 CMSIS 自带的 selftest(覆盖大部分基础函数)
// 注意: selftest 里有些用 *_f64,必须 lfd 库才能跑通
arm_status s = arm_mat_mult_f32_selftest();
if (s != ARM_MATH_SUCCESS) {
// 库没链对 / 宏没设对
}
}
3.4 从源码构建(推荐)
比 prebuilt pack 好的原因:版本可控、可选 flag、跟厂方 SDK 集成一致、CI 友好。
3.4.1 拉代码
bash
git clone --branch main https://github.com/ARM-software/CMSIS-DSP.git
cd CMSIS-DSP
# 6.0+ 用 CMake;5.x 还是 Makefile
3.4.2 CMake 构建命令
bash
mkdir build && cd build
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../cmsis-cm85-mve-dp.cmake \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=OFF \
-DDSP_CONFIG=Generic
# 工具链文件(自己写一个,或用 CMSIS 提供的)
工具链文件 cmsis-cm85-mve-dp.cmake:
cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
# 关键:M85 (Armv8.1-M) + FP-Armv8 DP + Helium
# 注意: -mcpu=cortex-m85 已隐含 MVE-I/MVE-F + FP-Armv8,不需要额外 -mfpu
set(CMAKE_C_FLAGS_INIT "-mcpu=cortex-m85 -mfloat-abi=hard -mthumb -O2")
set(CMAKE_ASM_FLAGS_INIT "-mcpu=cortex-m85 -mfloat-abi=hard -mthumb")
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> qcs <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "true")
3.4.3 编译 + 验证
bash
cmake --build . -j
# 产物路径(不同版本略有差异,6.0+ 一般在 build/lib/ 下)
ls build/lib/
# 期望: libarm_cortexM85lfd_mve_math.a ← 找这个
# 没找到?先看哪些 target 生成了
cmake --build . --target help | grep -i m85
3.4.4 链接到自己的工程
把 .a 文件路径加到工程 Makefile 的 LIBRARY_PATH,或直接绝对路径:
makefile
CMSIS_DSP_LIB = $(abspath third_party/CMSIS-DSP/build/lib/libarm_cortexM85lfd_mve_math.a)
LDFLAGS += -L$(dir $(CMSIS_DSP_LIB))
LIBS += -larm_cortexM85lfd_mve_math
4. CMSIS-NN 接入
4.1 库版本
- CMSIS-NN 8.0.0 起加入 M85 Helium 内核
- 低于 8.0.0 完全没有 M85 路径,编译器会报找不到
arm_convolve_Helium_*之类符号
4.2 头文件 + 宏
c
#include "arm_nnfunctions.h"
#include "arm_nn_types.h"
#include "arm_nn_math_types.h"
// 启用 Helium 路径(与 CMSIS-DSP 不同,CMSIS-NN 走更细的 NNLIB 宏)
// ARM_NN_TRUNCATE_INPUT:量化舍入策略开关(不是性能开关)
//
// 不定义(默认):round-to-nearest,TFLite Micro 默认行为
// → 与绝大多数 TFLite 导出模型匹配
//
// 定义时 :truncate toward zero(截断小数)
// → 仅当模型用 tf.quantization.fake_quant_with_min_max_vars
// 且 fake_quant 内部用 truncate 模式训练时才需要
//
// 错配后果:输出误差 1~3 LSB,分类模型置信度可能翻转
// 决策依据:看模型训练脚本 / TFLite converter 的 quantizer 配置
//
// 经验:不确定时**不定义**(与 TFLM 行为一致),不要默认开
// #define ARM_NN_TRUNCATE_INPUT // 默认注释,按模型训练配置决定
4.3 典型调用
c
// conv 2D,input/output/channed 布局跟普通 CMSIS-NN 一样
arm_cmsis_nn_status rv = arm_convolve_s8(
input_data, input_size,
filter_data, bias_data,
output_data, output_size);
4.4 选库注意
CMSIS-NN 库命名没完全统一,常见几种:
| 来源 | 库名 | 备注 |
|---|---|---|
| 官方 CMSIS-NN 源码(CMake build) | libarm_nn_M85_Helium.a 或 libarm_nn_mvei_*.a |
命名随版本变化 |
| 官方老 pack | libarm_nn_M85.a |
不一定带 Helium |
| 厂方 SDK(Alif / Renesas) | libalif_nn_M85.a / libr_nn.a 等 |
自定义 |
建议自己从源码构建 (跟 DSP 一样),避免命名混乱。仓库:github.com/ARM-software/CMSIS-NN,8.0+ 起支持 M85 Helium。
自己从 CMSIS-NN 源码 build,参考 [Build Instructions Reference](#Build Instructions Reference)。
5. 启动文件 / 链接脚本
5.1 栈对齐(🔴 必看)
M85 + Helium 严格要求 调用任何 Helium 函数前 SP 8-byte 对齐 。
__initial_sp 必须是 8 的倍数:即十六进制地址最后一位为 0 或 8(二进制末 3 位 = 000)。
ld
// .ld 里 __StackTop 必须 8-byte 对齐
_estack = 0x20020000; // 例:RAM 末地址,末位十六进制 = 0(8 字节对齐 ✓)
5.1.1 在汇编启动文件里强对齐(✅ 推荐做法)
栈对齐必须且只能 在汇编启动文件(startup_xxx.s)里完成,在跳转到 main 或 SystemInit 之前:
asm
/* startup_xxx.s */
/* 在调用 SystemInit / __libc_init_array 之前先把 SP 强对齐到 8 字节 */
Reset_Handler:
LDR R0, =__StackTop
MOV SP, R0
BIC SP, SP, #7 ; SP 强对齐到 8 字节
LDR R0, =SystemInit
BLX R0
LDR R0, =__libc_init_array
BLX R0
LDR R0, =main
BX R0
5.1.2 ❌ 不要在 C 函数里强对齐 SP
c
/* ❌ 这种写法会 HardFault,绝对不要用 */
__attribute__((constructor(0)))
static void align_sp(void) {
__asm volatile (
"mov r0, sp \n"
"bic r0, r0, #7 \n"
"mov sp, r0 \n"
::: "r0", "memory"
);
}
为什么是雷:
- 编译器在进入这个 C 函数时已经生成了 prologue(
push {r7, lr}),LR 已经被 push 到旧 SP - 函数返回时
pop {r7, pc}按新 SP 读,LR 位置错位 - 即使你把
__attribute__((naked))加上自己控制 prologue/epilogue 也不行------因为__libc_init_array(caller)自己的栈帧是按旧 SP 算的,你一调 SP,caller 的局部变量 / 帧指针全错位 - 真实现象:构造函数能跑完,但后续
__libc_init_array内部的循环 / caller 返回时会随机 HardFault
唯一 C 里的合法做法 :__attribute__((naked)) + 整个函数体就是手写汇编,并且只在 reset 之后第一个被调用的位置用------但本质上这就是在写启动文件,没有 C 的意义,直接用汇编。
5.2 链接脚本里的 .bss / .data 对齐
ld
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K
}
SECTIONS {
.bss :
{
. = ALIGN(8); /* 🔴 8 字节对齐 */
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(8);
__bss_end__ = .;
} >RAM
.data :
{
. = ALIGN(8); /* 🔴 8 字节对齐 */
__data_start__ = .;
*(.data*)
. = ALIGN(8);
__data_end__ = .;
} >RAM AT> FLASH
}
5.3 FPU 初始化(开 CPACR)
上电后 FPU 默认关,第一次用 Helium 前要开(用 CMSIS core 的标准写法):
c
#include "core_cm85.h" // 或厂家 SCB 头文件,里面定义了 SCB->CPACR
/* SystemInit() 里 */
SCB->CPACR |= ((3UL << (10U * 2U)) | (3UL << (11U * 2U))); /* set CP10 and CP11 Full Access */
__DSB();
__ISB();
要点:
- 用 CMSIS core 提供的
SCB->CPACR结构体访问,不要自己#define寄存器地址 - 用
3UL(unsigned long)而不是3U,位运算更安全 - 乘法用
(10U * 2U)显式括号,避免任何对优先级的疑虑 - 后面必须跟
__DSB(); __ISB();,等 CPACR 写入生效再继续执行
不打开 = 第一次用 FPU 就 hardfault(不是 Helium 自己的问题,是 FPU 的)。
6. 验证清单(build 完必跑)
6.1 编译期
bash
# 1. Helium 特性进来了
echo | arm-none-eabi-gcc -mcpu=cortex-m85 -mfloat-abi=hard -mthumb -E -dM -x c - | \
grep -E "__ARM_FEATURE_MVE|__ARM_FEATURE_FP_DP|__ARM_ARCH_8_1M_MAIN__"
# 2. 没开 auto-vec 宏
arm-none-eabi-gcc -dM -E main.c | grep -E "ARM_MATH_AUTOVECTORIZE"
# 期望: 没输出
# 3. ARM_MATH_CM85 / HELIUM 开了
arm-none-eabi-gcc -dM -E main.c | grep -E "ARM_MATH_CM85|ARM_MATH_HELIUM"
# 期望: 各一行 = 1
6.2 链接期
bash
# 1. 真的链上了 Helium DSP 库(不是回落标量)
arm-none-eabi-nm --print-armap build/firmware.elf | grep -E "arm_cortexM85lfd_mve"
# 期望: 一堆带 .o 的记录
# 2. NN 库同理
arm-none-eabi-nm --print-armap build/firmware.elf | grep -E "nn_M85|arm_convolve"
6.3 反汇编验证
bash
arm-none-eabi-objdump -d build/firmware.elf > firmware.disasm
# 看 DSP 函数体里有没有 MVE 指令
grep -E "vldrwq|vst1q|vmulq|vaddq" firmware.disasm | head -20
# 期望: 有大量匹配
# 看 NN conv 函数体
grep -E "vldrwq|vmlsdavax" firmware.disasm | head -20
# 期望: 有匹配(vmlsdavax 是 Helium 的 SDOT 指令)
6.4 运行时
bash
# 用 OpenOCD + arm-none-eabi-gdb 看 PC 是否落在 Helium 指令
(gdb) target remote :3333
(gdb) b arm_mat_mult_f32
(gdb) c
(gdb) si
(gdb) disassemble
# 期望: 看到 vldrwq / vst1q 等
7. 常见问题速查
| 现象 | 根因 | 排查 |
|---|---|---|
undefined reference to arm_mat_mult_f32 |
库没链或链错 | nm 看库内容;改用 lfd 变体 |
| 跳进 DSP 函数就 hardfault | 栈不对齐 / FPU 没开 / 库选错 | 看 5.1 / 5.3 / 3.1 |
| DSP 函数运行极慢,profile 看到全是标量指令 | 库没走 Helium 变体 | 重新选 lfd 库 |
ARM_MATH_AUTOVECTORIZE 被某头文件自动打开 |
CMSIS 5.9 之前有这种情况 | 在 Makefile 里 -UARM_MATH_AUTOVECTORIZE 显式 undef |
编译报 '__ARM_FEATURE_MVE' undeclared |
-mcpu=cortex-m85 没生效 |
检查编译参数顺序,避免被 -march= 覆盖 |
NN 函数 arm_convolve_Helium_s8 not found |
CMSIS-NN < 8.0.0 | 升级 pack |
link 时报 R_xxx out of range |
Helium 的 LD/ST 偏移量超 ±1024 | 函数里没用 register asm("qN") 约束,临时变量被打到栈外;加 -fno-strict-aliasing 或重构 |
__StackTop 不 8-byte 对齐 |
.ld 里的 RAM 末地址没对齐 |
把 LENGTH 改到 8 的倍数;或者启动时 bic sp, #7 强对齐 |
| 进 C 运行时随机 HardFault,SP 错位 | 用了 __attribute__((constructor)) + 内联汇编强改 SP |
删掉那段 C 代码;在 startup_xxx.s 里 Reset_Handler 入口处强对齐 |
| DSP / NN 输出偶发数值漂移 | 全局开了 -ffast-math,FMA 折叠 / 运算重排改了中间舍入 |
去掉 -ffast-math;真要 FMA 用函数级 __attribute__((optimize("ffp-contract=fast"))) |
8. 不要做的事
- ❌ 不要加
-DARM_MATH_AUTOVECTORIZE - ❌ 不要全局加
-ffast-math(CMSIS-DSP / NN 依赖 IEEE 754 严格语义;需要 FMA / 重排运算的,做成函数级__attribute__((optimize))) - ❌ 不要同时开
-march=armv8.1m.main又用-mcpu=cortex-m85(冲突,GCC 会取后者) - ❌ 不要给 M85 加
-mfpu=fpv5-d16(那是 M7 的 FPU 命名,会跟 M85 冲突) - ❌ 不要混用
-mfloat-abi=softfp和hard(CMSIS 库是 hard ABI 编译的) - ❌ 不要把
.bss/.data对齐降到 4 - ❌ 不要为了"省一点空间"用
arm_cortexM85lfd_math.a(这是标量 版,不带 Helium;你想要的 Helium 版是arm_cortexM85lfd_mve_math.a) - ❌ 不要在 Helium 函数里调用 printf / newlib syscall(会破坏 Q 寄存器现场)
- 改用 ITM / SWO / 单独的 log 任务
- ❌ 不要在 C 函数里(包括
constructor)用内联汇编强改 SP- 编译器的 prologue/epilogue 已经按旧 SP 布局好了,乱动 SP 会让 caller / callee 栈帧全错位
- 栈对齐必须且只能 在汇编启动文件
Reset_Handler入口处做
9. 参考
- Arm GNU Toolchain 13.3.Rel1 release notes: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
- CMSIS-DSP 5.9.0+ (M85 Helium): https://github.com/ARM-software/CMSIS-DSP
- CMSIS-NN 8.0.0+ (M85 Helium): https://github.com/ARM-software/CMSIS-NN
- Arm Cortex-M85 Processor Datasheet: https://developer.arm.com/Processors/Cortex-M85
- ACLE Q register usage: Arm C Language Extensions spec (chapter "M-profile Vector Extension")
维护 :这份笔记是 13.3 固定场景下的配置 baseline。
如果未来升 14.2 或更新 CMSIS 版本,要重新过一遍第 3、4、6 节。