Робот, объезжающий препятствия, на Arduino: руководство по HC-SR04 и сервоприводу
Собери на Arduino робота, объезжающего препятствия: HC-SR04 на сервоприводе, подключение L298N или L293D, логика «стоп — назад — поворот», ИК-датчики и ручной режим по Bluetooth.
Робот, объезжающий препятствия, — это самоходная робот-машинка, которая замечает стену, стул или коробку впереди, не врезаясь в них, и находит новый путь в обход. Чаще всего его собирают из Arduino Uno, ультразвукового датчика расстояния HC-SR04, сервопривода SG90, который поворачивает датчик влево и вправо, двух DC-моторов с редуктором и драйвера моторов L298N (или L293D). Робот едет прямо, останавливается, приблизившись к препятствию, немного отъезжает назад, поворачивает «голову» в обе стороны, чтобы провести измерения, и затем продолжает путь в самом свободном направлении.
В этом руководстве мы превратим двухколёсную (2WD) машинку из статьи Робот-машинка на Arduino в автономного робота, сохранив ту же распиновку. Шаг за шагом разберём таблицы подключения, расчёт порогов расстояния, код, который растёт в три этапа, поверхности, которые датчик не видит, и как закрыть эти пробелы инфракрасными датчиками, а в конце — переход в ручной режим по Bluetooth с телефона. Код для управления тем же роботом с ESP32 или Raspberry Pi Pico — в конце статьи.

Как работает робот, объезжающий препятствия?
Любой автономный робот десятки раз в секунду повторяет один и тот же цикл из трёх шагов: восприятие, решение, действие. За восприятие отвечает HC-SR04: он посылает короткий звук 40 kHz, который ухо не слышит, и измеряет, сколько времени эхо идёт обратно; если разделить это время в микросекундах на 58, получится расстояние в сантиметрах. Решения принимает Arduino, сравнивая измеренное расстояние с заданными порогами. Действия берут на себя L298N и моторы. Поскольку датчик стоит на сервоприводе, робот может «смотреть» влево и вправо, не поворачивая корпус, и поэтому принимает гораздо более удачные решения о поворотах, чем робот с неподвижным датчиком. Подробнее о том, как работает датчик, читай в статье Датчик расстояния HC-SR04.
| Деталь | Задача | Примечание |
|---|---|---|
| Arduino Uno (или Nano) | Мозг: измеряет, принимает решения и командует моторами | Подойдёт и ESP32 или Pico |
| HC-SR04 | Глаз: измеряет расстояние до препятствия впереди | Работает от 5 V, 2–400 см |
| Сервопривод SG90 | Шея: поворачивает датчик влево и вправо | Датчик крепится к качалке сервопривода пластиковым кронштейном |
| L298N или шилд L293D | Мышцы: задаёт моторам направление и скорость | Пин Arduino не может дать ток для мотора |
| 2 TT-мотора с редуктором и поворотное колесо | Дифференциальный привод | Поворот выполняется вращением двух колёс в разные стороны |
| 2 аккумулятора 18650 (7,4 V) | Питание | Батарейки-«кроны» на 9 V не хватит |
| 2 ИК-датчика препятствий (по желанию) | Ловят препятствия по углам и сбоку | Модули на LM393, например FC-51 |
| HC-05 (по желанию) | Ручное управление с телефона | Работает только с Android |




Питание: не дай сервоприводу перезагрузить робота
У робота, объезжающего препятствия, к моторам добавляется сервопривод, поэтому распределение питания здесь важнее, чем у обычной робот-машинки. SG90 в покое потребляет около 10 mA; в момент начала движения ток подскакивает до 100–250 mA, а под нагрузкой приближается к половине ампера. Если этот всплеск совпадёт с пусковым током моторов, линия 5 V у Arduino на мгновение проседает, и плата перезапускается. Решение простое: моторы питаются от аккумулятора через L298N, Arduino — от аккумулятора через VIN, а сервопривод, HC-SR04, ИК-датчики и HC-05 делят пин 5V платы Arduino, причём рядом с выводами питания сервопривода ставится конденсатор 470 µF. Даже когда сервопривод движется, общая нагрузка на линию 5 V остаётся меньше половины ампера, а так как она кратковременная, стабилизатор Uno с ней справляется. Чтобы рассчитать время работы от аккумулятора со своими числами, воспользуйся утилитой Время работы от батареи.
| Узел | Откуда питание | Типичный ток |
|---|---|---|
| 2 TT-мотора | Клемма 12V на L298N ← 2 × 18650 (7,4 V) | 0,3–0,6 A при движении, до 2 A при старте |
| Arduino Uno | VIN ← плюс аккумулятора (после выключателя) | Около 50 mA |
| Сервопривод SG90 | 5V Arduino + конденсатор 470 µF | ~10 mA в покое, 100–250 mA в движении |
| HC-SR04 | 5V Arduino | Около 15 mA |
| 2 ИК-датчика | 5V Arduino | 20–40 mA каждый |
| HC-05 | 5V Arduino | 30–40 mA при сопряжении, около 10 mA после подключения |
Схема подключения: Arduino Uno, ESP32 и Pico
Пины моторов те же, что в руководстве по робот-машинке, поэтому перейти к этому проекту можно, просто добавив головку с датчиком и не разбирая машинку. Пины скорости на 5 и 6 выбраны намеренно: на Uno библиотека Servo использует Timer1 и потому отключает ШИМ на пинах 9 и 10; если бы скорости моторов были на этих пинах, регулировка скорости сломалась бы сразу после подключения сервопривода. HC-SR04 и ИК-датчики садятся на A0–A3. Аналоговые пины Uno при необходимости работают и как цифровые пины 14–19, а такой выбор упрощает использование того же кода с шилдом L293D, потому что шилд эти пины не трогает. Расположение пинов своей платы можно посмотреть в утилите Распиновка.
| Подключение | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| L298N ENA (скорость левого мотора) | D5 | GPIO 25 | GP15 |
| L298N IN1, IN2 | D7, D8 | GPIO 27, 33 | GP14, GP13 |
| L298N ENB (скорость правого мотора) | D6 | GPIO 26 | GP10 |
| L298N IN3, IN4 | D9, D4 | GPIO 32, 4 | GP12, GP11 |
| Сигнал сервопривода (оранжевый) | D3 | GPIO 13 | GP16 |
| Сервопривод + (красный), − (коричневый) | 5V, GND | L298N 5V, GND | L298N 5V, GND |
| HC-SR04 TRIG | A0 | GPIO 18 | GP17 |
| HC-SR04 ECHO | A1 (напрямую) | GPIO 19 через делитель 1 kΩ / 2 kΩ | GP18 через делитель 1 kΩ / 2 kΩ |
| HC-SR04 VCC | 5V | L298N 5V | L298N 5V |
| OUT левого ИК, OUT правого ИК | A2, A3 | GPIO 23, 22 | GP19, GP20 |
| VCC ИК-датчиков | 5V | 3V3 | 3V3 |
| HC-05 TXD, RXD | D10, D11 (RXD через делитель) | Не нужен (встроенный Bluetooth) | GP1, GP0 (UART0) |
| Питание платы | VIN ← аккумулятор (после выключателя) | 5V (VIN) ← L298N 5V | VSYS ← L298N 5V |
Установка и проверка головки с датчиком

- Подключи сервопривод без качалки и загрузи тестовый скетч ниже. В конце каждого прохода код ставит сервопривод на 90 градусов и ждёт 2 секунды; если в этой паузе отключить кабель USB, сервопривод останется на месте. Надень качалку на вал так, чтобы датчик смотрел прямо вперёд; тогда 90 градусов будут означать «прямо».
- Закрепи HC-SR04 на качалке пластиковым кронштейном или термоклеем. Провода датчика должны быть достаточно длинными, чтобы не натягиваться при повороте сервопривода; кабель, цепляющийся за шасси во время поворота на 140 градусов, — частая неисправность.
- Установи датчик на высоте 8–12 см от пола и ближе к самой передней точке корпуса. Слишком низко — и он ловит эхо от пола и фактуры ковра; слишком высоко — и не видит низкие препятствия.
- Наклони датчик вверх на 3–5 градусов. Датчик, который стоит идеально ровно и низко, может принять эхо от пола за препятствие, особенно на неровном полу.
- Открой монитор порта на скорости 9600 бод. Если при руке слева от датчика маленькие значения появляются в строках 125 и 160 градусов, значит, левой стороне соответствуют большие углы; если наоборот, в продвинутом скетче поставь SERVO_DIR в -1, а в первом скетче поменяй местами значения LEFT_ANGLE и RIGHT_ANGLE.
1#include <Servo.h>
2
3const int SERVO_PIN = 3;
4const int TRIG = A0, ECHO = A1; // analog pinler dijital pin olarak / analog pins used as digital pins
5Servo head;
6
7int pingCm() {
8 digitalWrite(TRIG, LOW); delayMicroseconds(2);
9 digitalWrite(TRIG, HIGH); delayMicroseconds(10); // 10 µs tetik darbesi / 10 µs trigger pulse
10 digitalWrite(TRIG, LOW);
11 unsigned long t = pulseIn(ECHO, HIGH, 25000UL); // en fazla 25 ms bekle (~4,3 m) / wait at most 25 ms (~4.3 m)
12 return t / 58; // 0 = yankı yok / 0 = no echo
13}
14
15void setup() {
16 Serial.begin(9600);
17 pinMode(TRIG, OUTPUT);
18 pinMode(ECHO, INPUT);
19 head.attach(SERVO_PIN);
20 head.write(90); // önce ortala / center first
21 delay(1000);
22}
23
24void loop() {
25 const int angles[] = {20, 55, 90, 125, 160};
26 for (int a : angles) {
27 head.write(a);
28 delay(400); // servo yerine otursun / let the servo settle
29 Serial.print(a);
30 Serial.print(F(" derece / degrees: "));
31 Serial.print(pingCm());
32 Serial.println(F(" cm"));
33 }
34 Serial.println();
35 head.write(90);
36 delay(2000);
37}В каждой строке показаны угол и расстояние в этом направлении. Если в пределах примерно 4 метров от датчика ничего нет, ты увидишь 0 cm: pulseIn() возвращает 0, когда эхо не приходит в течение 25 мс. В основном коде мы будем считать 0 «путь свободен», но датчик с отошедшим проводом или перепутанными TRIG и ECHO обычно тоже показывает 0. Поэтому, прежде чем ставить робота на пол, подержи руку на расстоянии 10, 30 и 60 см и убедись, что значения действительно меняются; робот, запущенный без такой проверки, въезжает в стену, даже не притормозив.
Алгоритм принятия решений: стоп, назад, поворот в самую свободную сторону
Если разделить поведение робота на зоны по расстоянию, код получается и простым, и надёжным. Без препятствия впереди робот едет на полной скорости, по мере приближения к препятствию замедляется, а дойдя до порога остановки, останавливается и сканирует. Во время сканирования датчик смотрит на 70 и 35 градусов влево и на 35 и 70 градусов вправо; в каждом направлении снимаются три показания и берётся их медиана. Робот поворачивает в сторону с наибольшим расстоянием на соответствующий угол. Если свободного направления нет, например когда робот заехал в угол, он немного сдаёт назад и разворачивается на 180 градусов.
| Измеренное расстояние | Зона | Что делает робот |
|---|---|---|
| Больше 60 см (или нет эха) | Свободно | Полный вперёд (PWM 200) |
| 25–60 см | Сближение | Замедляется пропорционально расстоянию (PWM от 200 до 130) |
| 12–25 см | Препятствие | Останавливается, подтверждает показание, сканирует в обе стороны, поворачивает в самую свободную |
| Меньше 12 см | Слишком близко | Сначала отъезжает назад 350 мс, затем сканирует |
| Во всех направлениях меньше 35 см | Тупик | Сдаёт назад и разворачивается на 180 градусов |
Эти пороги выбраны не наугад. Расстояние остановки складывается из трёх запасов: запаса на реакцию (путь между двумя измерениями), запаса на торможение (сколько робот прокатится после отключения моторов) и запаса на поворот (чтобы нос не задел препятствие при развороте на месте). Машинка с TT-моторами на 6 V едет со скоростью около 0,5–0,7 м/с и за интервал измерения 60 мс проходит 3–4 см. Запас на торможение — около 5 см, а для корпуса шириной 15 см, разворачивающегося на месте, запас на поворот — около 10 см. В сумме получается около 18–20 см; с запасом прочности в несколько сантиметров сверху STOP_CM = 25 — хорошая отправная точка. Если твой робот быстрее или пол скользкий, подними порог до 30–35 см. А в узких коридорах слишком большой порог заставляет робота постоянно останавливаться и сканировать, поэтому окончательную настройку делай на своей трассе.
Пошаговый код 1: первый робот, объезжающий препятствия
Первая версия — это скелет алгоритма: ехать вперёд, остановиться на 25 см, отъехать назад, посмотреть влево и вправо, повернуть в более свободную сторону. Функция моторов drive(left, right) та же, что в руководстве по робот-машинке, и принимает значения от -255 до 255; положительные — вперёд, отрицательные — назад. После загрузки кода сначала поставь машинку на коробку так, чтобы колёса не касались пола, и понаблюдай, как она останавливается и поворачивает, когда подносишь руку к датчику; если всё выглядит правильно, ставь робота на пол.
1#include <Servo.h>
2
3const int ENA = 5, IN1 = 7, IN2 = 8; // sol motor / left motor
4const int ENB = 6, IN3 = 9, IN4 = 4; // sağ motor / right motor
5const int SERVO_PIN = 3;
6const int TRIG = A0, ECHO = A1;
7
8const int STOP_CM = 25; // bu mesafede dur / stop at this distance
9const int SPEED = 170; // düz gidiş hızı / cruising speed
10const int TURN = 170; // dönüş hızı / turning speed
11const int LEFT_ANGLE = 160, RIGHT_ANGLE = 20; // ters ise yer değiştir / swap if reversed
12
13Servo head;
14
15void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
16 digitalWrite(IN1, left > 0); digitalWrite(IN2, left < 0);
17 digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
18 analogWrite(ENA, abs(left));
19 analogWrite(ENB, abs(right));
20}
21
22int pingCm() {
23 digitalWrite(TRIG, LOW); delayMicroseconds(2);
24 digitalWrite(TRIG, HIGH); delayMicroseconds(10);
25 digitalWrite(TRIG, LOW);
26 unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
27 if (t == 0) return 400; // yankı yok: önü açık say / no echo: treat as open
28 return t / 58;
29}
30
31int lookAt(int angle) {
32 head.write(angle);
33 delay(400); // servo dönsün ve dursun / let the servo turn and settle
34 return pingCm();
35}
36
37void setup() {
38 int pins[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
39 for (int p : pins) pinMode(p, OUTPUT);
40 pinMode(ECHO, INPUT);
41 drive(0, 0);
42 head.attach(SERVO_PIN);
43 head.write(90);
44 delay(3000); // yere koyman için süre / time to put it on the floor
45}
46
47void loop() {
48 int front = pingCm();
49 if (front > STOP_CM) {
50 drive(SPEED, SPEED); // yol açık: ileri / path clear: forward
51 delay(60); // ölçümler arası 60 ms / 60 ms between readings
52 return;
53 }
54
55 drive(0, 0); // 1) dur / 1) stop
56 delay(150);
57 drive(-SPEED, -SPEED); // 2) biraz geri çekil / 2) back off a little
58 delay(300);
59 drive(0, 0);
60
61 int left = lookAt(LEFT_ANGLE); // 3) iki yana bak / 3) look both ways
62 int right = lookAt(RIGHT_ANGLE);
63 head.write(90);
64 delay(300);
65
66 if (left < STOP_CM && right < STOP_CM) { // iki yan kapalı: geri dön / both blocked: turn around
67 drive(-TURN, TURN);
68 delay(700);
69 } else if (left > right) { // 4) daha açık tarafa dön / 4) turn to the more open side
70 drive(-TURN, TURN);
71 delay(350);
72 } else {
73 drive(TURN, -TURN);
74 delay(350);
75 }
76 drive(0, 0);
77 delay(100);
78}Этот код справляется с задачей, но у него есть слабые места: одно неверное показание зря останавливает робота, к препятствиям он всегда подъезжает с одной и той же скоростью, смотрит только в две стороны, а время поворота не зависит от угла. А поскольку он ждёт с помощью delay(), во время сканирования он ни на что не может реагировать. Подробности логики ожидания — в статье millis() и delay(); следующая версия исправляет большинство этих проблем.
Пошаговый код 2: фильтрация, плавное замедление, ИК и Bluetooth
Продвинутая версия работает с тем же подключением, и ничего страшного, если ИК-датчики и HC-05 ещё не установлены. Входы ИК настроены как INPUT_PULLUP, поэтому неподключённый датчик читается как «нет препятствия» (HIGH); без HC-05 робот просто стартует в автоматическом режиме. В таблице ниже объясняется, что делает каждая часть кода.
1#include <Servo.h>
2#include <SoftwareSerial.h>
3
4// Pinler / Pins
5const int ENA = 5, IN1 = 7, IN2 = 8; // sol motor / left motor
6const int ENB = 6, IN3 = 9, IN4 = 4; // sağ motor / right motor
7const int SERVO_PIN = 3;
8const int TRIG = A0, ECHO = A1;
9const int IR_LEFT = A2, IR_RIGHT = A3; // LOW = engel (takılı değilse HIGH) / LOW = obstacle (HIGH if not fitted)
10SoftwareSerial bt(10, 11); // RX, TX → HC-05 TXD, RXD (RXD bölücüyle / RXD via divider)
11
12// Ayarlar / Settings
13const int BACK_CM = 12; // daha yakınsa geri çekil / closer: back off first
14const int STOP_CM = 25; // dur ve tara / stop and scan
15const int SLOW_CM = 60; // yavaşlamaya başla / start slowing down
16const int GUARD_CM = 15; // manuel modda çarpışma koruması / collision guard in manual mode
17const int MAX_CM = 400;
18const int CRUISE = 200, SLOW = 130, TURN = 170; // PWM hızları / PWM speeds
19const int SERVO_CENTER = 90; // sensör öne bakmıyorsa düzelt / adjust if not facing ahead
20const int SERVO_DIR = 1; // +70 sola bakmıyorsa -1 yap / set to -1 if +70 doesn't look left
21const float MS_PER_DEG = 4.0; // derece başına ms, kalibre et / ms per degree, calibrate
22const unsigned long BT_TIMEOUT = 600; // manuelde komut kesilirse dur / stop if commands stop
23
24Servo head;
25bool autoMode = true;
26int manualLeft = 0, manualRight = 0, manualSpeed = 200;
27unsigned long lastCmd = 0, lastPing = 0;
28
29void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
30 left = constrain(left, -255, 255);
31 right = constrain(right, -255, 255);
32 digitalWrite(IN1, left > 0); digitalWrite(IN2, left < 0);
33 digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
34 analogWrite(ENA, abs(left));
35 analogWrite(ENB, abs(right));
36}
37
38int pingCm() {
39 digitalWrite(TRIG, LOW); delayMicroseconds(2);
40 digitalWrite(TRIG, HIGH); delayMicroseconds(10);
41 digitalWrite(TRIG, LOW);
42 unsigned long t = pulseIn(ECHO, HIGH, 25000UL); // ~4,3 m zaman aşımı / ~4.3 m timeout
43 return t == 0 ? MAX_CM : (int)(t / 58); // yankı yok = açık alan / no echo = open space
44}
45
46int distanceCm() { // 3 ölçümün ortancası / median of 3 readings
47 int a = pingCm(); delay(60);
48 int b = pingCm(); delay(60);
49 int c = pingCm();
50 return max(min(a, b), min(max(a, b), c));
51}
52
53bool irBlocked(int pin) {
54 return digitalRead(pin) == LOW;
55}
56
57int lookAt(int offset) { // + sol, - sağ (derece) / + left, - right (degrees)
58 head.write(SERVO_CENTER + SERVO_DIR * offset);
59 delay(300); // servo yerine otursun / let the servo settle
60 return distanceCm();
61}
62
63void turnBy(int deg) { // + sola, - sağa / + left, - right
64 if (deg > 0) drive(-TURN, TURN);
65 else drive(TURN, -TURN);
66 delay((unsigned long)(abs(deg) * MS_PER_DEG));
67 drive(0, 0);
68 delay(100);
69}
70
71void avoid() {
72 drive(0, 0); // 1) dur / 1) stop
73 delay(120);
74 int front = distanceCm(); // ortancayla doğrula / confirm with the median
75 bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
76 if (front >= STOP_CM && !sides) return; // yanlış alarm / false alarm
77
78 if (front < BACK_CM || sides) { // 2) çok yakın: geri çekil / 2) too close: back off
79 drive(-TURN, -TURN);
80 delay(350);
81 drive(0, 0);
82 }
83
84 const int offsets[] = {70, 35, -35, -70}; // 3) tara / 3) scan
85 int bestOffset = 0, bestCm = 0;
86 for (int off : offsets) {
87 int cm = lookAt(off);
88 if (cm > bestCm) { bestCm = cm; bestOffset = off; }
89 }
90 head.write(SERVO_CENTER);
91 delay(200);
92
93 if (bestCm < STOP_CM + 10) { // her yön kapalı: çıkmaz / every way blocked: dead end
94 drive(-TURN, -TURN);
95 delay(400);
96 turnBy(180);
97 } else {
98 turnBy(bestOffset); // 4) en açık yöne dön / 4) turn to the most open direction
99 }
100}
101
102void autoStep() {
103 if (millis() - lastPing < 60) return; // 60 ms'de bir ölç / measure every 60 ms
104 lastPing = millis();
105 int d = pingCm();
106 bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
107
108 if (d < STOP_CM || (l && r)) {
109 avoid();
110 } else if (l) { // sol köşede engel: sağa kaç / front-left: dodge right
111 drive(TURN, -TURN);
112 delay(150);
113 } else if (r) { // sağ köşede engel: sola kaç / front-right: dodge left
114 drive(-TURN, TURN);
115 delay(150);
116 } else if (d < SLOW_CM) {
117 int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE); // yaklaştıkça yavaşla / slow down as you get closer
118 drive(s, s);
119 } else {
120 drive(CRUISE, CRUISE);
121 }
122}
123
124void manualStep() {
125 if (millis() - lastCmd > BT_TIMEOUT) { // komut yok: dur / no commands: stop
126 manualLeft = 0;
127 manualRight = 0;
128 }
129 static int front = MAX_CM;
130 if (millis() - lastPing >= 60) {
131 lastPing = millis();
132 front = pingCm();
133 }
134 bool forward = manualLeft > 0 && manualRight > 0;
135 if (forward && front < GUARD_CM) drive(0, 0); // çarpışma koruması / collision guard
136 else drive(manualLeft, manualRight);
137}
138
139void handleBluetooth() {
140 while (bt.available()) {
141 char c = bt.read();
142 Serial.println(c); // gelen harfi göster / show the incoming letter
143 lastCmd = millis();
144 if (c == 'A') { // otomatik mod / auto mode
145 autoMode = true;
146 continue;
147 }
148 if (c == 'M') { // manuel mod / manual mode
149 autoMode = false;
150 manualLeft = manualRight = 0;
151 drive(0, 0);
152 head.write(SERVO_CENTER);
153 continue;
154 }
155 if (autoMode) continue; // otomatikte yön harflerini yok say / ignore in auto mode
156 int s = manualSpeed;
157 switch (c) {
158 case 'F': manualLeft = s; manualRight = s; break; // ileri / forward
159 case 'B': manualLeft = -s; manualRight = -s; break; // geri / backward
160 case 'L': manualLeft = -s; manualRight = s; break; // yerinde sola / spin left
161 case 'R': manualLeft = s; manualRight = -s; break; // yerinde sağa / spin right
162 case 'S': manualLeft = 0; manualRight = 0; break; // dur / stop
163 default:
164 if (c >= '0' && c <= '9') manualSpeed = map(c - '0', 0, 9, 100, 240); // hız kademesi / speed level
165 }
166 }
167}
168
169void setup() {
170 int outs[] = {ENA, IN1, IN2, ENB, IN3, IN4, TRIG};
171 for (int p : outs) pinMode(p, OUTPUT);
172 drive(0, 0);
173 pinMode(ECHO, INPUT);
174 pinMode(IR_LEFT, INPUT_PULLUP);
175 pinMode(IR_RIGHT, INPUT_PULLUP);
176 Serial.begin(9600);
177 bt.begin(9600); // HC-05 veri modu hızı / HC-05 data mode baud rate
178 head.attach(SERVO_PIN);
179 head.write(SERVO_CENTER);
180 delay(3000); // yere koyman için süre / time to put it on the floor
181}
182
183void loop() {
184 handleBluetooth();
185 if (autoMode) autoStep();
186 else manualStep();
187}| Функция или настройка | Что делает |
|---|---|
| pingCm() | Снимает одно показание; если эхо не приходит в течение 25 мс, возвращает 400 см (свободное пространство) |
| distanceCm() | Снимает 3 показания с интервалом 60 мс и возвращает медиану; отбрасывает одиночный выброс |
| autoStep() | Измеряет каждые 60 мс; в зависимости от расстояния и ИК-датчиков едет дальше, замедляется, уворачивается или вызывает avoid() |
| avoid() | Останавливается, подтверждает показание, при необходимости отъезжает назад, смотрит в 4 стороны и поворачивает в самую свободную |
| turnBy(градусы) | Разворачивает робота на месте на заданный угол; время рассчитывается через MS_PER_DEG |
| manualStep() | Выполняет направление, присланное с телефона; при движении вперёд останавливается на 15 см, а если команд нет 600 мс, тоже останавливается |
| SERVO_CENTER, SERVO_DIR | Угол, при котором датчик смотрит прямо вперёд, и в какую сторону лево |
| STOP_CM, SLOW_CM, BACK_CM | Пороги остановки, замедления и отъезда назад |
Для робота, объезжающего препятствия, медианный фильтр лучше среднего: для трёх показаний вроде 24, 26 и 400 см среднее выходит 150 см и сбивает робота с толку, а медиана выбирает 26 см. Если хочешь копнуть глубже, загляни в статью Фильтрация данных с датчиков. Разделение на автоматический и ручной режим — по сути тоже небольшой конечный автомат; этот подход подробно разобран в руководстве Конечный автомат на Arduino. После загрузки кода настрой робота под свою комнату в таком порядке:
- Центр сервопривода: поставь робота ровно напротив плоской стены; если датчик смотрит на неё не прямо, меняй SERVO_CENTER небольшими шагами, например на 85 или 95.
- Время поворота: временно добавь строку turnBy(360); в конец setup() и посмотри, делает ли робот полный оборот. Если он поворачивается на 300 градусов вместо 360, поставь MS_PER_DEG = 4.0 × 360 / 300 = 4.8. Это значение меняется в зависимости от аккумулятора и пола, поэтому настраивай его с полностью заряженным аккумулятором на том полу, по которому будет ездить робот.
- Порог остановки: направь робота к стене и измерь, на каком расстоянии от стены остановится его нос. Если меньше 10 см, увеличь STOP_CM на 5 см.
- Медленная скорость: если SLOW ниже порога трогания моторов, робот застревает в зоне сближения; в этом случае подними SLOW до 150.
Слепые зоны HC-SR04: мягкие поверхности, стены под углом и тонкие препятствия
Ультразвуковой датчик видит поверхности, которые хорошо отражают звук, но далеко не каждое препятствие возвращает звук к датчику. Для хорошего результата производитель рекомендует цель площадью не меньше 0,5 м² и как можно более плоскую. А в настоящей комнате робот натыкается на шторы, диваны, ножки стульев и углы стен. Звук отражается под тем же углом, под которым пришёл, как бильярдный шар: датчик, смотрящий на стену прямо, получает сильное эхо, но если линия его взгляда отклоняется от перпендикуляра больше чем примерно на 30–40 градусов, звук скользит в другую сторону и не возвращается. Тогда pulseIn() возвращает 0, и код считает, что путь свободен. Это самая частая причина, по которой роботы чиркают по стенам.
| Препятствие или поверхность | Почему не видно | Решение |
|---|---|---|
| Шторы, диваны, подушки, одежда, шерсть питомцев | Мягкие пористые поверхности поглощают звук, и эхо возвращается очень слабым | Добавь ИК-датчик для ближней зоны и веди робота медленно |
| Подъезд к стене под углом | Звук отражается в сторону, и эхо не доходит до датчика | Поставь на передних углах ИК-датчики, развёрнутые наружу, и сканируй под меньшими углами |
| Ножки стульев и столов, тонкие стойки | Отражающая поверхность крошечная, эхо слабое | Снизь скорость, используй медианный фильтр, добавь ИК-датчики по бокам |
| Низкие препятствия ниже датчика (кабели, пороги, тапочки) | Они остаются под звуковым конусом | Добавь направленный вниз ИК-датчик или микропереключатель на передний бампер |
| Края стола, лестничные пролёты | Ультразвуковой датчик смотрит вперёд и не видит провала в полу | Обнаружение края с помощью TCRT5000, направленного в пол |
| Предметы ближе 2 см | Мёртвая зона датчика: эхо возвращается раньше, чем закончится звуковой импульс | Настрой робота на отъезд назад при 12 см |
| Углы и узкие коридоры | Звук отражается между двумя стенами и возвращается с опозданием, поэтому расстояние кажется больше | Медианный фильтр и измерения под несколькими углами |
Добавляем ИК-датчики
Ультразвуковые и инфракрасные (ИК) датчики восполняют слабые стороны друг друга. ИК-датчики препятствий вроде FC-51 состоят из ИК-светодиода и приёмника; когда отражённый свет достаточно сильный, компаратор LM393 переводит выход в LOW, и на плате загорается светодиод. Дальность регулируется подстроечным резистором примерно от 2 до 30 см, но расстояние датчик не измеряет, а только сообщает «препятствие» или «нет препятствия». Если поставить два ИК-датчика на передних углах робота, развернув их на 30–45 градусов наружу, они поймают ножки стульев и стены под углом, которые выпадают из узкого конуса ультразвукового датчика. В продвинутом коде робот, когда срабатывает левый ИК-датчик, ненадолго уворачивается вправо, а когда правый — влево; если срабатывают оба сразу, он останавливается и сканирует, как будто впереди препятствие.
| Характеристика | HC-SR04 (ультразвуковой) | FC-51 (ИК-датчик препятствий) | TCRT5000 (ИК-датчик отражения) |
|---|---|---|---|
| Выход | Расстояние (см) | Да / нет (цифровой) | Аналоговое значение и цифровой выход |
| Дальность | 2–400 см | Примерно 2–30 см (регулируется) | Примерно 1–15 мм |
| Угол обнаружения | Около 15° | Около 35° | Очень узкий |
| Слабое место | Мягкие, наклонные и тонкие поверхности | Чёрные матовые поверхности, солнечный свет | Перепады высоты, солнечный свет |
| Хорошо видит | Стекло, чёрные предметы, большие плоские поверхности | Ткань, углы, близкие препятствия | Цвет пола и края стола |
| Место на роботе | Главный глаз, поворачивается сервоприводом | Передние углы | Под передним бампером, смотрит в пол |

Для роботов, которые катаются по столу, самое опасное препятствие — это на самом деле обрыв. Если поставить под передним бампером модуль TCRT5000, направленный в пол, ты узнаешь о том, что пол исчез, в ту же секунду. Подключи цифровой выход модуля к D2, который на Uno ещё свободен; сначала проверь в мониторе порта, какой уровень появляется на выходе, когда пола нет, затем читай этот пин в самом начале autoStep() и, если пол пропал, сразу останавливай моторы и отводи робота назад. Эта проверка должна идти раньше измерения расстояния: падение всегда обходится дороже, чем удар.
Переход в ручной режим по Bluetooth
Иногда автономным роботом хочется поуправлять вручную: перевезти его из одной комнаты в другую, подвести к препятствию и проверить реакцию или просто поиграть. Подключение HC-05 в продвинутом коде делает это возможным. Когда телефон отправляет букву A, робот переходит в автоматический режим; когда отправляет M — в ручной. В ручном режиме используются управляющие буквы из руководства по робот-машинке (F, B, L, R, S) с одним отличием: если при движении вперёд робот видит препятствие ближе 15 см, он останавливается вопреки твоей команде. Такая защита от столкновений позволяет пользоваться датчиками автономного робота и при ручном управлении. Шаги сопряжения HC-05 и AT-команды — в статье Bluetooth-модуль HC-05.
| Буква | Действие | В каком режиме? |
|---|---|---|
| A | Перейти в автоматический режим | В обоих режимах |
| M | Перейти в ручной режим, остановить робота | В обоих режимах |
| F / B | Вперёд / назад | Ручной |
| L / R | Разворот влево / вправо на месте | Ручной |
| S | Стоп | Ручной |
| 0–9 | Уровень скорости (PWM 100–240) | Ручной |
- Подключи пин TXD модуля HC-05 к D10, пин RXD — к D11 через делитель 1 kΩ / 2 kΩ, VCC — к 5V, а GND — к GND.
- Выполни сопряжение с HC-05 в настройках Bluetooth телефона на Android с PIN-кодом 1234 (если не получится — 0000).
- Установи из Play Store приложение-пульт для Bluetooth-машинки и подключи его к HC-05. Если оставить Arduino подключённой по USB и открыть монитор порта на скорости 9600 бод, код будет печатать каждую букву, пришедшую с телефона.
- В большинстве приложений-пультов есть дополнительные кнопки, например фары или сигнал, и каждая отправляет свою букву. Посмотри эти буквы в мониторе порта и подставь их вместо 'A' и 'M' в коде; если хочется настроить кнопки по-своему, можно сделать собственный пульт по руководству App Inventor и Arduino по Bluetooth.
- Отключи кабель USB и щёлкни выключателем; через 3 секунды робот тронется в автоматическом режиме. Переключись в ручной режим и управляй им с телефона.
Сборка с моторным шилдом L293D

Если не нравится клубок проводов, вместо L298N можно взять моторный шилд L293D (Adafruit Motor Shield V1 и его клоны), который вставляется прямо в Arduino Uno. Шилд управляет четырьмя DC-моторами, имеет два разъёма для сервоприводов и питает их от линии 5 V платы Arduino. Моторы подключены не напрямую к пинам Arduino, а через сдвиговый регистр 74HC595 на плате, поэтому управлять ими нужно через библиотеку Adafruit Motor Shield (для V1, а не для V2), то есть AFMotor, установленную через «Менеджер библиотек» (Library Manager). Шилд занимает D3, D4, D5, D6, D7, D8, D11 и D12 под моторы и D9 и D10 под сервоприводы; A0–A5, D2 и D13 остаются свободными. HC-SR04 и ИК-датчики и так стоят на A0–A3, поэтому менять нужно только код моторов, пин сервопривода и пины HC-05.
| Параметр | С L298N | С шилдом L293D |
|---|---|---|
| Подключение моторов | Клеммы OUT1–OUT4 | Клеммы M1 (левый) и M2 (правый) |
| Код моторов | digitalWrite и analogWrite внутри drive() | AFMotor внутри drive() |
| SERVO_PIN | 3 | 9 или 10, в зависимости от разъёма, в который вставлен сервопривод |
| HC-05 | D10, D11 | A4 (RX) и A5 (TX): SoftwareSerial bt(A4, A5) |
| Ток на канал | 2 A длительно | 0,6 A длительно, 1,2 A пиковый |
| Питание моторов | Клемма 12V | Клемма EXT_PWR; с установленной перемычкой PWR Arduino питается от того же источника |
1// L293D shield: ENA–IN4 tanımlarını ve setup()'taki pinMode listesinden bu pinleri sil, drive() fonksiyonunu aşağıdakiyle değiştir
2// L293D shield: delete the ENA–IN4 definitions, remove them from the pinMode list in setup() and replace drive() with the one below
3// Ayrıca / Also: SERVO_PIN = 9 ya da / or 10, SoftwareSerial bt(A4, A5)
4#include <AFMotor.h> // Adafruit Motor Shield V1 kütüphanesi / library
5
6AF_DCMotor leftMotor(1); // M1 klemensi / M1 terminal
7AF_DCMotor rightMotor(2); // M2 klemensi / M2 terminal
8
9void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
10 leftMotor.setSpeed(min(abs(left), 255));
11 rightMotor.setSpeed(min(abs(right), 255));
12 leftMotor.run(left > 0 ? FORWARD : (left < 0 ? BACKWARD : RELEASE));
13 rightMotor.run(right > 0 ? FORWARD : (right < 0 ? BACKWARD : RELEASE));
14}Предела L293D в 0,6 A на канал хватает для обычной езды 2WD-робота с TT-моторами, но когда колесо упирается в препятствие и стопорится, мотор может потреблять больше 1 A, и микросхема быстро нагревается. Поэтому алгоритм сканирования, который останавливает робота до того, как он упрётся в препятствие, заодно продлевает жизнь шилда. Настройки перемычек шилда и скетч для четырёхмоторной машинки — в статье Моторный шилд L293D, а внутреннее устройство L298N — в руководстве Управление DC-мотором через L298N.
Робот, объезжающий препятствия, на ESP32
На ESP32 есть два важных отличия. Во-первых, библиотека Servo для Arduino на ESP32 не работает, поэтому сигнал сервопривода мы формируем напрямую через LEDC, ШИМ-модуль платы: в сигнале 50 Hz импульс 0,544–2,4 мс соответствует 0–180 градусам. Моторы работают на 1 kHz, а сервопривод на 50 Hz, поэтому сначала настраиваются каналы моторов, а за ними канал сервопривода, чтобы сервопривод получил собственный таймер. Во-вторых, в ESP32 встроен классический Bluetooth, так что HC-05 не нужен; робот отображается на телефоне как «BlueGrays-Robot», а буквы направления и режима те же, что в версии для Uno. Код написан для версии 3.x ядра ESP32 для Arduino (ledcAttach и ledcWrite работают с номерами пинов), и, поскольку ему нужен классический Bluetooth, для ESP32-S3 или ESP32-C3 он не скомпилируется.
1// ESP32 Arduino çekirdeği 3.x / ESP32 Arduino core 3.x
2#include "BluetoothSerial.h"
3
4const int ENA = 25, IN1 = 27, IN2 = 33; // sol motor / left motor
5const int ENB = 26, IN3 = 32, IN4 = 4; // sağ motor / right motor
6const int SERVO_PIN = 13;
7const int TRIG = 18, ECHO = 19; // ECHO 1 kΩ / 2 kΩ bölücüyle / ECHO via a 1 kΩ / 2 kΩ divider
8const int IR_LEFT = 23, IR_RIGHT = 22; // LOW = engel / LOW = obstacle
9
10const int BACK_CM = 12, STOP_CM = 25, SLOW_CM = 60, GUARD_CM = 15, MAX_CM = 400;
11const int CRUISE = 200, SLOW = 130, TURN = 170;
12const int SERVO_CENTER = 90, SERVO_DIR = 1;
13const float MS_PER_DEG = 4.0; // kalibre et / calibrate
14
15BluetoothSerial bt;
16bool autoMode = true;
17int manualLeft = 0, manualRight = 0;
18unsigned long lastCmd = 0, lastPing = 0;
19
20void drive(int left, int right) { // her biri -255 … 255 / each -255 … 255
21 left = constrain(left, -255, 255);
22 right = constrain(right, -255, 255);
23 digitalWrite(IN1, left > 0); digitalWrite(IN2, left < 0);
24 digitalWrite(IN3, right > 0); digitalWrite(IN4, right < 0);
25 ledcWrite(ENA, abs(left));
26 ledcWrite(ENB, abs(right));
27}
28
29void servoWrite(int angle) { // 0–180° → 544–2400 µs
30 angle = constrain(angle, 0, 180);
31 uint32_t us = map(angle, 0, 180, 544, 2400);
32 ledcWrite(SERVO_PIN, us * 16384 / 20000); // 14 bit, 20 ms periyot / 14-bit, 20 ms period
33}
34
35int pingCm() {
36 digitalWrite(TRIG, LOW); delayMicroseconds(2);
37 digitalWrite(TRIG, HIGH); delayMicroseconds(10);
38 digitalWrite(TRIG, LOW);
39 unsigned long t = pulseIn(ECHO, HIGH, 25000UL);
40 return t == 0 ? MAX_CM : (int)(t / 58);
41}
42
43int distanceCm() { // 3 ölçümün ortancası / median of 3 readings
44 int a = pingCm(); delay(60);
45 int b = pingCm(); delay(60);
46 int c = pingCm();
47 return max(min(a, b), min(max(a, b), c));
48}
49
50bool irBlocked(int pin) { return digitalRead(pin) == LOW; }
51
52void turnBy(int deg) { // + sola, - sağa / + left, - right
53 if (deg > 0) drive(-TURN, TURN);
54 else drive(TURN, -TURN);
55 delay((unsigned long)(abs(deg) * MS_PER_DEG));
56 drive(0, 0);
57 delay(100);
58}
59
60void avoid() {
61 drive(0, 0);
62 delay(120);
63 int front = distanceCm();
64 bool sides = irBlocked(IR_LEFT) || irBlocked(IR_RIGHT);
65 if (front >= STOP_CM && !sides) return; // yanlış alarm / false alarm
66 if (front < BACK_CM || sides) { // geri çekil / back off
67 drive(-TURN, -TURN);
68 delay(350);
69 drive(0, 0);
70 }
71 const int offsets[] = {70, 35, -35, -70};
72 int bestOffset = 0, bestCm = 0;
73 for (int off : offsets) {
74 servoWrite(SERVO_CENTER + SERVO_DIR * off);
75 delay(300);
76 int cm = distanceCm();
77 if (cm > bestCm) { bestCm = cm; bestOffset = off; }
78 }
79 servoWrite(SERVO_CENTER);
80 delay(200);
81 if (bestCm < STOP_CM + 10) { // çıkmaz / dead end
82 drive(-TURN, -TURN);
83 delay(400);
84 turnBy(180);
85 } else {
86 turnBy(bestOffset);
87 }
88}
89
90void setup() {
91 int outs[] = {IN1, IN2, IN3, IN4, TRIG};
92 for (int p : outs) pinMode(p, OUTPUT);
93 pinMode(ECHO, INPUT);
94 pinMode(IR_LEFT, INPUT_PULLUP);
95 pinMode(IR_RIGHT, INPUT_PULLUP);
96 ledcAttach(ENA, 1000, 8); // motor PWM: 1 kHz, 8 bit
97 ledcAttach(ENB, 1000, 8);
98 ledcAttach(SERVO_PIN, 50, 14); // servo: 50 Hz, 14 bit
99 drive(0, 0);
100 servoWrite(SERVO_CENTER);
101 bt.begin("BlueGrays-Robot"); // telefonda görünecek ad / name shown on the phone
102 delay(3000);
103}
104
105void loop() {
106 while (bt.available()) {
107 char c = bt.read();
108 lastCmd = millis();
109 if (c == 'A') autoMode = true; // otomatik / auto
110 else if (c == 'M') { // manuel / manual
111 autoMode = false;
112 manualLeft = manualRight = 0;
113 servoWrite(SERVO_CENTER);
114 }
115 else if (!autoMode) {
116 if (c == 'F') { manualLeft = 200; manualRight = 200; } // ileri / forward
117 else if (c == 'B') { manualLeft = -200; manualRight = -200; } // geri / backward
118 else if (c == 'L') { manualLeft = -200; manualRight = 200; } // sola / left
119 else if (c == 'R') { manualLeft = 200; manualRight = -200; } // sağa / right
120 else if (c == 'S') { manualLeft = 0; manualRight = 0; } // dur / stop
121 }
122 }
123
124 if (millis() - lastPing < 60) return; // 60 ms'de bir / every 60 ms
125 lastPing = millis();
126 int d = pingCm();
127
128 if (!autoMode) {
129 if (millis() - lastCmd > 600) manualLeft = manualRight = 0; // komut yok: dur / no command: stop
130 bool forward = manualLeft > 0 && manualRight > 0;
131 if (forward && d < GUARD_CM) drive(0, 0); // çarpışma koruması / collision guard
132 else drive(manualLeft, manualRight);
133 return;
134 }
135
136 bool l = irBlocked(IR_LEFT), r = irBlocked(IR_RIGHT);
137 if (d < STOP_CM || (l && r)) avoid();
138 else if (l) { drive(TURN, -TURN); delay(150); }
139 else if (r) { drive(-TURN, TURN); delay(150); }
140 else if (d < SLOW_CM) { int s = map(d, STOP_CM, SLOW_CM, SLOW, CRUISE); drive(s, s); }
141 else drive(CRUISE, CRUISE);
142}Робот, объезжающий препятствия, на Raspberry Pi Pico (MicroPython)
На Raspberry Pi Pico функция MicroPython time_pulse_us() работает как pulseIn() в Arduino; если эхо не приходит, она возвращает отрицательное значение, и мы считаем его равным 400 см, то есть свободному пространству. Для сервопривода используем ШИМ 50 Hz и duty_ns(), которая принимает ширину импульса в наносекундах. Сервопривод на GP16 и пины скорости моторов на GP15 и GP10 находятся на разных срезах ШИМ (slice), поэтому не сбивают друг другу частоту. На этот раз скорость задаётся в процентах. Bluetooth у Pico нет, так что для ручного режима можно подключить HC-05 к UART0 (GP0, GP1), как в руководстве по робот-машинке.
1from machine import Pin, PWM, time_pulse_us
2import time
3
4ena = PWM(Pin(15)); ena.freq(1000) # sol motor hızı / left motor speed
5enb = PWM(Pin(10)); enb.freq(1000) # sağ motor hızı / right motor speed
6in1, in2 = Pin(14, Pin.OUT), Pin(13, Pin.OUT)
7in3, in4 = Pin(12, Pin.OUT), Pin(11, Pin.OUT)
8servo = PWM(Pin(16)); servo.freq(50) # servo sinyali, 50 Hz / servo signal, 50 Hz
9trig = Pin(17, Pin.OUT, value=0)
10echo = Pin(18, Pin.IN) # 1 kΩ / 2 kΩ bölücü üzerinden / via a 1 kΩ / 2 kΩ divider
11ir_left = Pin(19, Pin.IN, Pin.PULL_UP) # 0 = engel / 0 = obstacle
12ir_right = Pin(20, Pin.IN, Pin.PULL_UP)
13
14STOP_CM, BACK_CM = 25, 12
15CRUISE, TURN = 75, 65 # hız yüzdesi / speed in percent
16MS_PER_DEG = 4 # kalibre et / calibrate
17SERVO_CENTER = 90 # öne bakmıyorsa düzelt / adjust if not facing ahead
18
19def drive(left, right): # -100 … 100 (%)
20 in1.value(left > 0); in2.value(left < 0)
21 in3.value(right > 0); in4.value(right < 0)
22 ena.duty_u16(int(min(abs(left), 100) * 655))
23 enb.duty_u16(int(min(abs(right), 100) * 655))
24
25def head(offset): # + sol, - sağ / + left, - right
26 angle = max(0, min(180, SERVO_CENTER + offset))
27 servo.duty_ns((544 + angle * 1856 // 180) * 1000) # 0,544–2,4 ms darbe / 0.544–2.4 ms pulse
28
29def ping_cm():
30 trig.value(1); time.sleep_us(10); trig.value(0) # 10 µs tetik / 10 µs trigger
31 t = time_pulse_us(echo, 1, 25000)
32 return 400 if t < 0 else t // 58 # negatif = yankı yok / negative = no echo
33
34def distance_cm(): # 3 ölçümün ortancası / median of 3 readings
35 r = []
36 for _ in range(3):
37 r.append(ping_cm())
38 time.sleep_ms(60)
39 return sorted(r)[1]
40
41def turn_by(deg): # + sola, - sağa / + left, - right
42 if deg > 0:
43 drive(-TURN, TURN)
44 else:
45 drive(TURN, -TURN)
46 time.sleep_ms(int(abs(deg) * MS_PER_DEG))
47 drive(0, 0)
48 time.sleep_ms(100)
49
50def avoid():
51 drive(0, 0)
52 time.sleep_ms(120)
53 front = distance_cm()
54 sides = not ir_left.value() or not ir_right.value()
55 if front >= STOP_CM and not sides:
56 return # yanlış alarm / false alarm
57 if front < BACK_CM or sides: # geri çekil / back off
58 drive(-TURN, -TURN); time.sleep_ms(350); drive(0, 0)
59 best_off, best_cm = 0, 0
60 for off in (70, 35, -35, -70): # tara / scan
61 head(off)
62 time.sleep_ms(300)
63 cm = distance_cm()
64 if cm > best_cm:
65 best_off, best_cm = off, cm
66 head(0)
67 time.sleep_ms(200)
68 if best_cm < STOP_CM + 10: # çıkmaz / dead end
69 drive(-TURN, -TURN); time.sleep_ms(400)
70 turn_by(180)
71 else:
72 turn_by(best_off)
73
74drive(0, 0)
75head(0)
76time.sleep(3) # yere koyman için süre / time to put it down
77while True:
78 d = ping_cm()
79 left_hit, right_hit = not ir_left.value(), not ir_right.value()
80 if d < STOP_CM or (left_hit and right_hit):
81 avoid()
82 elif left_hit:
83 drive(TURN, -TURN); time.sleep_ms(150) # sağa kaç / dodge right
84 elif right_hit:
85 drive(-TURN, TURN); time.sleep_ms(150) # sola kaç / dodge left
86 else:
87 drive(CRUISE, CRUISE)
88 time.sleep_ms(60) # ölçümler arası / between readingsИдеи проектов с роботом, объезжающим препятствия
- Экран радара: отправляй углы и расстояния из сканирования по последовательному порту и рисуй на компьютере зелёный экран радара; подробности — в статье Радар на Arduino.
- Линия плюс препятствие: пусть робот едет по трассе, как робот, следующий по линии, объезжает коробку на пути и возвращается на линию; это частое соревновательное задание.
- Робот, едущий вдоль стены: держи датчик повёрнутым на 60 градусов вправо и веди робота на постоянном расстоянии 20 см от стены; это первый шаг к прохождению лабиринтов.
- Робот с дисплеем: выводи расстояние и режим на маленький OLED-дисплей; отладка станет намного проще.
- Прототип робота-пылесоса: набросай чистящего робота, который бродит по дому со случайным выбором направления, концевиками на бампере и датчиком края.
- Умная трость: преврати ту же логику измерения расстояния в вибрационное предупреждение для людей с нарушениями зрения; подробнее — в статье Умная трость на Arduino.
- Школьный или соревновательный проект: покажи его на полосе препятствий с замером времени и демонстрацией автоматического и ручного режимов; за идеями загляни в статью Идеи проектов на Arduino для научных конкурсов.
Частые ошибки и как их исправить
| Проблема | Вероятная причина | Решение |
|---|---|---|
| Робот врезается в стену, не останавливаясь | Датчик показывает 0 (обрыв провода, перепутаны TRIG и ECHO), а код считает это свободным пространством | Запусти тестовый скетч, проверь рукой на 10–60 см, проверь пины TRIG и ECHO |
| Задевает стену при подъезде под углом | Звук отражается от наклонной поверхности в другую сторону | Поставь ИК-датчики на передних углах и снизь скорость |
| Arduino перезагружается, когда робот останавливается для сканирования | Ток сервопривода и моторов проваливает линию 5 V | Конденсатор 470 µF на сервоприводе, полностью заряженные 18650; при необходимости питай сервопривод от отдельного источника 5 V |
| Робот останавливается и сканирует без причины | Одиночное неверное показание или эхо от пола | Подними датчик на 8–12 см и слегка наклони вверх; используй подтверждение по медиане |
| Робот поворачивает в сторону, где препятствие | Сервопривод установлен наоборот, лево и право перепутаны | Поставь SERVO_DIR в -1 |
| Повороты слишком короткие или слишком длинные | MS_PER_DEG не откалиброван или аккумулятор разряжен | Измерь через turnBy(360) и перенастрой с полным аккумулятором |
| В углу постоянно крутится то влево, то вправо | Обе стороны близки к порогу, и решение всё время меняется | Увеличь порог тупика (STOP_CM + 10) или сделай так, чтобы робот предпочитал последнее направление поворота |
| Головка с датчиком дёргается | Слабое питание сервопривода или прерывания SoftwareSerial | Укороти кабель сервопривода, добавь конденсатор, не отправляй команды по Bluetooth чаще, чем нужно |
Робот, объезжающий препятствия, объединяет в одном проекте чтение датчиков, фильтрацию, принятие решений и управление моторами, поэтому это лучший вход в робототехнику. Сначала посмотри на базовое поведение с первым скетчем, затем настрой пороги под свою комнату с продвинутой версией и в конце усиль робота ИК-датчиками и Bluetooth. В приложении BlueGrays ты найдёшь пошаговые проекты робота, объезжающего препятствия, машинки на L298N с управлением по Bluetooth, измерения расстояния на HC-SR04 и парктроника со схемами подключения и кодом.
Частые вопросы
Что нужно, чтобы собрать робота, объезжающего препятствия?
Для простого робота, объезжающего препятствия, нужны Arduino Uno, ультразвуковой датчик HC-SR04, сервопривод SG90, драйвер моторов L298N или L293D, набор шасси 2WD с двумя DC-моторами с редуктором и два аккумулятора 18650. Можно добавить два ИК-датчика препятствий, чтобы видеть углы, и Bluetooth-модуль HC-05, чтобы управлять роботом с телефона.
Можно ли собрать робота, объезжающего препятствия, без сервопривода?
Да. Датчик смотрит прямо вперёд; увидев препятствие, робот сначала поворачивается в одну сторону и измеряет снова, а если путь всё ещё закрыт, поворачивается в другую. Так дешевле, но робот принимает решения медленнее, потому что для каждой попытки ему приходится поворачивать весь корпус; сервопривод позволяет найти самое свободное направление ещё до поворота.
Почему HC-SR04 не видит некоторые препятствия?
Ультразвуковой датчик измеряет по эху. Мягкие поверхности вроде штор и диванов поглощают звук, стены под углом отражают его в другую сторону, а тонкие предметы вроде ножек стульев дают очень слабое эхо. Кроме того, датчик не видит препятствия за пределами своего конуса примерно в 15 градусов или ниже него; эти пробелы можно закрыть ИК-датчиками.
На каком расстоянии должен останавливаться робот, объезжающий препятствия?
Хорошее начальное значение — 25 см: путь между двумя измерениями, тормозной путь и запас на разворот на месте в сумме дают около 20 см, плюс небольшой запас прочности. Для более быстрых роботов и скользких полов подними его до 30–35 см; если препятствие ближе 12 см, робот должен сначала отъехать назад.
Что лучше для робота: L298N или шилд L293D?
L298N выдаёт до 2 A на канал, поэтому он выносливее и позволяет свободно выбирать пины. Шилд L293D вставляется прямо в Uno, поэтому проводов меньше и он может управлять четырьмя моторами, но его длительный ток — 0,6 A на канал, и он занимает много пинов. Для небольшого 2WD-робота с TT-моторами подойдёт любой из них.
Можно ли управлять роботом, объезжающим препятствия, с телефона?
Да. Добавь Bluetooth-модуль HC-05 и отправляй буквы из приложения-пульта на телефоне с Android, чтобы переключаться между автоматическим и ручным режимом. Если в ручном режиме использовать ультразвуковой датчик как защиту от столкновений, робот не врежется в стену по твоей вине. С ESP32 отдельный модуль не нужен благодаря встроенному Bluetooth.
Что собрать после прочтения
Все проекты
Измерение расстояния HC-SR04 (вывод в порт)
Датчик HC-SR04 работает совсем как летучая мышь, которая находит дорогу в темноте: он посылает звук, которого ты не слышишь, слушает эхо и измеряет, насколько далеко предмет перед ним. Arduino каждые

Робот, объезжающий препятствия
Как насчёт того, чтобы собрать умную машину, которая сама находит дорогу? Робот, объезжающий препятствия, постоянно осматривает всё вокруг благодаря ультразвуковому датчику спереди. Встретив препятств

Машинка на L298N с управлением по Bluetooth
Преврати телефон в пульт и управляй своей машинкой-роботом. Arduino получает команды из приложения-пульта на телефоне через Bluetooth-модуль и вращает колёса через драйвер моторов; машинка едет вперёд

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

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

Чтение отражения (приближения) с TCRT5000
С маленьким датчиком, который посылает невидимый свет и измеряет, сколько его отражается обратно, можно определить поверхность перед ним. Чем ближе предмет или светлая поверхность, тем больше света во

Определение чёрной и белой поверхности с TCRT5000
С маленьким датчиком, который служит глазом роботов, едущих по линии, можно узнать, чёрный пол или белый. TCRT5000 светит на пол невидимым светом и проверяет, сколько его отражается обратно; на белом

Ультразвуковой датчик расстояния HC-SR04 на ESP32
Ультразвуковой датчик HC-SR04 вычисляет расстояние по времени, за которое испущенная им звуковая волна отражается от предмета и возвращается (Time of Flight). В этом проекте ESP32 подаёт импульс 10 микросекунд на пин TRIG датчика, а длительность эха читает с пина ECHO через pulseIn(). Вычисленное расстояние в сантиметрах выводится на I2C LCD 16x2. HC-SR04 работает от 5V, поэтому на пине ECHO нужен делитель напряжения.
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

