2026/10/23 1:28:57

自制TinyML开发板:从现成板到专用硬件的踩坑与设计复盘

自制TinyML开发板:从现成板到专用硬件的踩坑与设计复盘 做TinyML开发这几年我手上前后过手的现成开发板少说也有七八种。每次跟人聊起自己又重新做了一款几乎都会被问同一个问题市面上那么多现成的为什么要费这个劲这个问题我其实被问过很多次每次的答案都不太一样因为随着踩坑的增多我越来越清楚地意识到通用开发板和实际项目落地之间的那道鸿沟不是靠多买几块板子就能填平的。与其在现成方案上反复打补丁不如干脆按自己的需求做一款顺便把那些坑也一并填掉。这篇文章不打算写成一个完整的开源硬件教程这更像是我在做这块TinyML开发板过程中的复盘。我会把为什么放弃现成开发板的真实原因、重新设计时做过的关键取舍、硬件和TinyML工作流如何打通以及调试过程中踩到的那些最花时间的坑都拿出来说说。如果你也在TinyML这条路上折腾或者正准备为自己的特定场景选型甚至自制一块板子这篇东西应该能给你一些在官方文档里找不到的判断依据。1. 为什么现成的开发板越用越别扭1.1 看起来什么都行做起来什么都不太够TinyML这个方向最容易让人产生的一个错觉就是随便一块开发板都能跑模型。毕竟广告里随处可见在MCU上跑图像分类、关键词唤醒的演示似乎只要把模型塞进去就完事。但实际做过产品原型的人心里都清楚演示是一回事真正跑到你的场景里完全是另一回事。举个例子。我当时负责一个需要做振动异常检测的模拟项目现场部署条件比较苛刻只能用电池供电但又要保证至少连续运行两周以上。市面上的主流开发板大多数核心MCU本身功耗表现并不差深睡眠能做到微安级别。可问题从来不在芯片本身而在板子周边的配套。你随便拿一块流行的开发板看看板载调试器常年通电状态LED上电就亮USB转串口芯片一直在等主机连接这些外设加起来待机电流轻轻松松就多出几毫安。做原型验证的时候这几毫安没人会当回事可一旦算进电池续航里整个系统的运行时间就要打对折。更重要的是传感器接口。我那个项目需要同时接一个三轴加速度计和一个温湿度传感器要求它们能独立供电、独立控制。通用开发板的排针虽然都能引出I2C和GPIO但很多时候VCC和GND只有一组要同时给两个传感器供电就得外部飞线。飞线在实验台上没什么装进结构件里就变成隐患接头松一下、短路一下调试时间哗哗地就没了。所以你会发现现成开发板解决的是验证某个芯片能不能用的问题而不是快速搭建一个能落地的完整系统的问题。它把主控部分做得足够通用却把真正影响TinyML落地的外围配置、功耗管理、接口灵活性统统丢给你自己处理。1.2 算力、内存和成本的三角摇摆如果只做纯软件层的TinyML实验开发板的算力差异其实感知不强毕竟几百KB的模型跑起来几秒钟出结果大家都能接受。可一旦你开始认真调模型、跑量化、试不同网络结构开发板之间那点性能差距就会被放大得很明显。比如用一张常见的中低端MCU开发板做关键词识别受限的RAM意味着模型输入的分帧长度、特征维度都要反复压缩稍微把模型做大一点内存就直接爆掉。这时候你可能想换更高端的Cortex-M7或者带专用NPU的芯片。可换过去之后又会发现存储容量大了算力强了价格也上去了而TinyML在很多场景里本身就是冲着低成本、低功耗去的用户对单板成本非常敏感。我当时的算法原型验证阶段卡在内存和算力的矛盾里将近一个多月。同一套预处理代码在一个平台上跑得好好的换到另一个平台就不得不因为内存布局重写。这种适配工作做几轮下来你会特别渴望一块为自己定制的板子从存储分区、时钟配置到内存分配每一处细节都按需求设计而不是在别人的通用板上缝缝补补。1.3 扩展接口的隐性限制你真的需要那么多引脚吗做TinyML的人长期和传感器打交道经常会被开发板的引脚资源迷惑。比如一块板子宣称引出了几十个GPIO听起来扩展性很强但实际项目里你根本用不到那么多反而更在意特定引脚的映射是否合理比如I2C和SPI是否冲突、UART是否能做流控、ADC通道是否被板载功能占用。我踩过的一个典型坑是某块主流开发板把I2C和调试串口的引脚做了部分复用当你同时用这两个外设时引脚配置必须绕开几个固定编号。这在用Arduino库时不明显但一旦换成自己做底层驱动就非常容易被忽路。还有过板子的ADC参考电压和电源走线共享一个源头导致传感器采样值波动特别大排查了整整两天最后发现是板子布线的问题。所以最后做出来的板子引脚数量反而比很多通用开发板少但每个引脚的功能规划都很明确。该给传感器用的I2C单独走该给调试用的UART独立出来不做冗余不做花哨的镀金排针一切围绕实际项目展开。2. 设计这块TinyML开发板之前想清楚的几件事2.1 明确专用与通用的边界自制开发板最容易犯的错误就是做着做着又不自觉地往通用的方向靠觉得这个接口留着以后可能用得上那个传感器兼容了也不错。这是花很多时间做出来的板子却难以落地的最大原因。我在正式画原理图之前给自己定下了一条硬规矩这块板子只服务TinyML的两类最常见场景一类是传感器数据采集加本地推理另一类是推理结果通过无线模块做低功耗上报。其他需求哪怕是看起来很有用的显示屏驱动、音频输入、摄像头接口在这个版本里一律不做。这个决策听起来很简单真正执行的时候却很考验人对需求的把控。因为在做项目的过程中你总会被顺手加个功能的念头勾住看别人在板子上放了个彩屏自己也想要看到有人接了麦克风做唤醒词也想试试。但一旦把范围扩大了PCB的层数、布局的复杂度、调试的工作量都会成倍增加。尤其对于TinyML这种讲究极致功耗和响应时间的场景多余的功能意味着多余的外围器件直接浪费掉宝贵的睡眠电流。2.2 芯片选型从模型反推硬件做TinyML开发板和做普通MCU开发板有一个显著区别就是芯片选型最好是由软件反推硬件先确定你要跑什么样的模型再回头决定用哪颗芯片。我见过不少人先买了一块流行的开发板然后强行找一些能跑得动的模型往上凑这种做法技术上倒不是不能跑但往往离实际需求越来越远。我自己的做法是先把项目的几个候选模型在PC上跑通记录下参数量、激活值大小、中间计算的峰值内存然后按照TFLite Micro的典型开销估算出MCU端的RAM需求。比如一个几百KB的模型加上运行时缓冲区说保守一点MCU内置RAM至少需要一两百KB才够用得舒服否则每次推理都要小心翼翼地和预处理缓冲区抢内存开发体验会非常难受。选型过程里我比较了几款主流的MCU最终选了一颗Cortex-M4F内核的芯片主频不算激进但内置Flash和RAM容量都在当时预算内匹配得刚刚好。更重要的一点是它的浮点单元对TinyML里的矩阵运算有可感知的帮助在跑一些不方便做定点化的预处理步骤时浮点计算能快不少。另外一个加分项是它有独立的DMA通道做传感器数据搬运的时候可以不打扰CPU。2.3 功耗预算从电池倒推电路设计TinyML产品的功耗设计思路和普通可穿戴设备不太一样它通常是长时间休眠、短时间唤醒、快速推理、立刻睡回去这种突发工作模式。所以真正决定电池续航的不只是芯片本身的深睡眠电流还有整个系统的唤醒路径设计。我在做功耗预算的时候先用一张表把系统里所有会耗电的器件列出来MCU在活跃模式大概几毫安传感器测量时不到一毫安无线模块发送的时候可能一下冲到几十毫安再加上LDO自身的静态电流。然后设定一个目标整板的平均功耗要控制在微安级别。这个目标要求MCU绝大多数时间都处于深睡眠而且要保证板上没有多余的上拉电阻在睡眠期间白白耗电。电路上我做了一个很多现成开发板都不会做的设计给传感器和无线模块分别加独立的负载开关。这样MCU可以在需要时才给传感器供电采集完立刻断电。就这一个设计愣是把整板的平均电流从原本的接近毫安级别降到了几十微安。虽然这只是个很小的电路改动但在现成开发板上几乎不可能实现因为它们的电源设计都是固定的。3. 从原理图到PCB那些没人写在文档里的细节3.1 原理图设计的核心先画电源树再画外设我一直有个习惯设计开发板不管多简单都是从电源树开始画。很多人一上来就先把MCU的原理图符号放上去然后急匆匆地接线结果画到后面发现供电引脚不够、去耦电容摆不下、某个外设的电平逻辑对不上反反复复改原理图效率极其低下。电源树的做法是先明确系统的电压轨有哪些。我这块板子规划得比较简单一路主电源3.3V供MCU和大部分外设一路传感器专用3.3V经过负载开关单独控制无线模块则用一颗独立的降压芯片从电池电压转换过来。这样做的原因是无线模块发射时电流波动大如果和传感器共用一路电源采样数据很容易被干扰做出来的数据质量会很差。原理图里还有一个容易被忽略但影响很大的细节去耦电容的摆放。虽然理论上每个电源引脚都要加一个0.1uF的电容但实际布局的时候如果电容距离MCU的电源引脚太远它的去耦效果基本等于没有。我在画原理图的时候就会把每个去耦电容的物理位置预先想清楚宁可少画几个心理安慰级别的电容也要保证每一颗都有实际作用。3.2 引脚分配的学问比想象中更重要引脚分配是我这次做板子时花时间最多的地方之一。别看只是把外设连到哪个引脚这么简单要是没规划好后面写代码时的痛苦程度直接翻倍。我在规划引脚的时候遵循了两个原则。第一个原则是优先保证通信接口的纯净性。比如I2C就只接传感器SPI就只接无线模块绝对不让一个总线同时挂载多个设备。有人觉得浪费但实际上TinyML的代码里传感器读取和无线传输往往是紧耦合的一个频繁使用的总线上挂太多设备DMA的调度、中断优先级都会变得非常复杂。第二个原则是把所有可能需要在运行时动态切换的功能引脚分散开。比如某个引脚平时做GPIO控制传感器电源休眠时要配置成外部中断唤醒如果旁边刚好走了一根高频率的通信线休眠唤醒时很容易因为信号边沿干扰产生误触发。实际画完引脚分配表之后我又做了一个很加分的行为把烧录失败急救步骤也一并考虑进去。我专门留了一对引脚作为SWD调试接口并且物理上放在PCB边缘方便用探针直接接触。很多做原型的人不在意这个觉得USB烧录就够了可一旦板子跑到一半程序死住、USB枚举不出来了SWD就是最后的救命稻草。3.3 PCB布局天线净空区和传感器位置怎么处理PCB布局是整个硬件设计里最玄学但又能用常识解释清楚的部分。我只想说几个自己印象非常深刻的点。第一个是天线净空区。无线模块用的是2.4G频段PCB上就要为天线留出一整块没有任何铜皮的净空区绝对不能有地平面、走线或者元器件。我第一次画的时候没太在意想着反正是四层板把天线放在板边就行。结果实测无线传输距离比预期差了将近一半排查了很久才发现是天线下方有一层完整的地平面把信号基本屏蔽掉了。后来重新改版天线区域下方全部镂空距离立刻恢复正常。第二个是传感器布局。我之前提到传感器要单独供电其实它的物理位置也有讲究。加速度计这种对振动敏感的传感器最好不要放在板子角落或者靠近大电流走线的地方否则采集到的数据会掺入额外的高频噪声。我把加速度计放在了板子中心偏边缘一点的位置同时用一块铜皮在背面做了局部屏蔽实际采集效果比之前用现成开发板好了不少。第三个是测试点的价值。PCB上我预留了一排很小的测试点把电源、地、I2C、UART等关键信号都引出来。调试的时候探针往上一戳波形一看问题一目了然。这个习惯在前期会多花一点点PCB面积但后续排查问题节省的时间绝对值得。4. TinyML工作流与硬件如何真正打通4.1 不是把模型塞进去那么简单很多人第一次接触TinyML以为流程就是训练一个模型然后用工具转换一下拷贝到开发板上就能跑。真的往里走会发现从训练框架到MCU上的推理引擎中间隔着好几层需要手工介入的适配。我当时的做法是先确定推理框架再根据框架支持的算子集反向约束模型结构。像TFLite Micro它对某些复杂的激活函数、自定义层支持有限如果你的模型结构里用了一个它没实现的操作转换阶段就会直接报错或者生成一个跑不起来的二进制。所以我在模型设计阶段就刻意避开了一些花哨的结构只用了标准的卷积、深度可分离卷积和全连接层确保转换链路的顺畅。不过这个阶段真正让我头疼的并不是模型本身而是数据和硬件之间的对齐。我在PC上训练模型时用的数据是浮点数传感器原始数据是整数中间归一化的方式稍有偏差模型的表现就会出现非常明显的退化。后来我在PC端模拟的时候就专门加了模拟传感器量化噪声的处理步骤让训练数据的分布尽量接近真实采集到的数据果然效果提升了不少。4.2 预处理到底放在哪里一个性能和功耗的博弈TinyML项目中很多人都会忽略预处理这个环节觉得传感器原始数据直接丢给模型就行。实际上预处理做得好不好直接影响模型推理的准确率和系统的整体功耗。我遇到的一个具体问题是加速度计的原始数据带有高频噪声如果直接做FFT分析频谱里会出现不少虚假的峰值。传统做法是在上位机上做滤波但这在边缘设备上行不通必须把滤波算法放到MCU上实现。一开始我用的是简单的滑动平均效果一般模型精度还是不太够。后来换成了IIR滤波器参数经过模拟验证后再固化成定点版本推理准确率终于达到了可接受的水平。预处理放哪里的问题本质上是算力分配的问题。如果你用MCU做滤波会占用一部分CPU周期导致推理时延变长但如果你把原始数据完整上传无线模块的功耗又会剧增。我的方案是把预处理拆成两段硬件直接完成一部分简单的下采样和去直流偏置另外一部分如FFT或特征提取放在推理前执行。这样平均功耗和推理时延都控制在了合理范围内。4.3 模型量化和内存布局的实战经验模型量化是TinyML绕不开的一关。我在把模型从浮点转为INT8时遇到的第一个问题就是激活值溢出。模型里某些层的数值分布范围比较宽量化参数如果只统计了训练集的整体分布推理时遇到分布外的数据激活值就会被饱和截断精度掉得很难看。解决这个问题的标准做法是使用校准数据集重新统计每层的量化范围。听起来简单但校准数据集的选取直接决定了效果。我用的是从真实传感器采样的数据做校准效果比从训练集里随机抽出来的要好很多。后来我又加了一步在量化之后跑了一遍模拟推理逐层检查输出特征图的数值范围和浮点版本的差距把偏差比较大的层单独做了混合精度处理精度损失从最初的三四个百分点降到不足一个百分点。内存布局方面我的经验是尽量复用推理缓冲区。TFLite Micro有个机制叫内存规划器它会把中间计算结果放在同一个缓冲区里以减少RAM占用。但默认设置并不一定对你的模型最优我试过调整arena buffer的大小和初始对齐方式发现对某些网络结构性能提升还挺明显的。5. 调试实录那些最浪费时间的坑5.1 现成开发板能跑自己的板子跑不起来的排查思路自己做的板子第一次上电程序跑不起来这是极大概率会发生的事。关键是怎么系统性地排查而不是瞎猜。我最常用的一套排查顺序是这样的。第一步检查供电。拿万用表量各个电压轨是否正常尤其是电池供电时负载开关和LDO的连接是否正确。第二步确认MCU的时钟是否起振。USB没反应、程序下载不进去多半是时钟配置有问题。第三步用SWD读取MCU的IDCODE能读到说明芯片基本的连接没问题再往里烧Loader。曾经遇到过一种很诡异的情况SWD能连接上但是程序一运行就进HardFault。后来发现是中断向量表配置有问题因为自己的板子没有现成开发板那么默认正确的启动环境在工程配置里把启动文件和链接脚本看得特别紧。这种问题在现成开发板上很难遇到因为厂家已经把样板工程都调好了但自制板子就能真切地体会到从头搭建环境的细节。5.2 传感器读取偶发错误原因竟然在电源我的这块板子在稳定运行一段时间后偶尔会出现传感器读取超时或者数据跳变的问题。一开始怀疑是I2C时序不对调了上拉电阻阻值又加了重试机制问题还是偶发出现。后来用示波器同时抓电源轨和I2C波形发现每次数据跳变之前电源上都有一个非常窄的电压毛刺。追查下去才发现传感器供电的负载开关在切换瞬间会产生一个电流尖峰而这个尖峰恰好耦合到了I2C的SDA线上。解决办法不复杂在传感器电源引脚附近加了一颗大一点的储能电容同时把I2C信号线的走线稍微调整了一下远离电源开关的路径。从那以后传感器读取再也没有出现过偶发错误。这个坑教会我一件事硬件调试里当一个问题偶发到让你想砸电脑的时候优先去看电源质量而不是怀疑协议和软件。大多数偶发问题要么是电源上的噪声要么是地弹真正跟代码逻辑相关的反而好排查。5.3 无线功耗居高不下的真相板子整体的深睡眠电流已经压到很低的水平但整个系统跑起来之后平均电流还是偏高。我用了电流探头去抓无线模块发送周期的波形发现了一个问题模块每次发送完数据之后并没有立刻进入休眠模式而是还在等待一个很长的确认超时这个等待时间里电流一直维持在十几毫安。问题出在驱动代码上。我用的无线模块发送完数据后需要明确调用一个进入休眠的命令如果只是发送完成就直接退出函数模块会停留在接收模式等待应答。看起来是个很傻的错误但确实花了我两天时间才定位。从那之后我在驱动代码里养成了一个习惯任何涉及无线传输的代码路径都要明确地跟踪模块的状态机绝不依赖库函数里隐含的默认行为。6. 重新做一款TinyML开发板的真实心得6.1 什么情况下自制开发板才值得如果你只是刚接触TinyML还在学基础概念那我建议直接买现成的开发板别学我。自制开发板的前期投入非常大从画原理图、调PCB到焊接调试周期少说以月为单位。如果你的目标是快速验证一个想法、跑通一个Demo现成开发板就是最合适的选择。但如果你已经遇到了我前面说的那些问题比如功耗预算卡在现有板子上过不去、传感器接口各种飞线、模型适配被内存布局折腾那自制一块专用板就是值得的。因为它不止是给你一块合手的硬件更能逼着你把整个项目的所有技术细节都想清楚。你会被迫去梳理电源树、引脚分配、内存规划、模型量化这些环节而这些恰恰是做一个真正的TinyML产品最需要的基本功。6.2 硬件和软件要同步迭代不要等板子回来了才写代码自制开发板的过程中最容易犯的另一个错误是把硬件设计周期和软件开发周期完全分隔开。很多人在等PCB打样回来的那些天里什么也不做纯干等板子回来了再开始从头写驱动和调试。这样做非常浪费时间。我的做法是在画完原理图之后就立刻用评估板或者现成开发板开始写和硬件引脚匹配的驱动代码。原理图确定了哪个引脚接传感器、哪个引脚控制电源开关这些信息足够你先写底层驱动了。等自己板子回来的时候大部分驱动代码已经调通了剩下的事情就是针对新板子的引脚差异做映射和微调。这样硬件和软件是并行推进的到了联调阶段就非常顺畅。6.3 如果再做一次我会改掉哪些决定回头看看这次自制开发板的整个过程有几个决定如果能重来我会换个思路处理。第一外壳和结构设计应该更早点介入。我在PCB设计的时候几乎没考虑外壳安装孔的位置和传感器朝向导致后续打样外壳的时候发现两个传感器像装反了又折腾了一阵结构布局。硬件和结构设计同步推进能省掉不少返工的麻烦。第二我会在板子上留一个micro USB接口用于纯供电而不只是调试烧录。当时为了省空间把供电和调试合并成了一个接口结果现场调试时经常遇到共地或者供电不稳的问题。一个额外的供电口能提供很大的调试便利。第三我会更早引入自动化测试。在硬件联调稳定之后其实就可以写一个简单的自检固件一键检查所有传感器通断、电源电压、无线通信质量。我当时偷懒没做后续每次改版都要手动重复测试浪费了大量精力。这个自动化测试固件是我建议所有准备自己设计开发板的人都优先考虑的一个模块。那段时间天天对着示波器和焊台虽然累但每解决一个问题就会对整个系统有更深的理解。等到板子真正稳定地实现低功耗采集、本地推理和无线上报时那种成就感远不是直接用现成开发板跑通一个Demo能比的。如果你也在做类似的边缘AI项目不妨算算你手上的方案里有多少资源是被通用两个字浪费掉的再决定是否也给自己做一块真正顺手的板子。