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

Как работает умное мусорное ведро?
Проект — это цикл, который повторяется десятки раз в секунду. Каждые 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 |




Какой датчик выбрать для обнаружения руки?
Открывать ведро умеют разные датчики, но для этой задачи они подходят не одинаково. На самом деле нужно заметить руку, приблизившуюся к ведру на 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 см звук может отражаться от боковых стенок и давать ложные показания. Край мусорного пакета заворачивай наружу ведра: край пакета, свисающий внутрь, оказывается перед датчиком, и ведро выглядит полным, даже когда оно пустое.
Схема подключения: Arduino Uno, ESP32 и Pico
Таблица ниже точно соответствует всему коду в этой статье. На Uno датчик руки подключается к D2–D3, датчик заполнения к D4–D5, сервопривод к D9, светодиоды к D10–D12, а зуммер к D8. Библиотека Servo на Uno использует Timer1 и поэтому отключает ШИМ-выход на D9 и D10; светодиод на D10 мы только включаем и выключаем, так что нам это не мешает. Перед каждым светодиодом поставь резистор 220 Ω; номинал для своего светодиода можно рассчитать в утилите калькулятор резистора для светодиода. Расположение пинов своей платы смотри в утилите распиновки.
| Подключение | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| TRIG датчика руки | D2 | GPIO 18 | GP2 |
| ECHO датчика руки | D3 (напрямую) | GPIO 19, через делитель 1 kΩ / 2 kΩ | GP3, через делитель 1 kΩ / 2 kΩ |
| TRIG датчика заполнения | D4 | GPIO 25 | GP4 |
| ECHO датчика заполнения | D5 (напрямую) | GPIO 26, через делитель | GP5, через делитель |
| VCC обоих HC-SR04 | 5V | 5V (VIN) | VBUS |
| Сигнал сервопривода (оранжевый) | D9 | GPIO 13 | GP15 |
| + сервопривода (красный) | 5V (SG90) или внешние 5–6 V | 5V (VIN) или внешние 5–6 V | VBUS или внешние 5–6 V |
| Зелёный, жёлтый, красный светодиод (с 220 Ω) | D10, D11, D12 | GPIO 27, 32, 33 | GP10, GP11, GP12 |
| Активный зуммер (+) | D8 | GPIO 23 | GP13 |
| Все GND | GND | GND | GND |
Выбор сервопривода и расчёт момента
Самая частая проблема в проектах умного мусорного ведра — крышка открывается наполовину и дрожит или вовсе не поднимается. Причина почти всегда в том, что сервопривод слишком слаб для этой крышки. Прикинуть нужный момент легко: момент (кг·см) = вес крышки (кг) × расстояние от петли до её центра масс (см). У плоской крышки равномерной толщины центр масс ровно посередине; для крышки длиной 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) | Шестерни | Ток | Примечание |
|---|---|---|---|---|
| SG90 | 1,8 кг·см (указан только для 4,8 V) | Пластик | 100–250 mA в движении | Может питаться от пина 5V Arduino |
| MG90S | 1,8 / 2,2 кг·см | Металл | 150–300 mA в движении | Того же размера, что SG90, но долговечнее |
| MG996R | 9,4 / 11 кг·см | Металл | 0,5–0,9 A в движении, до 2,5 A при заторможенном валу | Обязательно отдельное питание 5–6 V |

Вместо покупки более мощного сервопривода часто хватает уравновесить крышку. Если у крышки есть короткий выступ за петлёй, несколько приклеенных туда болтов или монет уравновесят часть её веса; такой противовес может снизить нужный момент вдвое. Можно также заставить качалку сервопривода толкать крышку в точке, далёкой от петли, но рычаг не даёт момент бесплатно: чем дальше от петли, тем меньше нужна сила, но толкаемая точка проходит больший путь, значит, нужна более длинная качалка, а длинная качалка снова повышает момент, требуемый от сервопривода. Если крышка должна открываться почти на 90 градусов, нужный момент остаётся практически таким же, как при креплении сервопривода прямо у петли. Подробное сравнение типов сервоприводов — в статьях Что такое сервопривод? и сервопривод MG996R.
Советы по монтажу: петля, качалка и провода
- Вес должна нести петля: крышка крепится на настоящую петлю (родную петлю ведра или маленькую мебельную петлю). Если крышка лежит только на валу сервопривода, весь вес приходится на его подшипник, и пластиковые шестерни SG90 быстро ломаются.
- Совмести вал сервопривода с осью петли: проще всего привинтить сервопривод к заднему краю ведра так, чтобы его вал оказался точно на продолжении линии петли, а качалку закрепить на крышке винтом или термоклеем. Если оси не совпадают, сервопривод при каждом открывании толкает крышку вбок.
- Надёжно закрепи корпус сервопривода: не держи сервопривод на одном двустороннем скотче — корпус будет проворачиваться, когда он поднимает крышку. Используй два винта через монтажные ушки сервопривода или небольшой L-образный кронштейн.
- Не прижимай крышку к краю: выбери угол закрытия так, чтобы крышка лежала на краю ведра, но сервопривод на неё не давил. Если сервопривод постоянно давит на закрытую крышку, он жужжит, греется и тратит ток впустую.
- Оставь запас длины проводов: провода датчика заполнения двигаются вместе с крышкой. Проведи кабель рядом с петлёй, с такой длиной, чтобы он не натягивался даже при полностью открытой крышке, и закрепи его стяжкой.
- Держи электронику подальше от мусора: Arduino, батареи и макетную плату положи в небольшую коробку, приклеенную к задней стенке или дну ведра. Жидкость или влага из мусорного пакета не должна попадать на схему.
- Начни с картонного макета: для школьного проекта лёгкая крышка из обувной коробки или пенокартона прекрасно работает даже с SG90, а заодно служит хорошим примером для расчёта момента в презентации.
Пошаговая настройка и калибровка
Прежде чем переходить к основному коду, загрузи небольшой скетч калибровки ниже. Он поворачивает сервопривод на угол, введённый в монитор порта, и каждые полсекунды выводит расстояние с обоих датчиков. Так для твоего ведра измеряются углы закрытой и открытой крышки, глубина пустого ведра и порог обнаружения руки.
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}- Загрузи код, не надевая качалку, и открой монитор порта на скорости 9600 бод; выбери окончание строки Newline («Новая строка»). Сервопривод встанет на 90 градусов.
- Введи 10 и нажми Enter. Придерживая крышку закрытой, надень качалку на вал в этом положении, а затем закрепи её на крышке.
- Увеличивай угол шагами 20, 40, 60 и найди значение, при котором крышка полностью открыта; обычно это 90–110 градусов. Не открывай крышку дальше вертикали. Это значение — LID_OPEN. Если сервопривод вращается в другую сторону, выбери значения закрытия и открытия соответственно; код работает в обоих направлениях.
- Вернись к малым углам. От угла, при котором крышка полностью ложится на край, отступи на 2–3 градуса в сторону открывания; это LID_CLOSED. Если сервопривод в закрытом положении жужжит, он давит на крышку.
- С надетым пакетом и пустым ведром запиши значение датчика заполнения; это EMPTY_CM. Для FULL_CM выбери 6–10 см: когда мусор доходит до этой высоты, крышка всё ещё должна легко закрываться, а датчик — надёжно измерять, потому что HC-SR04 не измеряет ближе 2–3 см.
- Подноси руку к датчику руки на 5, 10, 20 и 30 см и смотри на показания. Потом пройди мимо ведра и запиши минимальное значение, которое он показывает. HAND_CM должно быть меньше этого значения; для большинства вёдер хорошо работают 15–25 см.
- Впиши найденные значения в настройки в начале основного кода и загрузи его.
Основной код: обнаружение руки, время ожидания и плавное закрытие
В основном коде нет длинных delay(): всё делается по меткам времени из millis(). Поэтому датчик руки опрашивается даже во время закрывания крышки, и если рука снова приближается, крышка сразу открывается обратно. Это простая, но важная защита, которая не даёт крышке захлопнуться на руке ребёнка. Подробности о неблокирующих задержках — в статье millis() и delay(), а идея мышления состояниями — в статье конечный автомат на Arduino.
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%. Разница между фильтрами подробно разобрана в статье фильтрация данных датчиков.
Питание от батарей: на сколько хватит?
Запустить умное ведро без провода заманчиво, но 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) | VIN | 5–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 mAh | USB | 3–4 дня | Некоторые пауэрбанки отключаются при нагрузке ниже 50–100 mA |
| Блок питания 5 V 2 A | USB или пин 5V | Без ограничений | Лучший выбор для постоянной работы дома |

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

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

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

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

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

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

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

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

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

