中三 RBT 的自动垃圾桶,说穿了就是三个方块:HC-SR04 量出手的距离,Arduino Uno 拿这个距离跟一个阈值比较,SG90 舵机把桶盖抬起来。最常出问题的地方不是代码,而是电源。舵机直接从 Uno 的 5V 脚取电,桶盖就会抖,板子也会在示范到一半时自己重启。
做这个自动垃圾桶要准备什么?
这个作品完全不必动烙铁 —— 所有连接都靠杜邦线和一块面包板完成。
材料清单 —— RBT 自动垃圾桶
Parts list
| Item | Price | Qty | |
|---|---|---|---|
Electrical Insulation Tape 18mm x 10m PVC Color Coded Marking Red White Blue Yellow Green Black - ELECTRICAL TAPE (BLACK)T1609BK | RM1.90 |
用来把传感器和舵机固定在桶身上,也用来包住电池盒接线的裸露部分。任何一款电工胶布都可以。
公头母头对不对得上,正是这个作品能不能免焊的关键。HC-SR04 露出来的是四支公针,所以要接公对母杜邦线的母头那一端 —— 那一端同时也充当延长线,让传感器可以装到桶的前面。SG90 舵机的线尾是一个黑色的三孔母座,所以插进去的是公对公杜邦线的公针。电池盒出来的则是多股软线、线头裸露:先把铜丝拧紧、再对折一次,然后才插进面包板的电源轨,不然细铜丝会散开、一拉就掉。这块 Uno 是用全尺寸 USB-B 口烧录的,所以要顺手带上一条 A 转 B 数据线;在 Arduino IDE 里开发板选 Arduino Uno,端口就选插上线之后才冒出来的那一个。
HC-SR04、Arduino 和舵机怎么组成输入—处理—输出?
HC-SR04 是输入方块。在 Trig 脚上给一个 10 微秒的脉冲,它就会发出一串 40kHz 的声波,接着把 Echo 脚拉高,并且维持高电平,长度正好等于声音来回一趟的时间。代码要做的,只是量这个脉冲有多宽。声音大约每秒走 343 米,也就是每微秒 0.0343 厘米;而这段时间涵盖的是去程加回程,所以距离 = 时间 × 0.0343 ÷ 2。房间热一些会让声速上升大约 2%,换算到 15cm 的阈值上只有 0.3cm —— 在这里无关痛痒。脉冲时序的其余细节,可以看我们那篇 HC-SR04 超声波传感器教程。
我们目前这一批 HC-SR04 的丝印把功能合并印在一起:Vcc、Trig_RX_SCL、Echo_TX_SDA、Gnd。多出来的那几个字是给别的通信模式用的,这个作品完全用不到 —— 第二支脚就当 Trig、第三支脚就当 Echo,跟任何一块 HC-SR04 一样。
SG90 舵机则是输出方块。它每 20 毫秒收到一个脉冲,而决定输出轴角度的是脉冲的宽度,不是电压,也不是电流。因为位置是以绝对角度下达的,所以桶盖要用的正是这颗 180° 舵机 —— 360° 那种是连续旋转舵机,只听得懂方向和速度,没办法把桶盖停在某个角度上。两者的差别写在我们的舵机 180°、270° 与 360° 区别那一篇。
夹在两者中间的就是处理方块 —— 阈值、读数确认、以及保持开启的计时 —— 这一段正好就是你 RBT 作业簿里「处理」那一栏要写的内容。

为什么舵机接在 5V 脚上,桶盖会抖、Uno 还会重启?
SG90 舵机里面是一颗普通的有刷直流电机加一组减速齿轮。在它刚要动起来的那一瞬间 —— 桶盖卡住时更严重 —— 转子还没转起来,没有反电动势去对抗电源,这时候流过的电流几乎只由线圈电阻决定。这就是启动涌流:一瞬间几百毫安,远高于它转起来之后的稳态电流。
再看这股电流得从哪里来。电脑的 USB 口额定 500mA,而 USB 电源进入 Uno R3 板子时还要先经过一颗 500mA 的自恢复保险丝 —— 它不是开关,而是一颗电流越大就越热、越热电阻就越高的元件,所以它的第一个效果不是断电,而是压降。空板本身已经吃掉大约 50mA,HC-SR04 再拿走 15mA 左右,剩下的连半安培都不到 —— 再加上 USB 线本身的电阻,舵机每一次涌流都会在电压还没送到板子之前就把电源轨拉下去。
抖动就是这样来的。但原因并不是舵机把自己的位置读错了。像 SG90 这类模拟舵机,内部的控制芯片自带一颗稳压器,专门给它自己的反馈电位器提供基准电压,而这个基准的设计目标就是在供电电压波动时仍然保持稳定;轴的位置也是以电阻的形式被读出来、再转换成脉冲宽度的,并不是拿原始电压去比。所以舵机不是糊涂了 —— 它是饿着了。电源轨塌下去等于扭力变小,轴到不了被要求的角度,误差一直存在,控制电路就一直驱动电机,电机也就一直拉电流。而每一个 20 毫秒的脉冲,都是在这条已经晃动、还叠着电机电刷噪声的电源轨上重新比较一次,于是驱动方向来回翻转 —— 你听到的嗡嗡声和抖动就是这么来的。挂在同一条轨上的 HC-SR04 读数也跟着跳;万一自恢复保险丝最终真的断开,Uno 会彻底断电,然后重新启动。
解法是把电源分开,而不是把角度调小或者把代码放慢。给舵机一路自己的电:四颗 AA 电池装进电池盒(四节串联,大约 6V,盒上自带 ON/OFF 开关)接到面包板的电源轨,舵机的红线和棕线就从这条轨上取电。这样一来,舵机的电流完全不再经过 Uno。
还有一条线绝对不能省:电池的 GND 必须接到 Uno 的 GND。舵机橙色线上的信号是电压,而电压只有相对于某个参考点才有意义。如果舵机的 0V 和 Uno 的 0V 从来没有在任何一点碰头,那 Arduino 送出去的「5V 脉冲」在舵机那一端就没有共同的参考,舵机要么完全不理它,要么随机抽搐。把两个 GND 接在一起,就是让两边对「零」有同一个定义。
千万别把这同一组 6V 电池接到 Uno 的 VIN 脚。这块板子的推荐输入是 7–12V,因为板上的稳压器需要一点余压才能工作;6V 已经贴在范围的底部,电池一旦变弱,5V 电源轨就会塌下来。也别图省事插到 5V 脚上:那支脚绕过了稳压器,直接通到 ATmega328P 的 VCC 引脚,而它的绝对最大额定值是 6.0V —— 比一组全新碱性电池的电压还低。Uno 请用 USB 供电 —— 上传代码时用笔电,要独立展示就用充电宝配那条 A 转 B 线。而四颗全新的碱性电池空载大约是 6.2–6.4V,略高于舵机 4.8–6V 的范围;一旦电机开始拉电流,电压就会掉回范围之内 —— 这也是为什么 4×AA 这个做法在每一张 RBT 桌子上都跑得动。想要全程都稳稳待在标称范围内,四颗 NiMH 充电电池(4 × 1.2V = 4.8V)才是照着规格书来的选择。

电路要怎么接?
接线之前先拔掉 USB,也把 AA 电池盒的开关关掉。这张表的最后一行 —— 共地 —— 正是最常被漏掉的一条。
| 从 | 接到 | 为什么 |
|---|---|---|
| HC-SR04 Vcc(最外侧那一支) | Uno 5V | 只有 15mA 左右 —— 挂在 USB 电源轨上很安全 |
| HC-SR04 Trig(第二支) | Uno D7 | Uno 由这里送出 10 µs 的触发脉冲 |
| HC-SR04 Echo(第三支) | Uno D6 | 脉冲宽度 = 声音来回的时间 |
| HC-SR04 Gnd(另一侧最外那一支) | Uno GND | Echo 信号的零参考点 |
| 舵机橙线 | Uno D9 | 50 Hz 位置脉冲;这条线上的电流几乎是零 |
| 舵机红线 | 面包板 + 轨 | 电机的电流来自电池,不是来自 Uno |
| 舵机棕线 | 面包板 − 轨 | 电机的回流路径 |
| 电池盒红线 | 面包板 + 轨 | 四节串联,大约 6V |
| 电池盒黑线 | 面包板 − 轨 | 把电池这一路的回路接通 |
| Uno GND(第二个 GND 脚) | 面包板 − 轨 | 共地 —— 少了这条,舵机不是抽搐就是完全不动 |
自动垃圾桶的代码是怎么运作的?
有三个设计决定让这个桶盖稳得下来。阈值把原始数字变成一个是/否的判断。连续两次读数确认能挡掉偶发的乱数:从桶壁边缘反弹回来的回声有时候会提早回来,凭空造出一个很小的假距离。保持开启计时器则把「桶盖开着的时间」和「手还在不在」这两件事分开,这样桶盖不会砸在还悬在上面的手上,手正好停在阈值线上时也不会一开一关地跳个不停。
loop() 里面没有任何 delay(),所以桶盖开着的整段时间里传感器都还在读 —— 这正是每一次新的侦测都能把计时刷新的原因。这份 sketch 占用 4,738 字节,也就是 Uno 程序存储空间的 14%。
// 自动垃圾桶 - Arduino Uno 上的 HC-SR04 + SG90 舵机。
// loop() 里没有 delay(),所以桶盖开着的时候传感器仍然一直在读。
#include <Servo.h>
const uint8_t TRIG_PIN = 7; // HC-SR04 Trig
const uint8_t ECHO_PIN = 6; // HC-SR04 Echo
const uint8_t SERVO_PIN = 9; // 舵机橙线(信号)
const int SUDUT_TUTUP = 10; // 依你自己的拉杆长度微调
const int SUDUT_BUKA = 90;
const long AMBANG_CM = 15; // 比这个更近就开盖
const uint8_t SAH_BERTURUT = 2; // 连续两次读数才当真
const unsigned long SELANG_PING = 60UL; // 两次发射之间的间隔(旧回声要先散掉)
const unsigned long MASA_TAHAN = 3000UL; // 最后一次侦测之后盖子还开多久
const unsigned long MASA_GERAK = 600UL; // 留给舵机走完行程的时间
const unsigned long HAD_ECHO_US = 25000UL; // 约 4.2 m 之后就不再等回声
Servo servoTutup;
bool tutupTerbuka = false;
bool servoAktif = false;
uint8_t kiraDekat = 0;
unsigned long pingTerakhir = 0;
unsigned long kesanTerakhir = 0;
unsigned long mulaGerak = 0;
// 发射一串声波,并量出回声要多久才回来。
// 返回 -1 表示在时限内完全没有回声(前方是空的)。
long bacaJarakCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10); // 模块要求的 10 us 触发脉冲
digitalWrite(TRIG_PIN, LOW);
unsigned long tempoh = pulseIn(ECHO_PIN, HIGH, HAD_ECHO_US);
if (tempoh == 0) return -1;
// 声音约 343 m/s = 每 us 0.0343 cm,而这段时间
// 涵盖了去程和回程,所以要除以二。
return (long)(tempoh * 0.0343 / 2.0);
}
// 只有在舵机真的需要动的时候才 attach,这样盖子关着的期间
// 它就不会一直嗡嗡叫、一直吃电流。
void gerakTutup(int sudut) {
if (!servoAktif) {
servoTutup.attach(SERVO_PIN);
servoAktif = true;
}
servoTutup.write(sudut);
mulaGerak = millis();
}
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT);
digitalWrite(TRIG_PIN, LOW);
pinMode(ECHO_PIN, INPUT);
gerakTutup(SUDUT_TUTUP); // 开机就先回到关闭位置
delay(MASA_GERAK);
servoTutup.detach();
servoAktif = false;
Serial.println(F("Tong sampah automatik sedia."));
}
void loop() {
unsigned long kini = millis();
// 1. 输入 - 每隔 SELANG_PING 毫秒发射一次。
if (kini - pingTerakhir >= SELANG_PING) {
pingTerakhir = kini;
long jarak = bacaJarakCm();
if (jarak > 0 && jarak <= AMBANG_CM) {
if (kiraDekat < SAH_BERTURUT) kiraDekat++;
} else {
kiraDekat = 0; // 只要有一次读到远的,计数就归零
}
// 2. 处理 - 读数确认之后才做判断。
if (kiraDekat >= SAH_BERTURUT) {
kesanTerakhir = kini; // 每一次确认都会刷新保持计时
if (!tutupTerbuka) {
tutupTerbuka = true;
gerakTutup(SUDUT_BUKA); // 3. 输出
Serial.print(F("Buka pada "));
Serial.print(jarak);
Serial.println(F(" cm"));
}
}
}
// 只有在整整 MASA_TAHAN 都没有任何确认时才关盖。
// 两个 unsigned long 相减,即使 millis() 溢出归零结果依然正确。
if (tutupTerbuka && (kini - kesanTerakhir >= MASA_TAHAN)) {
tutupTerbuka = false;
kiraDekat = 0;
gerakTutup(SUDUT_TUTUP);
Serial.println(F("Tutup"));
}
// 盖子回到位之后就放开舵机。
if (servoAktif && !tutupTerbuka && (kini - mulaGerak >= MASA_GERAK)) {
servoTutup.detach();
servoAktif = false;
}
}
两次发射之间那 60 毫秒并不是随手挑的整数:上一串声波的回声还在房间里来回弹,如果太早触发下一次,旧回声就会被当成新的读数收进来。而盖子关好之后那一句 detach(),作用是停掉送往舵机的脉冲 —— 盖子是靠重力压着的,所以电机没必要在整段等待时间里一直嗡嗡作响、一直吸电池的电。
舵机和传感器要怎么装到真的垃圾桶上?
SG90 舵机在 4.8V 下的额定扭力是 1.3kg·cm。扭力等于力乘以半径,所以在离轴 2cm 的拉杆上,它大约能推 0.65kg —— 对付一个轻的塑料桶盖绰绰有余,而这也正是拉杆做得太长就会变得没力的原因。
我们这颗舵机是按舵机本体单卖的 —— 舵盘和螺丝并不在这个价格里面,所以拉杆请自己用雪糕棒或者硬塑料条做:钻一个比花键轴略小的孔,压进去让花键咬住,再用电工胶布在轴套外面绕一圈,免得拉杆自己滑脱。先上传代码,再装拉杆 —— sketch 一上电就会把舵机推到 SUDUT_TUTUP,所以要在盖子处于关闭位置的时候才装拉杆;否则舵机会去顶一个已经合上的盖子,那正是前面讲的堵转状态,会拉出那一波涌流。拉杆和桶盖之间用一根拉直的回形针连接,两端各弯一个小圈 —— 这根连杆必须能转动,因为死接的两端会互相锁住,把舵机卡死。舵机装在靠近铰链的位置;如果它在行程末端一直嗡嗡响,就把 SUDUT_BUKA 调小一点。
传感器装在哪里,决定了盖子还关不关得回去。把 HC-SR04 装在桶的前脸、朝外,大约在手的高度,并且确认盖子抬起来之后它看不到自己的盖子。电工胶布只能贴在传感器电路板的边缘;那两个银色圆筒的网面绝对不能遮 —— 声音就是从那里出去、再从那里回来的。
亲眼看看拉杆和传感器真正装到桶上的样子,会更好理解:

我们常看到的错误
把舵机接到 Uno 的 5V 脚。盖子抖、读数乱跳、板子自己重启,头号原因就是这个。把电源分开,然后接上共地。
传感器对着自己的盖子。盖子一抬起来就进了侦测锥,传感器于是不停读到很近的距离,盖子就永远开着。打开 Serial Monitor:如果明明没有手,读数却一直很小,原因就在这里。
拿 delay(3000) 当保持时间。那三秒里 Arduino 是瞎的 —— 它看不见又伸回来的手,于是盖子正好压在使用者的手上。用 millis() 计时就能解决。
买成 360° 舵机。它只会转,不会走到某个角度,所以盖子永远停不到正确的位置上。买的时候请认准 180° 那一款。
常见问题
自动垃圾桶的阈值距离设多少比较合适?
15cm 是很好的起点。HC-SR04 量不了比 2cm 更近的距离,所以阈值设得太小几乎不可能达到;设得太大则会变成有人走过就开盖。一边看 Serial Monitor 一边调 AMBANG_CM 就好。
为什么我的盖子一直开开关关?
因为手正好停在阈值线上,读数于是在范围内外之间来回跳。这份 sketch 里的 MASA_TAHAN 计时和 SAH_BERTURUT 计数就是为此而存在的;如果还是会发生,把 MASA_TAHAN 调大一些。
四颗 AA 电池可以顺便给 Arduino 供电吗?
不能走 VIN 脚。Uno 的推荐输入是 7–12V,而 6V 在这个门槛之下,电池一变弱 5V 电源轨就会塌。Uno 请用 USB 供电,AA 电池组只负责舵机。
HC-SR04 需要额外装库吗?
HC-SR04 不需要 —— pulseIn() 本来就内建在 Arduino 里。至于 Servo 库,那是 Arduino 官方自己的库;如果 IDE 报 Servo.h: No such file or directory,打开 Library Manager 搜 Servo,装一次就行。
这个作品适合中三的 RBT 作业簿吗?
适合 —— 它的结构本来就是输入—处理—输出,而且每一个方块都可以单独演示。想找同样结构的其他题目,可以看我们整理的RBT 电子专题项目清单。
最后更新:2026 年 8 月。有问题?WhatsApp 联系我们。



Ultrasonic Sensor HC-SR04 Ultrasonic Range Detection Distance Finder Obstacle avoidance For Arduino Application
SG90 MG90 Tower Pro Micro Servo Motor 9g 180 360 Servo Motor for Arduino Robotic - SG90- 180°
Arduino Compatible UNO R3 FT232 Development Board Arduino Compatible CH340 Alternative
Data Cable Type-A Type-C MicroUSB Type-B 0.5m 1m 30cm 0.3m 100cm Data Transfer Upload Code - TYPE-A TO TYPE-B (1.0M)
MB102 Breadboard 170 400 830 Holes Breadboard Donut Board Arduino Prototype Multi Color - BREADBOARD (400 HOLES)
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-M 20CM
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-F 20CM
AA Battery Holder with Cover On/Off Switch 2/3/4 Slots Battery AA Holder Red/Black Wire - AA BATTERY CASE (4S)
Electrical Insulation Tape 18mm x 10m PVC Color Coded Marking Red White Blue Yellow Green Black - ELECTRICAL TAPE (BLACK)