基于 Keras 构建模型并部署到 STM32F4 的完整指南
概述
将基于 Keras 构建的模型部署到 STM32F4 微控制器上,属于典型的 TinyML(微型机器学习)应用。由于 STM32F4 的硬件资源(如 Flash 和 RAM)相对有限,无法直接运行完整的深度学习模型,必须经过模型转换、量化压缩以及 C 代码生成等一系列步骤。
本文档涵盖以下三个阶段:
- 阶段一:PC 端使用 Keras 构建与训练模型(Python + 正弦函数模拟数据)
- 阶段二:模型压缩为 INT8 并转换为 C 代码
- 阶段三:不使用 STM32CubeMX,手工搭建 STM32F4 工程并部署
下面是整个部署流程的概览图:
阶段一:PC 端生成数据与 Keras 模型训练
1.1 环境准备
在 Python 环境中安装以下依赖:
- numpy
- tensorflow(包含 Keras)
1.2 生成带噪声的正弦数据
使用正弦函数加高斯噪声模拟真实传感器数据:
importnumpyasnp np.random.seed(42)X=np.linspace(0,2*np.pi,1000)y=np.sin(X)+np.random.normal(0,0.05,X.shape)# 加入高斯噪声1.3 构建轻量级模型
为了适应 MCU 的低资源特性,精简隐藏层神经元数量,选择轻量级架构:
fromtensorflowimportkerasfromtensorflow.keras.modelsimportSequentialfromtensorflow.keras.layersimportDense model=Sequential([Dense(16,activation='relu',input_shape=(1,)),# 隐藏层16个节点Dense(1)# 输出层])model.compile(optimizer='adam',loss='mse')model.fit(X,y,epochs=100,batch_size=32,verbose=1)1.4 模型保存
训练完成后,将模型保存为 Keras 原生格式:
model.save('sine_model.h5')阶段一的完整流程如下:
阶段二:模型 INT8 量化与 C 代码转换
2.1 量化原理
STM32F4 的 FPU 处理浮点较慢,且 Flash/RAM 有限,必须将模型量化为 INT8 并转为 C 代码。
INT8 量化:将模型权重和激活值从 32 位浮点数(FP32)转换为 8 位整数(INT8)。这通常可使模型体积减小 75% 以上,并大幅降低内存占用和功耗。
量化感知训练:在训练或转换过程中模拟量化操作,以尽量保持模型在低精度下的准确率。
2.2 使用 NNoM 进行量化与代码生成
推荐使用轻量级开源框架NNoM,它专为 MCU 设计,支持 Keras 模型一键转 C 代码。
安装 NNoM:
pipinstallnnom量化与导出代码:
importnnomimporttensorflowastf# 加载模型model=tf.keras.models.load_model('sine_model.h5')# 使用 NNoM 进行 INT8 量化并导出 C 代码nnom_model=nnom.NNoM(model)nnom_model.build(representative_data=X[:100])# 传入部分真实数据用于校准量化参数nnom_model.save('nnom_sine_model')# 生成 weights.h, net.h, net.c 等文件说明:生成的
weights.h包含了量化后的权重数组,net.c包含了推理逻辑。
阶段二的量化与代码生成流程如下:
阶段三:STM32F4 端手工搭建工程与部署
不使用 STM32CubeMX,采用“古法”纯手工搭建工程,可以让你完全掌控代码的每一个细节。以 STM32F407 为例。
3.1 规划工程目录结构
在电脑上新建一个无中文、无空格的文件夹,创建以下目录:
STM32F4_Sine_AI/ ├── 01_MDK-ARM/ # Keil工程文件(.uvprojx) ├── 02_User/ # main.c 等用户业务代码 ├── 03_CMSIS/ # Cortex-M4内核文件 (core_cm4.h 等) ├── 04_Device/ # STM32F4专属文件 (启动文件, system_stm32f4xx.c) ├── 05_NNoM_Lib/ # 放入 NNoM 生成的 net.c, net.h, weights.h 及 NNoM 核心源码 └── 06_HAL_Driver/ # (可选) 如果不用寄存器,可放入标准外设库/HAL库3.2 提取底层核心文件
从 ST 官方的STM32CubeF4固件包中提取以下文件到对应目录:
| 文件 | 用途 | 存放目录 |
|---|---|---|
startup_stm32f407xx.s | 启动文件 | 04_Device |
system_stm32f4xx.c和.h | 系统时钟文件 | 04_Device |
core_cm4.h、cmsis_compiler.h等 | CMSIS 核心 | 03_CMSIS |
stm32f4xx.h,stm32f407xx.h | 设备头文件 | 04_Device |
3.3 Keil 工程配置(魔术棒设置)
Target 选项卡:
- 选择芯片型号(如 STM32F407ZGT6)
- 勾选
Use MicroLIB(NNoM 依赖标准 C 库的内存分配)
C/C++ 选项卡:
- Define 中添加宏定义:
STM32F407xx, USE_STDPERIPH_DRIVER(若使用了标准库) - Include Paths 添加所有头文件所在的路径
- 优化等级选择
-O2或-O3,并勾选C99 Mode
添加文件到工程:
在 Keil 左侧 Project 树中建立对应的 Group,将上述提取的.c文件、启动文件以及 NNoM 生成的net.c全部添加进去。
3.4 编写业务逻辑(main.c)
在main.c中初始化系统时钟,并调用 NNoM 的推理接口:
#include"stm32f4xx.h"#include"net.h"// NNoM 生成的头文件#include"weights.h"// NNoM 生成的权重头文件#include<stdio.h>intmain(void){// 1. 初始化系统时钟 (可调用 SystemInit() 或手动配置 RCC 寄存器)SystemInit();// 2. 初始化 NNoM 模型nnom_model_t*model=nnom_model_create();// 3. 准备输入数据 (例如输入 PI/2,期望输出接近 1.0)floatinput_val=1.57f;int8_tq_input;// 注意:需要根据 NNoM 生成的量化参数将 float 转为 int8q_input=(int8_t)(input_val/model->in->tensor->q_scale)+model->in->tensor->q_zero_point;// 4. 执行推理memcpy(model->in->tensor->p_data,&q_input,sizeof(int8_t));nnom_predict(model);// 5. 获取并反量化输出结果int8_tq_output=((int8_t*)model->out->tensor->p_data)[0];floatresult=(q_output-model->out->tensor->q_zero_point)*model->out->tensor->q_scale;// result 即为模型预测的 sin(PI/2) 的值while(1){// 可以在这里通过串口打印 result,或控制 LED}}阶段三的工程搭建与部署流程如下:
核心注意事项
4.1 内存分配
神经网络推理需要大量临时内存,务必在启动文件(.s)中将 Stack 和 Heap 调大(例如 Stack0x1000,Heap0x2000),否则极易发生 HardFault。
4.2 量化对齐
NNoM 在导出时会生成量化参数(Scale 和 Zero-point),在 C 端喂入数据和读取数据时,必须严格按照这些参数进行浮点与定点的转换。
总结与建议
对于 STM32F4,建议采用Keras 训练 + INT8 量化 + NNoM 生成代码的工作流。这条路径不仅避开了复杂的底层算子移植,还能充分利用开源框架的轻量化优势,是落地稳定且开发效率较高的选择。
扩展方向:可根据具体应用场景(如语音唤醒、手势识别、传感器分类等)设计更合适的轻量级模型结构,并进一步优化量化策略与推理性能。
整体工作流总结如下: