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的调试器确实更顺手。

我的建议:学习阶段用ARM GCC就够了。等你真正做项目,再根据团队情况选Keil。别一开始就纠结这个。

2.2 安装ARM GCC工具链

ARM GCC的官方名字叫gcc-arm-none-eabi。为什么叫这个?arm-none-eabi意思是目标架构是ARM,没有操作系统(none),使用嵌入式ABI(eabi)。说白了就是给裸机用的。

安装步骤:

  1. 去ARM官网下载gcc-arm-none-eabi的最新版本。我建议选.tar.bz2压缩包,别用exe安装器——那个经常捆绑多余的东西。
  2. 解压到某个目录,比如C:\arm_gcc/opt/arm_gcc
  3. bin目录加到系统PATH环境变量里。
  4. 打开终端,输入arm-none-eabi-gcc --version,看到版本号就说明装好了。
注意:我曾经遇到过一个问题——装了新版GCC后,旧项目编译报错。原因是新版本对C标准的检查更严格了。所以,做项目时最好固定一个GCC版本,别随便升级。

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/SourceCMSIS-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库需要手动添加。

步骤:

  1. 新建一个工程,选择你的Cortex-M芯片型号。
  2. Manage Run-Time Environment里勾选CMSIS-NN
  3. 如果找不到,就手动把CMSIS-NN的源文件拖进工程。
避坑指南:Keil的ARMCC编译器对C99的支持有点奇怪。我曾经遇到一个bug——用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,配合openocdpyocd

我个人建议先用QEMU验证逻辑,没问题再烧板子。为什么?因为QEMU调试方便,出错了不会烧硬件。

常见错误:
  • 链接错误:找不到CMSIS-NN的函数。检查一下源文件有没有加全。
  • 硬错误(HardFault):多半是栈溢出或者指针越界。CMSIS-NN的矩阵乘法要求输入输出缓冲区对齐到4字节。
  • 结果全零:检查一下矩阵维度对不对。我刚开始写的时候,把行和列搞反了,结果算出来全是0。

2.8 小结

这一章我们干了三件事:装工具链、配环境、跑通了第一个矩阵乘法。你可能会觉得「就这?不就是个矩阵乘法吗?」嗯,别急。这个程序虽然简单,但它验证了整个开发流程——从编译到链接,再到运行。后面咱们要做的卷积、池化、全连接,都是在这个基础上搭积木。

下一章,咱们会深入CMSIS-NN的源码,看看它到底是怎么优化的。到时候你会发现,原来一个简单的乘法,背后有这么多门道。

课后练习:把矩阵改成8x8,用随机数初始化,然后对比一下CMSIS-NN和普通三重循环的性能差异。你会惊讶的。

公众号:蓝海资料掘金营,微信deep3321