1. 环境搭建与第一个神经网络推理
任务目标
在STM32F407开发板上完成CMSIS-NN推理环境搭建,运行一个预训练的2-2-1全连接神经网络,对两个输入样本执行前向推理并验证输出正确性。本案例聚焦于环境验证与最小推理闭环,不涉及模型训练。
环境依赖
- 硬件:STM32F407 Discovery板(板载ST-Link)
- 软件:STM32CubeIDE 1.15+(或任意支持ARM GCC的IDE)
- CMSIS-NN库:从
ARM-software/CMSIS_5获取,路径CMSIS/NN与CMSIS/DSP、CMSIS/Core/Include - 模型权重:本案例使用硬编码权重,无需外部模型文件
实现原理
CMSIS-NN的arm_fully_connected_s8函数执行int8量化全连接层推理。本案例构建一个2输入→2隐藏→1输出的网络,分两次调用该函数(输入层→隐藏层,隐藏层→输出层),隐藏层使用ReLU激活,输出层直接输出logits。输入范围[-1,1]映射到int8范围[-128,127],权重预先量化并缩放。
关键完整代码
/* main.c 核心推理部分 */
#include "arm_nnfunctions.h"
#include <stdio.h>
// 网络结构: 2-2-1
// 隐藏层权重(2x2), int8量化, 缩放因子0.05
const int8_t w_hidden[4] = {40, -20, -30, 25}; // 实际浮点: [2,-1,-1.5,1.25]
const int32_t b_hidden[2] = {10, -5}; // 实际浮点: [0.5,-0.25]
// 输出层权重(2x1)
const int8_t w_output[2] = {30, -20}; // 实际浮点: [1.5,-1]
const int32_t b_output[1] = {5}; // 实际浮点: [0.25]
// 输入量化: 浮点x映射到 int8(round(x*100))
// 样本1: (0.5, -0.5) → (50, -50)
// 样本2: (-1.0, 0.8) → (-100, 80)
void run_inference(const int8_t *input, int8_t *result) {
int8_t hidden_out[2];
int8_t output_tmp[1];
// 输入层 → 隐藏层 (2→2)
arm_fully_connected_s8(input, w_hidden, b_hidden, 2, 2, 0, 0,
ARM_TILE(1), 1, 0, 0.05f, 0.0f,
hidden_out, NULL);
// ReLU激活
hidden_out[0] = hidden_out[0] > 0 ? hidden_out[0] : 0;
hidden_out[1] = hidden_out[1] > 0 ? hidden_out[1] : 0;
// 隐藏层 → 输出层 (2→1)
arm_fully_connected_s8(hidden_out, w_output, b_output, 2, 1, 0, 0,
ARM_TILE(1), 1, 0, 0.05f, 0.0f,
output_tmp, NULL);
*result = output_tmp[0];
}
int main(void) {
// 硬件初始化(略, 标准CubeMX生成)
const int8_t sample1[2] = {50, -50};
const int8_t sample2[2] = {-100, 80};
int8_t out1, out2;
run_inference(sample1, &out1);
run_inference(sample2, &out2);
// 浮点参考值(手算):
// sample1: 隐藏层 = [2*0.5+(-1)*(-0.5)+0.5, (-1.5)*0.5+1.25*(-0.5)-0.25]
// = [1.5+0.5+0.5, -0.75-0.625-0.25] = [2.5, -1.625] → ReLU → [2.5, 0]
// 输出 = 1.5*2.5 + (-1)*0 + 0.25 = 3.75+0.25 = 4.0 → 量化: 4.0/0.05 = 80
// sample2: 隐藏层 = [2*(-1)+(-1)*0.8+0.5, (-1.5)*(-1)+1.25*0.8-0.25]
// = [-2-0.8+0.5, 1.5+1.0-0.25] = [-2.3, 2.25] → ReLU → [0, 2.25]
// 输出 = 1.5*0 + (-1)*2.25 + 0.25 = -2.0 → 量化: -2.0/0.05 = -40
// 预期: out1=80, out2=-40 (允许±1量化误差)
printf("Sample1 output: %d (expected ~80)\n", out1);
printf("Sample2 output: %d (expected ~-40)\n", out2);
while(1);
}
构建与运行
- 在STM32CubeIDE中创建新工程,选择STM32F407VG芯片
- 将CMSIS-NN源码添加至工程:
CMSIS/NN/Source全部.c文件,CMSIS/DSP/Source中需要的arm_fully_connected_s8.c依赖的矩阵乘法文件 - 包含路径添加:
CMSIS/NN/Include、CMSIS/DSP/Include、CMSIS/Core/Include - 启用FPU(硬件浮点加速,非必需但推荐)
- 编译并烧录,使用串口或调试器观察printf输出
验证方法
通过串口(如USART2,115200-8-N-1)或IDE调试窗口查看输出。判定通过条件:out1在79~81范围内,out2在-41~-39范围内。若超出此范围,检查权重字节序(CMSIS-NN默认小端)和量化缩放因子是否与权重匹配。
常见问题
- 编译错误“undefined reference to arm_fully_connected_s8”:确认NN库源文件已添加且包含路径正确
- 输出恒为0:检查输入int8量化是否正确,或ReLU实现是否意外修改了权重缓冲区
- 精度偏差大于±2:检查
output_shift参数(本案例设为0)与权重实际缩放是否一致