Руководства · 17 мин чтения

Умное мусорное ведро на Arduino: HC-SR04, крышка на сервоприводе и сигнал заполнения

Умное мусорное ведро на Arduino: HC-SR04 замечает руку, сервопривод плавно закрывает крышку, светодиоды и зуммер показывают заполнение, а ESP32 шлёт уведомления в Telegram.

Умное мусорное ведро на Arduino — простой, но очень поучительный проект автоматизации: крышка открывается без прикосновения, и это одна из самых популярных школьных работ. Ультразвуковой датчик HC-SR04 на передней стенке ведра замечает приближающуюся руку, Arduino открывает крышку сервоприводом, а через несколько секунд после того, как рука убрана, закрывает её медленно, без хлопка. Второй HC-SR04 под крышкой измеряет высоту мусора: по мере заполнения по очереди загораются зелёный, жёлтый и красный светодиоды, а когда ведро заполнено целиком, пищит зуммер.

В этом руководстве идём пошагово: от списка деталей к тому, где закрепить датчики, как рассчитать момент сервопривода, как соединить крышку с петлёй, как откалибровать систему, и к полному коду, который одновременно отсчитывает время ожидания и плавно закрывает крышку. Затем посчитаем, сколько проект на самом деле проработает от батарей, отправим уведомление о заполнении с ESP32 на твой телефон сообщением в Telegram и перепишем тот же проект для Raspberry Pi Pico на MicroPython. В конце есть план презентации школьного проекта и таблица испытаний, которую можно заполнить собственными измерениями.

Ультразвуковой датчик расстояния HC-SR04
В умном мусорном ведре HC-SR04 выполняет две задачи: датчик спереди измеряет расстояние до приближающейся руки, а датчик под крышкой — высоту мусора. Он видит от 2 до 400 см в узком луче.

Как работает умное мусорное ведро?

Проект — это цикл, который повторяется десятки раз в секунду. Каждые 60 миллисекунд Arduino снимает показание с датчика руки. Если два измерения подряд показывают предмет ближе 20 см, она решает, что это рука, и отправляет сервопривод в открытое положение. Пока рука остаётся на месте, время ожидания перезапускается с каждым измерением; через 4 секунды после того, как рука исчезла, крышка медленно закрывается со скоростью около 50 градусов в секунду. Если рука возвращается, пока крышка закрывается, она сразу открывается снова. Через секунду после закрытия крышки датчик заполнения делает 5 измерений, берёт медиану как расстояние до поверхности мусора и переводит его в проценты. В коде работает небольшой конечный автомат, в котором крышка всегда находится в одном из трёх состояний: CLOSED (закрыта), OPEN (открыта) и CLOSING (закрывается).

ДетальЗадачаПримечание
Arduino Uno (или Nano)Мозг: измеряет, принимает решения, управляет сервоприводом и светодиодамиПодойдут и ESP32 или Pico
2 × HC-SR04Один замечает руку, другой измеряет заполнение5 V, около 15 mA во время измерения
Сервопривод SG90, MG90S или MG996RОткрывает и закрывает крышкуВыбирается по весу крышки (расчёт ниже)
3 светодиода (зелёный, жёлтый, красный) и 3 × 220 ΩИндикатор заполненияПо резистору на каждый светодиод
Активный зуммерСигнал «ведро полное»Пассивному зуммеру нужна tone()
Петля, винты, термоклейДержит крышку, крепит качалку сервоприводаВес крышки должна нести петля, а не сервопривод
Конденсатор 470 µFСглаживает пусковой ток сервоприводаМежду + и − сервопривода, соблюдай полярность
Источник питанияБлок питания 5 V, пауэрбанк или батареиРасчёт времени работы ниже
ESP32 (по желанию)Уведомление о заполнении на телефонНужен Wi-Fi
Arduino Uno
Arduino Uno
Сервопривод SG90
Сервопривод SG90
Зуммер
Зуммер
Светодиод
Светодиод

Какой датчик выбрать для обнаружения руки?

Открывать ведро умеют разные датчики, но для этой задачи они подходят не одинаково. На самом деле нужно заметить руку, приблизившуюся к ведру на 15–25 см, и не реагировать на человека, который просто проходит мимо. HC-SR04 выдаёт расстояние в сантиметрах, поэтому порог в коде можно задать ровно так, как нужно, — вот почему большинство проектов умного ведра используют именно его. Сильные и слабые стороны альтернатив:

ДатчикКак обнаруживаетДля мусорного ведра
HC-SR04 (ультразвуковой)Эхо звука, 2–400 см, узкий конусПорог задаётся в коде; самый сбалансированный вариант
ИК-датчик препятствий (FC-51)Отражённый инфракрасный свет, примерно 2–30 смДешёвый и быстрый; его сбивают солнечный свет и тёмные перчатки
PIR (HC-SR501)Движение теплового излучения тела, до 7 м, широкий уголСрабатывает на каждого прохожего и держит сигнал несколько секунд; не подходит
APDS-9960Близость на малом расстоянии и жесты рукойКрасивый вариант «открыть взмахом»; нужны I2C и 3,3 V

Как работает PIR-датчик, рассказано в статье PIR-датчик движения, а вариант с управлением жестами — в руководстве датчик жестов APDS-9960. Коротко о том, как работает HC-SR04: он посылает короткую звуковую посылку на 40 kHz и выдаёт время полёта эха импульсом на пине ECHO. Звук в воздухе при 20 °C распространяется со скоростью около 343 м/с, а путь проходится туда и обратно, поэтому время в микросекундах, делённое на 58, даёт расстояние в сантиметрах. Подробности — в статье датчик расстояния HC-SR04.

Размещение датчиков

Датчик руки ставится на корпус ведра, в верхней части передней стенки, а не на крышку: при открытии крышки датчик повернулся бы вместе с ней и смотрел бы в потолок. Если наклонить датчик вверх на 10–20 градусов, он поймает руку, которая тянется над ведром, и пропустит ноги проходящих мимо. Если ведро стоит у стены или шкафа, в направлении взгляда датчика в пределах 30 см не должно быть неподвижных поверхностей; иначе он примет такую поверхность за руку и крышка никогда не закроется.

Датчик заполнения приклеивается под крышкой, ближе к центру, и смотрит строго вниз. В этом положении он измеряет что-то осмысленное только при закрытой крышке; когда крышка открыта, он смотрит в стену или потолок. Поэтому код измеряет заполнение лишь через секунду после закрытия крышки. Звуковой конус датчика узкий (15 градусов по даташиту, на практике чуть шире), но на глубине 35 см он охватывает пятно диаметром 10–20 см; в вёдрах уже 20 см звук может отражаться от боковых стенок и давать ложные показания. Край мусорного пакета заворачивай наружу ведра: край пакета, свисающий внутрь, оказывается перед датчиком, и ведро выглядит полным, даже когда оно пустое.

Никогда не запускай оба HC-SR04 одновременно. Каждый примет эхо соседа за своё, и оба будут показывать чепуху. В коде два датчика опрашиваются по очереди, с интервалом не меньше 60 мс; пока датчик заполнения делает свои 5 измерений, датчик руки ждёт. Перед датчиками тоже должно быть свободно: край отверстий, вырезанных в ведре, должен быть вровень с лицевой стороной датчика, а даже тонкий кусочек пластика или прозрачного скотча перед ним гасит эхо.

Схема подключения: Arduino Uno, ESP32 и Pico

Таблица ниже точно соответствует всему коду в этой статье. На Uno датчик руки подключается к D2–D3, датчик заполнения к D4–D5, сервопривод к D9, светодиоды к D10–D12, а зуммер к D8. Библиотека Servo на Uno использует Timer1 и поэтому отключает ШИМ-выход на D9 и D10; светодиод на D10 мы только включаем и выключаем, так что нам это не мешает. Перед каждым светодиодом поставь резистор 220 Ω; номинал для своего светодиода можно рассчитать в утилите калькулятор резистора для светодиода. Расположение пинов своей платы смотри в утилите распиновки.

ПодключениеArduino UnoESP32Raspberry Pi Pico
TRIG датчика рукиD2GPIO 18GP2
ECHO датчика рукиD3 (напрямую)GPIO 19, через делитель 1 kΩ / 2 kΩGP3, через делитель 1 kΩ / 2 kΩ
TRIG датчика заполненияD4GPIO 25GP4
ECHO датчика заполненияD5 (напрямую)GPIO 26, через делительGP5, через делитель
VCC обоих HC-SR045V5V (VIN)VBUS
Сигнал сервопривода (оранжевый)D9GPIO 13GP15
+ сервопривода (красный)5V (SG90) или внешние 5–6 V5V (VIN) или внешние 5–6 VVBUS или внешние 5–6 V
Зелёный, жёлтый, красный светодиод (с 220 Ω)D10, D11, D12GPIO 27, 32, 33GP10, GP11, GP12
Активный зуммер (+)D8GPIO 23GP13
Все GNDGNDGNDGND
Пин ECHO у HC-SR04 выдаёт импульс 5 V, а пины ESP32 и Pico рассчитаны на 3,3 V и 5 V не выдерживают. На линии ECHO каждого датчика собери отдельный делитель напряжения: ECHO → 1 kΩ → точка, идущая к плате → 2 kΩ → GND. Получается 5 V × 2 / 3 ≈ 3,3 V. Для TRIG делитель не нужен: датчику хватает запуска уровнем 3,3 V. Пин 5V (VIN) у ESP32 и пин VBUS у Pico дают 5 V, только пока плата питается от USB.

Выбор сервопривода и расчёт момента

Самая частая проблема в проектах умного мусорного ведра — крышка открывается наполовину и дрожит или вовсе не поднимается. Причина почти всегда в том, что сервопривод слишком слаб для этой крышки. Прикинуть нужный момент легко: момент (кг·см) = вес крышки (кг) × расстояние от петли до её центра масс (см). У плоской крышки равномерной толщины центр масс ровно посередине; для крышки длиной 24 см это расстояние равно 12 см. Тяжелее всего в самом начале открывания, когда крышка горизонтальна; по мере подъёма нужный момент падает, а в вертикальном положении он почти нулевой. В каталогах сервоприводов указан момент при заторможенном валу, то есть точка, где сервопривод напрягается и останавливается; сервопривод, работающий близко к этому пределу, греется и изнашивает шестерни. Поэтому умножай полученное значение минимум на 2.

КрышкаВесЦентр массНужный моментС запасом 2×Рекомендация
Картон или пенокартон 20 × 20 см50 г10 см0,5 кг·см1,0 кг·смSG90
Пластиковая крышка ведра 25 см120 г12,5 см1,5 кг·см3,0 кг·смMG996R (MG90S на пределе)
Крышка из МДФ или фанеры 30 см300 г15 см4,5 кг·см9,0 кг·смMG996R плюс противовес
СервоприводМомент (4,8 V / 6 V)ШестерниТокПримечание
SG901,8 кг·см (указан только для 4,8 V)Пластик100–250 mA в движенииМожет питаться от пина 5V Arduino
MG90S1,8 / 2,2 кг·смМеталл150–300 mA в движенииТого же размера, что SG90, но долговечнее
MG996R9,4 / 11 кг·смМеталл0,5–0,9 A в движении, до 2,5 A при заторможенном валуОбязательно отдельное питание 5–6 V
Сервопривод MG996R с металлическими шестернями
MG996R — сервопривод с металлическими шестернями и моментом около 11 кг·см при 6 V. Он легко поднимает пластиковую крышку настоящего мусорного ведра, но при заторможенном валу может потреблять до 2,5 A, поэтому питать его нужно от отдельного источника, а не от Arduino.

Вместо покупки более мощного сервопривода часто хватает уравновесить крышку. Если у крышки есть короткий выступ за петлёй, несколько приклеенных туда болтов или монет уравновесят часть её веса; такой противовес может снизить нужный момент вдвое. Можно также заставить качалку сервопривода толкать крышку в точке, далёкой от петли, но рычаг не даёт момент бесплатно: чем дальше от петли, тем меньше нужна сила, но толкаемая точка проходит больший путь, значит, нужна более длинная качалка, а длинная качалка снова повышает момент, требуемый от сервопривода. Если крышка должна открываться почти на 90 градусов, нужный момент остаётся практически таким же, как при креплении сервопривода прямо у петли. Подробное сравнение типов сервоприводов — в статьях Что такое сервопривод? и сервопривод MG996R.

Советы по монтажу: петля, качалка и провода

  • Вес должна нести петля: крышка крепится на настоящую петлю (родную петлю ведра или маленькую мебельную петлю). Если крышка лежит только на валу сервопривода, весь вес приходится на его подшипник, и пластиковые шестерни SG90 быстро ломаются.
  • Совмести вал сервопривода с осью петли: проще всего привинтить сервопривод к заднему краю ведра так, чтобы его вал оказался точно на продолжении линии петли, а качалку закрепить на крышке винтом или термоклеем. Если оси не совпадают, сервопривод при каждом открывании толкает крышку вбок.
  • Надёжно закрепи корпус сервопривода: не держи сервопривод на одном двустороннем скотче — корпус будет проворачиваться, когда он поднимает крышку. Используй два винта через монтажные ушки сервопривода или небольшой L-образный кронштейн.
  • Не прижимай крышку к краю: выбери угол закрытия так, чтобы крышка лежала на краю ведра, но сервопривод на неё не давил. Если сервопривод постоянно давит на закрытую крышку, он жужжит, греется и тратит ток впустую.
  • Оставь запас длины проводов: провода датчика заполнения двигаются вместе с крышкой. Проведи кабель рядом с петлёй, с такой длиной, чтобы он не натягивался даже при полностью открытой крышке, и закрепи его стяжкой.
  • Держи электронику подальше от мусора: Arduino, батареи и макетную плату положи в небольшую коробку, приклеенную к задней стенке или дну ведра. Жидкость или влага из мусорного пакета не должна попадать на схему.
  • Начни с картонного макета: для школьного проекта лёгкая крышка из обувной коробки или пенокартона прекрасно работает даже с SG90, а заодно служит хорошим примером для расчёта момента в презентации.

Пошаговая настройка и калибровка

Прежде чем переходить к основному коду, загрузи небольшой скетч калибровки ниже. Он поворачивает сервопривод на угол, введённый в монитор порта, и каждые полсекунды выводит расстояние с обоих датчиков. Так для твоего ведра измеряются углы закрытой и открытой крышки, глубина пустого ведра и порог обнаружения руки.

C++ (Arduino IDE)
1#include <Servo.h>
2
3const int HAND_TRIG = 2, HAND_ECHO = 3;   // el sensörü / hand sensor
4const int FILL_TRIG = 4, FILL_ECHO = 5;   // doluluk sensörü / fill sensor
5const int SERVO_PIN = 9;
6
7Servo lid;
8unsigned long lastPrint = 0;
9
10int pingCm(int trig, int echo) {
11  digitalWrite(trig, LOW);  delayMicroseconds(2);
12  digitalWrite(trig, HIGH); delayMicroseconds(10);   // 10 µs tetik darbesi / 10 µs trigger pulse
13  digitalWrite(trig, LOW);
14  unsigned long t = pulseIn(echo, HIGH, 30000UL);    // en fazla 30 ms bekle / wait at most 30 ms
15  return t == 0 ? 999 : (int)(t / 58);               // 999 = yankı yok / 999 = no echo
16}
17
18void setup() {
19  Serial.begin(9600);
20  pinMode(HAND_TRIG, OUTPUT); pinMode(HAND_ECHO, INPUT);
21  pinMode(FILL_TRIG, OUTPUT); pinMode(FILL_ECHO, INPUT);
22  lid.write(90);                                     // başlangıçta orta konum / start at the middle position
23  lid.attach(SERVO_PIN);
24  Serial.println(F("Açı yaz (0-180) ve Enter'a bas / Type an angle (0-180) and press Enter"));
25}
26
27void loop() {
28  if (Serial.available()) {
29    String s = Serial.readStringUntil('\n');
30    s.trim();
31    if (s.length() > 0) {                            // boş satırı yok say / ignore empty lines
32      int a = constrain(s.toInt(), 0, 180);
33      lid.write(a);
34      Serial.print(F("Servo: "));
35      Serial.println(a);
36    }
37  }
38
39  if (millis() - lastPrint >= 500) {                 // yarım saniyede bir yaz / print every half second
40    lastPrint = millis();
41    int hand = pingCm(HAND_TRIG, HAND_ECHO);
42    delay(60);                                       // iki sensör arasında bekle / wait between the two sensors
43    int fill = pingCm(FILL_TRIG, FILL_ECHO);
44    Serial.print(F("El / Hand: "));
45    Serial.print(hand);
46    Serial.print(F(" cm   Doluluk / Fill: "));
47    Serial.print(fill);
48    Serial.println(F(" cm"));
49  }
50}
  1. Загрузи код, не надевая качалку, и открой монитор порта на скорости 9600 бод; выбери окончание строки Newline («Новая строка»). Сервопривод встанет на 90 градусов.
  2. Введи 10 и нажми Enter. Придерживая крышку закрытой, надень качалку на вал в этом положении, а затем закрепи её на крышке.
  3. Увеличивай угол шагами 20, 40, 60 и найди значение, при котором крышка полностью открыта; обычно это 90–110 градусов. Не открывай крышку дальше вертикали. Это значение — LID_OPEN. Если сервопривод вращается в другую сторону, выбери значения закрытия и открытия соответственно; код работает в обоих направлениях.
  4. Вернись к малым углам. От угла, при котором крышка полностью ложится на край, отступи на 2–3 градуса в сторону открывания; это LID_CLOSED. Если сервопривод в закрытом положении жужжит, он давит на крышку.
  5. С надетым пакетом и пустым ведром запиши значение датчика заполнения; это EMPTY_CM. Для FULL_CM выбери 6–10 см: когда мусор доходит до этой высоты, крышка всё ещё должна легко закрываться, а датчик — надёжно измерять, потому что HC-SR04 не измеряет ближе 2–3 см.
  6. Подноси руку к датчику руки на 5, 10, 20 и 30 см и смотри на показания. Потом пройди мимо ведра и запиши минимальное значение, которое он показывает. HAND_CM должно быть меньше этого значения; для большинства вёдер хорошо работают 15–25 см.
  7. Впиши найденные значения в настройки в начале основного кода и загрузи его.

Основной код: обнаружение руки, время ожидания и плавное закрытие

В основном коде нет длинных delay(): всё делается по меткам времени из millis(). Поэтому датчик руки опрашивается даже во время закрывания крышки, и если рука снова приближается, крышка сразу открывается обратно. Это простая, но важная защита, которая не даёт крышке захлопнуться на руке ребёнка. Подробности о неблокирующих задержках — в статье millis() и delay(), а идея мышления состояниями — в статье конечный автомат на Arduino.

C++ (Arduino IDE)
1#include <Servo.h>
2
3// Pinler / Pins
4const int HAND_TRIG = 2, HAND_ECHO = 3;    // el sensörü, kutunun önünde / hand sensor, front of the bin
5const int FILL_TRIG = 4, FILL_ECHO = 5;    // doluluk sensörü, kapağın altında / fill sensor, under the lid
6const int SERVO_PIN = 9;
7const int LED_GREEN = 10, LED_YELLOW = 11, LED_RED = 12;
8const int BUZZER = 8;                      // aktif buzzer / active buzzer
9
10// Ayarlar: kalibrasyon kodunda bulduğun değerleri yaz / Settings: use the values from the calibration sketch
11const int HAND_CM = 20;                    // bundan yakın el kapağı açar / a hand closer than this opens the lid
12const int LID_CLOSED = 10;                 // kapak kenara oturuyor, bastırmıyor / lid rests on the rim, not pressing
13const int LID_OPEN = 110;                  // kapak tam açık / lid fully open
14const unsigned long OPEN_TIME = 4000;      // son el algılamadan sonra açık kalma (ms) / stay open after the last hand (ms)
15const unsigned long CLOSE_STEP_MS = 20;    // kapanırken derece başına ms / ms per degree while closing
16const int EMPTY_CM = 35;                   // boş kutuda sensörden dibe / sensor to bottom when empty
17const int FULL_CM = 8;                     // bu mesafede kutu %100 dolu / the bin is 100% full at this distance
18
19enum LidState { CLOSED, OPEN, CLOSING };
20LidState state = CLOSED;
21Servo lid;
22
23unsigned long lastHand = 0, lastHandPing = 0, closeStart = 0, closedAt = 0, lastFill = 0;
24int fillPercent = 0;
25byte handHits = 0;
26bool fullWarned = false;
27
28int pingCm(int trig, int echo) {
29  digitalWrite(trig, LOW);  delayMicroseconds(2);
30  digitalWrite(trig, HIGH); delayMicroseconds(10);  // 10 µs tetik darbesi / 10 µs trigger pulse
31  digitalWrite(trig, LOW);
32  unsigned long t = pulseIn(echo, HIGH, 30000UL);   // en fazla 30 ms bekle / wait at most 30 ms
33  return t == 0 ? 999 : (int)(t / 58);              // 999 = yankı yok / 999 = no echo
34}
35
36int medianCm(int trig, int echo) {                  // 5 ölçümün ortancası / median of 5 readings
37  int v[5];
38  delay(60);                                        // önce el sensörünün yankısı sönsün / first let the hand sensor's echo die out
39  for (int i = 0; i < 5; i++) {
40    v[i] = pingCm(trig, echo);
41    delay(60);                                      // yankılar sönsün / let the echoes die out
42  }
43  for (int i = 0; i < 4; i++)                       // küçükten büyüğe sırala / sort ascending
44    for (int j = i + 1; j < 5; j++)
45      if (v[j] < v[i]) { int tmp = v[i]; v[i] = v[j]; v[j] = tmp; }
46  return v[2];
47}
48
49int toPercent(int cm) {                             // mesafe → doluluk yüzdesi / distance → fill percent
50  long p = (long)(EMPTY_CM - cm) * 100 / (EMPTY_CM - FULL_CM);
51  if (p < 0) p = 0;
52  if (p > 100) p = 100;
53  return (int)p;
54}
55
56void showLevel() {
57  digitalWrite(LED_GREEN, fillPercent < 50);
58  digitalWrite(LED_YELLOW, fillPercent >= 50 && fillPercent < 80);
59  digitalWrite(LED_RED, fillPercent >= 80);
60}
61
62void beep(int n) {
63  for (int i = 0; i < n; i++) {
64    digitalWrite(BUZZER, HIGH); delay(120);
65    digitalWrite(BUZZER, LOW);  delay(120);
66  }
67}
68
69void openLid() {
70  if (!lid.attached()) lid.attach(SERVO_PIN);
71  lid.write(LID_OPEN);                              // hızlı aç / open quickly
72  state = OPEN;
73  if (fillPercent >= 90) beep(2);                   // açılırken dolu uyarısı / full warning on opening
74}
75
76void setup() {
77  Serial.begin(9600);
78  pinMode(HAND_TRIG, OUTPUT); pinMode(HAND_ECHO, INPUT);
79  pinMode(FILL_TRIG, OUTPUT); pinMode(FILL_ECHO, INPUT);
80  pinMode(LED_GREEN, OUTPUT); pinMode(LED_YELLOW, OUTPUT); pinMode(LED_RED, OUTPUT);
81  pinMode(BUZZER, OUTPUT);
82  lid.write(LID_CLOSED);                            // attach'tan önce: açılışta 90°'ye sıçramasın / before attach: no jump to 90° at power-up
83  lid.attach(SERVO_PIN);
84  delay(500);
85  lid.detach();                                     // kapalıyken servoyu bırak / release the servo while closed
86  closedAt = millis();
87  showLevel();
88}
89
90void loop() {
91  unsigned long now = millis();
92
93  // 1) El algılama, 60 ms'de bir / Hand detection, every 60 ms
94  if (now - lastHandPing >= 60) {
95    lastHandPing = now;
96    int d = pingCm(HAND_TRIG, HAND_ECHO);
97    if (d >= 2 && d <= HAND_CM) {
98      if (handHits < 2) handHits++;
99    } else {
100      handHits = 0;
101    }
102    if (handHits >= 2) {                            // art arda 2 ölçüm / 2 readings in a row
103      lastHand = now;                               // bekleme süresi yeniden başlar / the wait time restarts
104      if (state != OPEN) openLid();                 // kapanırken el gelirse yeniden aç / reopen if a hand appears while closing
105    }
106  }
107
108  // 2) Bekleme süresi doldu mu? / Has the wait time passed?
109  if (state == OPEN && now - lastHand >= OPEN_TIME) {
110    state = CLOSING;
111    closeStart = now;
112  }
113
114  // 3) Yavaş kapanma: açı geçen süreden hesaplanır / Slow close: angle is computed from elapsed time
115  if (state == CLOSING) {
116    int moved = (now - closeStart) / CLOSE_STEP_MS;
117    int range = abs(LID_OPEN - LID_CLOSED);
118    if (moved >= range) {
119      lid.write(LID_CLOSED);
120      state = CLOSED;
121      closedAt = now;
122    } else {
123      lid.write(LID_OPEN > LID_CLOSED ? LID_OPEN - moved : LID_OPEN + moved);
124    }
125  }
126
127  // 4) Kapandıktan 0,4 s sonra servoyu bırak / Release the servo 0.4 s after closing
128  if (state == CLOSED && lid.attached() && now - closedAt > 400) lid.detach();
129
130  // 5) Doluluk: yalnızca kapak kapalı ve durgunken / Fill level: only while the lid is closed and still
131  if (state == CLOSED && now - closedAt > 1000 && now - lastFill >= 3000) {
132    lastFill = now;
133    int cm = medianCm(FILL_TRIG, FILL_ECHO);
134    if (cm < 999) fillPercent = toPercent(cm);     // yankı yoksa eski değeri koru / keep the old value if there is no echo
135    showLevel();
136    Serial.print(F("Doluluk / Fill: "));
137    Serial.print(cm);
138    Serial.print(F(" cm = %"));
139    Serial.println(fillPercent);
140    if (fillPercent >= 90 && !fullWarned) {         // doldu: bir kez öt / just filled: beep once
141      fullWarned = true;
142      beep(3);
143    }
144    if (fillPercent < 70) fullWarned = false;       // boşaltıldı / emptied
145  }
146}
Функция или настройкаЧто делает
pingCm()Делает одно измерение; возвращает 999, если эхо не пришло за 30 мс
medianCm()Делает 5 измерений с интервалом 60 мс, сортирует их и возвращает средний (медиану)
toPercent()Переводит расстояние в проценты 0–100 по EMPTY_CM и FULL_CM
handHitsСчитает, сколько измерений подряд видели руку; одно ложное показание крышку не откроет
OPEN_TIMEВремя от последнего момента, когда была видна рука, до начала закрывания (4000 мс)
CLOSE_STEP_MSВремя на один градус при закрывании: 100 градусов × 20 мс = закрытие за 2 секунды
lid.detach()Через 0,4 с после закрытия крышки перестаёт посылать импульсы на сервопривод
fullWarnedПодаёт сигнал заполнения только один раз; сбрасывается, когда заполнение падает ниже 70%

Секрет плавного закрытия в том, что угол вычисляется по прошедшему времени, а не увеличивается на градус за шаг. Через 600 мс после начала закрывания крышка должна пройти 600 / 20 = 30 градусов; даже если цикл ненадолго задержится на измерении заполнения или на зуммере, код поставит крышку на правильный угол, и движение не будет дёрганым. Чтобы закрывать ещё медленнее, увеличь CLOSE_STEP_MS: при 30 мс 100 градусов займут 3 секунды. После закрытия крышки сервопривод отпускается командой detach(): крышка уже лежит на краю, держать её не нужно, сервопривод не жужжит и меньше расходует батарею. Вызов write() перед attach() в коде тоже не случаен: при включении Arduino сервопривод не прыгает сначала на 90 градусов, а сразу стартует в закрытом положении.

Измерение заполнения и сигналы светодиодов и зуммера

Датчик заполнения измеряет расстояние от крышки до самой высокой точки мусора. Это расстояние максимально, когда ведро пустое (EMPTY_CM), и минимально, когда оно полное (FULL_CM). Процент считается по формуле: заполнение (%) = (EMPTY_CM − измеренное) / (EMPTY_CM − FULL_CM) × 100. Например, при EMPTY_CM = 35 см, FULL_CM = 8 см и измеренном расстоянии 20 см заполнение равно (35 − 20) / 27 × 100 ≈ 55%, и загорается жёлтый светодиод. Если показание больше 35 см (звук отразился от дна наискосок), результат ограничивается 0%; если меньше 8 см — 100%.

ЗаполнениеСветодиодЗуммерЗначение
0–49%ЗелёныйМолчитМеста много
50–79%ЖёлтыйМолчитЗаполнено больше чем наполовину, скоро пора выносить
80–89%КрасныйМолчитПочти полное
90–100%Красный3 коротких сигнала при заполнении, 2 сигнала при каждом открыванииПора выносить

Поверхность мусора неровная: если прямо под датчиком стоит вертикально одна бутылка, измерение увидит эту точку и покажет ведро более полным, чем оно есть. Медианный фильтр убирает случайное выпадающее показание, но форму мусора он не исправит, поэтому пороги вроде 80% и 90% стоит воспринимать как предупреждение «скоро выносить», а не как точное измерение. Чтобы сигнал не пищал постоянно, в коде используется гистерезис: сигнал заполнения звучит один раз при 90% и не повторяется, пока ведро не опустошат и уровень не упадёт ниже 70%. Разница между фильтрами подробно разобрана в статье фильтрация данных датчиков.

Скорость звука зависит от температуры: примерно v = 331,3 + 0,606 × T (м/с). Число 58 верно для 20 °C; на балконе при 5 °C звук идёт медленнее, и показания получаются примерно на 3% длиннее. В ведре глубиной 35 см это ошибка меньше 1 см, и на заполнение она не влияет, но если жюри спросит, знание причины производит хорошее впечатление.

Питание от батарей: на сколько хватит?

Запустить умное ведро без провода заманчиво, но Arduino Uno — не самая дружелюбная к батареям плата. Из-за USB-микросхемы, стабилизатора и светодиода питания плата потребляет около 45–50 mA, даже ничего не делая. Добавь ток покоя двух HC-SR04, одного горящего светодиода и электроники отпущенного сервопривода — и средний ток вырастает примерно до 70–80 mA. Ток 100–250 mA, который сервопривод берёт при открывании и закрывании, длится всего несколько секунд, поэтому в ведре, которое открывают 50 раз в день, он повышает среднее значение лишь примерно на 1 mA. Время работы от батареи — примерно ёмкость (mAh) / средний ток (mA); чтобы посчитать со своими значениями, воспользуйся утилитой калькулятор времени работы от батареи.

Источник питанияПодключениеПримерное время работы (75 mA)Вывод
Батарейка-«крона» (PP3) на 9 V (около 500 mAh)VIN5–6 часовНе рекомендуется: при старте сервопривода напряжение проседает, и плата перезагружается
6 щелочных батареек AA (9 V)Гнездо питания (DC) или VINОколо 1 дняХватит на день презентации
2 × 18650 (7,4 V, 2500 mAh)VIN; для сервопривода LM2596, настроенный на 5–6 VОколо 1,5 дняПерезаряжаемые; нужны зарядное устройство 2S и плата защиты
Пауэрбанк на 10 000 mAhUSB3–4 дняНекоторые пауэрбанки отключаются при нагрузке ниже 50–100 mA
Блок питания 5 V 2 AUSB или пин 5VБез ограниченийЛучший выбор для постоянной работы дома
Держатель для аккумуляторов 18650 Li-ion
Держатель для аккумуляторов 18650: два элемента последовательно дают 7,4 V. Такое напряжение подходит для питания Arduino через VIN; для сервопривода между ними ставится понижающий модуль, настроенный на 5–6 V.

Вывод из таблицы очевиден: ведро на Uno с батареями идеально для презентаций и демонстраций, но для ведра, которое месяцами будет работать дома, используй блок питания 5 V. Как настроить понижающий модуль между двумя 18650 и сервоприводом, рассказано в статье LM2596. Если очень хочется продлить работу от батарей, вместо Uno можно взять Arduino Pro Mini со снятым светодиодом питания, усыплять процессор между измерениями и зажигать светодиоды лишь ненадолго при открывании крышки; с этими мерами средний ток падает до 15–20 mA, и те же батареи служат в 4–5 раз дольше. Чем отличаются способы питания Arduino (USB, гнездо питания, VIN, пин 5V), рассказано в статье как запитать Arduino.

Все GND сервопривода, датчиков и Arduino должны быть общими; если GND сервопривода с отдельным питанием не соединён с Arduino, сервопривод не может разобрать сигнал и дёргается. Поставь конденсатор 470 µF на выводы питания сервопривода. Если Arduino перезагружается при каждом открывании крышки, значит, сервопривод при старте просаживает линию 5 V; причины и решения — в статье Arduino постоянно перезагружается.

Уведомление о заполнении с ESP32 (Telegram)

Чтобы увидеть светодиод полного ведра, к нему нужно подойти; а вот ESP32 может отправить эту информацию прямо на твой телефон. Проще всего через Telegram: это бесплатно, а собственный бот настраивается за несколько минут. В Telegram напиши @BotFather, создай бота командой /newbot и запиши выданный токен. Затем один раз отправь своему боту /start: бот не может писать тому, кто ни разу с ним не общался. Чтобы узнать свой chat ID, можно написать боту вроде @userinfobot или воспользоваться запросом getUpdates в Bot API. Дополнительная библиотека коду не нужна: он отправляет запрос на адрес Telegram sendMessage через встроенный в ESP32 HTTPClient.

Код использует тот же конечный автомат, что и версия для Uno; только сигнал сервопривода формируется не библиотекой Servo, а ШИМ-блоком ESP32 — LEDC. В сигнале 50 Hz импульс 0,544–2,4 мс соответствует 0–180 градусам, а остановка импульсов (запись 0 через ledcWrite) отпускает сервопривод. Код рассчитан на версию 3.x ядра ESP32 для Arduino; в версии 2.x нет функции ledcAttach(), поэтому обнови ядро в «Менеджере плат» (Boards Manager).

Отладочная плата ESP32
ESP32 благодаря Wi-Fi отправляет на телефон сообщение в Telegram, когда ведро заполнено. Линии ECHO датчиков HC-SR04 нельзя подключать к её пинам без делителя до 3,3 V.
C++ (Arduino IDE)
1// ESP32 Arduino çekirdeği 3.x / ESP32 Arduino core 3.x
2#include <WiFi.h>
3#include <WiFiClientSecure.h>
4#include <HTTPClient.h>
5
6const char* WIFI_SSID = "WIFI_ADIN";              // kendi ağın / your network
7const char* WIFI_PASS = "WIFI_SIFREN";
8const char* BOT_TOKEN = "123456789:ABCdefGhIJKlmNoPQRstuVWxyz";  // BotFather'dan / from BotFather
9const char* CHAT_ID   = "123456789";              // senin sohbet kimliğin / your chat ID
10
11// Pinler: ECHO'lar 1 kΩ / 2 kΩ bölücüyle / Pins: ECHOs via a 1 kΩ / 2 kΩ divider
12const int HAND_TRIG = 18, HAND_ECHO = 19;
13const int FILL_TRIG = 25, FILL_ECHO = 26;
14const int SERVO_PIN = 13;
15const int LED_GREEN = 27, LED_YELLOW = 32, LED_RED = 33;
16const int BUZZER = 23;
17
18const int HAND_CM = 20;
19const int LID_CLOSED = 10, LID_OPEN = 110;
20const unsigned long OPEN_TIME = 4000, CLOSE_STEP_MS = 20;
21const int EMPTY_CM = 35, FULL_CM = 8;
22const int NOTIFY_PCT = 85;                        // bu yüzdede bildir / notify at this percent
23const int RESET_PCT = 30;                         // bunun altına inince "boşaltıldı" / below this: "emptied"
24
25enum LidState { CLOSED, OPEN, CLOSING };
26LidState state = CLOSED;
27unsigned long lastHand = 0, lastHandPing = 0, closeStart = 0, closedAt = 0, lastFill = 0, lastTry = 0;
28int fillPercent = 0;
29byte handHits = 0;
30bool notified = false, servoOn = false, fullWarned = false;
31
32void servoWrite(int angle) {                      // 0–180° → 544–2400 µs, 50 Hz
33  uint32_t us = map(angle, 0, 180, 544, 2400);
34  ledcWrite(SERVO_PIN, us * 16384 / 20000);       // 14 bit, 20 ms periyot / 14-bit, 20 ms period
35  servoOn = true;
36}
37
38void servoRelease() {                             // darbe yok: servo serbest kalır / no pulses: the servo relaxes
39  ledcWrite(SERVO_PIN, 0);
40  servoOn = false;
41}
42
43int pingCm(int trig, int echo) {
44  digitalWrite(trig, LOW);  delayMicroseconds(2);
45  digitalWrite(trig, HIGH); delayMicroseconds(10);
46  digitalWrite(trig, LOW);
47  unsigned long t = pulseIn(echo, HIGH, 30000UL);
48  return t == 0 ? 999 : (int)(t / 58);            // 999 = yankı yok / 999 = no echo
49}
50
51int medianCm(int trig, int echo) {                // 5 ölçümün ortancası / median of 5 readings
52  int v[5];
53  delay(60);                                        // önce el sensörünün yankısı sönsün / first let the hand sensor's echo die out
54  for (int i = 0; i < 5; i++) {
55    v[i] = pingCm(trig, echo);
56    delay(60);
57  }
58  for (int i = 0; i < 4; i++)
59    for (int j = i + 1; j < 5; j++)
60      if (v[j] < v[i]) { int tmp = v[i]; v[i] = v[j]; v[j] = tmp; }
61  return v[2];
62}
63
64int toPercent(int cm) {
65  long p = (long)(EMPTY_CM - cm) * 100 / (EMPTY_CM - FULL_CM);
66  return (int)constrain(p, 0L, 100L);
67}
68
69void showLevel() {
70  digitalWrite(LED_GREEN, fillPercent < 50);
71  digitalWrite(LED_YELLOW, fillPercent >= 50 && fillPercent < 80);
72  digitalWrite(LED_RED, fillPercent >= 80);
73}
74
75void beep(int n) {
76  for (int i = 0; i < n; i++) {
77    digitalWrite(BUZZER, HIGH); delay(120);
78    digitalWrite(BUZZER, LOW);  delay(120);
79  }
80}
81
82String urlEncode(const String& s) {               // Türkçe harfler ve boşluk için / for Turkish letters and spaces
83  const char* hex = "0123456789ABCDEF";
84  String out;
85  for (size_t i = 0; i < s.length(); i++) {
86    uint8_t c = s[i];
87    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
88      out += (char)c;
89    } else {
90      out += '%';
91      out += hex[c >> 4];
92      out += hex[c & 15];
93    }
94  }
95  return out;
96}
97
98bool sendTelegram(const String& text) {
99  if (WiFi.status() != WL_CONNECTED) return false;
100  WiFiClientSecure client;
101  client.setInsecure();                           // sertifika denetimi yok, hobi projesi için yeterli / no certificate check, fine for a hobby project
102  HTTPClient http;
103  String url = String("https://api.telegram.org/bot") + BOT_TOKEN +
104               "/sendMessage?chat_id=" + CHAT_ID + "&text=" + urlEncode(text);
105  if (!http.begin(client, url)) return false;
106  int code = http.GET();                          // 200 = gönderildi / 200 = sent
107  Serial.printf("Telegram HTTP %d\n", code);
108  http.end();
109  return code == 200;
110}
111
112void openLid() {
113  servoWrite(LID_OPEN);
114  state = OPEN;
115  if (fillPercent >= 90) beep(2);
116}
117
118void setup() {
119  Serial.begin(115200);
120  pinMode(HAND_TRIG, OUTPUT); pinMode(HAND_ECHO, INPUT);
121  pinMode(FILL_TRIG, OUTPUT); pinMode(FILL_ECHO, INPUT);
122  pinMode(LED_GREEN, OUTPUT); pinMode(LED_YELLOW, OUTPUT); pinMode(LED_RED, OUTPUT);
123  pinMode(BUZZER, OUTPUT);
124  ledcAttach(SERVO_PIN, 50, 14);                  // 50 Hz, 14 bit servo sinyali / 50 Hz, 14-bit servo signal
125  servoWrite(LID_CLOSED);
126  delay(500);
127  servoRelease();
128  WiFi.mode(WIFI_STA);
129  WiFi.begin(WIFI_SSID, WIFI_PASS);               // arka planda bağlanır; kutu Wi-Fi olmadan da çalışır / connects in the background; the bin works without Wi-Fi too
130  closedAt = millis();
131  showLevel();
132}
133
134void loop() {
135  unsigned long now = millis();
136
137  if (now - lastHandPing >= 60) {                 // el sensörü / hand sensor
138    lastHandPing = now;
139    int d = pingCm(HAND_TRIG, HAND_ECHO);
140    if (d >= 2 && d <= HAND_CM) {
141      if (handHits < 2) handHits++;
142    } else {
143      handHits = 0;
144    }
145    if (handHits >= 2) {
146      lastHand = now;
147      if (state != OPEN) openLid();
148    }
149  }
150
151  if (state == OPEN && now - lastHand >= OPEN_TIME) {
152    state = CLOSING;
153    closeStart = now;
154  }
155
156  if (state == CLOSING) {                         // yavaş kapanma / slow close
157    int moved = (now - closeStart) / CLOSE_STEP_MS;
158    int range = abs(LID_OPEN - LID_CLOSED);
159    if (moved >= range) {
160      servoWrite(LID_CLOSED);
161      state = CLOSED;
162      closedAt = now;
163    } else {
164      servoWrite(LID_OPEN > LID_CLOSED ? LID_OPEN - moved : LID_OPEN + moved);
165    }
166  }
167
168  if (state == CLOSED && servoOn && now - closedAt > 400) servoRelease();
169
170  if (state == CLOSED && now - closedAt > 1000 && now - lastFill >= 3000) {
171    lastFill = now;
172    int cm = medianCm(FILL_TRIG, FILL_ECHO);
173    if (cm < 999) fillPercent = toPercent(cm);
174    showLevel();
175    Serial.printf("Doluluk / Fill: %d cm = %%%d\n", cm, fillPercent);
176
177    if (fillPercent >= 90 && !fullWarned) { fullWarned = true; beep(3); }
178    if (fillPercent < 70) fullWarned = false;
179
180    // Bildirim: kapak kapalıyken, en fazla dakikada bir dene / Notify: only with the lid closed, retry at most once a minute
181    if (!notified && fillPercent >= NOTIFY_PCT && (lastTry == 0 || now - lastTry > 60000UL)) {
182      lastTry = now;
183      notified = sendTelegram("Çöp kutusu %" + String(fillPercent) + " dolu, boşaltma zamanı. / The bin is " +
184                              String(fillPercent) + "% full.");
185    }
186    if (notified && fillPercent < RESET_PCT) {   // boşaltıldı: yeniden kur / emptied: re-arm
187      notified = false;
188      lastTry = 0;
189      sendTelegram("Çöp kutusu boşaltıldı. / The bin has been emptied.");
190    }
191  }
192}

Уведомление отправляется один раз, когда ведро доходит до 85%, и снова взводится сообщением «опустошено», когда уровень падает ниже 30%, поэтому телефон не будет вибрировать каждые 3 секунды. Если Wi-Fi нет или отправка не удалась, код повторяет попытку раз в минуту, а ведро тем временем работает как обычно. Отправка сообщения занимает 1–3 секунды из-за зашифрованного (TLS) соединения, поэтому код отправляет только при закрытой крышке. setInsecure() подключается без проверки сертификата сервера; для хобби-проекта это допустимо, но помни: токен — секретная информация, удали его перед тем, как делиться кодом. Если в мониторе порта видно «Telegram HTTP 200», сообщение ушло; 401 означает неверный токен, 400 — неверный chat ID, а 403 — что боту ещё не отправлено /start. Полноценный бот, отвечающий на команды, описан в руководстве Telegram-бот на ESP32.

Умное мусорное ведро на Raspberry Pi Pico (MicroPython)

На Raspberry Pi Pico та же логика строится на MicroPython. Функция time_pulse_us() работает как pulseIn() на Arduino; если эха нет, она возвращает отрицательное значение, и мы считаем его за 999, то есть «эха нет». Сервопривод управляется ШИМ на 50 Hz через duty_ns(), которая принимает ширину импульса в наносекундах; duty_ns(0) останавливает импульсы и отпускает сервопривод. Время сравнивается через ticks_ms() и ticks_diff(); ticks_diff даёт правильную разницу даже при переполнении счётчика. Если сохранить код на Pico в Thonny как main.py, он будет запускаться сам при каждом включении платы. Для Pico W можно добавить способ из статьи веб-сервер на Pico W, чтобы показывать заполнение по Wi-Fi.

MicroPython
1from machine import Pin, PWM, time_pulse_us
2import time
3
4# Pinler: ECHO'lar 1 kΩ / 2 kΩ bölücüyle / Pins: ECHOs via a 1 kΩ / 2 kΩ divider
5hand_trig, hand_echo = Pin(2, Pin.OUT), Pin(3, Pin.IN)
6fill_trig, fill_echo = Pin(4, Pin.OUT), Pin(5, Pin.IN)
7green, yellow, red = Pin(10, Pin.OUT), Pin(11, Pin.OUT), Pin(12, Pin.OUT)
8buzzer = Pin(13, Pin.OUT)
9servo = PWM(Pin(15))
10servo.freq(50)                                  # servo için 50 Hz / 50 Hz for the servo
11
12HAND_CM = 20
13LID_CLOSED, LID_OPEN = 10, 110
14OPEN_MS, CLOSE_STEP_MS = 4000, 20
15EMPTY_CM, FULL_CM = 35, 8
16
17def ping_cm(trig, echo):
18    trig.low(); time.sleep_us(2)
19    trig.high(); time.sleep_us(10)              # 10 µs tetik / 10 µs trigger
20    trig.low()
21    t = time_pulse_us(echo, 1, 30000)           # yankı yoksa negatif / negative if there is no echo
22    return 999 if t < 0 else t // 58
23
24def median_cm(trig, echo):                      # 5 ölçümün ortancası / median of 5 readings
25    v = []
26    time.sleep_ms(60)                           # önce el sensörünün yankısı sönsün / first let the hand sensor's echo die out
27    for _ in range(5):
28        v.append(ping_cm(trig, echo))
29        time.sleep_ms(60)
30    v.sort()
31    return v[2]
32
33def servo_write(angle):                         # 0–180° → 544–2400 µs
34    servo.duty_ns((544 + angle * 1856 // 180) * 1000)
35
36def servo_release():                            # darbe yok: servo serbest / no pulses: servo relaxes
37    servo.duty_ns(0)
38
39def show_level(p):
40    green.value(p < 50)
41    yellow.value(50 <= p < 80)
42    red.value(p >= 80)
43
44def beep(n):
45    for _ in range(n):
46        buzzer.on(); time.sleep_ms(120)
47        buzzer.off(); time.sleep_ms(120)
48
49state = "closed"
50fill, hits, warned, released = 0, 0, False, False
51last_hand = last_ping = close_start = last_fill = time.ticks_ms()
52servo_write(LID_CLOSED)
53time.sleep_ms(500)
54servo_release()
55released = True
56closed_at = time.ticks_ms()
57show_level(fill)
58
59while True:
60    now = time.ticks_ms()
61
62    if time.ticks_diff(now, last_ping) >= 60:   # el sensörü, 60 ms'de bir / hand sensor, every 60 ms
63        last_ping = now
64        d = ping_cm(hand_trig, hand_echo)
65        hits = min(hits + 1, 2) if 2 <= d <= HAND_CM else 0
66        if hits >= 2:
67            last_hand = now
68            if state != "open":                 # kapanırken de yeniden açar / also reopens while closing
69                servo_write(LID_OPEN)
70                state, released = "open", False
71                if fill >= 90:
72                    beep(2)
73
74    if state == "open" and time.ticks_diff(now, last_hand) >= OPEN_MS:
75        state, close_start = "closing", now
76
77    if state == "closing":                      # yavaş kapanma / slow close
78        moved = time.ticks_diff(now, close_start) // CLOSE_STEP_MS
79        if moved >= abs(LID_OPEN - LID_CLOSED):
80            servo_write(LID_CLOSED)
81            state, closed_at = "closed", now
82        else:
83            servo_write(LID_OPEN - moved if LID_OPEN > LID_CLOSED else LID_OPEN + moved)
84
85    if state == "closed" and not released and time.ticks_diff(now, closed_at) > 400:
86        servo_release()
87        released = True
88
89    if state == "closed" and time.ticks_diff(now, closed_at) > 1000 and time.ticks_diff(now, last_fill) >= 3000:
90        last_fill = now
91        cm = median_cm(fill_trig, fill_echo)
92        if cm < 999:                            # yankı yoksa eski değer kalır / keep the old value if no echo
93            fill = max(0, min(100, (EMPTY_CM - cm) * 100 // (EMPTY_CM - FULL_CM)))
94        show_level(fill)
95        print("Doluluk / Fill:", cm, "cm =", fill, "%")
96        if fill >= 90 and not warned:
97            warned = True
98            beep(3)
99        if fill < 70:
100            warned = False
101
102    time.sleep_ms(5)

Советы для презентации школьного проекта

Умное мусорное ведро — очень частый проект на научных выставках и школьных конкурсах вроде TÜBİTAK 4006, поэтому жюри интересует не столько открывание и закрывание крышки, сколько то, как поставлена задача, как проведены измерения и какие сделаны выводы. Превращают проект из обычной демонстрации в научную работу именно измерения: со скольких попыток крышка открылась, как быстро она открывается, насколько точно оценивается заполнение, сколько часов продержались батареи. Презентацию можно построить в таком порядке:

  1. Проблема: прикосновение к крышке рукой разносит микробы, а переполненные вёдра — это запах и насекомые. Начни с реальных данных: посчитай, сколько человек за одну перемену трогают крышку мусорного ведра в твоей школе.
  2. Цель: ведро, которое открывается без прикосновения, предупреждает о заполнении и по желанию присылает уведомление на телефон.
  3. Как это работает: нарисуй одну структурную схему с датчиком, Arduino, сервоприводом, светодиодами, зуммером, ESP32 и телефоном. Объясни скорость звука и формулу «делить на 58» на одном слайде.
  4. Материалы и стоимость: покажи список деталей и выбор сервопривода в виде таблицы вместе с расчётом момента.
  5. Живая демонстрация: поднеси руку, и пусть крышка откроется. Заполни ведро бумагой и покажи, как светодиод меняется с зелёного на жёлтый и красный; выведи на экран сообщение Telegram, пришедшее на телефон.
  6. Результаты испытаний: заполни таблицу ниже своими измерениями и преврати результаты в графики.
  7. Выводы и улучшения: честно расскажи, что сработало хорошо, что нет и что стоит изменить в следующей версии.
ИспытаниеКак провестиЧто записать
Дальность обнаруженияПодноси руку на 5, 10, 15, 20 и 25 см, по 10 раз на каждое расстояниеСколько попыток открыли крышку на каждом расстоянии (например, 10/10)
Время реакцииСними замедленное видео (240 fps) на телефонВремя между появлением руки и началом движения крышки (мс)
Ложные открыванияПройди мимо ведра 20 разЧисло нежелательных открываний
Точность заполненияЗаполни ведро бумагой на четверть, половину и три четверти, проверяя линейкойРазница между реальным заполнением и измеренным процентом
Скорость закрытияПоставь CLOSE_STEP_MS на 5, 20 и 40 мсВремя закрытия и уровень шума, измеренный телефоном
Время работы от батарейЗапусти на полностью заряженных батареях и измеряй напряжение каждый часСколько часов проработало ведро
Жюри чаще всего спрашивает о слабых местах, поэтому ответы стоит подготовить заранее. HC-SR04 плохо видит мягкие пористые поверхности, поэтому на толстую тканевую перчатку крышка может открыться с опозданием. Измерение заполнения видит самую высокую точку мусора, а не его средний уровень. Ведро на Uno работает от батарей всего 1–2 дня. Если показать, что эти ограничения известны, и предложить решение для каждого, это принесёт гораздо больше баллов, чем попытка выдать проект за безупречный.

Идеи для улучшения

  • Ведро с дисплеем: выводи процент заполнения и столбчатый индикатор на OLED-дисплей.
  • Голосовое «спасибо»: при каждом открывании крышки проигрывай короткое «спасибо» с помощью DFPlayer Mini.
  • Взвешивание: поставь под ведро HX711 с тензодатчиком, чтобы знать, сколько килограммов мусора в нём, и объедини объём с весом.
  • Сортировка для переработки: с помощью датчика цвета и второго сервопривода направляй отходы в два отсека.
  • Сигнал о запахе: замечай, что воздух внутри ведра портится, с помощью MQ-135.
  • Уличное ведро на солнечной энергии: полностью избавься от провода с маленькой солнечной панелью, аккумулятором 18650, модулем зарядки TP4056 и повышающим модулем на 5 V.
  • Журнал заполнения: записывай показания в Google Таблицы через ESP32 и построй график, в какие дни ведро заполняется быстрее всего.
  • На всю школу или жилой комплекс: следи за вёдрами на территории, куда не достаёт Wi-Fi, с расстояния в сотни метров с помощью модуля LoRa.

Частые ошибки и решения

СимптомВероятная причинаРешение
Крышка открывается и закрывается самаДатчик видит близкую стену или проходящих мимо людейУменьши HAND_CM, наклони датчик вверх, оставь перед ним 30 см свободного места
Arduino перезагружается при открывании крышкиПусковой ток сервопривода просаживает линию 5 VКонденсатор 470 µF, отдельное питание для мощного сервопривода, общий GND
При закрытой крышке сервопривод жужжит и греетсяLID_CLOSED прижимает крышку к краюОтступи на 2–3 градуса; не удаляй строку detach()
Крышка останавливается на полпути или дрожитНе хватает момента или оси не совпадаютСервопривод мощнее, крышка легче, противовес; совмести вал сервопривода с петлёй
Заполнение высокое даже у пустого ведраКрай пакета перед датчиком или эхо от стенок узкого ведраЗаверни пакет наружу, поставь датчик по центру, заново измерь EMPTY_CM
Заполнение никогда не меняетсяДатчик не получает эха (999) или отошёл провод ECHOСмотри сырое значение в см в мониторе порта и проверь подключение
На ESP32 расстояние всегда 999 или чепухаНет делителя на ECHO, он собран неправильно или провода перепутаныПроверь делитель 1 kΩ / 2 kΩ и порядок TRIG и ECHO
ESP32 постоянно перезагружаетсяWi-Fi и сервопривод одновременно потребляют ток, слабый USB-кабельВозьми короткий качественный кабель и питай сервопривод от отдельного источника 5 V
Сообщение в Telegram не приходитНеверный токен или chat ID, боту не отправлено /startПосмотри HTTP-код в мониторе порта (401, 400, 403)
При поиске неисправности проверяй детали по одной: сначала скетчем калибровки убедись, что оба датчика дают в мониторе порта разумные значения, затем — что сервопривод плавно встаёт в углы закрытия и открытия, и только потом загружай основной код. Если на ESP32 появляется сообщение «Brownout detector was triggered» (сработал детектор просадки питания), причины и решения — в статье ESP32 постоянно перезагружается.

Умное мусорное ведро объединяет в одном проекте чтение датчиков, управление сервоприводом, работу со временем и планирование питания, поэтому это один из самых увлекательных способов изучить Arduino. Когда этот проект собран, парктроник или автоматическая дверь из тех же деталей даются гораздо проще. В приложении BlueGrays есть пошаговые проекты с измерением расстояния на HC-SR04, управлением сервоприводом, зуммером, парктроником на ESP32 и сигнализацией движения с уведомлениями в Telegram — со схемами подключения и готовым кодом.

Частые вопросы

Какие материалы нужны для умного мусорного ведра на Arduino?

Для базовой версии достаточно Arduino Uno или Nano, одного ультразвукового датчика HC-SR04, сервопривода под вес крышки (SG90 для лёгкой крышки, MG996R для крышки настоящего ведра), проводов-перемычек и источника питания 5 V. Чтобы измерять заполнение, добавь второй HC-SR04, три светодиода с резисторами 220 Ω и активный зуммер. Если нужны уведомления на телефон, вместо Arduino можно взять ESP32.

Как работает умное мусорное ведро?

Ультразвуковой датчик на передней стенке ведра посылает звуковую волну и измеряет, через сколько вернётся эхо; когда он видит руку ближе 20 см, Arduino открывает крышку сервоприводом. Через несколько секунд после того, как рука убрана, крышка медленно закрывается. Второй датчик под крышкой измеряет высоту мусора и показывает заполнение светодиодами и зуммером.

Почему сервопривод не поднимает крышку мусорного ведра?

Обычно моменту сервопривода просто не хватает для этой крышки. Нужный момент равен весу крышки, умноженному на расстояние от петли до её центра масс; крышке весом 120 г и длиной 25 см нужно около 1,5 кг·см, а это предел для SG90. Выбирай сервопривод с моментом вдвое больше расчётного, облегчи крышку или добавь противовес и убедись, что источник питания сервопривода выдаёт достаточный ток.

Можно ли использовать в умном ведре ИК-датчик вместо HC-SR04?

Да, ИК-датчик препятствий вроде FC-51 замечает руку на расстоянии от 2 до 30 см, и код получается даже проще, потому что датчик сообщает только, есть что-то перед ним или нет. Однако на него влияет солнечный свет, а тёмные поверхности он видит позже. PIR-датчик для крышки ведра не подходит: он срабатывает на каждого прохожего.

Сколько умное мусорное ведро на Arduino работает от батарей?

Ведро на Arduino Uno в среднем потребляет 70–80 mA, поэтому от 6 батареек AA оно работает около суток, от двух аккумуляторов 18650 — около полутора суток, а от пауэрбанка на 10 000 mAh — 3–4 дня. Для постоянной работы рекомендуется блок питания 5 V. Pro Mini, режим сна и включение светодиодов только по необходимости продлевают работу от батарей в 4–5 раз.

Как получить уведомление на телефон, когда ведро заполнено?

ESP32 подключается к Wi-Fi и, когда заполнение превышает заданный процент, отправляет запрос в Telegram Bot API, который доставляет сообщение на телефон. Для этого нужны токен бота от BotFather и твой chat ID. Чтобы одно и то же уведомление не приходило снова и снова, код ждёт, пока ведро не опустошат.

Все проекты
Измерение расстояния HC-SR04 (вывод в порт)
#27Новичок

Измерение расстояния HC-SR04 (вывод в порт)

Датчик HC-SR04 работает совсем как летучая мышь, которая находит дорогу в темноте: он посылает звук, которого ты не слышишь, слушает эхо и измеряет, насколько далеко предмет перед ним. Arduino каждые

4.6(46)
71
Работа с зуммером
#13Новичок

Работа с зуммером

Интересно, как дверной звонок или сигнализация издают звук? Arduino заставляет маленький зуммер пищать через короткие промежутки: пик, пауза, пик, пауза. Ты видишь, как электричество превращается в зв

4.7(89)
161
Работа с сервоприводом
#35Новичок
Premium

Работа с сервоприводом

Рычаг маленького сервопривода по очереди поворачивается в три положения: до упора в начало, ровно на середину и до упора в конец, задерживаясь на каждой остановке на две секунды. Arduino сообщает мото

4.7(48)
67
Парктроник на HC-SR04
#28Средний
Premium

Парктроник на HC-SR04

Мини-версия автомобильного парктроника: чем ближе предмет, тем чаще сигнал. Датчик расстояния измеряет предмет перед собой; если он дальше 20 сантиметров, горит зелёный светодиод, а если ближе — мигае

4.8(64)
85
Ультразвуковой датчик расстояния HC-SR04 на ESP32
#116Средний

Ультразвуковой датчик расстояния HC-SR04 на ESP32

Ультразвуковой датчик HC-SR04 вычисляет расстояние по времени, за которое испущенная им звуковая волна отражается от предмета и возвращается (Time of Flight). В этом проекте ESP32 подаёт импульс 10 микросекунд на пин TRIG датчика, а длительность эха читает с пина ECHO через pulseIn(). Вычисленное расстояние в сантиметрах выводится на I2C LCD 16x2. HC-SR04 работает от 5V, поэтому на пине ECHO нужен делитель напряжения.

4.9(28)
45
Сервопривод на ESP32 (управление потенциометром)
#118Средний
Premium

Сервопривод на ESP32 (управление потенциометром)

В этом проекте сервопривод SG90 управляется потенциометром. Угол сервопривода задаётся сопоставлением аналогового значения потенциометра с диапазоном 0–180 градусов. Библиотека ESP32Servo формирует сигналы сервопривода на подходящих выходных пинах GPIO у ESP32; этот проект использует GPIO 18. Это базовый способ управления для роборук, подвесов камер и механизмов автоматических дверей.

4.3(29)
42
Парктроник на ESP32 (HC-SR04 + шкала из 5 светодиодов + зуммер + OLED)
#142Продвинутый
Premium

Парктроник на ESP32 (HC-SR04 + шкала из 5 светодиодов + зуммер + OLED)

ESP32-версия автомобильного парктроника! Ультразвуковой датчик HC-SR04 измеряет расстояние до предмета перед ним. По мере уменьшения расстояния шкала из 5 светодиодов заполняется (зелёный → жёлтый → красный), а интервал сигналов зуммера сокращается. Когда предмет совсем близко (меньше 10 см), звучит непрерывная тревога и горят все светодиоды. OLED SSD1306 показывает расстояние в см крупным шрифтом и сообщение состояния (STOP / CAUTION / CLOSE / STOP! / DANGER).

0.0(0)
14
Сигнализация движения на ESP32 (PIR + зуммер + светодиод + Telegram)
#145Продвинутый
Premium

Сигнализация движения на ESP32 (PIR + зуммер + светодиод + Telegram)

Умная охранная сигнализация, которая при обнаружении движения PIR-датчиком включает зуммер и светодиод и одновременно сразу отправляет владельцу уведомление в Telegram. В ней есть 30-секундная пауза, ограничивающая частоту повторных тревог; этот механизм не проверяет срабатывания PIR и не устраняет ложные тревоги. Подходит для дома, гаража, склада или мастерской. С ESP32-CAM можно добавить уведомления с фото, а с обычной ESP32 DevKit V1 приходят текстовые.

0.0(0)
9