1. 从一颗传感器说起:为什么要把 AI 塞进设备里
前阵子帮一个做工业设备的朋友看方案,他手里有一台振动监测仪,采样率 2kHz,三轴加速度传感器,原本的做法是把原始数据通过总线传到上位机,由上位机跑 FFT 和简单的阈值判断。问题出在现场:一台网关要接 30 多个节点,带宽吃紧,延迟忽高忽低,偶尔网络抖动还会丢包,导致异常事件漏报。他问我,能不能让传感器节点自己判断“这段振动是不是异常”,只把结论和少量特征传上来。
这个需求其实就是嵌入式人工智能最典型的落地场景——把推理能力从云端或上位机下沉到 MCU 端,让设备在本地完成感知、判断和决策。围绕这个方向,业内通常用TinyML来指代在资源受限设备上跑机器学习模型的技术栈,核心载体是MCU(微控制器),前端是各类传感器。这几个词放在一起,构成了当前设备智能化改造的一条主线。
我写这篇东西,不是要讲什么宏大叙事,而是想把过去几年在几个实际项目里踩过的坑、验证过的路径整理出来。适合谁看?如果你手上有 MCU 开发经验,想给产品加一点“智能判断”的能力;或者你是算法出身,想把模型落到真实硬件上;再或者你只是好奇一颗几块钱的传感器怎么就能“看懂”世界——这篇都能给你一些可直接参考的东西。全文围绕方案选型、模型训练、部署实操、问题排查四个层面展开,尽量把“为什么这么做”讲透,而不是只给一堆结论。
2. 方案整体设计与选型思路拆解
2.1 为什么是“端侧推理”而不是“云端判断”
先把最根本的问题说清楚:为什么要把 AI 放到设备端,而不是继续用云端或网关集中处理?这不是赶时髦,而是被几个现实约束逼出来的。
第一是延迟。工业场景里,一次异常振动从发生到造成损伤可能只有几十毫秒,数据传到云端、推理、再传回控制指令,这个往返时间在公网环境下根本不可控。端侧推理的延迟是确定的,通常在一次采样周期内就能出结果。
第二是带宽与功耗。一颗三轴加速度传感器以 2kHz 采样,16 位精度,每秒产生 12KB 原始数据。30 个节点就是 360KB/s,如果再加上温度、电流等多模态数据,总线压力非常大。而端侧推理只需要上传“正常/异常”加几个特征值,数据量能降两三个数量级。对于电池供电的节点,射频收发是耗电大户,少传数据直接等于延长续航。
第三是隐私与可靠性。有些场景的数据不适合外传,比如涉及工艺参数的振动频谱。另外,网络本身不可靠,端侧能独立判断意味着断网时设备依然能保护自己。
当然,端侧推理不是没有代价。MCU 的算力和内存极其有限,模型必须做得非常小,精度也会打折扣。所以选型的核心逻辑是:先判断这个任务是否真的需要端侧智能,再决定用什么硬件和什么模型。如果任务本身对延迟不敏感、数据量也不大,那老老实实传数据到上位机反而是更省事的选择。
2.2 MCU、传感器与模型三者的匹配关系
确定要走端侧路线后,接下来是硬件和模型的匹配。这里最容易犯的错误是“先选模型再找硬件”或者“先买硬件再想模型”,两者都会导致返工。
我的经验是从任务的数据特征反推。先明确三件事:传感器输出什么形式的数据(是一维时序、图像还是多通道融合)、采样率多高、需要多快的响应。这三件事基本决定了 MCU 的算力下限和内存下限。
举个具体的例子。做振动异常检测,传感器是模拟输出的加速度计,经过 MCU 内部 ADC 采样,采样率 2kHz,每次推理窗口 512 个点,三轴。那么单次推理的输入是 512×3 个 16 位整数,约 3KB。如果用一个 5 层的一维卷积网络,参数量控制在 20KB 以内,中间激活值峰值约 10KB,那么 MCU 至少需要 64KB RAM 和 128KB Flash 才比较从容。这个估算方法后面会详细展开。
传感器这边也有讲究。模拟传感器需要 MCU 的 ADC 采样,灵活但占用 CPU;数字传感器(I2C/SPI 接口)自带 ADC 和滤波,输出直接是数字量,省事但采样率受接口速度限制。像光电传感器、霍尔传感器、颜色传感器这类,很多都有数字输出版本,选型时优先考虑数字接口能省掉不少模拟电路的麻烦。
2.3 工具链选型的几个现实考量
工具链这块,市面上的选择不少,但真正落到项目里,要考虑的是团队熟悉度、芯片支持度、调试便利性三者的平衡。
模型训练侧,TensorFlow 配合 Keras 是最成熟的路径,TFLite Micro 作为推理引擎在 MCU 上的支持也最广。PyTorch 训练完转 ONNX 再转 TFLite 也可行,但中间转换环节容易出问题,尤其是自定义算子。如果团队本来就是 PyTorch 栈,可以走这条路,但要预留调试时间。
MCU 侧,各家厂商都有自己的 AI 工具,比如 ST 的 X-CUBE-AI、NXP 的 eIQ、Infineon 的相关配置工具。这些工具的好处是和自家芯片深度集成,能自动做算子映射和内存规划。缺点是跨平台迁移困难,换芯片基本要重来。
我个人的建议是:如果项目周期紧、团队没有太多 AI 部署经验,优先选厂商工具链,虽然灵活性差一点,但能快速跑通。如果要做长期产品、可能换芯片,那就用 TFLite Micro 这类通用框架,前期投入大但后期迁移成本低。
3. 核心细节解析与实操要点
3.1 数据采集:模型效果的天花板在这里
很多人把精力全放在模型结构上,结果模型在测试集上表现很好,一到现场就拉胯。十有八九是数据的问题。端侧 AI 项目里,数据质量决定了模型效果的上限,模型结构只是逼近这个上限。
采集数据时要注意几个点。首先是标注的准确性。做异常检测,你得清楚每一段数据到底是不是异常,异常的类型是什么。工业现场很多异常是偶发的,采集时要有意识地制造或等待异常发生,同时记录工况参数(转速、负载、温度),否则模型学到的可能是工况差异而不是异常特征。
其次是数据的代表性。训练数据要覆盖设备可能遇到的各种工况,包括正常工况的边界情况。我见过一个项目,训练数据全是空载和满载,结果半载工况下误报率飙升。采集时要有计划地遍历工况空间。
第三是采样参数的一致性。训练时的采样率、量程、滤波设置必须和部署时完全一致。曾经有个坑:训练数据用 1kHz 采样,部署时为了省资源改成 500Hz,频域特征完全对不上,模型直接失效。这个参数一旦定下来,训练和部署必须锁死。
数据量方面,对于简单的分类任务,每类几百到几千个样本通常够用;对于异常检测这种正负样本极不均衡的任务,正常样本可以多采,异常样本哪怕只有几十个也有价值,但要配合数据增强。
3.2 特征工程与模型轻量化的取舍
端侧资源有限,特征工程和模型轻量化是绕不开的。这里有个基本判断:如果传统信号处理方法能解决的问题,就不要上深度学习。
比如振动监测里的轴承故障,用包络谱分析加阈值判断,在 MCU 上几行代码就能实现,算力消耗几乎为零。只有当特征不明显、工况多变、传统方法难以覆盖时,才值得上模型。这个判断能帮你省掉大量不必要的复杂度。
确定要用模型后,轻量化有两条路。一条是手工设计小模型,比如用少量卷积层加全局池化,参数量控制在几十 KB。另一条是用大模型训练再压缩,通过剪枝、量化、知识蒸馏把模型变小。前者可控性强,后者上限更高但流程复杂。
我倾向于先用手工小模型跑通 baseline,再考虑是否用压缩技术提升。因为端侧部署的调试成本很高,模型越简单,出问题时越容易定位。
量化是必做的环节。把 float32 权重转成 int8,模型体积缩小 4 倍,推理速度提升 2-4 倍,精度损失通常在 1-2 个百分点以内。但要注意,量化不是无脑转就行,需要提供代表性校准数据集,让量化工具统计激活值的动态范围。校准集要覆盖实际工况,否则量化后的模型在某些工况下会崩。
3.3 内存与算力的精确估算方法
这是实操中最容易翻车的地方。模型训练时在 PC 上跑得好好的,一放到 MCU 上就报内存不足或者跑不动。所以部署前必须做精确估算。
Flash 占用主要来自三部分:模型权重、推理引擎代码、应用代码。权重占用可以直接从量化后的模型文件大小读出。推理引擎代码取决于用了多少算子,TFLite Micro 的基础运行时约 20-30KB,每增加一类算子再加几 KB。
RAM 占用分静态和动态。静态是权重(如果放在 RAM 里执行)和持久化缓冲区;动态是推理时的中间激活值。中间激活值的峰值可以用工具分析,也可以手工估算:对于卷积网络,峰值通常出现在前几层,因为特征图尺寸大。
一个实用的估算公式:RAM 需求 ≈ 输入张量大小 + 最大中间层张量大小 × 2(双缓冲)+ 栈空间(通常 4-8KB)。按这个算出来的值再留 30% 余量,基本不会翻车。
算力方面,MCU 的主频和是否有硬件加速单元(如 DSP 指令、NPU)决定推理时间。一个粗略的参考:Cortex-M4 在 80MHz 下,int8 卷积大约能做到 1-2 MAC/周期。如果你的模型有 100 万次乘加运算,推理时间大约在 10-20ms。这个量级对于 2kHz 采样、512 点窗口(256ms)的任务是够用的,但如果采样率更高或窗口更短,就要考虑带 NPU 的芯片。
4. 实操过程与核心环节实现
4.1 从零搭建一个振动异常检测的完整流程
下面用一个具体项目把整个流程串起来。任务:用一颗三轴加速度传感器,在 MCU 上实现电机振动异常检测,采样率 2kHz,窗口 512 点,输出正常/异常二分类。
第一步,数据采集与标注。用开发板加传感器搭一个采集节点,通过串口把原始数据传到 PC 存成 CSV。正常数据采集电机在多种转速、负载下的运行数据,每种工况至少 5 分钟。异常数据通过人为制造(如松动螺栓、加不平衡质量)来获取,每种异常至少 1 分钟。标注时记录每段数据的工况和异常类型。
第二步,数据预处理与特征分析。在 PC 上用 Python 做初步分析,画时域波形和频谱,确认异常在频域上是否有明显特征。这一步的目的是判断任务难度,如果频谱上一眼就能看出差异,说明传统方法可能就够用;如果差异微弱或随工况变化,才需要模型。
第三步,模型设计与训练。用一个简单的一维卷积网络:
import tensorflow as tf from tensorflow.keras import layers, models def build_model(input_len=512, channels=3): inputs = layers.Input(shape=(input_len, channels)) x = layers.Conv1D(16, 7, strides=2, activation='relu', padding='same')(inputs) x = layers.MaxPooling1D(2)(x) x = layers.Conv1D(32, 5, strides=2, activation='relu', padding='same')(x) x = layers.MaxPooling1D(2)(x) x = layers.Conv1D(64, 3, strides=2, activation='relu', padding='same')(x) x = layers.GlobalAveragePooling1D()(x) x = layers.Dense(32, activation='relu')(x) outputs = layers.Dense(1, activation='sigmoid')(x) return models.Model(inputs, outputs) model = build_model() model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])这个模型参数量大约 15KB(float32),量化后约 4KB。训练时用 80% 数据训练,20% 验证,注意按工况分层划分,避免同一工况的数据同时出现在训练和验证集里。
第四步,模型转换与量化。训练完成后转成 TFLite 格式,并做 int8 量化:
converter = tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations = [tf.lite.Optimize.DEFAULT] converter.representative_dataset = representative_data_gen converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type = tf.int8 converter.inference_output_type = tf.int8 tflite_model = converter.convert() open('model_int8.tflite', 'wb').write(tflite_model)representative_data_gen要提供覆盖各种工况的校准数据,通常 100-500 个样本就够。
第五步,MCU 端集成。把 tflite 模型转成 C 数组,集成到工程里。推理流程是:ADC 采样 512 点 → 数据归一化到 int8 范围 → 调用推理 → 读取输出 → 判断。这里要注意,归一化参数必须和训练时一致,训练时用的均值和方差要硬编码到 MCU 代码里。
第六步,现场验证与迭代。部署到实际设备上跑一段时间,记录误报和漏报情况。如果误报多,检查是不是某些正常工况没覆盖到;如果漏报多,检查异常样本是否足够或模型是否欠拟合。根据反馈补充数据、重新训练、重新部署。
4.2 关键参数的确定过程
上面流程里有几个参数需要仔细确定,这里展开说。
窗口长度 512 点怎么来的?采样率 2kHz,512 点对应 256ms。这个长度要能覆盖至少一个完整的振动周期。电机转速假设 1500rpm,即 25Hz,一个周期 40ms,256ms 覆盖 6 个周期,足够提取频域特征。如果转速更低,窗口要相应加长。窗口太短,频域分辨率不够;窗口太长,响应延迟大且内存占用高。
模型层数和通道数怎么定?从任务复杂度出发。振动异常检测的特征相对明确,不需要很深的网络。我试过 3 层卷积和 5 层卷积,效果差异不大,但 5 层的内存占用翻倍。所以选了 3 层。通道数从 16 起步,逐层翻倍,这是卷积网络的常规做法,能在控制参数量的同时保证特征提取能力。
量化校准集怎么选?从训练集里按工况分层抽样,每种工况抽 50 个样本,总共 300 个左右。校准集要包含正常和异常样本,比例接近实际分布。如果校准集全是正常样本,量化后模型对异常的敏感度会下降。
4.3 部署现场的实操记录
实际部署时,有几个细节值得记录。
ADC 配置。用 MCU 内部 ADC 采样模拟传感器,要注意采样时钟和转换时间的匹配。2kHz 采样率不算高,但三轴轮询采样时,如果 ADC 转换时间太长,会导致通道间有时间偏差。解决办法是用定时器触发 ADC,配合 DMA 搬运,让采样在后台自动进行,CPU 只处理数据。
内存布局。TFLite Micro 需要一块“张量竞技场”内存,所有中间张量都在这块内存里分配。这块内存的大小要精确设置,太小会报错,太大会浪费。可以用工具分析出精确值,也可以先设大一点,跑通后再逐步缩小。
实时性保障。推理任务要和采样任务协调好。我的做法是双缓冲:一个缓冲区在采样时,另一个缓冲区在推理,采满后交换。这样采样不中断,推理也不阻塞采样。但要注意,推理时间必须小于采样窗口时间,否则会丢数据。实测这个模型在 80MHz Cortex-M4 上推理约 15ms,远小于 256ms 的窗口时间,余量充足。
日志与调试。MCU 上调试不方便,我习惯在关键节点打日志,通过串口输出。比如记录每次推理的输入均值、输出值、推理耗时。这些日志在排查问题时非常有用。但要注意日志本身会占用时间和空间,正式版本要精简。
5. 常见问题与排查技巧实录
5.1 模型部署阶段的典型问题
问题一:模型转换后精度大幅下降。最常见的原因是量化校准集不具代表性。排查方法是先用 float32 模型在 MCU 上跑(如果内存够),对比 int8 和 float32 的输出差异。如果差异大,重新选校准集。另一个原因是输入数据的归一化参数不一致,检查训练和部署时的预处理是否完全相同。
问题二:推理结果全是同一个值。通常是输入数据的问题。检查 ADC 采样是否正常,数据是否真的在变化。也可能是归一化时除了零或者溢出,导致输入全是 0 或饱和值。在 MCU 上打印输入张量的最小值和最大值,一眼就能看出来。
问题三:内存不足报错。TFLite Micro 会明确报出需要多少内存。如果超出,先检查张量竞技场设置是否合理,再考虑减小模型或换更大内存的芯片。有时候是栈空间不够,推理时的局部变量把栈撑爆了,这种情况把栈调大即可。
问题四:推理时间超出预期。先确认 MCU 主频是否跑满,有些工程默认用内部低速时钟。再检查是否开了硬件 FPU 和 DSP 指令,int8 推理用不上 FPU,但 DSP 指令能加速卷积。如果还是慢,考虑降低模型复杂度或换带 NPU 的芯片。
5.2 现场运行阶段的排查思路
现场问题和实验室问题不一样,往往是间歇性的,排查难度大。我的经验是先建立基线,再找偏差。
先在实验室环境下记录正常运行的各项指标:推理输出值的分布、推理耗时、内存使用。部署到现场后,持续记录同样的指标。一旦出现异常行为,对比指标找偏差。比如误报增多,看推理输出值是不是整体偏移了,如果是,可能是传感器漂移或工况变化;如果是个别尖峰,可能是干扰。
传感器漂移是现场常见问题。温度变化、老化都会导致传感器输出偏移。解决办法是定期校准,或者在预处理里做自适应归一化。但自适应归一化要小心,如果异常持续存在,归一化会把异常“学”成正常,导致漏报。我的做法是用一个长窗口的滑动统计做基线,短窗口做检测,两者结合。
电磁干扰在工业现场很普遍。表现为数据里出现规律性的尖峰。硬件上加滤波电容、用屏蔽线、远离动力线;软件上加中值滤波或限幅。但滤波会损失高频信息,如果异常特征在高频段,要谨慎。
工况变化导致的误报,本质是模型泛化能力不足。解决办法是补充新工况的数据重新训练。如果工况变化频繁且难以穷举,可以考虑在线学习或自适应阈值,但这两者在 MCU 上实现复杂,要权衡。
5.3 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 精度下降 | 量化校准集不具代表性 | 对比 float32 与 int8 输出 | 重选校准集,覆盖全工况 |
| 输出恒定 | 输入数据异常 | 打印输入张量范围 | 检查 ADC、归一化 |
| 内存不足 | 竞技场或栈设置不当 | 查看报错信息 | 调整内存配置或换芯片 |
| 推理超时 | 主频未跑满或算子低效 | 测量实际推理耗时 | 提频、换算子、换芯片 |
| 现场误报 | 传感器漂移或干扰 | 对比实验室基线 | 校准、滤波、补数据 |
| 现场漏报 | 异常样本不足或阈值过高 | 检查异常检出率 | 补异常数据、调阈值 |
| 间歇性崩溃 | 栈溢出或内存越界 | 加栈保护、开内存检查 | 增大栈、修复越界 |
5.4 几条踩坑换来的经验
不要迷信模型,先做好信号处理。很多项目失败不是因为模型不好,而是因为前端信号质量差。传感器选型、电路设计、采样时序,这些基础工作做扎实,模型效果自然好。
量化不是万能的。有些模型量化后精度掉得厉害,尤其是输出层对数值敏感的任务。如果量化后效果不达标,可以尝试混合量化,只量化部分层。
留足调试接口。MCU 资源紧张,但调试接口不能省。至少留一个串口用于输出日志,留一个 GPIO 用于指示状态。现场排查时,这些接口能救命。
版本管理要严格。模型、固件、配置参数要一起版本化。我见过因为模型和固件版本不匹配导致现场设备行为异常的案例,排查了很久才发现是版本问题。
现场数据要回收。设备部署后,定期回收现场数据用于模型迭代。可以设计一个机制,在设备空闲时上传少量代表性数据。这些数据是模型持续改进的燃料。
6. 这套方案还能怎么扩展
上面讲的是一维时序振动的例子,但嵌入式 AI 的适用范围远不止于此。把思路打开,同样的方法论可以迁移到很多场景。
多传感器融合是一个自然的方向。振动加温度加电流,多模态输入能提升判断准确率。但要注意,多模态意味着数据量增加,MCU 的内存和算力压力更大。可以先用简单的特征级融合,把各传感器的特征拼在一起送进模型,而不是原始数据级融合。
视觉类任务在 MCU 上也可行,但要用极轻量的模型,比如 MobileNet 的裁剪版,输入分辨率降到 96×96 甚至更低。颜色传感器、光电传感器阵列这类,本质上也是低分辨率视觉,处理思路类似。
在线自适应是进阶方向。设备在运行中持续采集数据,用无监督方法检测分布变化,自动调整模型或阈值。这在 MCU 上实现有难度,但简单的统计自适应是可行的。
与上位机协同也值得考虑。端侧做快速判断,把不确定的样本上传给上位机做精细分析,上位机的结论再反馈给端侧更新模型。这种边缘-云端协同架构能兼顾实时性和准确性。
我个人在实际操作中的体会是,嵌入式 AI 项目的成败,七分在数据和工程,三分在模型。把传感器选好、数据采好、工程做扎实,模型哪怕简单一点也能出效果。反过来,模型再花哨,前端一塌糊涂,现场一定翻车。所以如果你正准备启动这类项目,建议先把数据采集和信号处理这条链路打通,再考虑模型的事。另外,别一上来就追求端到端,先用传统方法做个 baseline,知道任务的难度边界在哪里,再决定要不要上模型、上多大的模型。这个顺序能帮你省下大量时间和返工成本。