基于WIO Terminal与TinyML的气味感知机器人:从硬件搭建到边缘AI部署
1. 项目缘起当“嗅觉”遇见“轮子”几年前我在一个关于环境监测的创客马拉松上看到过一个挺有意思的挑战如何让一个移动的机器人去主动“嗅探”并定位一个未知的气味源。当时大部分方案都集中在固定传感器网络或者昂贵的工业机器人上。这让我萌生了一个想法能不能用更亲民、更“好玩”的硬件做一个既能满地跑又能识别特定气味的机器人呢这个想法一直搁置着直到我最近深度体验了WIO Terminal和Edge Impulse这套组合拳才觉得时机成熟了。于是就有了这个“Smell Sensing Drivable Hedgehog Robot”——一个可遥控驾驶、具备气味感知能力的“刺猬”机器人。它的核心目标很简单赋予一个普通的遥控小车以“嗅觉”让它不仅能被人为控制着四处跑还能在跑动过程中实时“闻”到周围的气味并对特定气味比如酒精、氨气、烟雾做出反应比如亮灯、鸣叫或者记录数据。这听起来有点像给玩具车装上了警犬的鼻子但其背后的技术栈——Arduino生态、TinyML微型机器学习和气体传感器——却充满了极客的乐趣和实用的潜力。这个项目非常适合那些已经玩腻了循迹、避障小车想向“环境感知”和“边缘AI”迈出第一步的硬件爱好者、学生和创客。你不需要有深厚的机器学习背景因为Edge Impulse这个平台已经把模型训练的门槛降得非常低了。你需要的是对硬件接线的一点耐心对代码逻辑的基本理解以及最重要的——把想法变成实物的动手热情。接下来我会从头到尾拆解这个“嗅觉刺猬”的打造过程从硬件选型、气味数据采集、模型训练到最后的集成与驱动分享我踩过的每一个坑和收获的每一个技巧。2. 硬件拼图为什么是WIO Terminal MQ系列传感器打造一个嗅觉机器人硬件选型是地基。我的核心思路是主控要足够强大以运行轻量级AI模型传感器要专一且稳定底盘要灵活可靠。经过一番对比和实测我锁定了下面这套组合。2.1 主控大脑WIO Terminal的压倒性优势为什么放弃更常见的Arduino Uno或ESP32而选择WIO Terminal原因在于它是一个“All-in-One”的解决方案完美契合我们这个项目的需求。首先它内置了LCD屏幕、按键、蜂鸣器和光传感器这为我们省去了大量外接模块和连线的麻烦。屏幕可以直接显示当前检测到的气体浓度、模型推理结果或机器人状态按键可以用来切换模式比如手动驾驶模式、自动嗅探模式蜂鸣器可以在检测到目标气味时发出警报。光这一点就让它从一堆需要“插面包板”的开发板中脱颖而出。其次它的核心是一颗ATSAMD51P19微控制器主频120MHz拥有192KB RAM和8MB外部Flash。这个配置对于运行TinyML模型来说比Arduino Uno2KB RAM, 32KB Flash要富裕得多甚至比一些基础款的ESP32通常520KB SRAM在特定优化下更有优势。充足的RAM保证了模型推理过程中中间张量运算的流畅性。最关键的是WIO Terminal对Edge Impulse的支持是“开箱即级”的。Seeed StudioWIO的制造商提供了官方的Edge Impulse固件和详细教程你可以通过USB直接将它变成一个数据采集设备并将训练好的模型一键部署回板子上整个过程丝滑流畅。相比之下在Arduino Uno上部署TinyML模型你往往需要和内存搏斗裁剪再裁剪模型而在ESP32上虽然也能做但前期环境搭建和库的兼容性有时会让人头疼。WIO Terminal在易用性和性能之间取得了很好的平衡。注意WIO Terminal的8MB Flash中有一部分被系统占用实际可用于存储模型和文件系统的空间大约在3-4MB。对于绝大多数轻量级的气体分类模型来说这完全足够了。2.2 嗅觉器官MQ系列气体传感器的选择与局限“嗅觉”的核心是气体传感器。我选择了经典的MQ系列模拟传感器具体来说是MQ-3酒精、MQ-135空气质量/有害气体和MQ-2可燃气体/烟雾。选择它们的原因很直接便宜、易用、资料多。这些传感器的工作原理类似其核心是一个由金属氧化物半导体构成的敏感元件。当目标气体分子吸附在半导体表面时会引起其电导率的变化从而改变其与一个负载电阻串联分压后的输出电压。我们通过WIO Terminal的模拟输入引脚ADC读取这个电压值就能得到一个与气体浓度大致相关的信号。但是这里有几个必须清楚的局限性直接影响了我们后续的数据处理和模型设计非特异性与交叉敏感MQ-3虽然对酒精最敏感但它也会对苯、氢气等有反应。MQ-135更是“广谱”传感器对二氧化碳、氨气、硫化物、苯系物都有响应。这意味着传感器读数高不一定就是你的目标气体。我们的模型任务很大程度上就是在学习这种复杂的、交叉的响应模式。预热与漂移MQ传感器需要通电预热一段时间通常5-30分钟才能输出稳定值。即使预热后其基线也会随着时间、环境温湿度缓慢漂移。因此不能依赖绝对的电压值而应该关注相对变化或使用差分信号。模拟信号的噪声传感器的输出是模拟量容易受到电源噪声和电磁干扰。必须在硬件如添加滤波电容和软件如滑动平均滤波上做处理。在我的项目中我将三个MQ传感器都连接到了WIO Terminal的模拟引脚A0、A1、A2。每个传感器都需要接上其对应的负载电阻通常买来的模块已经集成好了并提供一个5V的加热电压Vcc和一个独立的模拟输出AOUT。接线时务必确保加热电压稳定这是传感器正常工作的前提。2.3 身体与四肢底盘、电机与驱动为了让“刺猬”动起来我选择了一个非常常见的两轮差分驱动小车底盘。这种底盘由两个带减速箱的直流电机分别驱动左右轮通过控制两个电机的转速和方向就能实现前进、后退、左转、右转以及原地旋转非常灵活。电机驱动芯片我用了L298N双H桥模块。这是一个经久不衰的选择驱动能力足够单桥2A价格便宜而且有现成的Arduino库如AFMotor支持。将L298N的输入控制引脚IN1, IN2, IN3, IN4连接到WIO Terminal的数字IO口接收PWM信号来控制电机速度和方向。电源方面我使用了两套独立的电源一套7.4V的2S锂电池为L298N和电机供电另一套通过一个降压模块降到5V为WIO Terminal和传感器模块供电。强烈建议电机和逻辑电路电源隔离否则电机启动和急停时产生的大电流波动会严重干扰微控制器和传感器的稳定工作导致程序跑飞或传感器读数乱跳这是我早期调试时踩过的一个大坑。最后为了实现“可驾驶”我增加了一个蓝牙模块HC-05或HC-06连接到WIO Terminal的串口引脚。这样我就可以用手机上的蓝牙串口APP比如Serial Bluetooth Terminal发送简单的字符命令如‘F’前进‘B’后退‘L’左转‘R’右转来遥控机器人。将驾驶控制与嗅觉感知的逻辑在代码中结合就构成了机器人的核心行为。3. 数据的“气味”采集、预处理与特征工程有了硬件下一步就是教机器人“闻味儿”。这是整个项目中最需要耐心和技巧的环节直接决定了后续AI模型的好坏。我的经验是垃圾数据进垃圾模型出。在TinyML的世界里高质量的数据比复杂的模型结构更重要。3.1 设计数据采集场景我设定了三种需要机器人识别的“气味场景”场景A酒精在机器人前方放置一小杯医用酒精。场景B烟雾点燃一小段蚊香模拟烟雾环境注意通风和安全。场景C背景空气在通风良好的室内作为对照。采集数据时我使用Edge Impulse Studio的“数据采集”功能。将WIO Terminal通过USB连接到电脑在Edge Impulse中选择它作为采集设备并设置采样频率。对于气体传感器这种变化相对缓慢的信号10Hz每秒10个样本的采样率已经绰绰有余设置过高只会增加无用的数据量和计算负担。采集过程是这样的将机器人放置在场景C中静置1分钟让传感器稳定然后开始采集30秒的“背景空气”数据在Edge Impulse中将其标签设为“background”。将机器人缓慢推向场景A酒精杯在靠近的过程中开始采集持续30秒标签设为“alcohol”。然后移开等待传感器读数恢复。同样方法采集场景B烟雾的数据标签设为“smoke”。关键技巧每个场景我重复采集了至少20组数据。并且在采集“alcohol”和“smoke”时我会有意地改变机器人与气味源的距离、相对角度甚至让机器人轻微移动。这样采集到的数据包含了浓度梯度变化和一定的空间信息能让模型学习到更鲁棒的模式而不是死记一个固定的电压值。3.2 信号预处理从原始电压到干净波形从WIO Terminal ADC读出来的原始值0-1023对应0-3.3V噪声很大不能直接喂给模型。Edge Impulse提供了强大的DSP数字信号处理模块来处理这些数据。我的预处理流水线如下滑动平均滤波在数据上传到Edge Impulse之前我在Arduino代码里就对每个传感器的读数做了一个窗口大小为5的简单滑动平均。这能平滑掉一些高频毛刺。Edge Impulse DSP配置窗口大小我设置为2000ms。因为气体扩散和传感器响应需要时间一个较长的窗口能包含一个完整的气味“事件”。窗口增量设置为500ms。这意味着窗口之间有75%的重叠能生成更多训练样本也有利于提高模型在连续推理时的响应速度。频谱分析对于缓慢变化的传感器信号我没有使用FFT等频谱特征。相反我直接使用了“原始特征”Raw features让后续的神经网络自己去学习时间序列中的模式。同时我勾选了“计算均值、标准差、偏度、峰度等统计特征”作为补充。这些统计量能刻画一个时间窗口内信号的整体分布形状。经过DSP处理后原本的3路传感器原始时间序列数据被转换成了一个个固定长度的“特征向量”每个向量代表一个2000ms时间窗口内气味信号的数字化摘要。3.3 特征可视化与洞察在Edge Impulse的“特征探索”页面你可以看到所有样本数据经过DSP处理后的三维散点图通过PCA降维。这是一个极其重要的步骤。理想情况下不同标签的数据点比如“alcohol”的蓝点、“smoke”的绿点、“background”的红点应该在图上形成各自独立的簇。如果它们混杂在一起说明当前采集的数据或提取的特征区分度不够模型很难学会分类。在我的第一次尝试中“alcohol”和“background”的点团有部分重叠。我回溯检查发现是因为采集“background”数据时房间内偶尔有微弱的酒精残留比如消毒液。于是我改在完全无酒精源的阳台重新采集背景数据重叠问题就大大改善了。这个可视化工具让你在训练模型前就能对数据的质量有一个直观的判断避免盲目训练节省大量时间。4. 构建“嗅觉大脑”TinyML模型训练与部署数据准备好了接下来就是打造机器人的“大脑”——一个能在WIO Terminal上运行的轻量级神经网络模型。4.1 模型选择与配置Keras与神经网络在Edge Impulse的“脉冲设计”环节我选择了“Keras”作为学习块。相比于更简单的“迁移学习图像”或“异常检测”Keras允许我自定义一个适用于时间序列分类的神经网络结构。我设计的模型结构并不复杂但针对传感器数据特点做了优化输入层 (Input) - 扁平层 (Flatten) - 全连接层 (Dense, 20个神经元ReLU激活) - Dropout层 (0.25) - 输出层 (Dense, 3个神经元Softmax激活)为什么用全连接网络而不是RNN或1D-CNN对于只有3个传感器通道、且窗口时间不长的数据一个浅层的全连接网络已经足够捕捉特征。RNN和CNN虽然强大但会带来更多的参数和计算量在WIO Terminal上可能影响实时性。我们的原则是在保证精度的前提下模型越小越好。Dropout层的作用随机“丢弃”一部分神经元的输出这是一种防止模型过拟合的正则化手段。对于数据量有限的我们来说这能帮助模型学得更泛化的规律而不是死记硬背训练数据。输出层3个神经元对应我们的3个类别alcohol, smoke, backgroundSoftmax函数将输出转换为概率分布表示属于每个类别的可能性。训练参数设置如下训练周期30个epoch。我观察到在20个epoch后验证集损失就基本不再下降设30是为了确保充分训练。学习率0.0005。一个较小的学习率能让训练更稳定避免震荡。验证集比例20%。Edge Impulse会自动从训练数据中分出这部分数据用于在训练过程中评估模型泛化能力防止过拟合。4.2 模型训练、评估与性能分析点击“开始训练”后几分钟内就能完成。训练完成后Edge Impulse会给出详细的性能报告。我最关注以下几个指标准确率我的模型在验证集上达到了**94.2%**的准确率。这是一个相当不错的起点。混淆矩阵这个矩阵告诉我模型具体在哪里犯错。在我的矩阵中大部分错误发生在“smoke”被误判为“background”或者反过来。这很合理因为低浓度的烟雾和干净的空气对MQ传感器的刺激可能比较接近。而“alcohol”的识别率则接近100%。模型概览Edge Impulse会显示模型的内存占用RAM和存储占用Flash。我的模型大约占用15KB的RAM和70KB的Flash远低于WIO Terminal的硬件限制这让我很放心。接下来我使用Edge Impulse的“模型测试”功能用之前完全没参与训练的一小部分数据来测试模型。最终测试准确率为92.5%与验证集准确率接近说明模型没有严重过拟合泛化能力可以接受。4.3 部署到设备从云端到指尖模型训练和验证都在云端完成最后一步就是将它“塞进”WIO Terminal。在Edge Impulse的“部署”页面选择“WIO Terminal”作为目标设备然后选择“Arduino Library”的部署方式。这会生成并下载一个包含模型参数和完整推理库的Arduino项目压缩包。将这个库解压到你的Arduino IDE的libraries文件夹下。然后你可以在Arduino IDE的示例中找到这个新库里面会有一个完整的、可运行的推理示例代码。这个示例代码已经包含了初始化传感器、读取数据、运行DSP预处理、执行模型推理的全套流程。这里有一个至关重要的步骤你需要将示例代码中关于传感器数据采集的部分替换成你自己的实际硬件连接代码。例如示例可能假设数据来自内置的IMU而你需要改成从A0、A1、A2引脚读取MQ传感器的模拟值。同时你还需要将DSP预处理环节中特征提取的配置与你之前在Edge Impulse Studio里设计的窗口大小、特征类型等严格对应起来。如果这里配置错误模型接收到的特征向量格式不对推理结果就会完全错误。将修改好的代码编译并上传到WIO Terminal。如果一切顺利打开串口监视器你就能看到机器人实时“闻”到的气味以及模型给出的分类结果和置信度了。这一刻你会感觉你真的赋予了硬件以“智能”。5. 系统集成与行为逻辑让刺猬“活”起来现在我们有了一个能跑的车底盘驱动一个能思考的大脑TinyML模型接下来要把它们整合起来并赋予机器人有趣的行为逻辑。这是项目从“演示”走向“实用”的关键。5.1 代码架构状态机与多任务处理在资源有限的微控制器上编程清晰的架构至关重要。我采用了基于状态机和非阻塞定时的简单多任务模型避免使用delay()这类会阻塞整个程序的函数。我定义了机器人的几种状态STATE_IDLE空闲状态等待命令。STATE_MANUAL手动驾驶模式通过蓝牙接收指令控制移动。STATE_AUTO_SNIFF自动嗅探模式在此状态下机器人会缓慢前进并持续进行气味检测。主程序loop()函数的核心逻辑如下void loop() { unsigned long currentMillis millis(); // 获取当前时间 // 任务1检查蓝牙指令每50ms一次 if (currentMillis - previousBtMillis 50) { previousBtMillis currentMillis; checkBluetoothCommand(); // 此函数会更新机器人状态和电机控制指令 } // 任务2气味检测与推理每500ms一次 if (currentMillis - previousSniffMillis 500) { previousSniffMillis currentMillis; if (currentState STATE_AUTO_SNIFF) { performSmellDetection(); // 读取传感器运行EI模型推理 reactToSmell(); // 根据推理结果做出反应 } } // 任务3更新电机控制每100ms一次 if (currentMillis - previousMotorMillis 100) { previousMotorMillis currentMillis; updateMotorSpeed(); // 根据当前指令更新PWM输出 } // 其他任务如更新显示等... }这种非阻塞的方式确保了蓝牙控制响应及时、电机运行平滑同时气味检测也能以固定的频率稳定进行互不干扰。5.2 嗅觉触发行为设计在reactToSmell()函数中我设计了机器人在不同气味下的反应检测到“alcohol”且置信度 0.8机器人立即停车stop()蜂鸣器以短促音“滴滴”响屏幕显示一个酒杯图标和“Alcohol Detected!”。同时通过蓝牙向手机APP回传一条消息。这个设计模拟了发现目标气味源后“报警并等待指示”的行为。检测到“smoke”且置信度 0.7机器人同样停车蜂鸣器发出连续的警报声屏幕显示火焰图标和“Smoke Warning!”。考虑到烟雾可能意味着危险这个反应更强烈。检测到“background”或置信度低于阈值在自动嗅探模式下机器人会以缓慢的速度向前移动屏幕显示一个鼻子在嗅探的动画表示正在搜索气味。置信度阈值的设定是一个经验值。一开始我设为0.9发现机器人反应迟钝经常“闻到”了也不停车。降到0.7又过于敏感容易误报。最终通过反复测试对不同的气体设定了不同的阈值酒精0.8烟雾0.7在响应速度和准确性之间取得了平衡。5.3 蓝牙遥控协议与手机端交互蓝牙通信协议设计得尽可能简单。我定义了几个单字符命令F前进B后退L左转R右转S停止A切换至自动嗅探模式M切换至手动驾驶模式在手机端我使用Serial Bluetooth Terminal这个APP将其键盘布局自定义成这些命令按钮一键发送操作非常直观。机器人也会将重要的状态信息如[MODE] AUTO、[SMELL] ALCOHOL 85%回传到手机APP实现双向通信。6. 实测、调优与进阶思考将所有部分组装好烧录代码就是激动人心的实测环节了。这个过程远非一帆风顺但正是解决问题的过程带来了最大的收获。6.1 遇到的主要问题与解决方案电机干扰导致传感器读数跳变现象在机器人移动尤其是电机启动或转向时气体传感器的读数会出现剧烈的、不规律的跳变导致模型误判。排查首先用万用表监测给WIO Terminal和传感器供电的5V电压发现在电机动作时电压有轻微但快速的毛刺。这说明尽管电源隔离了但通过地线或空间电磁辐射干扰依然存在。解决硬件在每一个传感器模块的VCC和GND之间并联一个100uF的电解电容滤波低频波动和一个0.1uF的陶瓷电容滤波高频噪声。在电机的两个引脚之间焊接一个0.1uF的陶瓷电容以吸收电刷产生的火花噪声。软件在performSmellDetection()函数中我增加了一个简单的“运动抑制”逻辑。在读取传感器数据前先检查最近一次电机控制指令是否不是“停止”。如果是运动状态则跳过本次检测直接返回“background”结果。虽然这会降低检测频率但极大地提高了检测时的信号质量。效果硬件滤波结合软件逻辑后传感器读数在静止和运动状态下都变得非常稳定模型误报率显著下降。模型在边缘场景下置信度波动大现象当气味浓度处于临界状态比如酒精杯拿远一点模型会在“alcohol”和“background”之间反复横跳置信度在0.5左右徘徊。解决我引入了置信度迟滞和多数投票机制。迟滞当从“background”切换到“alcohol”时需要置信度连续3次超过0.8才确认而从“alcohol”切换回“background”时需要置信度连续2次低于0.6才确认。这避免了在临界点的频繁抖动。投票记录最近5次的推理结果取出现次数最多的类别作为最终输出。这利用了时间上的连续性使判断更平滑。效果机器人的行为变得“沉稳”了很多不会因为一次偶然的读数波动而大惊小怪。6.2 性能优化与续航考量推理速度在WIO Terminal上一次完整的推理包括数据读取、DSP特征提取、神经网络前向传播大约需要120ms。这对于我们500ms一次的检测周期来说完全够用还有充足的CPU时间处理其他任务。功耗最大的耗电大户是三个MQ传感器的加热器。为了延长续航我修改了代码在STATE_IDLE状态下每隔10秒才给传感器通电预热并检测一次只有在STATE_AUTO_SNIFF模式下才以全功率持续工作。电机仅在移动时耗电。通过合理的电源管理一套2000mAh的电池可以支持机器人连续工作1-2小时。模型量化Edge Impulse在部署时默认已经对模型进行了int8量化。这是TinyML的核心技术之一将模型权重和激活值从浮点数转换为8位整数不仅能将模型体积缩小约75%还能利用微控制器的整数计算单元大幅提升推理速度同时降低功耗。这是我们的小模型能在WIO Terminal上流畅运行的根本。6.3 项目的延伸想象这个“嗅觉刺猬”机器人是一个完美的起点你可以基于它拓展出很多有趣的方向多传感器融合除了气体传感器可以加入温湿度传感器如DHT11、PM2.5传感器打造一个移动的微型环境监测站。地图构建与气味溯源结合一个廉价的编码器或里程计可以粗略估计机器人的移动轨迹。当检测到目标气味时记录下当前位置。通过多次探测或许能在屏幕上画出一个简单的“气味浓度热力图”并尝试向浓度更高的区域自主移动实现简单的气味源定位。云端数据同步给WIO Terminal配上Wi-Fi功能比如使用WIO Terminal的无线扩展模块可以将检测到的事件时间、地点、气体类型、浓度估计上传到物联网平台如ThingsBoard、Blynk实现远程监控和历史数据查询。更复杂的模型尝试使用1D-CNN来更好地捕捉传感器信号中的时间序列模式或者尝试“异常检测”模型用于发现未知的、异常的气味。回过头看这个项目最大的价值不在于做出了一个多么精密的仪器而在于完整地走通了一个嵌入式AI产品的原型流程从问题定义、硬件选型、数据采集、模型训练与部署到最后的系统集成与调试。它让你亲身体会到在资源受限的设备上让AI“跑起来”需要考虑的远不止算法精度还有信号稳定性、功耗、实时性、成本等一系列工程化问题。下一次当你闻到某种特殊气味时或许你会想能不能也教一个小机器把它识别出来呢动手去试答案就在你的代码和焊点里。