
先交代一下我做这个项目的起因。家里厨房的垃圾桶是那种带盖的翻转桶平时扔果皮、剩菜都得先用手去掀盖子炒完菜满手油污的时候尤其烦摸一下把手就得重新洗一次手。后来我逛购物网站看了一圈所谓感应垃圾桶便宜的要六七十块而且多数用的是红外反射方案手没伸到就开走过去一个穿深色衣服的人又不开体验很一般。于是决定自己用Arduino做一台算下来硬件成本三十块钱左右最关键的是触发逻辑能自己改不会被厂商预设死。这篇文章就从需求拆解、硬件选型、信号调测、控制逻辑到实际踩坑把整个做智能垃圾桶的过程完整记录一遍。如果你手头有Arduino Uno或者Nano想做一个能真正放厨房用的感应垃圾桶这篇可以直接照抄作业。1. 先想清楚这个垃圾桶到底需要哪些智能1.1 真实场景下的需求拆解做硬件项目最忌讳一上来就接线上代码先站在使用场景里把需求一条条列清楚。我拿着自家厨房的垃圾桶蹲了两天总结了这几个核心痛点非接触开盖手靠近时盖子自动打开人不用碰桶身。延时关盖人离开后盖子自动盖上不能一直敞着。防误触发人从旁边路过、猫在桶边转悠、厨房门关上带起一阵风都不能让盖子乱开。供电稳定垃圾桶放在角落里要长时间运行不能用一会儿就电压不稳重启。成本可控整体预算最好控制在四十块以内不然不如直接买商品。市售的感应垃圾桶我之前也拆过一台核心逻辑就两路红外反射模块检测到障碍物就开盖再加个555定时器延时关盖。这种方案便宜是便宜但红外反射受环境光影响很大强光直射时经常误判而且黑色物体吸光手伸过去反而没反应。我这次做智能垃圾桶第一个决定就是抛弃红外反射方案。1.2 为什么最终选择超声波测距选HC-SR04超声波模块是最折腾犹豫的一个环节因为市面上还有人体红外热释电、微波雷达、激光测距等选项。逐个说说我的判断人体红外热释电PIR只能检测运动的人手静止伸过去反而不触发而且检测范围是一个扇形区域人站着不动超过几秒就自动消失了不适合近距离精确检测。微波雷达模块灵敏度高、穿透力强但是抗干扰调校麻烦小空间里反射杂波多容易误触发成本也偏高。激光测距VL53L0X/TOF精度很高但是检测面小需要目标大致对准光斑位置装在手可能从多个方向伸过来的垃圾桶上并不合适。超声波成本低、视角锥形范围广、不受环境光影响测距范围2cm到4m放在厨房这种小空间里完全够用。最终选了HC-SR04单价六块多。这里补充一个容易踩坑的知识点超声波模块的原理是发射40kHz脉冲并接收回波靠时间差算距离。它对软性物体比如海绵、布吸波回波衰减严重对硬质光滑表面反射最强。垃圾桶盖子如果是塑料的回波特征还算明显但是如果桶周围有白墙、冰箱门角度凑巧会把回波反射回来造成假目标这部分我在后面第5章专门说。2. 硬件选型清单主控、传感器与供电方案的一次性算清2.1 主控板选Uno还是NanoArduino家族里最常见的两款板子就是Uno R3和Nano。我的建议是分阶段选择初期调代码用Uno体积大、USB口在边缘插拔方便最终塞进垃圾桶壳体用Nano尺寸只有Uno的三分之一左右。先说Uno。板载ATmega328P单片机14路数字IO、6路模拟输入处理一个超声波模块加一个舵机绰绰有余。新手调试时Uno的优势是板载USB转串口芯片CH340或Atmega16U2插上电脑就能下载程序。排针间距规整面包板、杜邦线都好接。如果程序死循环卡住按一下板载复位键就能恢复不用拔线。最终集成时如果空间紧张换Nano的话引脚定义和Uno基本一致代码不用改只要重新接线就行。我这次成品用的是Nano但以下电路连接讲解都以Uno为例概念通用。2.2 传感器、舵机与供电方案的配合关系关键硬件清单我直接列出来器件型号/规格数量单价参考作用主控板Arduino Uno R3调试/ Nano成品120-35元控制核心超声波模块HC-SR0416-9元检测人手距离舵机SG90 9g18-12元带动桶盖开合电源适配器5V 2A USB电源110元主供电电解电容470μF/16V10.5元舵机供电稳压瓷片电容0.1μF10.1元高频滤波万用板/3D打印支架自备15-15元结构固定垃圾桶方形塑料带盖桶110元改装主体这里要特别说供电方案这是整个项目里最容易出问题的地方。SG90舵机标称工作电压4.8V到6V启动和堵转时电流冲击很大实测堵转电流能到700mA甚至更高。Arduino Uno板载稳压芯片AMS1117-5.0只支持最大1A输出如果舵机直接接Uno的5V引脚一开盖的瞬间就可能把电压拉低到4.5V以下轻则舵机抖动抽搐重则主控复位重启。更糟的是USB供电时电脑口的限流往往只有500mA舵机一动作整个板子直接掉电。我最终的供电接法是电源适配器5V进来直接分成两路一路给Uno的5V引脚不走USB口另一路经过一个470μF电解电容和一个0.1μF瓷片电容滤波后单独给舵机的电源线。两个电容并联在5V和GND之间作用是蓄水池舵机瞬间拉电流时能从电容里补充能量电压不会瞬间塌掉。热词里有人搜arduino在5v端口接电容说的就是这个场景亲测有效。2.3 结构件和桶体改装思路传感器和舵机选好了桶的选择也讲究。我一开始用的是家里的圆形脚踏垃圾桶装上舵机后盖子转轴角度匹配很难调后来换成方形塑料垃圾桶改造难度直线下降。推荐选那种盖子边缘有平面、转轴在桶后侧的方形桶舵机直接装在桶体后壁上舵盘通过一根金属连杆连接盖子边缘形成杠杆舵机旋转就能带动盖子抬起。结构上要注意两件事一是连杆长度和角度要算好舵机转动110°时盖子刚好完全打开二是传感器安装高度和角度HC-SR04的发射锥角大概在30°到60°之间要让传感器稍微朝斜上方倾斜而不是水平朝前否则会把对侧墙壁的反射算进来。没有3D打印条件的话用如下方案替代找一块万用板做支架塑料扎带固定舵机传感器用3M胶粘在桶盖前缘下方。我实际试过稳定性不输3D打印件只是丑了点。有3D打印的朋友可以自己画一个卡扣式支架把传感器卡在桶盖前缘舵机座固定在桶身后壁两件加起来材料成本不到三块钱。3. 电路连接与信号链路不搞懂原理就去调代码多半要翻车3.1 引脚分配与接线表我最终的接线方式如下照着接就行Arduino引脚外设引脚说明5VHC-SR04 VCC给传感器供电GNDHC-SR04 GND共地D7HC-SR04 TRIG触发测距信号D8HC-SR04 ECHO回波接收信号单独5V路径SG90 电源线红经过电容滤波后供电GNDSG90 地线棕与Arduino共地D9SG90 信号线橙PWM控制信号USB/适配器Arduino 5V或Vin系统总电源接线时几个容易忽视的细节我踩过之后才长记性HC-SR04的ECHO引脚输出是5V电平接Arduino数字引脚没问题。但你如果以后换ESP32这类3.3V主控ECHO必须加分压电阻否则会把引脚烧掉。舵机信号线尽量短不要跟超声波的四根线绑在一起走线舵机电机转动时会产生电磁干扰耦合到ECHO线上会让测距值突然跳变。我实测过两根线绑在一起时开盖瞬间串口打印的距离值会飘十几厘米分开走线后恢复正常。所有模块的GND必须共地不要各接各的不共地会导致信号电平参考点不一致触发电平判断错误。3.2 超声波测距的真实时序很多教程把测距写成一行代码就完事了但不懂时序出了bug没法排。HC-SR04的工作时序是这样的主控把TRIG引脚拉高至少10微秒然后拉低。模块内部检测到触发信号后自动发射8个40kHz的超声波脉冲。同时模块的ECHO引脚被拉高进入等待回波状态。超声波碰到障碍物返回模块收到回波后把ECHO引脚拉低。ECHO引脚高电平持续的时间就是超声波从发射到返回的往返时间。知道了往返时间距离就等于时间乘以声速再除以2。声速在15℃空气中大约是340m/s也就是0.034cm/微秒所以距离cm 高电平时间微秒× 0.034 / 2。这里面有一个很关键的坑pulseIn函数如果没有收到回波默认会一直等下去程序就卡住了。所以我在代码里给pulseIn加了超时参数比如pulseIn(ECHO_PIN, HIGH, 30000)意思是超过30毫秒还没等到高电平就放弃返回0。30毫秒对应大约5米远超实际安装场景。这个细节不加程序一旦遇到传感器前方没有反射物的情况就会无限卡死。3.3 舵机控制原理和Servo库的隐藏问题舵机不是普通的直流电机它内部有电机、减速齿轮、电位器和控制电路靠PWM信号确定目标角度。标准的模拟舵机信号是50Hz也就是周期20毫秒每个周期里高电平时间决定了舵机角度0.5毫秒对应0°1.5毫秒对应90°2.5毫秒对应180°。Arduino的Servo库封装好了这些细节servo.write(angle)就能控制角度。但这个库有两个隐藏问题它一旦attach到某个引脚就会占用该引脚对应的定时器。Uno上有三个定时器Servo库用Timer1所以D9和D10两个引脚通常会冲突D9已经被用了就别在D10再接一个Servo。如果你要用两个舵机只能换其他支持Servo的引脚。Servo库的write函数其实是在每次loop循环之后、下一个周期生成PWM如果你写了一个阻塞式的大循环比如delay(1000)舵机信号就会出现明显抖动。所以我在控制逻辑里尽量用millis()做非阻塞延时而不是用delay()。另一个实测注意点SG90的物理转动范围标称0°到180°但不同批次差异很大有些在角度超过大约170°时会卡在齿轮限位处长时间卡住会烧坏舵机。所以在代码里我限制了最大开盖角度为110°既能保证盖子完全打开又不会让舵机走到物理极限位置。4. 控制逻辑三段式防抖和状态机是如何让桶盖不发疯的4.1 为什么单次测距直接触发开关一定会误动作新手最容易犯的错误就是把测距结果直接当作触发的开关小于某个距离就开盖大于某个距离就关盖。我刚开始也这么干结果垃圾桶在家里表演了二十分钟的抽搐舞蹈。原因在于超声波单次测距值波动很大。HC-SR04的读数受目标表面材质、反射角度、温度、环境噪声影响即使目标静止不动读到的距离也可能在±3厘米范围内抖动。如果阈值设在25厘米目标在阈值边缘静止就会一会儿判定有人一会儿判定无人舵机就反复开合。更麻烦的是桶盖本身。超声波模块装在桶前缘当盖子打开到某个角度时盖子恰好处于传感器的检测锥角范围内传感器会测到20厘米左右的距离——这个值刚好落在人手接近的阈值区间。如果逻辑简单就会陷入开盖→测到盖子→以为有人→保持开盖→人走→测到盖子→以为有人→永远不关盖的死循环。解决方案从两个层面入手物理层面调整传感器朝向让开盖后的盖子尽量不在检测锥角范围内或者用吸波材料贴在盖子上对应位置逻辑层面就靠防抖和状态机下面展开说。4.2 状态机设计五个状态的跳转规则我把整个控制流程拆成五个状态分别是CLOSED待机、OPENING开盖中、OPEN已打开、CLOSING关盖中、LEAVING等待关盖。状态跳转规则是这样的CLOSED状态每100毫秒测一次距离连续3次读到距离小于25厘米才进入OPENING状态。这就是三段式防抖本质是消除单次读数的随机抖动。连续3次总共需要300毫秒确认人体伸手动作一般在500毫秒以上不会觉得卡顿。OPENING状态开盖动作需要时间舵机从0°转到110°大约0.4秒。这里用500毫秒的延时确保舵机走完然后进入OPEN状态同时记录状态进入时间。OPEN状态每100毫秒测一次距离只要距离小于25厘米就不断刷新最后保持时间。如果距离大于等于25厘米启动一个5秒倒计时5秒内没有检测到人就进入CLOSING状态。这里用5秒而不是立刻关盖是为了应对扔垃圾时的动作手伸过去扔完垃圾会退回来但人还站在桶边不能一退手就关盖。CLOSING状态舵机从110°转回0°同样留500毫秒动作时间然后回到CLOSED。LEAVING状态这个状态是为了处理特殊情况——如果开盖过程中突然检测到近距离目标消失或者传感器数据异常先进入LEAVING等待而不是直接回到CLOSED避免舵机刚打开就关、刚关又打开的振荡。实际加了这个状态后系统稳定性提升明显。4.3 完整代码与关键注释以下是我最终跑稳的完整代码直接复制到Arduino IDE就能用。引脚定义按照我上面的接线表。#include Servo.h #define TRIG_PIN 7 #define ECHO_PIN 8 #define SERVO_PIN 9 #define TRIGGER_DISTANCE 25.0 // 触发开盖距离阈值厘米 #define OPEN_KEEP_MS 5000 // 开盖后保持时间毫秒 #define APPROACH_SAMPLES 3 // 触发需要连续采样的次数 #define LEAVE_SAMPLES 5 // 判定人离开需要连续采样的次数 Servo lidServo; enum State { ST_CLOSED, ST_OPENING, ST_OPEN, ST_CLOSING, ST_LEAVING }; State currentState ST_CLOSED; unsigned long stateStartMs 0; unsigned long lastMeasureMs 0; int approachCount 0; int leaveCount 0; float readDistance() { // 触发一次超声波测距 digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); // 等待回波超时30ms视为无效 long duration pulseIn(ECHO_PIN, HIGH, 30000); if (duration 0) return -1; // 声速0.034cm/us往返所以要除以2 float dist duration * 0.034 / 2; // 过滤无效范围小于2cm或大于100cm的数据直接丢弃 if (dist 2.0 || dist 100.0) return -1; return dist; } void setLid(bool open) { if (open) { lidServo.write(110); // 开盖角度避免走到舵机物理极限 } else { lidServo.write(0); // 关盖角度 } } void setup() { Serial.begin(9600); pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); lidServo.attach(SERVO_PIN); setLid(false); stateStartMs millis(); } void loop() { // 非阻塞测距每100ms测量一次 if (millis() - lastMeasureMs 100) { lastMeasureMs millis(); float dist readDistance(); bool personNear (dist 0 dist TRIGGER_DISTANCE); // 串口输出方便调试 Serial.print(F(dist)); Serial.print(dist); Serial.print(F( state)); Serial.println(currentState); switch (currentState) { case ST_CLOSED: // 三段式防抖连续多次判定为有人才开盖 if (personNear) { approachCount; if (approachCount APPROACH_SAMPLES) { approachCount 0; currentState ST_OPENING; stateStartMs millis(); setLid(true); } } else { approachCount 0; } break; case ST_OPENING: // 等待舵机完成开盖动作 if (millis() - stateStartMs 500) { currentState ST_OPEN; stateStartMs millis(); } break; case ST_OPEN: // 有人就刷新保持时间没人开始倒计时 if (personNear) { stateStartMs millis(); leaveCount 0; } else { leaveCount; if (leaveCount LEAVE_SAMPLES millis() - stateStartMs OPEN_KEEP_MS) { currentState ST_CLOSING; stateStartMs millis(); setLid(false); leaveCount 0; } } break; case ST_CLOSING: // 等待舵机完成关盖动作 if (millis() - stateStartMs 500) { currentState ST_CLOSED; stateStartMs millis(); } break; case ST_LEAVING: // 过渡状态如果数据恢复有效且有人回去保持开盖 if (personNear) { currentState ST_OPEN; stateStartMs millis(); setLid(true); } else if (millis() - stateStartMs 3000) { currentState ST_CLOSED; stateStartMs millis(); setLid(false); } break; default: currentState ST_CLOSED; break; } } }代码里我用的是millis()非阻塞延时没有用delay()因为delay()会让整个程序在等待期间对外界变化毫无反应超声波也无法继续监测。这也是所有单片机实时控制的标准写法建议养成习惯。5. 实测阶段踩过的四个坑从乱抖到自动复位5.1 坑一一开盖就疯狂抖动根源是机构干涉和电磁干扰叠加第一次装进桶体测试时出现了手一靠近→盖子打开→舵机开始抖动→盖子又关上→再次打开的现象。我最初以为是程序逻辑问题反复改距离阈值都没用。排查过程是这样的先把舵机从桶体上拆下来只让它带着空载转程序运行正常装回桶盖后每次开盖都抖动。用串口打印距离值发现每次开盖后测距结果都在15到25厘米之间跳变——这是舵机转动瞬间电磁干扰耦合到了ECHO线测距值乱跳加上打开后的盖子本身就在传感器正下方。处理办法分两步结构上把传感器向前上方倾斜约10度开盖后盖子不在传感器正下方线路上把舵机信号线单独走线远离超声波的四根线并且在舵机电源两端并联电容。处理后抖动彻底消失。这个案例说明遇到软件逻辑诡异的问题先怀疑物理干扰用串口监视器看原始数据定位别一上来就改代码。5.2 坑二供电不足导致隔几分钟自动重启装了适配器供电后垃圾桶运行几分钟就重启一次没有任何规律。我一度怀疑是程序内存溢出换了Nano和Uno都试问题依旧。后来用万用表实测舵机启动瞬间VCC引脚电压发现会跌到4.3V。查资料确认Arduino Uno的复位芯片在VCC低于约4.5V时会触发断电复位把主控重启。罪魁祸首是劣质USB线和部分充电头带载能力不足舵机启动电流高峰时压降过大。解决方法是开头提到的双路供电适配器5V先经过电容滤波再分别供电同时换一根粗短USB线电阻小压降小。如果你手头只有普通充电头还有一个保险做法是用4节AA镍氢电池总电压约4.8V接到Vin引脚电池内阻低能扛住瞬间大电流。实测下来4节新电池的供电稳定性比我那根劣质USB线强得多。网上搜arduino在5v端口接电容的人大概率都是遇到同类问题原理就是用电容储能补上电流缺口。5.3 坑三厨房白墙和冰箱门反射造成的幽灵触发桶装好后放在厨房角落里出现了一个很诡异的现象人不靠近盖子隔几分钟自己开一次过一会儿又自己关上。串口打印距离值发现时不时会测到30厘米左右的数据可传感器前方根本没东西。排查过程绕了不少弯路后来用手机摄像头对着超声波模块拍发现模块前方没有遮挡物但是侧后方冰箱门形成了一个镜面反射路径。超声波从模块发出后经过冰箱门反射再经墙壁反射回传感器相当于一条虚拟通道反射路径总长度恰好落在触发距离内。解决办法有三个一是把传感器检测距离上限从100厘米收窄到60厘米增加误触需要更长的反射路径二是把传感器朝向调整为向下倾斜15度减少水平方向的镜面反射三是在代码里增加连续3次测距有效才响应的防抖逻辑单个异常读数直接丢弃。三管齐下之后幽灵触发彻底消失。后来我还发现如果桶周围放了金属垃圾桶或者光滑不锈钢台面反射会更严重这种情况下建议把触发距离阈值调到15厘米以内减小反射虚影进入判定区间的概率。5.4 坑四串口监视器里出现距离0的幽灵值模块裸测阶段串口偶尔会打印0厘米或者一个明显不合理的超小值比如0.2厘米。我最初以为是代码里除法算错了后来用示波器量ECHO引脚波形才发现HC-SR04在目标距离小于约2厘米时回波信号会和发射信号混叠部分模块会乱输出电平。这个问题的实际影响是如果模块安装位置太靠近桶壁或盖子当盖子处于某个角度时传感器会测到一个小于真实距离的假值触发误判。处理方式也简单代码里把小于2厘米的读数全部视为无效返回-1同时安装时保证传感器前方至少有5厘米的净空区域别贴着任何结构件。最后分享一个实测有用的调试顺序先只跑超声波例程用串口监视器看距离是否稳定再只调舵机让它转到指定角度后停住最后才把两者合并成完整逻辑。每次只改一个变量出现问题时一眼就能定位而不是面对一整坨代码无从下手。6. 后续升级方向从单机桶盖到家庭传感节点6.1 主控升级到ESP32实现远程监控与OTA做完基础版后我第二版的思路是用ESP32替换Uno。ESP32自带WiFi和蓝牙成本在25块左右跑同样的代码几乎不用改用Arduino框架开发编译时选择ESP32 Dev Module即可但多了联网能力通过MQTT协议把桶盖状态、开关次数、当前测距值推送到手机能知道一天开合了多少次、垃圾桶在哪个时间段使用最频繁。接一个分压电阻测电池电压电量低于某阈值时远程推送提醒换电池。OTA在线升级不需要把垃圾桶拆开重新插线下载程序人站旁边就能刷新固件。腾讯连连、阿里云IoT这类平台都有Arduino SDK配置好三元组信息就能接入数据面板在手机上直接看。我目前是把开关次数和电池电压上报到云平台晚上回家扫一眼数据就知道垃圾桶当天使用情况挺有意思。6.2 加一块小屏幕显示状态和统计信息如果不想联网最低成本的升级是加一块0.96寸OLED显示屏SSD1306驱动芯片I2C接口四根线就能接。Arduino有现成的Adafruit_SSD1306和U8g2库可以显示当前距离、开盖次数、电池电压、上次使用时间等。不过要提醒一点OLED屏幕持续点亮会耗电如果是电池供电记得在程序里加自动休眠逻辑比如连续10分钟没有测到人靠近就关屏等到有动作再唤醒。U8g2库的setPowerSave(true)函数可以关闭显示面板但保留I2C通信实测省电效果明显。6.3 垃圾满溢检测比我想象中简单另一个很实用的扩展是检测垃圾桶是否满了。思路有两种在桶盖关闭状态下放置一个朝下的短距离传感器比如VL53L0X激光测距测垃圾堆顶部到传感器的距离小于某个值就认为满了。在桶内壁安装红外对射管垃圾堆高到挡住光束就触发满溢信号。我采用的是第一种方案因为代码逻辑和现有超声波测距高度相似只是在桶盖闭合时测量开盖时不测避免舱盖本身对测距的干扰。实测塑料袋垃圾对激光测距的反射效果一般测量值波动较大但如果只检测小于15厘米这个条件满溢判断准确率还是够的。6.4 工具链的替代选择从Arduino IDE到仿真平台最后说下开发工具。我最初用Arduino IDE写程序简单场景完全够用但代码量大了之后发现编辑体验一般后来换了VSCode加PlatformIO插件代码补全、语法检查、文件管理都方便很多。PlatformIO装扩展后会自动下载Arduino框架和编译器创建一个项目就能编译上传不需要再单独安装Arduino IDE。如果还没买硬件可以先在Wokwi仿真平台上把电路和代码跑通。Wokwi是网页版仿真工具支持Arduino Uno、ESP32、HC-SR04、舵机等常见元器件可以直接把这份代码复制进去试跑用浏览器里的虚拟串口看输出。本地仿真软件还有Proteus功能更重适合画原理图和PCB的人用单纯验证逻辑的话Wokwi更轻量。对于完全没写过代码的朋友有两条学习路径一条是继续用Arduino IDE逐步理解C语法另一条是用米思齐Mixly这种图形化编程工具把积木块拼出逻辑它支持Arduino Uno和ESP32。米思齐有时候会下载不到Arduino库我遇到过的原因大多是网络源的问题手动下载库文件放进libraries目录就能解决。做这个项目最大的收获不是省了几十块钱而是彻底搞清楚了传感器采样→信号处理→执行机构动作这条完整的控制链路。垃圾桶只是载体把超声波测距、舵机控制、状态机设计、供电可靠性这几块吃透了后面再做自动喂食器、感应洗手液机、智能浇花系统全是同一套方法论换壳。最后再分享一个小技巧舵机用了几个月后如果出现吱吱声或者回中不准多半是减速齿轮缺油了。不用买专门润滑脂用牙签挑一点点凡士林涂到齿轮啮合面装回去就能满血复活。别用机油流动性太强会渗进舵机电位器导致角度漂移。