去年年底我在搞一个离线关键词唤醒的小项目,目标很朴素:一块电池、一个麦克风、一个MCU,能稳定识别三到五个唤醒词,待机电流控制在微安级。一开始我也没多想,直接用手边一块通用开发板开搞。结果越做越不对劲——板子四周全是为别的场景准备的功能,我需要的传感器接口根本没引出,电源设计完全没考虑电池场景,整个板子功耗高得离谱。后来被逼到自己在画板软件里从零布局,才意识到一个很现实的问题:
通用开发板的目标用户是调试外设、跑RTOS应用、验证通信协议的人,而TinyML的目标是“最小系统里跑推理模型+长时间待机”。这两个方向根本不是一回事。
这篇文章就把我重新做一款TinyML专用板的全过程拆开讲:从需求分析、选型、原理图设计、PCB布局到模型部署,中间踩了不少坑,也总结出一些常规文档里不会写的细节,希望能给同样在折腾TinyML的人一个参考。
1. 现成开发板为什么“看着合适,用着别扭”
先别急着否定通用开发板,它在很多场景下确实是好东西。问题是TinyML场景有几个特殊需求,恰好是通用板最容易忽视的地方。
1.1 外设丰富不等于接口对路
通用板的标准配置是LED、按键、USB转串口、排针扩展,可能再来个以太网口或者CAN收发器。这些对做外设驱动、上位机通信的开发确实方便。但TinyML主要打交道的是MEMS麦克风、加速度计、低功耗温湿度传感器,这些传感器的接口不是I2C就是PDM,需要的引脚下限其实很低,但对布局走线、供电隔离、时钟匹配有要求。
我当时用的那块通用板,翻遍底板原理图,发现麦克风焊盘是有的,可音频信号线跟一个高速数字信号线并行走了一段,实测采集到的音频数据里有非常明显的周期性噪声。后来我把同一颗麦克风拆下来,接到自己布的板上,底噪立刻降了十几个dB。这说明传感器周边的布局,比传感器本身型号更重要。
通用板不可能为你专门优化某一路模拟信号的质量,因为它的用户群体太杂。这就是第一个矛盾:通用板的接口规划按通用逻辑做,而TinyML设备的“数据入口”往往只有一路传感器,这一路信号的质量直接决定模型效果。
1.2 功耗设计完全不是电池思维
通用开发板默认是插USB供电,板上放两颗LDO、一颗基准电压源、若干LED,整板空载电流轻松跑上二三十毫安。对桌面调试这不是问题,但TinyML产品往往要电池供电,而且不是“工作三五小时”那种,是“休眠一星期还活着”那种。
我当初拿通用板做的第一版原型,整板待机电流大概25mA。这意味着即便用一块500mAh的锂电池,什么都不干,也就撑20个小时。后来我把板子上的LED全拆了、USB转串口芯片断电、禁用不必要的时钟树,才压到3mA左右。可这3mA里绝大部分是板上那颗USB转串口芯片和电源指示灯的漏电流,想再往下压,已经不是固件能解决的问题了,必须从硬件设计层面重新规划供电树。
1.3 数据采集和调试通道割裂
TinyML开发流程里有一个高频环节:采集真实场景数据、标注、训练、量化、部署,然后回到场景里测试,发现不理想,又得采集数据……数据采集能力其实应该被当成板子的核心功能来设计。可通用板只会给你一个串口和一个调试器,最多再加个SD卡槽。
我需要的是一键进入数据采集模式、同时把日志和原始传感器数据打包输出,甚至能直接通过USB把所有数据拉出来。通用板当然也能用,但每次要接一堆杜邦线、敲一堆命令、再手动转移文件,效率很低。折腾几天后我意识到,这不是“功能缺失”的问题,是板子的设计哲学跟我的工作流不兼容。
2. 重新定义一块TinyML开发板:先想清楚要解决什么问题
重新做板之前,我列了一个需求清单,把所有要跑的模型、要用的传感器、要做的部署姿势都写清楚。这一步极其关键,因为硬件设计最怕的就是“什么都想要”。
2.1 我的核心场景和算力需求
我这个项目最核心的模型是关键词唤醒(KWS),结构不算大,大概十来万参数。如果按INT8量化,权重加中间激活缓冲区,总共需要的Flash和RAM其实很小。但我不想把自己限制在KWS,后续还要跑跌倒检测、声纹识别、甚至一个小型目标检测,所以算力底线必须往上提。
最终我把主控定位在一颗带向量扩展支持的Cortex-M55内核MCU,主频300MHz以上,内置Flash至少1MB,SRAM至少512KB。选它不是因为单核性能多夸张,而是这个级别的内核跑TFLite Micro的INT8算子时,DSP加速和向量扩展能把卷积层的耗时从几十毫秒压到个位数毫秒。对TinyML场景来说,推理速度不光是用户体验问题,更直接影响功耗——同样的计算量,越快算完,就能越早回到睡眠状态。
2.2 存储和内存账:量化模型到底占多大地方
很多人低估了模型对存储空间的真实需求。拿我那个KWS模型举例,训练完的FP32权重大约400KB,这个尺寸在通用开发板上根本不算事,但在电池供电的小板子上就要精打细算。
我用一个简单模型体积计算方式估过:
import os import numpy as np def estimate_model_size(params, bit_width=8): """ 粗略估算量化后模型权重占用空间 params: 模型参数数量 bit_width: 量化位宽,TinyML常用8 """ bytes_per_param = bit_width // 8 size_bytes = params * bytes_per_param # 考虑算子元数据、张量描述等开销 overhead_ratio = 1.15 return int(size_bytes * overhead_ratio) # 假设KWS模型参数量约120k params_kws = 120_000 kws_size = estimate_model_size(params_kws) print(f"KWS模型量化后预计占用: {kws_size/1024:.1f} KB")算下来不到150KB,Flash绰绰有余。但如果我换成稍大的视觉模型——比如一个轻量目标检测网络,参数会冲到八百万到一千万,量化后仍然要一兆以上的Flash。这个时候片内空间就不够了,得考虑外置QSPI Flash做XIP或者边加载边推理。
所以我在板上放了一颗4MB QSPI NOR Flash,不只为存模型,还想做OTA固件升级的双备份区域。做成产品后会发现,存储选项几乎和MCU选型一样重要,因为每次更新模型都要动存储分区规划。
2.3 功耗目标是TinyML的隐藏主线
TinyML的功耗目标分两级:运行时功耗和待机功耗。运行时功耗取决于主频、外设活动和传感器数量,通常几十毫瓦级别;真正决定设备续航的是待机功耗,任务间隙MCU进入深度睡眠,整个板子可能只有几十微安。
我把目标定成:
- 推理期间整板功耗不超过30mW(1.8V电压域加传感器)
- 深度睡眠带SRAM保持不超过15uA
- 支持RTC或GPIO中断唤醒,唤醒时间不超过1ms
这个待机电流目标如果用通用开发板几乎不可能实现,因为板上总有USB转串口芯片、LDO、LED这些背景电流大户。我设计时直接把所有非必要器件放到可切断的电源域里,睡眠前用MOS管把整个外设域断开,就留MCU的RTC部分继续跑。
3. 硬件设计的几个核心决策:从原理图到PCB的考究
需求列完就进入具体设计。这一阶段有太多细节,一个个直接决定了板子最终好不好用。
3.1 电源树设计:睡眠电流从哪里来
硬件设计领域有一句话,“电源树等于功耗树”。我画电源树时彻底采用了“域分割”思路。
整个板子分三个电源域:
- 常供域:MCU核心、RTC、备份寄存器、唤醒管理器。50uA级别的负载。
- 传感器域:MEMS麦克风、加速度计、温湿度传感器的电源由MCU一个GPIO控制的负载开关提供,睡眠时直接断电。
- 扩展域:QSPI Flash、SD卡槽、调试接口的电源,由另一个负载开关控制,平时全靠软件自动切断。
常供域用的是超低功耗LDO,空载自身功耗只有微安级;传感器域和扩展域用的是低导通电阻的MOS负载开关,关断后漏电流能做到0.1uA以下。
这里有个新手容易踩的坑:**LDO不是越大越好。**大电流能力的LDO,自身静态电流往往也大,你只是给一颗传感器供电,结果LDO自己吃掉一半预算。选择负载开关和LDO时,一定要同时关注“工作电流带宽”和“自身静态电流ICQ”,这两个参数经常互相矛盾。
3.2 传感器布局:麦克风是唯一的高保真模拟前端
TinyML里最容易被板级设计拖累的传感器是MEMS麦克风。它输出的是PDM信号或者模拟信号,但不管是哪种,焊盘周围有一点高频噪声,就会直接污染采样数据。
我在PCB布局上处理的几个关键点:
- PDMP数据线和时钟线必须等长、紧耦合,且远离开关电源的SW节点。
- 麦克风底下完整铺地,不要有走线穿过。
- 麦克风开孔方向朝板外,最好在板边留出声孔位置,避免外壳挡住。
- 麦克风供电加一个RC低通滤波,滤掉电源上的高频毛刺。
这些细节看起来不复杂,但通用板很少这么讲究。我的第一版自研板在麦克风布线时因为贪图省距离,把PDM时钟线跨过了一个电源通孔,结果采集到的信号里有一个固定的300kHz纹波。后来重新绕线,把时钟线和数据线贴着走、旁边加了一排地过孔,问题立刻消失。
3.3 调试接口:没有好调试就是慢性自杀
TinyML调试是出了名的“看不见摸不着”:你很难知道模型推理到了哪一层,中间的张量值是什么,算子是不是走了期望的加速路径。所以硬件板必须支持:
- 标准的SWD调试接口,用于下载和断点调试。
- 至少一个串口,用于打印日志和导出数据。
- 一个按键用于强制进入Bootloader升级模式。
除了这些“标配”,我还加了两个很实用的东西:一个板载的IPEX天线接口(以后接BLE)和一个用于测量功耗的电流采样跳线。电流跳线这个设计太值了:电池路径上有一个0.1欧姆采样电阻和跳线帽,拔下跳线就能串联万用表测整机电流,免去拿杜邦线悬空测量的痛苦。
4. 固件和部署管线:硬件只是半块开发板
板子画完、打样、焊接,这只是开始。真正的TinyML开发有另外一半工程量在固件和部署工具链上。
4.1 跑TFLite Micro的板级适配
TI级TFLite Micro针对不同MCU都有例程,但直接烧进自己板子的标准例程,多半跑不起来。你需要处理几个板级相关的事:
- 时钟源配置:从HSE还是HSI起振,PLL倍频参数,各总线的分频。这往往是reset后第一个坑。
- 引擎buffer内存分配:默认示例用的内存池可能不够你模型用,需要在链接脚本里专门划一块对齐的RAM段。
- Flash等待周期和缓存配置:高主频读Flash时如果设置不对,跑模型会随机死机。
我自己在移植时,光链接脚本就调了好几版。TFLite Micro需要一个大块连续内存做arena,早期我把arena放在默认堆区域,结果跟其他系统组件的内存产生竞争,模型一加载就崩溃。后来在链接脚本里专门定义了一个.ml_arena段,并把arena固定放在那段固定地址上,终于稳定。
4.2 数据采集固件:模型迭代的加速器
这块板我在设计之初就决定,固件里内置三种模式:
- Bootloader模式(出厂引导和OTA)
- 推理模式(正常运行模型推断)
- 数据采集模式(通过USB串口把原始传感器数据持续流出)
数据采集模式是TinyML开发的“隐藏核心功能”。做数据集时最痛苦的是要保证采集到的数据和实际部署环境一致。如果训练数据是电脑麦克风录的,部署到MCU上之后,麦克风频率响应、AGC、采样率都不同,模型的准确率会掉得很厉害。
我的数据采集模式会固定采样率,把原始PCM数据和IMU数据打包成带时间戳的帧,通过USB CDC发送到上位机,直接进入训练流水线。这样训练数据来自真实的“板子云端”环境,部署回去之后闭环非常顺。
4.3 模型转换和部署流水线
部署流程我固定成一条命令:
# 1. 训练并导出模型,已经是TFLite格式 # 2. 量化并转换为C数组 xxd -i model_int8.tflite > model_data.c # 3. 复制到工程目录 cp model_data.c firmware/src/ # 4. 编译并烧录 make firmware -j8 && make flash这个流程看起来朴素,但核心是量化和转换参数一定要跟部署代码对齐。我吃了很多亏后总结出几个关键点:
- INT8量化时,输入输出张量的scale和zero_point必须固定,不能在板端再自行反算。
- 所有算子必须在TFLite Micro的算子列表中,不支持的算子要么替换成支持的结构,要么拆解成基础算子。
- 每层输出最好加一层校验代码,跟PC端跑同一个输入的输出比对,误差范围容忍度按INT8的1/256去估算。
5. 实测数据与踩坑实录:这块板到底值不值
板子做出来之后,我建了一套完整的测试流程,把数据跑了一遍。
5.1 性能与功耗实测
在300MHz主频下,我跑一个约120K参数的KWS模型,INT8量化的推理时间是28ms,单次推理消耗的总电量大约0.9mJ。这意味着如果每秒唤醒一次词检测,整机工作功耗大约1mW左右;如果平时深度睡眠,一秒钟醒来检查一次音频能量、再决定要不要进推理,平均功耗可以控制在200uW量级。
这个数字远优于我用通用开发板做出来的效果。同样是这个模型,在通用板上跑了110ms不说,还没法关传感器域的电源,整机功耗直接高出一个数量级。差距不是出在模型优化,而是板级设计的针对性。
5.2 遇到的几个实际问题及解决方式
我按实际踩坑顺序整理了一张速查表:
| 现象 | 直接原因 | 解决方式 |
|---|---|---|
| 板子上电后MCU不跑 | 复位电路的上电时序不够,MCU要求电源稳定后延迟释放复位 | 加一个RC延时电路,同时把LDO的PG输出接到复位芯片 |
| PDM麦克风采出来全是0 | 麦克风时钟频率超出规格,PDM接口分频配置错误 | 重新按麦克风规格配置时钟分频,规格表写的最大频率是3.2MHz |
| 推理结果总是跟PC端差一个固定偏移 | 量化时对输入做了浮点均值归一化,但MCU端归一化参数没写 | 把归一化参数固化到代码里,确保两端一致 |
| 睡眠电流有50uA下不去 | 外部Nor Flash的断电引脚没连到MCU,悬空状态漏电 | 把FLASH的电源/片选/复位都统一控制,单片MOS切断 |
| 模型加载中途死机 | QSPI Flash的XIP数据读到内存后,Flash没退出memory mapped模式 | 切换前先执行reset命令,读取完成后再切回外设模式 |
这些问题没有一个是原理级难题,但全部是“不在实际板子上遇到根本不会意识到”的经验型问题。这也正是自己做一块专用板的重要收获:因为每一根线都是自己画的,出了问题你能很快定位是哪里的锅。
6. 我最后想说的:自研TinyML板到底图什么
如果只看成本,自己做板子的成本不会比买现成开发板低。打样、物料、人工焊板、一次做错再来一次的代价,都不小。但TinyML的价值从来不是“跑通一个Demo”,而是“做出一个能长期稳定运行的边缘设备”。而后者几乎必须硬件深度定制。
这块板做到第三版时,我已经可以把整个调试环境塞进口袋:核心板加一颗电池加一颗麦克风,跑一个完整的唤醒模型。那种体验是任何通用板都给不了的。
从我自己的经验看,如果你只是学习TinyML、跑跑官方示例,直接拿现成开发板完全没问题。但如果你有了明确的场景,比如要做低功耗唤醒、要跑一个小模型实现具体功能,我非常建议在你的MCU选型和板级方案确定后,花一周时间自己画一版专用板。正式画之前,先把电源树、传感器接口、调试方案列出来,跟选型报告放一起反复推敲。到物料和layout都定了再动手也不迟——这其实比埋头焊板子更省时间。
最后分享一个小技巧:测试微安级待机电流时,普通万用表表笔接触电阻就能带来几十微安误差。我后来直接给板子预留了两排镀金的电流测试焊盘,用开尔文接法把采压线分开,测出来的数值才真正可信。这种细节看起来微不足道,但如果你打算把功耗当成产品指标,它就是那个让你不被数据欺骗的底线。