STM32F103C8T6最小系统板在边缘AI中的应用
本文探讨了STM32F103C8T6最小系统板在资源受限场景下的边缘AI应用,例如实现简单的手势识别。开发者可在星图GPU平台上自动化部署深度学习项目训练环境镜像,高效完成轻量级AI模型的训练与优化,为后续在微控制器上的部署奠定基础。
STM32F103C8T6最小系统板在边缘AI中的应用
说到边缘AI,很多人第一反应是树莓派、Jetson Nano这些“明星”板卡。但如果你手头预算有限,或者项目对成本极其敏感,有没有更接地气的选择?今天咱们就来聊聊一个老朋友——STM32F103C8T6最小系统板,看看这个经典的“蓝色药丸”如何在边缘AI项目中发挥意想不到的作用。
你可能觉得,一个72MHz主频、64KB内存的MCU跑AI模型是天方夜谭。但实际情况是,经过精心优化的轻量级模型,完全可以在这样的资源约束下完成一些实用的推理任务。比如,识别几个简单的物体、检测异常声音、或者做个手势控制开关。这背后的核心思路不是追求大而全,而是“小而精”——用最小的资源解决特定的问题。
接下来,我会带你看看怎么把AI模型“塞进”这块小小的板子里,从硬件连接到模型部署,一步步实现一个能跑起来的边缘AI应用。
1. 为什么选择STM32F103C8T6做边缘AI?
首先得明确一点,用STM32F103做AI推理,和用树莓派、Jetson是完全不同的思路。它不是要跑YOLO或者ResNet这种大家伙,而是瞄准那些对实时性、功耗、成本有苛刻要求的场景。
想想看,一个智能家居的窗帘控制器,需要识别“拉开”和“关闭”两种手势;一个工业设备的状态监测器,需要判断电机声音是否正常;或者一个简单的垃圾分类桶,识别塑料瓶和易拉罐。这些任务都不需要识别上千个类别,往往几个、几十个类别就足够了。
STM32F103C8T6的核心优势就在这里:便宜、功耗低、实时性强。一块板子也就十几块钱,用电池能跑上好几天甚至几周,而且响应是毫秒级的。对于很多消费电子、物联网设备来说,这些特性比单纯的识别精度更重要。
当然,它的局限性也很明显:内存小、算力有限、没有硬件加速单元。这意味着你没法直接扔一个现成的TensorFlow模型上去,必须经过专门的优化和裁剪。但好消息是,现在有很多工具能帮你完成这个转换过程。
2. 硬件准备与连接
要玩转边缘AI,光有核心板还不够,你得给它配上“眼睛”和“耳朵”。下面是我常用的一套搭配,兼顾了成本和易用性。
2.1 核心硬件清单
| 组件 | 型号/说明 | 大致价格 | 作用 |
|---|---|---|---|
| 主控板 | STM32F103C8T6最小系统板(蓝色药丸) | 15-20元 | 运行AI模型的核心 |
| 摄像头模块 | OV7670带FIFO版本 | 30-40元 | 采集图像,注意要带FIFO,不然STM32处理不过来 |
| 或麦克风模块 | MAX9814驻极体麦克风放大模块 | 10-15元 | 采集音频,用于声音分类 |
| 调试器 | ST-Link V2(或兼容版) | 15-20元 | 下载程序、调试 |
| 杜邦线 | 母对母、公对母若干 | 5-10元 | 连接各模块 |
| 电源 | USB转5V模块或3.7V锂电池 | 5-15元 | 供电 |
如果你主要做图像识别,就选OV7670;如果做声音分类,就选MAX9814。两个都买也行,但一次只能接一个,因为STM32的资源和引脚都有限。
2.2 硬件连接示意图
以OV7670摄像头为例,连接其实不复杂,主要是电源、时钟、数据和几个控制信号。下面这个表格是我实际测试过的连接方式,比较稳定:
| OV7670引脚 | STM32F103C8T6引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
| SIOC | PB10 | I2C时钟,用于配置摄像头参数 |
| SIOD | PB11 | I2C数据,用于配置摄像头参数 |
| VSYNC | PA8 | 垂直同步信号,告诉MCU一帧图像开始 |
| HREF | PA9 | 行同步信号,告诉MCU一行数据开始 |
| PCLK | PA10 | 像素时钟,每个时钟传输一个像素 |
| D0-D7 | PA0-PA7 | 8位数据总线,传输像素数据 |
| XCLK | PA11 | 给摄像头的主时钟,通常用MCU的MCO输出 |
连接的时候注意,OV7670的D0-D7最好按顺序接到PA0-PA7,这样程序里处理起来方便。如果引脚冲突,也可以换到其他端口,但代码要相应调整。
电源一定要接3.3V,OV7670是3.3V器件,接5V会烧掉。STM32的IO口也是3.3V电平,所以直接连就行,不需要电平转换。
2.3 上电检查
全部接好后,先别急着写代码,做个简单的上电检查:
- 检查电源:用万用表量一下OV7670的VCC和GND之间是不是3.3V左右。
- 检查时钟:用示波器或者逻辑分析仪看看PA11(XCLK)有没有输出时钟信号(通常8MHz左右)。
- 检查I2C:同样用工具看看PB10和PB11上有没有I2C波形。
如果没工具,也有土办法:先下载一个简单的测试程序(比如让一个LED闪烁),确保STM32本身是好的。然后下载OV7670的初始化代码,如果摄像头旁边的LED亮了,说明至少电源和基本通信是通的。
3. 软件环境搭建
硬件搞定后,就该准备软件环境了。这里的关键是把AI模型转换成STM32能理解的格式,并且准备好相应的推理库。
3.1 开发工具链
我推荐用STM32CubeIDE,它是ST官方推出的免费IDE,基于Eclipse,集成了编译器、调试器和STM32CubeMX配置工具。用起来比Keil或者IAR更顺手,特别是对新手来说。
安装步骤很简单:
- 去ST官网下载STM32CubeIDE安装包。
- 一路Next安装完成。
- 第一次启动时,它会自动下载STM32F1的芯片支持包。
如果你习惯用命令行,也可以装Arm GNU Toolchain,然后用Makefile管理项目。但对于大多数开发者,STM32CubeIDE的图形化界面更友好。
3.2 AI模型转换工具链
这是最核心的部分。STM32跑AI模型,通常需要经过这么几个步骤:
- 训练模型:在PC上用TensorFlow或PyTorch训练一个轻量级模型。
- 转换为ONNX:把训练好的模型转成ONNX格式,这是一个中间表示。
- 转换为TFLite:如果你用TensorFlow,可以直接转成TFLite;如果是从PyTorch来的,可能需要先转ONNX再转TFLite。
- 用STM32Cube.AI转换:ST官方提供的工具,把TFLite或ONNX模型转换成C代码,包含权重和网络结构。
这里重点说说STM32Cube.AI。它有两个版本:一个是命令行工具,可以集成到你的CI/CD流程里;另一个是STM32CubeMX的插件,图形化操作更直观。
我通常这么用:
# 安装STM32Cube.AI命令行工具(需要Java环境)
java -jar stm32ai.jar generate -m model.tflite -o ./generated
# 或者用Python接口
from stm32ai import stm32ai
ai = stm32ai.STM32AI()
ai.generate(model='model.tflite', output_dir='./generated')
生成的代码里会有一个network.c和network.h,还有一堆权重数组。你只需要把这些文件加到你的工程里,然后调用几个简单的API就能做推理了。
3.3 必要的库文件
除了模型代码,你还需要一些底层驱动和中间件:
- CMSIS-NN:Arm提供的针对Cortex-M系列优化的神经网络内核库。STM32Cube.AI生成的代码会用到它。
- DSP库:STM32CubeF1包里自带的DSP库,里面有一些优化过的数学函数。
- 摄像头驱动:OV7670的初始化代码和图像采集代码。
这些库在STM32CubeIDE里都可以通过图形化界面添加,基本上点几下鼠标就行。
4. 从零实现一个图像分类应用
理论说了这么多,咱们来点实际的。下面我带你一步步实现一个简单的手势分类应用:识别“手掌”和“拳头”两种手势。
4.1 模型训练与转换
首先在PC上训练一个超轻量的模型。这里我用TensorFlow的Keras接口,建一个只有几层的CNN:
import tensorflow as tf
from tensorflow import keras
# 构建一个极简的CNN
model = keras.Sequential([
keras.layers.Input(shape=(64, 64, 1)), # 输入64x64的灰度图
keras.layers.Conv2D(8, (3, 3), activation='relu'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Conv2D(16, (3, 3), activation='relu'),
keras.layers.MaxPooling2D((2, 2)),
keras.layers.Flatten(),
keras.layers.Dense(32, activation='relu'),
keras.layers.Dense(2, activation='softmax') # 两个类别:手掌和拳头
])
# 编译模型
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 这里假设你已经有数据集了,实际训练代码略
# model.fit(train_images, train_labels, epochs=10)
# 转换为TFLite格式
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
# 保存模型
with open('gesture_model.tflite', 'wb') as f:
f.write(tflite_model)
这个模型非常小,参数量大概就几千个,完全能在STM32F103上跑起来。训练的时候注意用灰度图,分辨率降到64x64,这样既能减少计算量,又能满足基本识别需求。
训练好后,用STM32Cube.AI转换:
stm32ai generate -m gesture_model.tflite -o ./stm32_code --name gesture_net
4.2 STM32工程搭建
打开STM32CubeIDE,新建一个STM32F103C8T6工程:
- 配置时钟:在CubeMX里把系统时钟调到72MHz(最高速度)。
- 配置GPIO:按照前面的连接表,把PA0-PA7、PA8-PA11、PB10、PB11都配置好。
- 配置I2C:PB10和PB11配置为I2C2,用于配置OV7670。
- 配置DMA(可选但推荐):用DMA来搬运摄像头数据,能大大减轻CPU负担。
- 添加中间件:在Software Packs里添加CMSIS-NN和DSP库。
生成代码后,把STM32Cube.AI生成的gesture_net.c和gesture_net.h复制到工程目录,然后在IDE里添加这些文件。
4.3 摄像头驱动与图像采集
OV7670的驱动稍微有点复杂,因为它有一堆寄存器要配置。好在网上有很多现成的代码可以参考。这里我给出一个简化的版本:
// ov7670.c 部分关键代码
#include "ov7670.h"
// I2C写寄存器
uint8_t OV7670_WriteReg(uint8_t reg, uint8_t data) {
uint8_t status = 0;
HAL_I2C_Mem_Write(&hi2c2, OV7670_ADDR, reg, 1, &data, 1, 100);
return status;
}
// 初始化OV7670
void OV7670_Init(void) {
// 复位摄像头
OV7670_WriteReg(0x12, 0x80);
HAL_Delay(100);
// 配置为QVGA灰度图模式
OV7670_WriteReg(0x12, 0x0C); // COM7:QVGA,YUV输出
OV7670_WriteReg(0x11, 0x80); // CLKRC:内部时钟
OV7670_WriteReg(0x0C, 0x00); // COM3:默认
OV7670_WriteReg(0x3E, 0x00); // COM14:默认
OV7670_WriteReg(0x40, 0xD0); // COM15:RGB565格式
OV7670_WriteReg(0x1C, 0x00); // COM9:默认
// ... 更多寄存器配置
}
// 采集一帧图像
void OV7670_CaptureFrame(uint8_t *buffer) {
// 等待VSYNC下降沿(一帧开始)
while(HAL_GPIO_ReadPin(VSYNC_GPIO_Port, VSYNC_Pin) == GPIO_PIN_SET);
// 采集320x240个像素
for(int row = 0; row < 240; row++) {
// 等待HREF变高(一行开始)
while(HAL_GPIO_ReadPin(HREF_GPIO_Port, HREF_Pin) == GPIO_PIN_RESET);
for(int col = 0; col < 320; col++) {
// 等待PCLK上升沿
while(HAL_GPIO_ReadPin(PCLK_GPIO_Port, PCLK_Pin) == GPIO_PIN_RESET);
// 读取数据总线
uint8_t high_byte = GPIOA->IDR & 0xFF;
while(HAL_GPIO_ReadPin(PCLK_GPIO_Port, PCLK_Pin) == GPIO_PIN_SET);
uint8_t low_byte = GPIOA->IDR & 0xFF;
// 转换为灰度值(简单取高字节作为亮度)
buffer[row * 320 + col] = high_byte;
}
}
}
这段代码是最基础的轮询方式,实际使用时最好用DMA+中断,不然CPU占用率会很高。但作为演示,轮询方式更容易理解。
4.4 推理代码集成
图像采集到后,需要先预处理,然后送给模型推理:
// main.c 中的关键部分
#include "gesture_net.h"
// 图像缓冲区
uint8_t camera_buffer[320 * 240]; // 原始图像
float input_buffer[64 * 64]; // 预处理后的输入
// 图像预处理:缩放+归一化
void preprocess_image(uint8_t *src, float *dst) {
for(int y = 0; y < 64; y++) {
for(int x = 0; x < 64; x++) {
// 从320x240中取对应的像素(简单平均)
int src_x = x * 320 / 64;
int src_y = y * 240 / 64;
uint8_t pixel = src[src_y * 320 + src_x];
// 归一化到[0, 1]
dst[y * 64 + x] = pixel / 255.0f;
}
}
}
int main(void) {
// 初始化硬件
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C2_Init();
// 初始化摄像头
OV7670_Init();
// 初始化AI模型
gesture_net_init();
while(1) {
// 采集图像
OV7670_CaptureFrame(camera_buffer);
// 预处理
preprocess_image(camera_buffer, input_buffer);
// 推理
float output[2];
gesture_net_run(input_buffer, output);
// 判断结果
if(output[0] > output[1]) {
// 手掌
printf("Detected: Palm (%.2f%%)\n", output[0] * 100);
} else {
// 拳头
printf("Detected: Fist (%.2f%%)\n", output[1] * 100);
}
HAL_Delay(1000); // 每秒识别一次
}
}
这里我做了最大程度的简化。实际应用中,预处理可能需要更精细的算法(比如双线性插值),推理结果也可能需要加个滑动平均滤波来稳定输出。
4.5 性能优化技巧
如果你的应用发现帧率太低或者准确度不够,可以试试下面这些优化方法:
- 降低分辨率:从64x64降到32x32,计算量直接降到1/4。
- 量化模型:在转换TFLite时使用全整数量化,这样STM32就不需要做浮点运算了。
- 使用CMSIS-NN的优化函数:STM32Cube.AI生成的代码默认会用,但你可以检查一下。
- 开启编译优化:在CubeIDE里把优化等级调到-O2或-O3。
- 合理使用内存:STM32F103只有64KB RAM,要精打细算。大的缓冲区尽量用全局数组,避免在函数内定义大数组。
我实测过,一个8层的小模型在STM32F103上跑一次推理大概需要50-100ms,对于很多实时性要求不高的应用来说足够了。
5. 更多应用场景与扩展
除了图像分类,STM32F103还能做不少其他有趣的AI应用。下面我列举几个实际项目中的例子:
5.1 声音异常检测
这个特别适合工业设备监测。比如一台电机,正常运行时有一种声音,出现故障时声音频率会变化。用MAX9814麦克风采集声音,提取MFCC特征,然后用一个小的神经网络做二分类(正常/异常)。
关键点在于特征提取要在STM32上实现,因为原始音频数据量太大。好在MFCC算法不算复杂,用C语言实现一个简化版完全可行。
5.2 简单的人体姿态检测
如果你用两个以上的热释电红外传感器(PIR),可以大致判断人的移动方向。比如在智能灯的应用中,判断人是走进房间还是走出房间。
这其实不算严格的AI,更像是一个规则系统。但你可以用一个小神经网络来融合多个传感器的数据,提高判断准确率。
5.3 手势控制开关
前面演示的手势识别可以进一步扩展。比如用三个手势:手掌(开灯)、拳头(关灯)、V字手势(调光)。配合一个继电器模块,就能做成一个手势控制的智能开关。
这种应用在厨房特别实用,手上沾了水或者面粉时,不用碰开关就能控制灯光。
5.4 结合其他传感器
STM32的ADC可以接很多模拟传感器,比如温湿度、光照强度、气体浓度等。你可以用AI模型来做传感器融合,实现更智能的判断。
举个例子:一个智能花盆,根据土壤湿度、环境温度和光照强度,判断是否需要浇水。用一个很小的神经网络就能学会这三者之间的关系,比写一堆if-else规则更灵活。
6. 总结
回过头来看,用STM32F103C8T6做边缘AI,核心思想不是“能做什么”,而是“在资源有限的情况下,如何巧妙地解决问题”。它可能永远也达不到Jetson Nano的识别精度,但在成本、功耗、实时性这些维度上,它有独特的优势。
实际用下来,最大的感受是“够用就好”。很多物联网应用并不需要识别一千种物体,往往只需要区分几种状态。这时候,一个经过精心裁剪的小模型,配上STM32这样的低成本MCU,反而是最合适的方案。
当然,这个过程里会遇到不少挑战:内存不够用、算力吃紧、模型精度下降。但每解决一个问题,你对边缘AI的理解就会深一层。这种在约束条件下寻找最优解的过程,本身就是一种很好的工程训练。
如果你手头正好有STM32F103的板子,不妨从最简单的手势识别开始试试。先让摄像头能出图,再让模型能跑起来,最后慢慢优化性能。这个过程可能会踩不少坑,但走通之后,你会对边缘AI有完全不同的认识。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)