1. 从一块“不合群”的板子说起
前阵子在一个嵌入式交流群里,有人甩了张图出来,是一块巴掌大的小板子,上面焊着一颗摄像头模组、一颗麦克风、一颗六轴传感器,外加一块小小的屏幕,主控芯片的型号被刻意打了码。配文就一句话:“有那么多现成的开发板,我为什么还要为TinyML费劲巴拉的重新做一款?”
底下瞬间炸了锅。有人说这是重复造轮子,有人说现成的板子拿来就能跑,何必折腾。也有人默默问了一句:“跑得动吗?”
这个问题其实戳到了很多做过端侧智能的人心里的痛处。TinyML,也就是在资源极度受限的微控制器上跑机器学习模型,这几年从学术圈一路烧到了工程圈。你想在只有几百KB内存、主频几十兆的芯片上做关键词唤醒、做手势识别、做异常检测,现成的开发板确实一抓一大把。但真正上手之后你会发现,那些板子要么传感器是“赠品级”的,要么功耗和算力配比别扭,要么软件栈和你的模型框架对不上。用起来就像穿了一双尺码不对的鞋,能走,但走不远。
这篇文章不打算给你灌鸡汤,也不打算复述TinyML的概念。我想从一个实际动手做过几轮板子的人的角度,把“为什么现成的不够用”这件事拆开揉碎讲清楚。核心会围绕硬件选型逻辑、传感器与算力的匹配、软件栈的适配成本、功耗与实时性的取舍这几个关键词展开。如果你正在纠结是直接买一块现成的开发板凑合,还是自己画一版,或者你只是好奇TinyML到底卡在哪,这篇内容应该能帮你省下不少试错的时间。
我默认你有一点嵌入式基础,知道GPIO、I2C、SPI是什么,也大概听说过TensorFlow Lite Micro或者类似的推理框架。但即使你只是刚入门,我也会尽量把每个决策背后的“为什么”讲明白,让你能直接抄作业,也能理解为什么这么抄。
2. 现成开发板的“三座大山”
2.1 第一座山:传感器是“能用”还是“好用”
市面上大多数通用开发板,传感器配置的逻辑是“有就行”。一颗低成本的加速度计,一颗普通的麦克风,摄像头模组往往是外接的。这种配置用来跑跑官方的Hello World示例没问题,一旦你要做真实场景的TinyML应用,问题就来了。
举个例子,做关键词唤醒。你需要麦克风的信噪比足够高,采样率稳定,而且最好有硬件级的PDM或者I2S接口,能直接把音频流喂给MCU,不占用太多CPU周期。但很多板子上的麦克风是模拟输出的,你得用ADC去采,采样精度和时序抖动直接影响到后续的特征提取。我试过在一块很流行的通用板上做关键词识别,模型本身只有几十KB,推理一次不到10毫秒,但音频前端采集的噪声让准确率怎么调都上不去。后来换了一颗带PDM接口的数字麦克风,同样的模型,准确率直接提了十几个百分点。
再比如做手势识别,你需要加速度计和陀螺仪的零偏稳定性好,量程合适。有些板子上的六轴传感器量程固定得很大,适合做剧烈运动的检测,但你要做的是细微的手势变化,信号分辨率就不够了。这种“传感器选型与任务不匹配”的问题,是现成开发板最常见的坑。
注意:不要只看传感器型号的数据手册标称参数,一定要看它在你的目标应用场景下的实际表现。数据手册上的信噪比是在理想条件下测的,板级布局、电源噪声、外壳结构都会让实际性能打折扣。
2.2 第二座山:算力和内存的“虚假繁荣”
很多开发板宣传的时候会写“搭载高性能MCU,主频XXX MHz,内存XXX KB”。听起来很美好,但你仔细一算,跑TinyML模型的时候,内存是被切得很碎的。
一个典型的TinyML模型,比如关键词唤醒的DS-CNN,参数量可能在20KB到100KB之间。推理的时候,TensorFlow Lite Micro需要一块“张量竞技场”来存放中间激活值,这块内存通常是模型参数量的两到三倍。再加上音频前端做MFCC特征提取需要的缓冲区,操作系统或者调度器的开销,一块标称256KB RAM的芯片,实际能留给模型的可能只有一半。
更隐蔽的问题是缓存和总线架构。有些MCU主频很高,但Flash和RAM之间的总线带宽有限,模型权重从Flash加载到RAM的速度成了瓶颈。你测出来单次推理时间很短,但加上权重加载的时间,整体延迟就上去了。这种问题在跑连续推理的任务时特别明显,比如实时姿态估计,每一帧都要重新加载部分权重,帧率怎么都上不去。
我自己的经验是,选型的时候不要只看主频和内存大小,一定要看内存带宽和Flash访问速度。如果数据手册里没写,就找一块开发板实测,跑一个内存拷贝的基准测试,心里就有数了。
2.3 第三座山:软件栈的“最后一公里”
这是最容易被低估的一座山。你选好了芯片,传感器也满意,算力也够,结果发现官方SDK里的推理框架版本太老,不支持你用的算子;或者你用的训练框架导出的模型格式,转换工具链各种报错。
TinyML的软件栈现在还是碎片化的。TensorFlow Lite Micro、ONNX Runtime for Micro、还有各家芯片厂商自己的推理引擎,各有各的算子支持列表,各有各的量化工具链。你用一个框架训练模型,想部署到另一家的芯片上,中间可能要经过好几次格式转换,每次转换都可能引入精度损失或者算子不支持的问题。
更麻烦的是,很多现成开发板的BSP包更新很慢,里面的驱动和中间件版本锁死。你想升级一下推理框架,发现和现有的驱动冲突,改起来牵一发动全身。这种“软件栈锁定”效应,让开发板的选择变得非常关键。一旦选错,后面要么忍受老版本,要么花大量时间做移植。
提示:在决定用哪块开发板之前,先去它的官方仓库看看最近一次提交是什么时候,issue区里有没有大量关于推理框架的未解决问题。一个活跃维护的软件栈,比多那几十KB内存重要得多。
3. 自己动手做板子的核心逻辑
3.1 从应用倒推硬件,而不是反过来
自己画板子最大的好处,是你可以从应用需求倒推硬件配置,而不是拿着一块现成的板子去凑合应用。
我一般会先列一个需求清单,按优先级排序。比如做一个电池供电的穿戴式手势识别设备,需求可能是这样的:
- 续航至少一周,每天使用8小时
- 手势识别延迟低于50毫秒
- 能识别至少6种手势
- 设备体积小于40mm x 40mm
- 成本控制在某个范围内
有了这个清单,硬件选型就有了明确的约束。续航一周意味着平均功耗要控制在几百微安级别,这就排除了那些主频很高、没有低功耗模式的MCU。延迟低于50毫秒意味着单次推理加上传感器采集和特征提取的时间要留够余量。识别6种手势,根据经验,一个轻量级的CNN模型参数量大概在30KB到80KB之间,那么RAM至少要留出200KB左右给张量竞技场和系统开销。
这种倒推的逻辑,让你在选型的时候不会盲目追求高性能,而是找到那个刚好够用且留有余量的平衡点。现成开发板往往是“性能过剩但功耗也过剩”,或者“功耗够低但算力不够”,很难刚好卡在你的需求点上。
3.2 传感器选型的“场景优先”原则
自己选传感器,核心原则是场景优先,而不是参数优先。
拿麦克风来说,如果你做的是关键词唤醒,关注的是人声频段(300Hz到3kHz)的信噪比和灵敏度一致性。如果你做的是异常声音检测,比如机器故障的异响,关注的是高频段的响应和动态范围。这两种场景对麦克风的要求完全不同,但现成开发板往往只给一种配置。
再拿加速度计来说,做手势识别的量程一般在±2g到±4g就够了,但需要低噪声密度和高分辨率。做跌倒检测可能需要±8g甚至±16g的量程,但对分辨率的要求没那么高。自己选型的时候,你可以根据具体场景去匹配传感器的量程、带宽、噪声密度和接口类型,而不是被开发板上的固定配置绑死。
我个人的习惯是,在确定传感器型号之前,先买几颗不同型号的传感器做对比测试。用同一段数据,跑同一个模型,看哪个传感器的信号质量最好。这个测试花不了多少时间,但能避免后面大量的调参工作。
3.3 算力与内存的“留白”艺术
自己设计硬件的时候,算力和内存的配置需要一点“留白”的智慧。
留少了,模型跑不动,或者跑起来之后没有余量做其他任务。留多了,功耗和成本都上去了,对于电池供电的设备来说,每一毫安时都很宝贵。
我的经验是,按照当前模型的需求,RAM留出1.5倍的余量,Flash留出2倍的余量,主频留出30%的余量。这个比例不是拍脑袋来的。RAM的1.5倍是为了应对张量竞技场的内存碎片和峰值占用。Flash的2倍是为了后续模型迭代和固件升级。主频的30%余量是为了应对传感器数据采集和预处理的开销,以及系统调度的不确定性。
举个例子,假设你的模型推理需要100KB的RAM和200KB的Flash,单次推理在80MHz主频下需要20毫秒。那么选型的时候,RAM至少选192KB(实际可用可能只有160KB左右),Flash至少选512KB,主频至少选104MHz。这样配置下来,你还有余量去加一些额外的功能,比如数据记录、蓝牙传输、或者更复杂的预处理算法。
注意:留白不是浪费。在TinyML项目里,最怕的就是“刚刚好”。因为模型会迭代,需求会变化,传感器数据会有波动。留白是给未来的自己留退路。
4. 实操:从零到一打造一块TinyML开发板
4.1 需求定义与规格书撰写
动手画原理图之前,先写一份规格书。这份规格书不用很正式,但要把关键指标定下来。
我一般会包含这几个部分:
- 应用场景描述:一句话说清楚这块板子要干什么。
- 核心功能列表:按优先级排列,区分“必须有”和“最好有”。
- 性能指标:推理延迟、准确率目标、续航时间、体积限制。
- 接口需求:需要哪些传感器接口(I2C、SPI、I2S、PDM),需要哪些对外接口(USB、UART、SWD)。
- 功耗预算:分场景给出功耗目标,比如“推理时平均电流小于20mA,待机电流小于100uA”。
- 成本目标:单板物料成本的上限。
这份规格书是后续所有决策的依据。选型的时候,任何不满足“必须有”功能的方案直接排除。任何满足“必须有”但影响“最好有”的方案,需要权衡。
我见过很多人跳过这一步,直接开始选芯片、画图,结果做到一半发现某个传感器接口不匹配,或者功耗超标,只能推倒重来。花半天时间写规格书,能省下后面好几天的返工。
4.2 主控芯片选型:算力、功耗、生态的三维权衡
主控芯片是板子的心脏,选型的时候要在算力、功耗和生态三个维度上做权衡。
算力维度,主要看核心架构和主频。ARM Cortex-M系列是TinyML的主流选择,M0/M0+适合极低功耗的简单任务,M4/M4F带FPU适合需要浮点运算的模型,M7/M55算力更强但功耗也更高。最近几年RISC-V架构的MCU也开始出现在TinyML场景里,生态还在完善中,但有些型号的能效比很有竞争力。
功耗维度,要看芯片的低功耗模式和支持的唤醒源。TinyML设备很多时候是在“休眠-唤醒-推理-休眠”的循环里工作,所以休眠电流和唤醒时间非常关键。有些芯片休眠电流只有几百纳安,但唤醒后初始化外设的时间很长,整体平均功耗反而下不来。
生态维度,要看芯片厂商提供的SDK、推理框架的支持程度、社区活跃度。一个芯片就算参数再好,如果SDK里没有现成的TinyML示例,或者推理框架的移植文档写得不清不楚,开发成本会成倍增加。
我自己的选型流程一般是:先根据算力需求筛出几款候选芯片,然后对比它们的低功耗模式参数,最后去查推理框架的官方支持列表和社区案例。三个维度都过关的芯片,才会进入下一轮。
4.3 传感器与外围电路的设计要点
传感器选好型号之后,电路设计有几个容易踩坑的地方。
电源噪声是第一个坑。TinyML应用里,传感器信号往往很微弱,电源上的纹波会直接耦合到信号里。我一般会给模拟传感器单独加LDO供电,数字传感器和MCU共用一路电源但加磁珠隔离。去耦电容要靠近传感器电源引脚放置,容值组合一般是10uF加0.1uF加1nF。
接口电平匹配是第二个坑。有些传感器是1.8V电平,MCU是3.3V,直接连会烧传感器或者通信不稳定。要么选支持双电压的MCU,要么加电平转换芯片。我倾向于选支持1.8V IO的MCU,省掉转换芯片,也省掉转换带来的额外功耗。
时钟和同步是第三个坑。如果多个传感器需要同步采样,比如加速度计和陀螺仪要严格对齐时间戳,那么它们最好共享同一个时钟源,或者MCU提供硬件同步信号。软件轮询的方式在高速采样时会有不可忽略的抖动。
调试接口一定要留出来。SWD或者JTAG接口,加上一个串口,是调试TinyML应用的命脉。没有这些接口,你只能靠LED灯来猜程序跑到哪了,效率极低。
4.4 软件栈搭建:从训练到部署的完整链路
硬件做好之后,软件栈的搭建是另一个重头戏。完整的链路一般是:数据采集 -> 模型训练 -> 模型转换 -> 量化 -> 部署 -> 推理。
数据采集阶段,我习惯用板子本身加一个高速串口或者SD卡来记录原始传感器数据。这样采集到的数据分布和实际部署时一致,训练出来的模型泛化能力更好。
模型训练一般在PC或者服务器上完成,用TensorFlow或者PyTorch。模型结构要尽量轻量,卷积核数量、层数、全连接层大小都要控制。训练的时候可以加一些数据增强,比如加噪声、时间偏移、幅度缩放,提升模型在真实环境下的鲁棒性。
模型转换和量化是TinyML特有的环节。把训练好的模型转成TensorFlow Lite格式,然后做全整数量化。量化的时候要注意校准数据集的代表性,校准数据要覆盖实际场景的各种情况,否则量化后的精度损失会很大。我一般会留出10%的训练数据作为校准集,确保量化后的模型在验证集上的精度下降不超过2%。
部署和推理阶段,把量化后的模型转成C数组,集成到MCU工程里。推理框架的初始化、张量竞技场的分配、输入输出的绑定,这些步骤每个框架的API不太一样,但逻辑是相通的。调试的时候先用固定输入测试推理结果,确认模型本身没问题,再接真实传感器数据。
提示:量化后的模型精度下降是正常的,但如果下降超过5%,就要检查校准集是否具有代表性,或者模型里是否有对量化敏感的算子。有些算子比如softmax,在量化后精度损失较大,可以考虑用查表法替代。
5. 常见问题与排查技巧实录
5.1 推理结果不稳定,时好时坏
这是最常见的问题。表现是同样的输入,推理输出偶尔正确偶尔错误,或者准确率波动很大。
排查思路一般是这样的:先确认输入数据是否稳定。用固定的测试数据喂给模型,如果输出稳定,说明问题在传感器数据采集环节。检查传感器的采样率是否稳定,电源是否有噪声,接口通信是否有丢包。如果固定输入下输出也不稳定,那问题可能在推理框架或者内存分配上。
我遇到过一次,推理结果随机跳变,查了很久发现是张量竞技场的内存对齐问题。某些MCU架构对内存访问有对齐要求,如果竞技场的起始地址没有对齐到4字节或者8字节边界,推理过程中会出现数据错位。解决办法是在分配竞技场内存的时候,用编译器的对齐属性强制对齐。
另一个常见原因是栈溢出。推理过程中局部变量和函数调用会占用栈空间,如果栈设置得太小,会覆盖其他内存区域,导致不可预测的行为。我一般会把栈大小设置为推理框架文档推荐值的1.5倍,然后在调试的时候监控栈的使用峰值。
5.2 功耗比预期高出一大截
电池供电的设备,功耗超标是致命的。排查功耗问题,我一般分三步走。
第一步,测各个工作模式下的电流。用高精度的电流表或者功耗分析仪,分别测休眠、传感器采集、推理、通信这几个状态的电流。先确认哪个状态超标。
第二步,查外设的功耗。传感器、屏幕、通信模块,这些外设的功耗加起来可能比MCU本身还高。检查每个外设是否在不需要的时候进入了低功耗模式,是否有漏电流。我遇到过一次,屏幕的背光电路在休眠时没有完全关断,漏了几百微安的电流,把续航直接砍半。
第三步,查MCU的低功耗配置。休眠模式选对了吗?唤醒源配置合理吗?有没有外设在休眠时还在运行?有些MCU的低功耗模式需要手动关闭一些内部模块,比如PLL、ADC、比较器,如果忘了关,休眠电流会高很多。
注意:功耗测试一定要在真实的工作循环下测,不能只测单个状态。因为状态切换本身也有功耗开销,切换越频繁,平均功耗越高。优化的时候要综合考虑切换频率和单次切换的能耗。
5.3 模型精度在板子上比在PC上差很多
这个问题通常和量化有关。PC上训练和验证用的是浮点模型,部署到MCU上用的是量化后的整数模型,两者之间有精度差距是正常的。但如果差距太大,就要排查原因。
首先检查量化校准集。校准集的数据分布要和实际推理时的数据分布一致。如果校准集用的是实验室安静环境的数据,实际部署在嘈杂环境,量化后的模型对噪声的鲁棒性会差很多。
其次检查模型里是否有对量化敏感的算子。比如除法、指数、对数这些算子,在整数量化后精度损失较大。可以考虑用多项式近似或者查表法替代。
最后检查输入数据的预处理。PC上训练的时候,输入数据可能做了归一化或者标准化。部署到MCU上,预处理代码是否和训练时一致?有没有因为整数运算的截断误差导致输入分布偏移?我一般会在MCU上把预处理后的数据通过串口打印出来,和PC上的预处理结果对比,确认一致后再往下查。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
| 推理结果随机跳变 | 内存对齐问题、栈溢出 | 固定输入测试、监控栈使用 | 强制内存对齐、增大栈空间 |
| 功耗超标 | 外设漏电、低功耗模式配置不当 | 分状态测电流、检查外设配置 | 关闭闲置外设、优化休眠配置 |
| 精度下降明显 | 量化校准集不具代表性、敏感算子 | 对比PC和MCU预处理结果 | 更换校准集、替换敏感算子 |
| 推理延迟大 | 权重加载慢、总线带宽瓶颈 | 测内存拷贝速度、分析推理耗时 | 优化权重存储、使用缓存 |
| 传感器数据噪声大 | 电源纹波、接口干扰 | 示波器看电源和信号波形 | 加LDO、磁珠、去耦电容 |
| 通信丢包 | 时序不匹配、电平不兼容 | 逻辑分析仪抓时序 | 调整时序参数、加电平转换 |
6. 一些关于“值不值得”的真心话
自己画板子这件事,说到底是一个投入产出比的权衡。如果你只是想做一个小Demo,验证一个想法,现成的开发板绝对是最快的方式。买一块,跑通官方示例,改改模型,几天就能出结果。这种情况下,自己画板子就是浪费时间。
但如果你要做的是一个产品原型,或者一个需要长期迭代的项目,现成开发板的局限性就会越来越明显。传感器不匹配、功耗下不来、软件栈锁死,这些问题每一个都会消耗大量的时间。而自己画板子,虽然前期投入大,但后续的每一个优化都有空间,每一个需求变化都能响应。
我自己的体会是,当你的需求在现成开发板上找不到一个“刚好”的匹配时,就是考虑自己画板子的时候了。这个“刚好”包括传感器的类型和性能、算力和内存的配比、功耗和体积的约束、软件栈的适配程度。如果现成板子能满足其中80%的需求,剩下的20%可以通过外接模块或者软件优化来解决,那就先用现成的。如果只能满足50%,那自己画可能更省时间。
还有一个容易被忽略的点是学习价值。自己画一块TinyML板子,从需求定义到选型到画图到调试到部署,整个流程走一遍,你对TinyML的理解会完全不一样。你会知道为什么某个参数重要,为什么某个设计决策会影响最终效果。这种系统级的认知,是拿现成板子跑几个示例得不到的。
最后分享一个小技巧。如果你决定自己画板子,第一版不要追求完美。把核心功能做出来,留出调试接口和扩展接口,先跑通整个链路。第二版再优化功耗、缩小体积、降低成本。TinyML的硬件设计是一个迭代的过程,第一版就想着量产,往往会因为各种意外情况而延期。先让板子跑起来,再让它跑得好。