2、开发环境搭建:安装ARM GCC工具链、CMSIS-NN库、配置VS Code或Keil MDK开发环境,编写第一个Cortex-M上的矩阵乘法程序
好,咱们直接进入正题。这一章说白了就是「把家伙事儿备齐」。我见过太多人,理论背得滚瓜烂熟,结果卡在环境搭建上,一卡就是半天。别急,跟着我的节奏来,半小时内你就能跑起第一个矩阵乘法程序。
2.1 工具链的选择:ARM GCC vs Keil MDK
做Cortex-M开发,编译器就两个主流选择:ARM GCC(免费开源)和Keil MDK(商业收费)。我个人习惯用ARM GCC,原因很简单——免费、跨平台、社区活跃。但如果你在公司里做产品,Keil MDK的调试器确实更顺手。
2.2 安装ARM GCC工具链
ARM GCC的官方名字叫gcc-arm-none-eabi。为什么叫这个?arm-none-eabi意思是目标架构是ARM,没有操作系统(none),使用嵌入式ABI(eabi)。说白了就是给裸机用的。
安装步骤:
- 去ARM官网下载
gcc-arm-none-eabi的最新版本。我建议选.tar.bz2压缩包,别用exe安装器——那个经常捆绑多余的东西。 - 解压到某个目录,比如
C:\arm_gcc或/opt/arm_gcc。 - 把
bin目录加到系统PATH环境变量里。 - 打开终端,输入
arm-none-eabi-gcc --version,看到版本号就说明装好了。
2.3 安装CMSIS-NN库
CMSIS-NN是ARM官方出的神经网络加速库。它用汇编和 intrinsics 实现了卷积、池化、全连接等操作,专门针对Cortex-M优化过。说白了,就是帮你把神经网络算得快一点。
获取方式:
- 从GitHub上克隆:
git clone https://github.com/ARM-software/CMSIS-NN.git - 或者直接下载ZIP包。我建议用git clone,方便以后更新。
下载后,你只需要把CMSIS-NN/Source和CMSIS-NN/Include两个目录加到你的工程里。嗯,这里要注意:CMSIS-NN依赖CMSIS-Core,所以你还得把CMSIS-Core的头文件也加进来。
project/
├── CMSIS/
│ ├── Core/ // CMSIS-Core 头文件
│ └── NN/ // CMSIS-NN 源码和头文件
├── src/
│ └── main.c
└── Makefile
2.4 配置VS Code开发环境
VS Code做嵌入式开发,需要装几个插件。我个人觉得这是目前最舒服的开发方式——轻量、免费、插件生态好。
必装插件:
- C/C++:微软官方,提供代码补全和语法高亮。
- Cortex-Debug:支持JLink和OpenOCD调试。
- Makefile Tools:方便编译和烧录。
配置.vscode/tasks.json,把编译命令写进去。我一般这样写:
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
然后按Ctrl+Shift+B就能编译了。是不是很简单?
2.5 配置Keil MDK(备选方案)
如果你非要用Keil,那也简单。Keil MDK自带ARMCC编译器,但CMSIS-NN库需要手动添加。
步骤:
- 新建一个工程,选择你的Cortex-M芯片型号。
- 在Manage Run-Time Environment里勾选CMSIS-NN。
- 如果找不到,就手动把CMSIS-NN的源文件拖进工程。
for(int i=0;...)这种写法,ARMCC会报错。解决办法是改成int i; for(i=0;...)。嗯,老编译器就这样。
2.6 编写第一个矩阵乘法程序
好了,环境搭好了,咱们写个程序试试。矩阵乘法是神经网络里最基础的操作,也是优化空间最大的地方。
代码示例:
#include <stdio.h>
#include "arm_nnfunctions.h"
#define ROW_A 4
#define COL_A 4
#define COL_B 4
int main() {
// 定义两个4x4矩阵
q7_t matA[ROW_A * COL_A] = {
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};
q7_t matB[COL_A * COL_B] = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
q7_t matC[ROW_A * COL_B] = {0};
// 调用CMSIS-NN的矩阵乘法函数
arm_nn_mat_mult_kernel_q7_q15(matA, matB, matC,
ROW_A, COL_A, COL_B, NULL);
// 打印结果
for(int i = 0; i < ROW_A; i++) {
for(int j = 0; j < COL_B; j++) {
printf("%d ", matC[i * COL_B + j]);
}
printf("\n");
}
return 0;
}
这个程序做了什么事?说白了就是让矩阵A乘以单位矩阵B,结果应该还是A本身。如果打印出来是1,2,3,4...那就说明环境没问题。
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 \
-I./CMSIS/Core/Include -I./CMSIS/NN/Include \
-o matrix_mult.elf main.c \
./CMSIS/NN/Source/arm_nn_mat_mult_kernel_q7_q15.c
这里我用了Cortex-M4的浮点单元(FPU)。如果你的芯片是M3或M0,去掉-mfloat-abi=hard和-mfpu=...就行。
2.7 验证与调试
编译成功后,你会得到一个.elf文件。怎么跑?两种方式:
- 用QEMU模拟器:
qemu-system-arm -machine lm3s6965evb -kernel matrix_mult.elf -nographic - 烧录到开发板:用JLink或ST-Link,配合
openocd或pyocd。
我个人建议先用QEMU验证逻辑,没问题再烧板子。为什么?因为QEMU调试方便,出错了不会烧硬件。
- 链接错误:找不到CMSIS-NN的函数。检查一下源文件有没有加全。
- 硬错误(HardFault):多半是栈溢出或者指针越界。CMSIS-NN的矩阵乘法要求输入输出缓冲区对齐到4字节。
- 结果全零:检查一下矩阵维度对不对。我刚开始写的时候,把行和列搞反了,结果算出来全是0。
2.8 小结
这一章我们干了三件事:装工具链、配环境、跑通了第一个矩阵乘法。你可能会觉得「就这?不就是个矩阵乘法吗?」嗯,别急。这个程序虽然简单,但它验证了整个开发流程——从编译到链接,再到运行。后面咱们要做的卷积、池化、全连接,都是在这个基础上搭积木。
下一章,咱们会深入CMSIS-NN的源码,看看它到底是怎么优化的。到时候你会发现,原来一个简单的乘法,背后有这么多门道。
公众号:蓝海资料掘金营,微信deep3321