ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Arduino与离线语音模块的智能垃圾桶DIY指南

基于Arduino与离线语音模块的智能垃圾桶DIY指南

如果你在厨房垃圾桶前犹豫过要不要伸手去掀那个沾满油渍的盖子,那你已经懂了这个项目最原始的动机。我做的这个基于Arduino的语音识别垃圾箱,说白了就是让普通垃圾桶听懂一句话就自动开盖、保持几秒再自动合上,全程不碰桶盖。比起那些挂在云端、要联网才能响应的智能音箱式方案,这个用离线语音模块的版本断电也能用,识别也在本地完成,特别适合放在厨房角落、走廊过道,也适合当成电子入门课的综合练手项目。

1. 这个项目解决的真实痛点与整体架构拆解

1.1 为什么需要"听得懂话"的垃圾箱

先说几个真实到不能再真实的场景。厨房垃圾桶盖子内侧经常粘着菜叶、汤汁,你把盖子掀起来,手指就得很小心地避开那些位置;公共走廊的垃圾桶为了挡异味,一般会做常闭盖,但每次丢垃圾都要用另一只手压住盖子,提着奶茶腾不出手的时候特别狼狈;学校宿舍里的垃圾桶盖,慢慢就变成了"永远敞着"——因为大家都嫌麻烦,结果异味全跑出来。

语音识别垃圾箱解决的就是"无接触开盖"这个需求。核心体验是:你站在桶前说一句"开盖",盖子自己抬起来,你丢完垃圾转身走,过了设定时间盖子自己合上。整个过程手完全不需要接触桶盖。如果是公共场景,还能避免有人用手猛拍盖子导致转轴损坏的问题——按键和触摸开关其实也能做到无接触开盖,但语音在"手上拿着东西、腾不出手"的时候优势最明显。

这个项目还有一个隐藏价值:它是很好的物联网入门综合实验。一条指令从麦克风到扬声器(或者蜂鸣器反馈)会经历"采集-识别-通信-解析-控制-动作"整条链路,涉及硬件电路、串口协议、舵机驱动、状态机设计。做完一次,你对嵌入式系统的理解会比单纯点灯深刻得多。

1.2 整个系统的六层架构

我把这个项目拆成六层,每一层只负责一件事:

层级负责内容典型硬件
语音输入层拾音、本地识别、输出命令帧离线语音识别模块
逻辑控制层解析命令、安排开盖/关盖流程Arduino Nano/Uno
感知层检测桶口是否有手或障碍物超声波模块 HC-SR04
执行层带动桶盖打开和关闭SG90舵机 + 连杆支架
反馈层语音/蜂鸣器提示"已开盖""注意手"语音模块播报或蜂鸣器
电源层给各模块提供合适的电压和电流5V/2A适配器或18650电池组

这样拆有个实际好处:排查故障时能定位到具体层。比如你说"开盖"但盖子没动,问题可能在第2层(串口没接对),可能在第4层(舵机堵转),而不是整机从头到尾重试。我第一次做的时候没有这个思路,只要不动就怀疑代码,结果最后发现是舵机供电线太细、电流不够。

1.3 适合谁做、大概什么工作量

如果你已经会用Arduino写基本的digitalWrite和delay,那这个项目可以直接上手,不需要会写复杂的上位机程序,也不需要懂信号处理算法——市面上的离线语音模块已经把语音模型封装好了。从买零件到调试完成,大概需要一个完整的下午到一天,主要时间消耗在机械固定和串口调试上。

2. 方案选型:离线语音模块、主控板和舵机的边界条件

2.1 语音识别方案横向对比:离线、在线和"硬跑"

市面上的语音识别接入方式大致有三种,很多人第一次做容易直接选错。

第一种是离线语音模块。典型代表是很早的LD3320,以及现在更常见的带配置工具的可烧录模块。优点是不联网、低延迟、一次买断没有云服务费,缺点是词条需要提前定制,不能识别任意话术。对垃圾桶这种固定指令场景,这是最合适的方案。

第二种是ESP32接云平台,比如常见的Wi-Fi语音识别方案。识别率确实高,什么说法都能听懂,但有一个致命问题:依赖网络。万一路由器重启、云服务波动,垃圾桶就"变哑"了。而且在线方案延迟一般在几百毫秒到一秒多,用在公共垃圾桶上体验并不好。如果后续想记录开盖数据做统计,可以考虑ESP32,但那属于第7节的进阶改造。

第三种是让Arduino直接跑语音识别算法,我直接说结论:别试。Arduino Uno只有2KB RAM,ATmega328P主频16MHz,要跑MFCC特征提取加声学模型打分完全不够。语音识别需要密集的浮点运算或矩阵运算,这种MCU就算勉强放一个极小模型,也会导致麦克风数据采集和处理一帧需要几十毫秒,根本无法实时响应。专业的事交给专用芯片,这是嵌入式的第一课。

2.2 主控板和语音模块怎么选

主控我建议用Arduino Nano或者Uno。两者核心都是ATmega328P,区别是体积和USB接口位置。Nano体积小,适合藏在垃圾桶的夹层里,我用的是Nano;Uno引脚更宽好插,适合面包板验证阶段。特别提醒:不要为了省钱买Pro Mini,除非你手里有USB转TTL的烧录器。Pro Mini没有板载USB转串口芯片,新手第一次烧录很容易卡在看不懂的"avrdude: ser_open()"错误上,白白折腾半小时。

语音模块我推荐用支持上位机做词条定制的离线模块。这类模块的工作方式类似:你在电脑软件里输入"唤醒词"和"命令词",软件会把语音特征模型编译好,通过USB转TTL烧录到模块的Flash里。产品运行时,模块上的麦克风采集声音,芯片实时比对,一旦匹配就通过串口输出一个命令代码。

2.3 舵机与供电:看似不起眼,实际最容易翻车

很多教程会用SG90塑料齿舵机。我实测下来,普通塑料垃圾桶盖配合轻质3D打印支架,SG90足够。但要注意,SG90在这里属于"刚好够用",如果你的桶盖是金属材质,或者密封圈卡得比较紧,建议直接上MG996R这种金属齿大扭矩舵机,因为塑料齿在持续堵转时很容易扫齿报废。

供电是整个项目最容易被低估的环节。舵机开盖瞬间的峰值电流可以达到500mA甚至更高,很多"电池带不动"的问题都出在这里。我整理了一个对照表:

供电方式是否推荐原因
9V叠层电池不推荐内阻大,舵机一启动电压就被拉低到4V以下,Arduino直接复位
USB 5V/2A适配器推荐固定场景电流充足,成本低,但要接对接线
2节18650电池+5V升压模块推荐无插座场景容量大,能撑一天展示,但需要充电管理
4节5号镍氢电池勉强电压约4.8V,舵机力矩会下降,可能出现角度不够

我最后用的方案是5V/2A适配器,舵机单独从电源取电,Arduino和语音模块统一从板载5V供电,共地解决信号参考问题。这个细节后面展开。

3. 离线语音模块的通信逻辑:串口帧、电平匹配与词条烧录

3.1 词条和槽位:语音模块的"预训练"过程

离线语音模块不是完全不用训练的。你需要先用厂商的上位机软件设定词条。以我用的模块为例,软件里有"唤醒词"和"命令词"两类:

  • 唤醒词:比如"小助手",用于把模块从低功耗状态叫醒,并开始监听后续命令。
  • 命令词:比如"开盖""关盖""丢垃圾",唤醒之后才生效。

不要小看这个分层设计。如果你把"小助手"和"开盖"合并成一个词条,只喊"开盖"也能触发,但公共场合有人喊一句"开个盖"就可能导致误开盖。分开之后,必须完整喊出"小助手,开盖"才执行,误触发概率大幅下降。

烧录词条时有个细节:命令词尽量用三个字以上的短语,方便识别端区分。单字"开"太容易和周围人的"该开""要开"撞上;"打开垃圾桶"虽然字数多,但口语里很少有人完整说,所以我的建议是"小助手"加"开盖"这个组合,识别率和自然度比较均衡。

3.2 模块识别后发送的串口帧格式

离线语音模块识别到命令词之后,通常会通过串口主动发一帧二进制数据。不同厂商的协议不一样,但基本都是"帧头+命令类型+槽位ID+校验"的结构。我用的模块发送格式简化后如下:

AA 01 00 01 00 02 01 00 00 01

解释一下:AA是帧头,01表示识别结果事件,后面第3个字节01就是槽位ID(我烧录时把"开盖"放在第1号槽位),末尾是简单的和校验。只要读到槽位ID是1,就知道要开盖。

这里有一个很多人踩过的坑:Arduino端不能用Serial.readString()去读。原因有两个。第一,模块发的是二进制字节,不是可打印的ASCII字符,readString读出来是乱码或者空串。第二,模块可能在一次识别后连续发多帧,如果不按帧头去对齐,第二帧就会被当成新命令,导致已经关盖的垃圾桶又莫名其妙开一次。正确做法是逐字节读取,先等帧头,再按协议解析完整帧。

3.3 电平匹配:3.3V语音模块和5V Arduino怎么对接

很多离线语音模块的工作电压是3.3V,Arduino Nano的引脚逻辑电平是5V。直接把模块的TX接Arduino的RX是能用的,因为模块向Arduino发送,Arduino用5V逻辑读取3.3V高电平,大多数情况下高电平阈值是2.5V左右,还能识别。但反方向,Arduino向模块发送就危险了——如果模块的RX不耐5V,长期会烧掉模块IO口内部的ESD保护二极管。

稳妥的方案是在Arduino TX到模块RX之间串一个1kΩ电阻,利用分压把5V降到约3.3V。虽然数据速率不高时这个简单方法很可靠,但更规范的做法是使用一颗电平转换模块。我自己是直接在中间串了1k电阻,实测9600波特率下通信稳定,跑了一整天没有出过错。

另外要注意模块的波特率设置。大部分模块默认9600,但也见过出厂默认115200的。确认方法很简单:用USB转TTL接上模块,先用默认波特率发一条测试指令,看返回数据的可读性。如果全是乱码但帧头偶尔能对齐,那就换个波特率再试一遍。

4. 机械改造与电气接线:让普通垃圾桶"听话"的关键细节

4.1 桶盖联动结构:舵机怎么稳稳抬起盖子

机械部分决定这个项目是"能演示"还是"能用一年"。最初我偷懒,直接用双面胶把舵机贴在桶盖侧面,舵机臂上绑一根铁丝勾住盖子。结果是:开盖能开,但铁丝在桶盖重力下发生形变,角度越来越不准,两天之后舵机开始空转。

正确的做法是杠杆结构。用合页把桶盖的一边固定在垃圾桶上,让盖子只绕一个铰链轴旋转。舵机安装在桶盖铰链的另一侧,舵机摇臂上连接一根连杆,连杆的另一端固定在桶盖表面。舵机转动时,连杆像"推杆"一样把桶盖顶起。这样受力方向是确定的,不会出现盖子歪斜卡死。

连杆长度决定了舵机的负载。根据杠杆原理,舵机力臂越短、盖子受力点离铰链越远,需要的力矩越大。我一开始用原厂舵机摇臂(力臂只有1cm),盖子重点在15cm处,算下来需要约15倍于盖重的力,SG90很容易超载。后来从网上买了加长的舵机臂(长约4cm),配合轻质打印连杆,普通塑料盖就能被轻松顶起。

角度也要做限制。我设置的舵机运动范围大概是0°关、95°开。不要在程序里写180°,因为大部分桶盖到90°出头就已经完全翻开,再往大角度转就会顶到机械限位,舵机进入堵转状态,持续电流飙升,轻则电源电压被拉低,重则烧舵机驱动。

4.2 电气接线和完整接线表

接线表如下:

模块引脚接到Arduino/电源
语音模块VCC3.3V经AMS1117-3.3稳压模块输出
语音模块GNDGNDArduino GND(共地)
语音模块TXD2软串口SoftSerial RX
语音模块RXD3软串口SoftSerial TX(串1kΩ)
舵机信号线D9Arduino D9(PWM)
舵机电源线VCC外部5V/2A电源正极
舵机地线GND外部电源负极和Arduino GND共地
超声波TrigA0数字输出
超声波EchoA1数字输入
超声波VCC5VArduino板载5V输出

关于共地,新手一定要记住:两个电路必须在同一个公共参考点上才能通信。如果Arduino用USB供电,舵机用另一个5V适配器供电,两块电源的GND必须连在一起,否则Arduino以为的高电平在舵机电源参考系里可能是错误的。串行通信看似只需要一根信号线,其实信号是相对于地的,不共地就全是噪声。

舵机的电源线不要用跳线帽直接怼到Arduino的5V引脚上。我实测SG90开关盖瞬间,Arduino板载稳压芯片的电压会被拉到明显跌落,容易导致板子重启。正确接法是外部5V电源经过一个较粗的杜邦线或者直接焊线接到舵机,Arduino只提供信号,不从5V引脚给舵机喂电。

4.3 外壳开孔与走线的几个原则

语音模块的麦克风位置很关键。如果你把模块完全埋在铁皮桶里,声音会被金属壁反射,识别率明显下降。最稳妥的做法是在垃圾桶外壳上开一个直径约5mm的麦克风孔,让模块的麦克风朝向桶外。同时让麦克风尽量远离舵机,因为舵机PWM信号切换期间会产生电磁噪声,噪声距离麦克风太近会被拾音电路放大。

我还踩过一个走线坑:舵机信号线从桶盖活动处穿过,桶盖一开一合,线就被夹住。后来我把信号线沿着合页轴方向走,留出一段松弛的线环,才彻底解决。公共场合长期使用的设备,每个运动部件旁边的线都要当成"会被反复弯折"来处理。

5. 核心代码实现:非阻塞状态机与防夹手逻辑

5.1 为什么不用delay,而要用状态机

大多数入门教程的写法是:听到开盖指令→openLid()→delay(5000)→closeLid()。这个方法在demo演示时没问题,但你会发现一个致命缺陷:delay()期间整个程序被阻塞,超声波传感器无法响应。如果垃圾都丢完了,有人把手伸进去之后再蒙一下关盖,就会出现夹手风险。

所以我改用状态机。整个程序有四个状态:

  • IDLE:空闲,等待语音命令
  • OPEN_HOLD:桶盖已打开,保持一段时间,同时检测桶口是否有遮挡
  • CLOSING:正在关盖
  • BLOCKED:检测到有障碍物,暂停关盖,等障碍物移开

状态切换不依赖阻塞延时,而是用millis()记录时间戳。开盖后比如设定8秒,如果8秒内超声波一直检测到桶口有东西,就自动顺延;如果检测到无遮挡,才开始关盖。关盖过程中如果超声波突然检测到遮挡,立即把舵机转回开盖位置,等人离开后再重试。

5.2 完整可编译代码

#include <SoftwareSerial.h> #include <Servo.h> // 软串口:接语音模块 // 注意:Nano上D2是软串口RX,接模块TX;D3是软串口TX,接模块RX SoftwareSerial voice(D3, D2); // 我这里交换了方向,TX=D3, RX=D2 Servo lidServo; // 引脚定义 const int TRIG_PIN = A0; const int ECHO_PIN = A1; const int SERVO_PIN = 9; // 舵机角度 const int LID_CLOSED_ANGLE = 0; const int LID_OPEN_ANGLE = 95; // 状态机 enum State { IDLE, OPEN_HOLD, CLOSING, BLOCKED }; State currentState = IDLE; unsigned long stateStartTime = 0; const unsigned long OPEN_HOLD_MS = 8000; // 保持8秒 const unsigned long BLOCKED_TIMEOUT_MS = 3000; // 障碍物超过3秒触发播报提示 // 语音帧缓存的临时变量 const int FRAME_LEN = 10; // 按你的模块协议调整 byte frameBuf[10]; int frameIndex = 0; bool frameReady = false; void setup() { Serial.begin(9600); // 调试口 voice.begin(9600); // 和语音模块波特率一致 lidServo.attach(SERVO_PIN); lidServo.write(LID_CLOSED_ANGLE); pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); currentState = IDLE; Serial.println("System ready."); } void loop() { // 1. 不断读取语音模块数据,解析出槽位ID int commandSlot = readVoiceCommand(); if (commandSlot == 1 && currentState == IDLE) { startOpen(); // 槽位1是"开盖" } else if (commandSlot == 2 && currentState != IDLE) { forceClose(); // 槽位2是"关盖",可随时强制关闭 } // 2. 状态机流转(非阻塞) unsigned long now = millis(); switch (currentState) { case IDLE: // 什么都不做,等待语音 break; case OPEN_HOLD: // 如果检测到桶口有障碍物,重置保持时间 if (isObstacleDetected()) { stateStartTime = now; // 可选:串口输出提示信息 Serial.println("Obstacle detected, hold open."); currentState = BLOCKED; } else if (now - stateStartTime >= OPEN_HOLD_MS) { // 无遮挡且超时,开始关盖 startClose(); currentState = CLOSING; stateStartTime = now; } break; case BLOCKED: // 障碍物移开且持续1秒无遮挡,才真正进入关盖进程 if (!isObstacleDetected()) { if (now - stateStartTime >= 1000) { currentState = CLOSING; stateStartTime = now; startClose(); } } else { stateStartTime = now; // 又伸进去了,重新计时 } break; case CLOSING: // 关盖途中如果检测到遮挡,立刻退回打开状态 if (isObstacleDetected()) { Serial.println("Obstruction during closing! Reopen."); startOpen(); currentState = OPEN_HOLD; stateStartTime = now; break; } if (now - stateStartTime >= 1500) { // 留足关盖动作时间 lidServo.write(LID_CLOSED_ANGLE); currentState = IDLE; Serial.println("Closed."); } break; } } void startOpen() { Serial.println("Open command received."); lidServo.write(LID_OPEN_ANGLE); currentState = OPEN_HOLD; stateStartTime = millis(); } void startClose() { Serial.println("Close command."); // 这里从开盖角度逐步往回走,实际可以加一个for循环/转动过渡 lidServo.write(LID_CLOSED_ANGLE); } void forceClose() { lidServo.write(LID_CLOSED_ANGLE); currentState = IDLE; Serial.println("Force closed."); } /** * 读取语音模块发送的二进制帧 * 返回命令槽位ID,没有有效命令返回-1 */ int readVoiceCommand() { while (voice.available() > 0) { byte b = voice.read(); if (frameIndex == 0) { // 等帧头 if (b == 0xAA) { frameBuf[0] = b; frameIndex = 1; } } else if (frameIndex < FRAME_LEN) { frameBuf[frameIndex++] = b; if (frameIndex == FRAME_LEN) { // 这里做一个简化校验:确认是识别事件且槽位是第3字节 frameIndex = 0; if (frameBuf[1] == 0x01) { return frameBuf[2]; // 槽位ID } } } } return -1; } /** * 超声波测距,返回障碍物是否距离过近 * 当距离小于15cm时视为有障碍物 */ bool isObstacleDetected() { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); long duration = pulseIn(ECHO_PIN, HIGH); float distance = duration * 0.034 / 2.0; // 声速340m/s if (distance > 0 && distance < 15) { Serial.print("Obstacle distance: "); Serial.println(distance); return true; } return false; }

代码里的FRAME_LEN要根据你实际模块的帧长度修改,不要照抄。串口协议如果不对,先写一个简单的打印程序把收到字节打印成十六进制,看到真实帧再调。

5.3 关键逻辑的工程化说明

readVoiceCommand()采用逐字节找帧头的方式,优点是天然支持"粘包"处理。即使一次收到多帧,也能每帧独立解析,不会把上一帧的尾巴当成下一帧的开始。

isObstacleDetected()里的声速换算公式是距离(cm)=时间(us)*0.034/2。0.034是把微秒转厘米再除以声速,除以2是因为超声波要往返。这里有个细节是pulseIn会阻塞等待回声,最坏等待时间大约几十毫秒,对开盖场景完全可接受;如果你做更复杂的控制,可以改成非阻塞测距,但代码复杂度会高不少。

防夹手的逻辑我写了两层:一是在OPEN_HOLD状态下持续监测,一旦发现手伸进桶口就延长保持时间;二是在CLOSING过程,如果已经关盖到一半发现有障碍物,立刻重新打开。这两层分别防止"还没开够就关"和"关了才发现有人伸手"两种意外。

6. 调试实录:从识别率骤降到舵机抖动,我踩过的五个坑

6.1 坑一:铁皮桶屏蔽效应,识别率从90%掉到20%

我把语音模块装进一个铁皮垃圾桶后,本来在桌面上测试识别得好好的模块,装进桶里之后基本变成聋子。起初以为是接线有问题,排查半天发现没动过接线。后来用手机录音对比,发现放到桶内后,模块麦克风听到的语音混响特别严重,而且铁皮外壁还会反射空气中的环境噪声。

解决办法很直接:模块位置从桶内部移到桶外侧的塑料夹层,麦克风朝外开孔,前后左右留出至少1cm的声学开放空间。另外麦克风离舵机至少15cm,否则舵机转动时的电磁干扰会被拾音电路识别成"噪声特征",触发一些奇奇怪怪的误识别。

6.2 坑二:串口完全没反应,最后发现是TX/RX接反了

第一次接线,我下意识把"语音模块的TX接Arduino的TX",因为觉得都是"T"开头应该是一边。结果是完全没反应。其实串口是交叉接线:模块的TX要接Arduino的RX,模块的RX要接Arduino的TX。这个错误非常普遍,因为很多教学板子上的丝印会把人的直觉带偏。

如果接对之后还是没反应,再查软串口定义。我用的是SoftwareSerial voice(D3, D2),这是指D3用于TX、D2用于RX,注意参数顺序和实物接线要匹配。如果用的硬串口Serial,则模块TX接Arduino的0号脚(RX),模块RX接1号脚(TX)。

6.3 坑三:舵机只抖不动,整板重启

我最初用一根USB线从电脑给Arduino供电,舵机也接在Arduino的5V上。现象是:语音识别到了,舵机只抖了一下,然后Arduino的LED闪了一下,像是重启了。用万用表测量舵机供电处的电压,开盖瞬间从5V掉到不足3.8V——舵机堵在起始位置,电流大得把板载稳压拉垮了。

按前面说的独立供电方案解决后,又出现一个轻微抖动:舵机在停止时不断小幅摆动。这是电源纹波导致的信号抖动,在舵机电源两端并联一个470uF电解电容之后彻底消除。如果还抖,检查舵机信号线上是否和舵机电源线绑在一起走线过长,尽量让信号线远离电源线。

6.4 坑四:舵机"咯咯"响,角度超限堵转

装好连杆之后,我设置的开盖角度是180°,结果舵机转到约100°时桶盖已经顶到最大,舵机还想继续转,就发出了急促的"咯咯"声。因为舵机内部是齿轮组加电位器反馈,卡住时电机持续堵转,电流升高,时间一长轻则舵机发烫,重则烧掉驱动芯片。

解决方法是实测调整角度。写一个小循环让舵机从0°以5°步进转动,每次停1秒,观察桶盖在多少度完全打开且不卡顿,然后把LID_OPEN_ANGLE设成那个值加上10°余量。注意加余量是为了防止机械老化后天冷转不动,但不要超过限位。

6.5 坑五:公共环境误触发关盖,超声波被脸盆挡住

在走廊测试时,旁边有人拖着一个大纸箱从垃圾桶边经过,超声波模块误判为"有障碍物正在丢垃圾",于是保持开盖状态很久。这个不算严重,因为最多费一点电。更让人头疼的是另一种场景:垃圾桶盖子打开后,超声波正对着桶口下方,如果桶里垃圾堆得比较高,就会一直算作"障碍物",导致永远不关盖。

解决方案有两个:一是调整超声波探头方向,让它稍微斜向下对准桶口外沿,而不是正对桶内垃圾;二是在代码里加一个"障碍物持续超时"分支,如果检测到遮挡超过比如30秒,说明不是人手在丢垃圾,而是桶满了或者误报,强制进入关盖流程并播报提示。

7. 升级方向:从语音开盖到智能垃圾管理

7.1 满桶检测和语音播报反馈

在垃圾桶内壁靠近上沿的位置再放一个向下的超声波模块,可以做满桶检测。原理是测量垃圾表面到传感器的距离,垃圾积累越多,距离越小。当小于某个阈值(比如20cm)时,判断为快满桶,通过语音模块播报"垃圾桶已满,请及时处理"。

配合上一节说的"障碍物连续遮挡"问题,满桶检测还能解决一个实际痛点:语音识别正确开盖后,如果垃圾已经满到捅不进去,系统连续多次检测到桶口有遮挡,会提示人员来清理,而不是一直开着盖子干等。

7.2 低功耗与太阳能供电:户外场景的必修课

如果你想把语音垃圾箱放在小区户外或校园角落,首先要解决供电和功耗。固定电源不一定拉得过去,我建议用两块18650电池串联,再经过5V升压模块给系统供电。语音模块平时处于休眠状态,需要配置"唤醒词唤醒"功能,只有喊出唤醒词才进入工作状态;Arduino进入sleep模式,舵机电源用一颗MOS管做开关,只有开盖瞬间才给舵机通电。这样整体待机电流能压到20mA以下,两节18650可以撑好几个星期。

太阳能是另一个方向。一块10W左右的太阳能板搭配充电管理模块,白天给电池充电,晚上低功耗运行。但要注意,舵机的瞬时大电流和电池容量要匹配,我见过直接用太阳能板输出带动舵机的方案,阳光稍弱就重启,这是容量设计没做好。

7.3 联网与数据上报:让垃圾桶数据"会说话"

如果换用ESP32做主控,就可以在保留离线语音识别的同时,把开盖次数、开盖时长、每次丢垃圾的时间戳上传到物联网平台。这些数据可以用于分析校园或社区的垃圾投放高峰时段,辅助保洁人员安排清运时间,也能用于观察某个区域的公共垃圾桶是否存在长期闲置或超负荷。

联网方案的注意点是安全与隐私。上报数据最好像我这样只传"事件ID+时间戳"这种最小化字段,不采集麦克风音频内容,也不采集任何能识别到个人的身份信息。毕竟垃圾桶旁边的人流量数据也属于敏感信息,做公共场景设计时要有意识地做脱敏处理。

7.4 我现在还会继续做的小改动

项目完成之后我又加了一个很实用的小功能:在垃圾桶侧面装一个红外反射式传感器,识别"有人靠近"后先播报一句"请说开盖",引导第一次使用的人快速上手。这个提示对老人小孩特别友好,不然你装好了,别人不知道它是语音控制的,又会习惯性去用手掀盖,机械结构容易被掰坏。

另外一个我很喜欢的改动是给桶盖加了阻尼缓冲。语音开盖后桶盖靠舵机推起,关盖时靠舵机压回,如果舵机角度变化太快,缓冲不足,会发出"砰"的一声。代码里把关盖过程拆成了6到8个小角度逐级下降,每次间隔80ms,盖子就慢慢合上,既安静又不容易砸到手。就我个人使用下来的感觉,这套语音识别垃圾箱真正的门槛其实不在语音模块,而在机械和供电——把这两个地方想透了,剩下的更多是耐心调试的问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进