3. 静态库集成与内存布局优化
任务目标
在STM32F407裸机工程中,将CMSIS-NN编译为静态库(.a)并链接,通过调整链接脚本与变量属性,将神经网络的权重、输入/输出缓冲区分别放置到内部Flash、DTCM与AXI SRAM,最终通过map文件确认各段内存布局符合预期。
环境与依赖
- 硬件:STM32F407VET6(192KB SRAM:128KB AXI SRAM + 64KB DTCM,1MB Flash)
- 工具链:arm-none-eabi-gcc 10.3+,Makefile构建
- CMSIS-NN:从CMSIS 5.9.0提取
CMSIS/NN/Source与CMSIS/DSP/Include(arm_math.h依赖) - HAL驱动:仅需启动文件与系统时钟初始化(使用STM32CubeF4固件包中的system_stm32f4xx.c)
实现步骤
1. 构建静态库
创建 lib_nn.mk 编译CMSIS-NN全部.c文件为静态库 libcmsisnn.a:
# lib_nn.mk
NN_SRC = $(wildcard $(NN_DIR)/Source/*.c)
NN_OBJ = $(patsubst %.c,%.o,$(NN_SRC))
libcmsisnn.a: $(NN_OBJ)
arm-none-eabi-ar rcs $@ $^
%.o: %.c
arm-none-eabi-gcc -c $< -O2 -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 \
-I$(NN_DIR)/Include -I$(DSP_DIR)/Include -o $@
2. 内存布局定义
在链接脚本 stm32f407.ld 中显式划分三个SRAM区域:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M
DTCM (rw) : ORIGIN = 0x20000000, LENGTH = 64K
AXI (rw) : ORIGIN = 0x20010000, LENGTH = 128K
}
SECTIONS
{
.nn_weights : { *(.nn_weights) } > FLASH
.bss.dtcm : { *(.bss.dtcm) } > DTCM
.bss.axi : { *(.bss.axi) } > AXI
}
3. 关键代码与属性标注
以下代码定义了一个2层全连接网络(输入8→隐藏16→输出4),使用CMSIS-NN的 arm_fully_connected_s8 接口,并通过变量属性控制内存放置:
#include "arm_nnfunctions.h"
#include "arm_nnsupportfunctions.h"
// 权重与偏置放入Flash(.nn_weights段)
const int8_t w1[128] __attribute__((section(".nn_weights"), aligned(16))) = {
// 8x16 量化权重,取值范围[-3,3]
1,-2,0,3,-1,2,-3,1, 0,2,-1,3,-2,1,0,-1,
-3,1,2,0,-1,-2,3,1, 2,0,-3,1,-1,2,0,3,
1,0,-2,3,-1,2,1,-3, 0,1,3,-2,2,-1,0,1,
-2,3,0,-1,1,-3,2,0, 3,-1,1,2,0,-2,3,-1,
0,2,-3,1,-1,3,0,-2, 1,-1,2,0,3,-2,1,0,
-1,3,1,-2,0,2,-3,1, 2,0,-1,3,-2,1,0,-1,
3,-1,0,2,-2,1,-3,0, 1,2,-1,0,3,-2,1,-3,
0,-2,3,-1,1,0,2,-3, -1,3,0,2,-2,1,0,3
};
const int32_t b1[16] __attribute__((section(".nn_weights"))) = {0};
const int8_t w2[64] __attribute__((section(".nn_weights"), aligned(16))) = {
// 16x4 量化权重
1,-2,0,3, -1,2,-3,1, 0,2,-1,3, -2,1,0,-1,
-3,1,2,0, -1,-2,3,1, 2,0,-3,1, -1,2,0,3,
1,0,-2,3, -1,2,1,-3, 0,1,3,-2, 2,-1,0,1,
-2,3,0,-1, 1,-3,2,0, 3,-1,1,2, 0,-2,3,-1
};
const int32_t b2[4] __attribute__((section(".nn_weights"))) = {0};
// 输入/输出缓冲区:输入放DTCM,中间结果与输出放AXI SRAM
static int8_t input[8] __attribute__((section(".bss.dtcm"), aligned(16)));
static int8_t hidden[16] __attribute__((section(".bss.axi"), aligned(16)));
static int8_t output[4] __attribute__((section(".bss.axi"), aligned(16)));
// 量化参数(scale = 0.1, offset = 0)
static const int16_t in_scale = 10, out_scale = 10;
int nn_inference(const int8_t *raw_input, int8_t *result) {
// 复制输入到DTCM缓冲区
for (int i = 0; i < 8; i++) input[i] = raw_input[i];
// 第1层:8→16
arm_fully_connected_s8(input, w1, b1, 8, 16, 0, 0, in_scale, 0, out_scale, 0, hidden);
// 第2层:16→4
arm_fully_connected_s8(hidden, w2, b2, 16, 4, 0, 0, in_scale, 0, out_scale, 0, output);
for (int i = 0; i < 4; i++) result[i] = output[i];
return 0;
}
4. 主程序与验证入口
#include <stdio.h>
extern int nn_inference(const int8_t*, int8_t*);
// 通过map文件验证地址:&input应在0x20000000-0x2000FFFF,
// &hidden/output应在0x20010000-0x2002FFFF,w1/w2在0x08000000之后
int main(void) {
int8_t test_input[8] = {1, -2, 3, 0, -1, 2, -3, 1};
int8_t result[4];
nn_inference(test_input, result);
// 此处可设置断点查看result值,或通过串口打印
while(1);
}
编译与链接
# 编译主程序并链接静态库
arm-none-eabi-gcc -c main.c -O2 -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -I$(NN_DIR)/Include -o main.o
arm-none-eabi-gcc main.o -L. -lcmsisnn -T stm32f407.ld -nostartfiles -Wl,--gc-sections -o nn_test.elf
arm-none-eabi-objdump -h nn_test.elf | grep -E "nn_weights|bss.dtcm|bss.axi"
验证方法与通过条件
- 段地址检查:执行上述
objdump命令,确认输出中.nn_weights的地址在0x08000000之后(Flash区域),.bss.dtcm在0x20000000-0x2000FFFF,.bss.axi在0x20010000-0x2002FFFF。 - 符号地址验证:在调试器中查看
&input、&hidden、&w1的地址,应分别落在上述三个区域范围内。 - 功能正确性:在
main的while(1)处设置断点,单步执行后检查result数组值。由于权重全为0偏置(b1/b2全0),且输入非零,输出应全为0(量化后0*scale=0)。若输出非0,说明内存访问越界或链接配置错误。
预期输出说明:由于本案例权重和偏置均初始化为0,无论输入如何,两层全连接的计算结果恒为0。此设计用于隔离验证内存布局正确性——若输出非0,则说明缓冲区地址重叠或权重段未正确映射到Flash。
常见问题
- 链接报错“region DTCM overflowed”:检查是否有其他未标注的全局变量默认放入DTCM。在链接脚本中增加
*(.bss*)到AXI区域,或使用-fdata-sections配合--gc-sections丢弃未用变量。 - CMSIS-NN编译警告“arm_math.h not found”:确认DSP Include路径已通过
-I正确传入,且CMSIS版本匹配(5.9.0的NN依赖DSP 1.10.0+)。 - 性能不达标:检查
SCB->CPACR是否已使能FPU访问(在system_stm32f4xx.c中默认开启),并确认编译选项包含-mfloat-abi=hard -mfpu=fpv4-sp-d16。