Arduino 自动垃圾桶(RBT 中三专题):HC-SR04 + SG90 舵机接线与完整代码

Kartun tong sampah kelabu dengan penutup terbuka diangkat oleh servo biru lut sinar di engsel, dan sensor ultrasonik biru berpita hitam di hadapan tong mengesan tangan

中三 RBT 的自动垃圾桶,说穿了就是三个方块:HC-SR04 量出手的距离,Arduino Uno 拿这个距离跟一个阈值比较,SG90 舵机把桶盖抬起来。最常出问题的地方不是代码,而是电源。舵机直接从 Uno 的 5V 脚取电,桶盖就会抖,板子也会在示范到一半时自己重启。

做这个自动垃圾桶要准备什么?

这个作品完全不必动烙铁 —— 所有连接都靠杜邦线和一块面包板完成。

Parts list

ItemPriceQty
Electrical Insulation Tape 18mm x 10m PVC Color Coded Marking Red White Blue Yellow Green Black - ELECTRICAL TAPE (BLACK)Electrical Insulation Tape 18mm x 10m PVC Color Coded Marking Red White Blue Yellow Green Black - ELECTRICAL TAPE (BLACK)T1609BKRM1.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 的丝印把功能合并印在一起:VccTrig_RX_SCLEcho_TX_SDAGnd。多出来的那几个字是给别的通信模式用的,这个作品完全用不到 —— 第二支脚就当 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 供电的蓝色 Arduino 板,以及给白色面包板和蓝色半透明舵机供电的黑色 AA 电池盒,两者由一条发亮的粗黑色 GND 线连起来
两个各自独立的供电孤岛 —— Uno 走 USB,舵机走 4 颗 AA 电池 —— 靠一条共地线连成一体。少了那条粗线,橙色信号线就毫无意义。

电路要怎么接?

接线之前先拔掉 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 联系我们

Leave a Reply

Your email address will not be published. Required fields are marked *