This guide builds an autonomous edge-detecting robot vacuum powered by an ESP32 microcontroller. The robot uses dual DC motors for mobility, IR line tracking sensors to detect table edges, and a fan motor controlled by a logic-level MOSFET for suction. The L298N motor driver coordinates the drive motors while the IRLZ44N handles high-current fan switching with protection from a Schottky flyback diode.
Followers will receive a complete wiring diagram showing motor controller connections, sensor pinouts, and power distribution, a full parts list with recommended suppliers, the Arduino firmware for autonomous edge avoidance behavior, and step-by-step assembly instructions for chassis construction, sensor calibration, and safety testing.
Wiring diagram

Gather all the parts
Assemble it in 6 steps
1. Соберите шасси и пылесборник
Закрепите два TT‑редукторных мотора с колёсами по бокам лёгкого основания. Спереди поставьте скользящую опору. Перед центробежным вентилятором сделайте небольшой закрытый контейнер‑пылесборник с входной щелью и фильтром: крыльчатка должна тянуть воздух из контейнера и выбрасывать его наружу, а не быть открытой рядом с пальцами.
- Расположите входную щель и мягкую щётку на передней кромке корпуса.
- Оставьте фильтр доступным для очистки.
- Не запускайте вентилятор без защитного кожуха и фильтра.
- Этот компактный вариант собирает крошки и пыль, но не рассчитан на жидкость или тяжёлый мусор.
2. Установите датчики края
Закрепите edge_left и edge_right на нижней стороне передней части корпуса, направив их строго вниз. Между датчиком и столом оставьте примерно 4–8 мм; датчики должны быть разнесены к левому и правому углу.
- Провода датчиков закрепите так, чтобы они не касались колёс.
- На тёмном или глянцевом столе чувствительность придётся подстроить позже.
- Перед первым движением обязательно проверьте реакцию датчиков, держа робот над поверхностью.
3. Подключите моторный драйвер и ходовые моторы
Подайте +5 V на VS и 5V драйвера driver, а общий минус на GND. Соедините OUT1/OUT2 с двумя выводами left_motor, OUT3/OUT4 — с двумя выводами right_motor. К ESP32 подключите IN1→GPIO4, IN2→GPIO13, IN3→GPIO14, IN4→GPIO16, ENA→GPIO17, ENB→GPIO18.
- Если колесо вращается в обратную сторону, поменяйте местами только два провода соответствующего мотора на OUT‑выходах.
- Общий GND обязателен для ESP32, драйвера, датчиков и питания.
- L298N заметно греется и имеет падение напряжения; используйте только малые 3–6 V моторы и не закрывайте радиатор.
- Не берите питание моторов с контакта 3V3 ESP32.
4. Соберите силовой ключ вентилятора
Подключите + вентилятора blower к шине +5 V, его минус — к DRAIN ключа fan_mosfet. SOURCE ключа соедините с общим GND. У диода flyback_diode катод K подключите к +5 V, анод A — к DRAIN. От GPIO25 проведите сигнал через gate_resistor 100 Ω к GATE. Резистор gate_pulldown 10 kΩ соедините между GATE и GND.
- Полоса на корпусе 1N5819 обозначает катод — он должен смотреть к +5 V.
- Расположите диод близко к проводам вентилятора.
- Не перепутайте DRAIN и SOURCE MOSFET; это приведёт к неправильной работе или перегреву.
- Отключайте питание перед любыми изменениями проводки.
5. Подключите датчики и питание
Для каждого TCRT5000: VCC подключите к 3V3 ESP32, GND — к общему GND; DO левого датчика подключите к GPIO32, правого — к GPIO33. Питание usb_supply +5 V подключите к общей шине +5 V, GND — к общей земле. ESP32 также питается от этой регулируемой 5‑вольтовой USB линии через свой USB-разъём или 5V/VIN, но не подключайте одновременно два разных USB‑источника.
- Нужен стабильный адаптер минимум 5 V / 2 A; короткие толстые провода питания уменьшают просадки при старте моторов.
- Сначала включайте робот, удерживая его поднятым над столом.
- Все земли должны быть соединены вместе.
- Не подавайте более 5 V на шину питания.
6. Настройте безопасное обнаружение края
После прошивки через кнопку Deploy в Schematik удерживайте робота над поверхностью. Поверните подстроечный винт каждого TCRT5000 так, чтобы при наличии столешницы под датчиком DO был LOW, а при свисании за край — HIGH. Затем поставьте робота далеко от края и проверьте, что он едет вперёд, отступает и поворачивает при поднесении одного датчика к краю.
- Если робот поворачивает не туда, поменяйте местами провода одного мотора на выходе драйвера.
- Начните тест на большой пустой поверхности и держите руку рядом, чтобы остановить робот.
- Не тестируйте возле воды, стекла, людей, клавиатуры или края выше безопасной высоты.
- Никогда не оставляйте настольный робот без присмотра.
Review all connections
1. Connections between "usb_supply" and "ESP32"
2. Connections between "driver" and "ESP32"
3. Connections between "blower" and "ESP32"
4. Connections between "fan_mosfet" and "ESP32"
5. Connections between "gate_resistor" and "ESP32"
6. Connections between "gate_pulldown" and "ESP32"
7. Connections between "flyback_diode" and "ESP32"
8. Connections between "edge_left" and "ESP32"
9. Connections between "edge_right" and "ESP32"
10. Connections between "left_motor" and "ESP32"
11. Connections between "right_motor" and "ESP32"
Deploy the firmware
#include <Arduino.h>
// Hoisted type definitions
enum MotionState { CRUISE, BACK_UP, TURN_LEFT, TURN_RIGHT };
// Forward declarations
void setMotor(int in1, int in2, int enablePin, int speed, bool forward);
void stopMotors();
void driveForward(uint8_t speed);
void driveBackward(uint8_t speed);
void turnInPlace(bool right);
bool edgeDetected();
void beginAvoidance(unsigned long now);
const int LEFT_IN1 = 4;
const int LEFT_IN2 = 13;
const int RIGHT_IN1 = 14;
const int RIGHT_IN2 = 16;
const int LEFT_EN = 17;
const int RIGHT_EN = 18;
const int FAN_GATE = 25;
const int EDGE_LEFT = 32;
const int EDGE_RIGHT = 33;
const uint8_t DRIVE_SPEED = 155;
const uint8_t TURN_SPEED = 175;
const uint8_t FAN_SPEED = 220;
const unsigned long BACKUP_MS = 420;
const unsigned long TURN_MS = 520;
const unsigned long CRUISE_CHANGE_MS = 2600;
MotionState state = CRUISE;
unsigned long stateStarted = 0;
unsigned long cruiseStarted = 0;
bool turnRightNext = true;
void setMotor(int in1, int in2, int enablePin, int speed, bool forward) {
digitalWrite(in1, forward ? HIGH : LOW);
digitalWrite(in2, forward ? LOW : HIGH);
analogWrite(enablePin, speed);
}
void stopMotors() {
analogWrite(LEFT_EN, 0);
analogWrite(RIGHT_EN, 0);
digitalWrite(LEFT_IN1, LOW);
digitalWrite(LEFT_IN2, LOW);
digitalWrite(RIGHT_IN1, LOW);
digitalWrite(RIGHT_IN2, LOW);
}
void driveForward(uint8_t speed) {
setMotor(LEFT_IN1, LEFT_IN2, LEFT_EN, speed, true);
setMotor(RIGHT_IN1, RIGHT_IN2, RIGHT_EN, speed, true);
}
void driveBackward(uint8_t speed) {
setMotor(LEFT_IN1, LEFT_IN2, LEFT_EN, speed, false);
setMotor(RIGHT_IN1, RIGHT_IN2, RIGHT_EN, speed, false);
}
void turnInPlace(bool right) {
setMotor(LEFT_IN1, LEFT_IN2, LEFT_EN, TURN_SPEED, right);
setMotor(RIGHT_IN1, RIGHT_IN2, RIGHT_EN, TURN_SPEED, !right);
}
bool edgeDetected() {
// Adjust each sensor trimmer so DO is HIGH only when the sensor sees no tabletop.
return digitalRead(EDGE_LEFT) == HIGH || digitalRead(EDGE_RIGHT) == HIGH;
}
void beginAvoidance(unsigned long now) {
state = BACK_UP;
stateStarted = now;
driveBackward(DRIVE_SPEED);
}
void setup() {
pinMode(LEFT_IN1, OUTPUT);
pinMode(LEFT_IN2, OUTPUT);
pinMode(RIGHT_IN1, OUTPUT);
pinMode(RIGHT_IN2, OUTPUT);
pinMode(LEFT_EN, OUTPUT);
pinMode(RIGHT_EN, OUTPUT);
pinMode(FAN_GATE, OUTPUT);
pinMode(EDGE_LEFT, INPUT);
pinMode(EDGE_RIGHT, INPUT);
stopMotors();
analogWrite(FAN_GATE, FAN_SPEED);
stateStarted = millis();
cruiseStarted = stateStarted;
}
void loop() {
const unsigned long now = millis();
if (state == CRUISE) {
if (edgeDetected()) {
beginAvoidance(now);
} else if (now - cruiseStarted >= CRUISE_CHANGE_MS) {
state = turnRightNext ? TURN_RIGHT : TURN_LEFT;
turnRightNext = !turnRightNext;
stateStarted = now;
turnInPlace(state == TURN_RIGHT);
} else {
driveForward(DRIVE_SPEED);
}
return;
}
if (state == BACK_UP) {
if (now - stateStarted >= BACKUP_MS) {
state = turnRightNext ? TURN_RIGHT : TURN_LEFT;
turnRightNext = !turnRightNext;
stateStarted = now;
turnInPlace(state == TURN_RIGHT);
}
return;
}
if (now - stateStarted >= TURN_MS) {
state = CRUISE;
cruiseStarted = now;
driveForward(DRIVE_SPEED);
}
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




