引子
一个 YOLOv8n 茶叶包装识别模型,从 PyTorch 训练结束,到 STM32N6 裸机 NPU 上稳定跑起来——单帧 160 ms(取帧 15 / 预处理 20 / NPU 推理 105 / 后处理 20),端到端出结果 0.8 s(5 帧 3 票)。中间经历了 12 轮调试、4 个平台级 bug。
这篇是系列总览(Hub)。链路图、为什么这么选、四个坑在哪里、性能怎么拆——都在这里,单篇深挖见文 2–文 7。
一、全链路总图
[训练] Ultralytics / PyTorch 训练 YOLOv8n(256×256,5 类,2000 张自采图) ↓ 导出 ONNX(固定 1×3×256×256 NCHW,不接 NMS) [转换] ST Edge AI Core 命令行:INT8 量化(ONNX QDQ)→ Neural-ART 网络代码 / LL_ATON 运行时 ↓ 产物:network.c / network.h / 权重 blob(2.91 MB) [烧录] 权重写外部 octo-Flash(XSPI2 内存映射 0x70A00000) [集成] 手动集成 LL_ATON 到 CubeMX + HAL + CMake + FreeRTOS + LVGL 工程 ↓ .ioc 重配(RAMCFG / RIF)、链接脚本内存三段布局(FSBL / App / 权重) [部署] 摄像头帧 → 预处理(中心裁剪→256²→RGB888→NCHW)→ NPU 推理 → 后处理 top-1 ↓ 分级阈值 + 类别增益 + 5 帧 3 票 burst 投票 [上线] 看门狗 + fail-soft 降级,NPU 挂了不拖垮整机二、为什么选 ST Edge AI Core 而不是 X-CUBE-AI
详见文 5《抛弃 X-CUBE-AI》。一句话:X-CUBE-AI 是黑盒插件,内存布局、启动时序、工程结构都由它决定;用命令行版 ST Edge AI Core 自己拿回全部掌控权——代价是文档少、坑自己踩(四个坑里三个都是这么踩出来的)。
试过X-CUBE-AI,发现跑不通有很多bug,而且正点原子提供的相关例程也没走X-CUBE-AI。
三、内存三段布局(FSBL / App / 权重)
外部 Flash 上三段不重叠:
| 段 | 内容 | 地址(示意) |
|---|---|---|
| FSBL | 第一级引导 | 0x70000000 起 |
| App | 应用固件 | 链接脚本指定 |
| 权重 | 2.91 MB int8 权重 | 0x70A00000 起 |
FSBL 决定“跳去跑哪个固件”——这也是日后 OTA 双区切换的基础。
这里app按照我们的需求我设置了8mb空间。
四、四个平台级 bug 概览
| # | Bug | 主题 | 深挖文 |
|---|---|---|---|
| 1 | AXISRAM 默认断电 → 无异常卡死 | 电源 | 文 2 |
| 2 | WFE 低功耗门控冻死 NPU 时钟 | 时钟 | 文 3 |
| 3 | RISAF 防火墙拦截 NPU 读权重 | 安全 | 文 4 |
| 4 | CACHEAXI 空 weak 函数致缓存未使能 | 缓存一致性 | 文 5 |
实际调试顺序:AXISRAM → RISAF → 时钟门控(真·根因)→ CACHEAXI。前三个修完仍然卡,最后才在时钟上破案——“配置值正常 ≠ 实际在跑”是最贵的一课。
五、性能拆解:160 ms 花在哪
| 阶段 | 耗时 | 说明 |
|---|---|---|
| 取帧 | 15 ms | DCMIPP 帧同步等待 |
| 预处理 | 20 ms | 中心裁剪 + 缩放 + RGB565→888 + NCHW 重排(单循环整数运算) |
| NPU 推理 | 105 ms | LL_ATON_RT_Main,CPU 睡眠等待 |
| 后处理 | 20 ms | 1344 框 × 5 类 top-1 + 阈值/增益 |
| 合计 | 160 ms | 5 帧 × 160 = 0.8 s 端到端 |
瓶颈在 NPU 推理(占 66%)——不是算力不够,是权重从外部 Flash 读(XSPI2)+ 内存搬运的开销,NPU 峰值 600 GOPS 只是标称。
六、可复用清单
- 新平台 RAM 先确认时钟 + 供电 + 写读自检
- 多主设备 SoC:“CPU 能读 ≠ NPU 能读”(RISAF/CID)
- 睡眠之前,逐个确认谁会被低功耗门控
- HAL weak 空函数是“假初始化”高发区,写完读回验证
- 诊断读“反映实际状态”的寄存器,不读配置寄存器
文 1 完 · 下一文:《STM32N6 NPU“配置值正常≠实际在跑”:WFE 低功耗门控冻死 NPU 时钟排查》