安装与部署:把 CMSIS-NN 编进 Cortex-M 工程
安装与部署:把 CMSIS-NN 编进 Cortex-M 工程
CMSIS-NN 以源码形式分发,没有需要安装的二进制包。工程里的动作基本是四步:拉源码、加头文件路径、把 Source 下的实现加进编译、定义正确的能力宏。
- 取源码。可以从 Arm 官方的 CMSIS-NN 仓库拉取,也可以直接用厂商 SDK 里已经带好的副本(STM32Cube、MCUXpresso、Nordic 等 SDK 一般随包附带)。用 SDK 自带版本的好处是它与 SDK 其他组件已经对齐过。
git clone https://github.com/ARM-software/CMSIS-NN.git # 仓库结构大致为: # Include/ 公共头文件(arm_nnfunctions.h、arm_nn_types.h 等) # Source/ 按算子分类的 .c(ConvolutionFunctions、FullyConnectedFunctions 等) # Tests/ 单元测试 - 加头文件路径。至少要把
Include加进编译器的 include path,否则#include <arm_nnfunctions.h>直接找不到。 - 把 Source 下的实现加进来。可以整个
Source目录递归编译,也可以按需只编用到的算子目录(ConvolutionFunctions、FullyConnectedFunctions、ActivationFunctions、PoolingFunctions、SoftmaxFunctions、BasicMathFunctions等)。按需编译能减小固件体积,代价是漏文件时会在链接期报undefined reference。 - 定义目标能力宏。这是最容易漏、也最影响性能的一步。库内部靠这些宏选择内核实现,没定义就退回 C 参考实现。
# Cortex-M4 / M7(带 DSP 扩展与单精度 FPU) arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb \ -mfpu=fpv4-sp-d16 -mfloat-abi=hard \ -DARM_MATH_DSP -DARM_MATH_LOOPUNROLL \ -Os -ffunction-sections -fdata-sections \ -I path/to/CMSIS-NN/Include \ -c path/to/CMSIS-NN/Source/FullyConnectedFunctions/arm_fully_connected_s8.c # Cortex-M55(带 Helium / MVE) arm-none-eabi-gcc -mcpu=cortex-m55 -mthumb \ -DARM_MATH_MVEI -DARM_MATH_DSP \ -Os -I path/to/CMSIS-NN/Include \ -c path/to/CMSIS-NN/Source/FullyConnectedFunctions/arm_fully_connected_s8.c具体要开哪些宏、宏名在不同版本之间有没有调整,以官方文档与所用内核的编译指南为准。
- Keil MDK 与 IAR 的接法。Keil 里把
Include加进 C/C++ 选项卡的 Include Paths,在 Target 里选对 Device 与 FPU(Use FPU 选 Single Precision),Preprocessor Symbols 里补上能力宏,再把 Source 下的 .c 加进工程组。IAR 同理:Extra Include 路径、FPU 选对应的 VFP 版本、Defined symbols 里加宏。两条原则一致:能力宏要开、优化等级别用 -O0。
常见报错与排查:
| 现象 | 原因 | 处理 |
|---|---|---|
| undefined reference to arm_convolve_s8 | 实现文件没进编译 | 把 Source/ConvolutionFunctions 下的 .c 加进工程 |
| arm_nnfunctions.h: No such file or directory | include path 缺 CMSIS-NN/Include | 补 include path |
| 链接期 ABI 报错、浮点参数传错 | 库与工程其他目标文件的 -mfloat-abi 不一致 | 全工程统一 hard 或统一 softfp,不要混编 |
| 能跑但慢得离谱 | 缺 ARM_MATH_DSP 或 ARM_MATH_MVEI,或者库用 -O0 编译 | 补能力宏,优化等级至少 -O2 或 -Os |
| 结果精度明显变差 | 零点、乘子、移位抄错,或 per-channel 参数被当成 per-tensor 用 | 直接用转换器导出的参数,不要手算 |

