Guide · 18 min di lettura

Robot evita ostacoli con Arduino: HC-SR04 e servo passo passo

Robot evita ostacoli con Arduino: HC-SR04 su servo, collegamento L298N o L293D, logica stop-retro-svolta, soglie di distanza, sensori IR e modalità Bluetooth.

Un robot evita ostacoli è una macchinina robot autonoma che riconosce un muro, una sedia o una scatola davanti a sé senza urtarli e si trova una nuova strada. La versione più diffusa usa un Arduino Uno, un sensore a ultrasuoni HC-SR04, un servomotore SG90 che ruota il sensore a sinistra e a destra, due motoriduttori in corrente continua e un driver motori L298N (o L293D). Il robot procede dritto, si ferma quando si avvicina a un ostacolo, arretra un po', gira la testa da entrambi i lati per misurare e poi riparte nella direzione più libera.

In questa guida trasformiamo la macchinina 2WD del nostro articolo macchinina robot con Arduino in un robot autonomo, mantenendo la stessa piedinatura. Passo dopo passo vediamo le tabelle dei collegamenti, come si calcolano le soglie di distanza, un codice che cresce in tre fasi, le superfici che il sensore non vede e come coprire questi vuoti con sensori a infrarossi e, infine, il passaggio alla modalità manuale via Bluetooth dallo smartphone. In fondo all'articolo trovi anche il codice per guidare lo stesso robot con un ESP32 o un Raspberry Pi Pico.

Sensore di distanza a ultrasuoni HC-SR04
HC-SR04: il trasduttore con la scritta T emette un suono a 40 kHz, quello con la R ascolta l'eco. È l'occhio del robot; misura da 2 a 400 cm entro un angolo stretto di circa 15 gradi.

Come funziona un robot evita ostacoli?

Ogni robot autonomo ripete lo stesso ciclo in tre passi decine di volte al secondo: percepire, decidere, agire. La percezione è affidata all'HC-SR04: emette un breve suono a 40 kHz che il tuo orecchio non sente e misura quanto impiega l'eco a tornare; dividendo questo tempo in microsecondi per 58 ottieni la distanza in centimetri. La decisione spetta ad Arduino, che confronta la distanza misurata con le soglie che hai impostato. All'azione pensano l'L298N e i motori. Dato che il sensore è montato su un servo, il robot può "guardare" a sinistra e a destra senza girare il telaio, e così sceglie le svolte molto meglio di un robot con sensore fisso. I dettagli sul funzionamento del sensore sono nell'articolo Sensore di distanza HC-SR04.

ComponenteCompitoNota
Arduino Uno (o Nano)Cervello: misura, decide e comanda i motoriVanno bene anche ESP32 o Pico
HC-SR04Occhio: misura la distanza dall'ostacolo davantiFunziona a 5 V, 2–400 cm
Servo SG90Collo: ruota il sensore a sinistra e a destraIl sensore si fissa alla squadretta del servo con un supporto in plastica
L298N o shield L293DMuscoli: danno direzione e velocità ai motoriUn pin di Arduino non può fornire la corrente del motore
2 motoriduttori TT e ruota pivotanteTrazione differenzialeSterza facendo girare le due ruote in versi opposti
2 batterie 18650 (7,4 V)EnergiaUna pila da 9 V non basta
2 sensori IR di ostacolo (opzionali)Rilevano ostacoli agli angoli e ai latiModuli con LM393 come l'FC-51
HC-05 (opzionale)Controllo manuale dallo smartphoneFunziona solo con Android
Arduino Uno
Arduino Uno
L298N
L298N
TT motor
TT motor
18650 Li-ion
18650 Li-ion

Alimentazione: evitare che il servo resetti il robot

Dato che il robot evita ostacoli aggiunge un servo ai motori, la distribuzione dell'alimentazione conta più che su una normale macchinina robot. Un SG90 assorbe circa 10 mA da fermo; appena inizia a muoversi sale a 100–250 mA e, sotto sforzo, si avvicina a mezzo ampere. Se questo picco coincide con la corrente di spunto dei motori, la linea a 5 V di Arduino crolla per un istante e la scheda si riavvia. La soluzione è semplice: i motori si alimentano dalla batteria tramite l'L298N, Arduino dalla batteria tramite VIN, mentre servo, HC-SR04, sensori IR e HC-05 condividono il pin 5V di Arduino, con un condensatore da 470 µF vicino ai fili di alimentazione del servo. Anche con il servo in movimento, il carico totale sulla linea a 5 V resta sotto il mezzo ampere e, dato che dura poco, il regolatore dell'Uno lo regge. Per calcolare l'autonomia con i tuoi valori usa lo strumento Durata della batteria.

ParteAlimentata daCorrente tipica
2 motori TTMorsetto 12V dell'L298N ← 2 × 18650 (7,4 V)0,3–0,6 A in marcia, fino a 2 A alla partenza
Arduino UnoVIN ← positivo della batteria (dopo l'interruttore)Circa 50 mA
Servo SG905V di Arduino + condensatore da 470 µF~10 mA da fermo, 100–250 mA in movimento
HC-SR045V di ArduinoCirca 15 mA
2 sensori IR5V di Arduino20–40 mA ciascuno
HC-055V di Arduino30–40 mA in accoppiamento, circa 10 mA connesso
Collega tra loro tutti i GND (negativo della batteria, L298N, Arduino, servo, sensori); senza una massa comune il sensore legge distanze assurde e il servo trema. Non alimentare il servo con lo stesso cavetto jumper sottile dei motori. Se usi un servo più potente come l'MG90S o l'MG996R, non alimentarlo da Arduino ma da un modulo step-down separato regolato a 5 V (per esempio un LM2596).

Schema dei collegamenti: Arduino Uno, ESP32 e Pico

I pin dei motori sono gli stessi della guida alla macchinina robot, quindi puoi passare a questo progetto aggiungendo solo la testa del sensore, senza smontare la macchinina. Mettere i pin di velocità su 5 e 6 è una scelta voluta: sull'Uno la libreria Servo usa il Timer1 e quindi disattiva il PWM sui pin 9 e 10; se le velocità dei motori fossero lì, il controllo di velocità smetterebbe di funzionare appena collegato il servo. HC-SR04 e sensori IR vanno su A0–A3. I pin analogici dell'Uno funzionano all'occorrenza anche come pin digitali 14–19, e questa scelta rende facile usare lo stesso codice con uno shield L293D, perché lo shield non tocca questi pin. Puoi vedere la piedinatura della tua scheda nello strumento Pinout.

CollegamentoArduino UnoESP32Raspberry Pi Pico
L298N ENA (velocità motore sinistro)D5GPIO 25GP15
L298N IN1, IN2D7, D8GPIO 27, 33GP14, GP13
L298N ENB (velocità motore destro)D6GPIO 26GP10
L298N IN3, IN4D9, D4GPIO 32, 4GP12, GP11
Segnale del servo (arancione)D3GPIO 13GP16
Servo + (rosso), − (marrone)5V, GND5V dell'L298N, GND5V dell'L298N, GND
HC-SR04 TRIGA0GPIO 18GP17
HC-SR04 ECHOA1 (diretto)GPIO 19, tramite partitore 1 kΩ / 2 kΩGP18, tramite partitore 1 kΩ / 2 kΩ
HC-SR04 VCC5V5V dell'L298N5V dell'L298N
OUT IR sinistro, OUT IR destroA2, A3GPIO 23, 22GP19, GP20
VCC dei sensori IR5V3V33V3
HC-05 TXD, RXDD10, D11 (RXD tramite partitore)Non serve (Bluetooth integrato)GP1, GP0 (UART0)
Alimentazione della schedaVIN ← batteria (dopo l'interruttore)5V (VIN) ← 5V dell'L298NVSYS ← 5V dell'L298N
Il pin ECHO dell'HC-SR04 fornisce un segnale a 5 V, mentre i pin di ESP32 e Pico lavorano a 3,3 V e non sono tolleranti ai 5 V. Realizza un partitore di tensione così: ECHO → 1 kΩ → punto collegato alla scheda → 2 kΩ → GND, cioè 5 V × 2 / 3 ≈ 3,3 V. TRIG non ha bisogno di partitore; un segnale a 3,3 V basta per attivare il sensore. Su ESP32 e Pico la scheda, il servo e l'HC-SR04 condividono la stessa uscita 5V dell'L298N, quindi puoi avvicinarti al limite di circa 0,5 A di quel piccolo regolatore; se noti dei reset, alimenta il servo da una sorgente a 5 V separata.

Montare e provare la testa del sensore

Micro servomotore SG90
SG90: un micro servo da 9 grammi che a 4,8 V ruota di 60 gradi in circa 0,1 secondi. Il supporto in plastica con l'HC-SR04 si avvita sulla squadretta del servo.
  1. Collega il servo senza squadretta e carica lo sketch di prova qui sotto. Alla fine di ogni scansione il codice porta il servo a 90 gradi e aspetta 2 secondi; stacca il cavo USB durante questa pausa e il servo resta fermo dov'è. Monta la squadretta sull'albero in modo che il sensore guardi dritto davanti; così 90 gradi significa "dritto".
  2. Fissa l'HC-SR04 alla squadretta con un supporto in plastica o con colla a caldo. I cavi del sensore devono essere abbastanza lunghi da non tendersi quando il servo ruota; un cavo che si impiglia nel telaio durante la scansione di 140 gradi è un guasto frequente.
  3. Monta il sensore a 8–12 cm da terra, vicino al punto più avanzato del telaio. Troppo in basso riceve echi dal pavimento e dalla trama del tappeto; troppo in alto non vede gli ostacoli bassi.
  4. Inclina il sensore di 3–5 gradi verso l'alto. Un sensore perfettamente orizzontale e montato in basso può scambiare un'eco del pavimento per un ostacolo, soprattutto sui pavimenti ruvidi.
  5. Apri il Monitor seriale a 9600 baud. Se mettendo la mano a sinistra del sensore vedi valori piccoli nelle righe a 125 e 160 gradi, la sinistra corrisponde agli angoli grandi; se succede il contrario, imposta SERVO_DIR a -1 nello sketch avanzato e scambia i valori di LEFT_ANGLE e RIGHT_ANGLE nel primo sketch.
Arduino (C++)
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}

Ogni riga mostra un angolo e la distanza in quella direzione. Se davanti al sensore non c'è niente entro circa 4 metri, leggerai 0 cm: pulseIn() restituisce 0 se non arriva nessuna eco entro 25 ms. Nel codice principale tratteremo lo 0 come "strada libera", ma anche un sensore con un cavo staccato o con TRIG ed ECHO invertiti di solito legge 0. Perciò, prima di mettere il robot a terra, tieni la mano a 10, 30 e 60 cm e controlla che i valori cambino davvero; un robot avviato senza questa prova va a sbattere contro il muro senza rallentare.

L'algoritmo di decisione: fermarsi, arretrare, girare verso la direzione più libera

Suddividere il comportamento del robot in zone in base alla distanza rende il codice semplice e affidabile. Senza ostacoli davanti avanza alla massima velocità, rallenta man mano che un ostacolo si avvicina e, quando raggiunge la soglia di stop, si ferma e fa una scansione. Durante la scansione il sensore guarda a 70 e 35 gradi a sinistra e a 35 e 70 gradi a destra; in ogni direzione si fanno tre misure e si usa la mediana. Il robot gira verso la direzione con la distanza maggiore, esattamente di quell'angolo. Se nessuna direzione è libera, per esempio perché è finito in un angolo, arretra un po' e gira di 180 gradi.

Distanza misurataZonaCosa fa il robot
Oltre 60 cm (o nessuna eco)LiberaAvanti alla massima velocità (PWM 200)
25–60 cmAvvicinamentoRallenta in proporzione alla distanza (da PWM 200 a 130)
12–25 cmOstacoloSi ferma, conferma la misura, scansiona entrambi i lati e gira verso la direzione più libera
Meno di 12 cmTroppo vicinoPrima arretra per 350 ms, poi scansiona
Tutte le direzioni sotto i 35 cmVicolo ciecoArretra e gira di 180 gradi

Queste soglie non sono scelte a caso. La distanza di arresto è la somma di tre margini: il margine di reazione (la strada percorsa tra due misure), il margine di frenata (quanto scivola il robot dopo che i motori si spengono) e il margine di rotazione (perché il muso non tocchi l'ostacolo mentre gira sul posto). Una macchinina con motori TT a 6 V va a circa 0,5–0,7 m/s e percorre 3–4 cm in un intervallo di misura di 60 ms. Il margine di frenata è di circa 5 cm e, per un telaio largo 15 cm che gira sul posto, il margine di rotazione è intorno ai 10 cm. In totale fanno circa 18–20 cm; aggiungendo qualche centimetro di margine di sicurezza, STOP_CM = 25 è un buon punto di partenza. Se il tuo robot è più veloce o il pavimento è scivoloso, alza la soglia a 30–35 cm. Nei corridoi stretti, invece, una soglia troppo grande fa sì che il robot si fermi e scansioni di continuo; fai la regolazione finale sul tuo percorso.

Codice passo passo 1: il tuo primo robot evita ostacoli

La prima versione è lo scheletro dell'algoritmo: avanzare, fermarsi a 25 cm, arretrare, guardare a sinistra e a destra e girare verso il lato più libero. La funzione dei motori drive(sinistro, destro) è la stessa della guida alla macchinina robot e accetta valori da -255 a 255; positivo significa avanti, negativo indietro. Dopo aver caricato il codice, appoggia prima la macchinina su una scatola con le ruote sollevate e osserva come si ferma e gira quando avvicini la mano al sensore; se va tutto bene, metti il robot a terra.

Arduino (C++)
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}

Questo codice fa il suo lavoro, ma ha qualche punto debole: una sola misura sbagliata ferma il robot senza motivo, si avvicina agli ostacoli sempre alla stessa velocità, guarda solo in due direzioni e i tempi di rotazione non dipendono dall'angolo. Inoltre, dato che aspetta con delay(), durante la scansione non può reagire a nulla. I dettagli sulla logica di attesa sono nell'articolo millis() e delay(); la versione successiva risolve la maggior parte di questi problemi.

Codice passo passo 2: filtro, rallentamento graduale, IR e Bluetooth

La versione avanzata funziona con gli stessi collegamenti, e non è un problema se non hai ancora montato i sensori IR e l'HC-05. Dato che gli ingressi IR sono impostati con INPUT_PULLUP, un sensore non collegato legge "nessun ostacolo" (HIGH); senza HC-05 il robot parte semplicemente in modalità automatica. Nella tabella qui sotto trovi cosa fa ogni parte del codice.

Arduino (C++)
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}
Funzione o impostazioneCosa fa
pingCm()Esegue una misura; restituisce 400 cm (spazio libero) se non arriva eco entro 25 ms
distanceCm()Fa 3 misure a 60 ms di distanza e restituisce la mediana; scarta un singolo valore anomalo
autoStep()Misura ogni 60 ms; in base alla distanza e agli IR avanza, rallenta, schiva o chiama avoid()
avoid()Si ferma, conferma la misura, arretra se serve, guarda in 4 direzioni e gira verso la più libera
turnBy(gradi)Fa ruotare il robot sul posto dell'angolo indicato; il tempo si calcola con MS_PER_DEG
manualStep()Applica la direzione inviata dallo smartphone; in avanti si ferma a 15 cm e si arresta se per 600 ms non arrivano comandi
SERVO_CENTER, SERVO_DIRL'angolo a cui il sensore guarda dritto e il verso corrispondente alla sinistra
STOP_CM, SLOW_CM, BACK_CMSoglie di arresto, rallentamento e retromarcia

Per un robot evita ostacoli il filtro mediano è meglio della media: con tre misure come 24, 26 e 400 cm la media risulta 150 cm e inganna il robot, mentre la mediana sceglie 26 cm. Se vuoi approfondire, leggi l'articolo filtrare i dati dei sensori. Anche la separazione tra modalità automatica e manuale è in realtà una piccola macchina a stati; spieghiamo questo approccio nel dettaglio nella guida macchina a stati con Arduino. Dopo aver caricato il codice, regola il robot sulla tua stanza in quest'ordine:

  1. Centro del servo: con il robot perpendicolare a un muro liscio, se il sensore non punta dritto verso di esso, cambia SERVO_CENTER a piccoli passi, per esempio 85 o 95.
  2. Tempo di rotazione: aggiungi temporaneamente la riga turnBy(360); alla fine di setup() e guarda se il robot compie un giro completo. Se gira di 300 gradi invece di 360, imposta MS_PER_DEG a 4,0 × 360 / 300 = 4,8. Dato che questo valore cambia con la batteria e il pavimento, regolalo con la batteria carica sul pavimento dove il robot dovrà muoversi.
  3. Soglia di stop: manda il robot verso un muro e misura a che distanza dal muro si trova il muso quando si ferma. Se è meno di 10 cm, aumenta STOP_CM di 5 cm.
  4. Velocità lenta: se SLOW è sotto la soglia di avvio dei motori, il robot resta fermo nella zona di avvicinamento; in quel caso alza SLOW a 150.

Punti ciechi dell'HC-SR04: superfici morbide, pareti inclinate e ostacoli sottili

Un sensore a ultrasuoni vede bene le superfici che riflettono il suono, ma non tutti gli ostacoli lo rimandano verso il sensore. Il produttore consiglia un bersaglio di almeno 0,5 m² e il più liscio possibile per ottenere buoni risultati. In una stanza reale, però, il robot incontra tende, divani, gambe di sedie e angoli. Il suono rimbalza con lo stesso angolo con cui arriva, come una palla da biliardo: un sensore rivolto frontalmente a un muro riceve un'eco forte, ma se la sua direzione si discosta di più di circa 30–40 gradi dalla perpendicolare, il suono viene deviato altrove e non torna mai. In quel caso pulseIn() restituisce 0 e il codice crede che la strada sia libera. È il motivo più comune per cui i robot strisciano contro i muri.

Ostacolo o superficiePerché non viene vistoSoluzione
Tende, divani, cuscini, vestiti, pelo di animaliLe superfici morbide e porose assorbono il suono e l'eco torna molto deboleAggiungi un sensore IR per la breve distanza e guida il robot piano
Avvicinarsi a un muro in diagonaleIl suono viene riflesso altrove e l'eco non raggiunge il sensoreAggiungi agli angoli anteriori sensori IR inclinati verso l'esterno e infittisci gli angoli di scansione
Gambe di sedie e tavoli, pali sottiliLa superficie riflettente è minuscola e l'eco è deboleRiduci la velocità, usa il filtro mediano, aggiungi IR ai lati
Ostacoli bassi sotto il sensore (cavi, soglie, pantofole)Restano sotto il cono del suonoAggiungi al paraurti anteriore un sensore IR rivolto in basso o un microinterruttore
Bordi del tavolo, trombe delle scaleIl sensore a ultrasuoni guarda avanti e non vede un vuoto nel pavimentoRilevamento del vuoto con un TCRT5000 rivolto al pavimento
Oggetti a meno di 2 cmZona morta del sensore: l'eco torna prima che l'emissione del suono sia finitaImposta il robot in modo che arretri a 12 cm
Angoli e corridoi strettiIl suono rimbalza tra due pareti e torna in ritardo, quindi la distanza sembra maggioreFiltro mediano e misure da più angoli
Coprire ciò che un sensore non vede con un altro sensore è sempre più efficace che cercare di rendere perfetto un sensore solo. Ma il primo passo è rallentare: un robot a 0,3 m/s non urta forte come uno a 0,7 m/s dopo la stessa misura sbagliata, e gli resta il tempo per una seconda misura. Abbiamo raccolto pregi e difetti dei diversi sensori di distanza nell'articolo confronto tra sensori di distanza per Arduino.

Rinforzo con sensori IR

I sensori a ultrasuoni e a infrarossi (IR) compensano a vicenda i propri limiti. I sensori IR di ostacolo come l'FC-51 sono formati da un LED a infrarossi e da un ricevitore; quando la luce riflessa è abbastanza forte, il comparatore LM393 porta l'uscita a LOW e il LED sulla scheda si accende. La portata si regola con un trimmer tra circa 2 e 30 cm, ma il sensore non misura la distanza: dice solo "ostacolo" o "nessun ostacolo". Se metti due sensori IR agli angoli anteriori del robot, inclinati di 30–45 gradi verso l'esterno, intercetti le gambe delle sedie e le pareti inclinate che restano fuori dal cono stretto degli ultrasuoni. Nel codice avanzato il robot schiva brevemente a destra quando scatta l'IR sinistro e a sinistra quando scatta quello destro; se scattano entrambi insieme, si ferma e scansiona come se avesse un ostacolo davanti.

CaratteristicaHC-SR04 (ultrasuoni)FC-51 (IR di ostacolo)TCRT5000 (IR a riflessione)
UscitaDistanza (cm)Sì / no (digitale)Valore analogico e uscita digitale
Portata2–400 cmCirca 2–30 cm (regolabile)Circa 1–15 mm
Angolo di rilevamentoCirca 15°Circa 35°Molto stretto
Punto deboleSuperfici morbide, inclinate e sottiliSuperfici nere e opache, luce del soleVariazioni di altezza, luce del sole
Vede beneVetro, oggetti neri, grandi superfici lisceTessuti, angoli, ostacoli viciniColore del pavimento e bordi del tavolo
Posizione sul robotOcchio principale, mosso dal servoAngoli anterioriSotto il paraurti anteriore, rivolto al pavimento
Sensore a riflessione a infrarossi TCRT5000
TCRT5000: montato rivolto in basso a 5–10 mm dal pavimento, riconosce i bordi del tavolo e le trombe delle scale; appena la riflessione scompare, il robot può fermarsi e arretrare.

Per i robot che girano su un tavolo, l'ostacolo più pericoloso è in realtà il vuoto. Se monti sotto il paraurti anteriore un modulo TCRT5000 rivolto verso il pavimento, saprai subito quando il pavimento sparisce. Collega l'uscita digitale del modulo a D2, ancora libero sull'Uno; controlla prima nel Monitor seriale quale livello assume l'uscita quando manca il pavimento, poi leggi questo pin all'inizio di autoStep() e, se il pavimento è sparito, ferma subito i motori e fai arretrare il robot. Questo controllo deve venire prima della misura di distanza; cadere costa sempre più che urtare.

Passare alla modalità manuale via Bluetooth

A volte vorrai guidare a mano il robot autonomo: per portarlo da una stanza all'altra, metterlo davanti a un ostacolo e provarne la reazione o semplicemente per giocare. Il collegamento con l'HC-05 del codice avanzato lo rende possibile. Quando lo smartphone invia la lettera A, il robot passa alla modalità automatica; quando invia M, passa a quella manuale. In modalità manuale si usano le lettere di comando della guida alla macchinina robot (F, B, L, R, S), con una differenza: se il robot vede un ostacolo a meno di 15 cm mentre va avanti, si ferma nonostante il tuo comando. Questa protezione anticollisione ti permette di sfruttare i sensori del robot autonomo anche nella guida manuale. I passaggi per accoppiare l'HC-05 e i comandi AT sono nell'articolo Modulo Bluetooth HC-05.

LetteraAzioneIn quale modalità?
APassa alla modalità automaticaIn entrambe
MPassa alla modalità manuale, ferma il robotIn entrambe
F / BAvanti / indietroManuale
L / RRuota sul posto a sinistra / destraManuale
SStopManuale
0–9Livello di velocità (PWM 100–240)Manuale
  1. Collega il pin TXD dell'HC-05 a D10, il pin RXD a D11 tramite un partitore 1 kΩ / 2 kΩ, VCC al 5V e GND al GND.
  2. Accoppia l'HC-05 nelle impostazioni Bluetooth del tuo smartphone Android con il PIN 1234 (o 0000 se non funziona).
  3. Installa dal Play Store un'app di telecomando per auto Bluetooth e collegala all'HC-05. Se tieni Arduino collegato via USB e apri il Monitor seriale a 9600 baud, il codice mostra ogni lettera che arriva dallo smartphone.
  4. La maggior parte delle app di telecomando ha tasti extra, come luci o clacson, e ognuno invia una propria lettera. Leggi queste lettere nel Monitor seriale e mettile nel codice al posto di 'A' e 'M'; se preferisci definire tu i tasti, puoi crearti un telecomando con la guida App Inventor con Arduino via Bluetooth.
  5. Stacca il cavo USB e accendi l'interruttore; dopo 3 secondi il robot parte in modalità automatica. Passa alla modalità manuale e guidalo dallo smartphone.
Sull'Uno, SoftwareSerial tiene in attesa gli altri interrupt per circa 1 ms mentre riceve un byte; in quel momento gli impulsi della libreria Servo possono allungarsi e la testa del sensore può dare piccoli scatti. Non altera il comportamento del robot, ma se ti dà fastidio puoi passare al Bluetooth integrato dell'ESP32. HC-05 e HC-06 non funzionano con l'iPhone; per l'iPhone serve un modulo BLE o un ESP32 programmato in BLE.

Montaggio con uno shield motori L293D

Shield driver motori L293D
Shield motori L293D: si innesta direttamente su un Arduino Uno; quattro motori in corrente continua vanno ai morsetti M1–M4 e due servo ai connettori nell'angolo.

Se non ti piace il groviglio di cavi, al posto dell'L298N puoi usare uno shield motori L293D (l'Adafruit Motor Shield V1 e i suoi cloni) che si innesta direttamente sopra l'Arduino Uno. Lo shield può pilotare quattro motori in corrente continua, ha due connettori per servo e alimenta i servo dalla linea a 5 V di Arduino. I motori non sono collegati direttamente ai pin di Arduino ma passano per il registro a scorrimento 74HC595 della scheda, quindi si pilotano con la Adafruit Motor Shield library (quella per la V1, non la V2), cioè AFMotor, che installi dal Gestore librerie. Lo shield usa D3, D4, D5, D6, D7, D8, D11 e D12 per i motori e D9 e D10 per i servo; A0–A5, D2 e D13 restano liberi. Dato che HC-SR04 e sensori IR sono già su A0–A3, devi cambiare solo il codice dei motori, il pin del servo e i pin dell'HC-05.

ImpostazioneCon l'L298NCon lo shield L293D
Collegamento dei motoriMorsetti OUT1–OUT4Morsetti M1 (sinistro) e M2 (destro)
Codice dei motoridigitalWrite e analogWrite dentro drive()AFMotor dentro drive()
SERVO_PIN39 o 10, a seconda del connettore in cui inserisci il servo
HC-05D10, D11A4 (RX) e A5 (TX): SoftwareSerial bt(A4, A5)
Corrente per canale2 A continui0,6 A continui, 1,2 A di picco
Alimentazione dei motoriMorsetto 12VMorsetto EXT_PWR; con il jumper PWR inserito anche Arduino si alimenta dalla stessa sorgente
Arduino (C++)
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}

Il limite di 0,6 A per canale dell'L293D basta per la marcia normale di un robot 2WD con motori TT, ma quando una ruota spinge contro un ostacolo e si blocca, il motore può assorbire più di 1 A e il chip si scalda in fretta. Per questo un algoritmo di scansione che ferma il robot prima che spinga contro un ostacolo allunga anche la vita dello shield. Le impostazioni dei jumper dello shield e uno sketch per una macchinina a quattro motori sono nell'articolo Shield motori L293D, mentre il funzionamento interno dell'L298N è nella guida Controllo di motori DC con l'L298N.

Robot evita ostacoli con ESP32

Sull'ESP32 ci sono due differenze importanti. La prima è che la libreria Servo di Arduino non funziona sull'ESP32, quindi generiamo il segnale del servo direttamente con LEDC, l'unità PWM della scheda: in un segnale a 50 Hz un impulso di 0,544–2,4 ms corrisponde a 0–180 gradi. Dato che i motori lavorano a 1 kHz e il servo a 50 Hz, si configurano prima i canali dei motori e poi quello del servo; così il servo usa un proprio timer. La seconda è che l'ESP32 ha il Bluetooth classico integrato, quindi l'HC-05 non serve; il robot compare sullo smartphone come "BlueGrays-Robot" e le lettere di direzione e di modalità sono le stesse della versione per Uno. Il codice è per la versione 3.x del core Arduino per ESP32 (ledcAttach e ledcWrite lavorano con i numeri dei pin) e, poiché richiede il Bluetooth classico, non si compila per ESP32-S3 ed ESP32-C3.

Arduino (C++)
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}

Robot evita ostacoli con Raspberry Pi Pico (MicroPython)

Sul Raspberry Pi Pico la funzione time_pulse_us() di MicroPython funziona come pulseIn() su Arduino; se non arriva nessuna eco restituisce un valore negativo, che trattiamo come 400 cm, cioè spazio libero. Per il servo si usano il PWM a 50 Hz e duty_ns(), che riceve la larghezza dell'impulso in nanosecondi. Il servo su GP16 e i pin di velocità dei motori su GP15 e GP10 si trovano su slice PWM diversi, quindi non si disturbano la frequenza a vicenda. Questa volta le velocità sono espresse in percentuale. Il Pico non ha Bluetooth; se vuoi la modalità manuale, collega un HC-05 alla UART0 (GP0, GP1) come nella guida alla macchinina robot.

MicroPython
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

Idee di progetto con un robot evita ostacoli

  • Schermo radar: invia sulla porta seriale gli angoli e le distanze della scansione e disegna uno schermo radar verde sul computer; i dettagli sono nell'articolo Progetto radar con Arduino.
  • Linea e ostacolo: fai in modo che il robot, mentre segue un percorso come un robot segui linea, aggiri una scatola che trova sulla strada e torni sulla linea; è una prova comune nelle gare.
  • Robot che segue i muri: tieni il sensore girato di 60 gradi a destra e procedi a 20 cm costanti dal muro; è il primo passo per risolvere i labirinti.
  • Robot con display: mostra la distanza e la modalità su un piccolo display OLED; il debug diventa molto più semplice.
  • Prototipo di robot aspirapolvere: progetta un robot per le pulizie che gira per casa con scelta casuale della direzione, interruttori d'urto e sensore di vuoto.
  • Bastone intelligente: trasforma la stessa logica di misura della distanza in un avviso a vibrazione per persone ipovedenti; vedi Bastone intelligente con Arduino.
  • Progetto scolastico o da gara: presentalo con un percorso a ostacoli, il cronometro e una dimostrazione delle modalità automatica e manuale; per altre idee dai un'occhiata all'articolo idee di progetti Arduino per concorsi scientifici.

Errori comuni e soluzioni

ProblemaCausa probabileSoluzione
Il robot va a sbattere contro il muro senza fermarsiIl sensore legge 0 (cavo interrotto, TRIG ed ECHO invertiti) e il codice lo considera spazio liberoEsegui lo sketch di prova, prova con la mano tra 10 e 60 cm, controlla i pin TRIG ed ECHO
Striscia contro il muro quando ci arriva in diagonaleIl suono viene riflesso altrove dalla superficie inclinataAggiungi sensori IR agli angoli anteriori e riduci la velocità
Arduino si riavvia quando si ferma a scansionareLa corrente del servo e dei motori fa crollare la linea a 5 VCondensatore da 470 µF sul servo, batterie 18650 cariche; se serve, alimenta il servo da una sorgente a 5 V separata
Il robot si ferma e scansiona senza motivoUna singola misura sbagliata o un'eco dal pavimentoPorta il sensore a 8–12 cm e inclinalo leggermente verso l'alto; usa la conferma con la mediana
Il robot gira verso il lato bloccatoServo montato al contrario, sinistra e destra scambiateImposta SERVO_DIR a -1
Le rotazioni sono troppo corte o troppo lungheMS_PER_DEG non è calibrato o la batteria è scaricaMisura con turnBy(360) e regola di nuovo con la batteria carica
In un angolo continua a girare a destra e a sinistraEntrambi i lati sono vicini alla soglia e la decisione cambia di continuoAumenta la soglia di vicolo cieco (STOP_CM + 10) o fagli preferire l'ultimo verso di rotazione
La testa del sensore dà scattiAlimentazione del servo debole o interrupt di SoftwareSerialAccorcia il cavo del servo, aggiungi un condensatore, non inviare comandi Bluetooth più spesso del necessario
Il modo più rapido per trovare l'origine di un problema è lasciare il robot collegato via USB, farlo funzionare con le ruote sollevate e stampare i valori di pingCm() nel Monitor seriale. Se i valori sono sensati, il problema è meccanico o di alimentazione. Per altre cause per cui un Arduino si riavvia di continuo, leggi l'articolo Arduino si resetta da solo.

Dato che riunisce in un solo progetto lettura dei sensori, filtraggio, decisioni e controllo dei motori, il robot evita ostacoli è il modo migliore per entrare nella robotica. Osserva prima il comportamento di base con il primo sketch, poi regola le soglie sulla tua stanza con la versione avanzata e infine potenzia il tuo robot con sensori IR e Bluetooth. Nell'app BlueGrays trovi passo passo, con schemi elettrici e codice, i progetti del robot evita ostacoli, della macchinina comandata via Bluetooth con L298N, della misura di distanza con l'HC-SR04 e del sensore di parcheggio.

Domande frequenti

Cosa serve per costruire un robot evita ostacoli?

Per un robot evita ostacoli di base bastano un Arduino Uno, un sensore a ultrasuoni HC-SR04, un servo SG90, un driver motori L298N o L293D, un kit telaio 2WD con due motoriduttori e due batterie 18650. Puoi aggiungere due sensori IR di ostacolo per vedere gli angoli e un modulo Bluetooth HC-05 per guidarlo dallo smartphone.

Si può fare un robot evita ostacoli senza servo?

Sì. Il sensore guarda fisso in avanti; quando il robot vede un ostacolo, prima gira da una parte e misura di nuovo, e se la strada è ancora bloccata gira dall'altra. Costa meno, ma il robot decide più lentamente perché a ogni tentativo deve girare tutto il telaio; con un servo trova la direzione più libera prima di girare.

Perché l'HC-SR04 non rileva alcuni ostacoli?

Un sensore a ultrasuoni misura tramite l'eco. Le superfici morbide come tende e divani assorbono il suono, le pareti inclinate lo deviano altrove e gli oggetti sottili come le gambe delle sedie danno un'eco molto debole. Il sensore non vede nemmeno gli ostacoli che restano fuori o sotto il suo cono di circa 15 gradi; puoi coprire questi vuoti con sensori IR.

A che distanza deve fermarsi un robot evita ostacoli?

25 cm sono un buon valore di partenza: la strada percorsa tra due misure, lo spazio di frenata e il margine per girare sul posto sommati fanno circa 20 cm, più un piccolo margine di sicurezza. Per robot più veloci e pavimenti scivolosi sali a 30–35 cm; con un ostacolo a meno di 12 cm conviene prima arretrare.

Per un robot è meglio l'L298N o uno shield L293D?

L'L298N fornisce 2 A per canale, quindi è più robusto e ti lascia scegliere i pin liberamente. Lo shield L293D si innesta sull'Uno, riduce i cablaggi e può pilotare quattro motori, ma la sua corrente continua è di 0,6 A per canale e occupa molti pin. Per un piccolo robot 2WD con motori TT vanno bene entrambi.

Si può comandare un robot evita ostacoli dallo smartphone?

Sì. Aggiungi un modulo Bluetooth HC-05 e invia lettere da un'app di telecomando su uno smartphone Android per passare dalla modalità automatica a quella manuale. Usare il sensore a ultrasuoni come protezione anticollisione in modalità manuale ti impedisce di mandare il robot contro il muro. Con un ESP32 non serve un modulo separato grazie al Bluetooth integrato.

Vedi tutti i progetti
HC-SR04 Misura della distanza (display seriale)
#27Principiante

HC-SR04 Misura della distanza (display seriale)

Il sensore HC-SR04 funziona come un pipistrello che trova la strada al buio: manda un suono che non puoi sentire, ne ascolta l'eco e misura quanto è lontano l'oggetto davanti a sé. L'Arduino scrive qu

4.6(45)
68
Robot che evita gli ostacoli
#81Avanzato
Premium

Robot che evita gli ostacoli

Che ne dite di costruire un veicolo intelligente che trova la propria strada? L'Obstacle Avoiding Robot scruta costantemente l'ambiente circostante grazie al sensore a ultrasuoni posto davanti a lui.

5.0(38)
49
Auto controllata via Bluetooth con L298N
#80Avanzato
Premium

Auto controllata via Bluetooth con L298N

Trasforma il telefono in un telecomando e guida la tua auto robot. L'Arduino riceve tramite un modulo Bluetooth i comandi di un'app di controllo remoto sul telefono e fa girare le ruote con un driver

5.0(38)
65
Utilizzo del servomotore
#35Principiante
Premium

Utilizzo del servomotore

Il braccio di un piccolo servomotore ruota a turno in tre posizioni: tutto all'inizio, esattamente a metà e tutto alla fine, aspettando due secondi a ogni fermata. L'Arduino dice al motore a quale ang

4.7(48)
67
Sensore di parcheggio con HC-SR04
#28Intermedio
Premium

Sensore di parcheggio con HC-SR04

Una versione in miniatura del sensore di parcheggio delle auto: più qualcosa si avvicina, più i bip accelerano. Il sensore di distanza misura l'oggetto davanti a sé; se è a più di 20 centimetri resta

4.8(64)
84
Lettura della riflessione (prossimità) con TCRT5000
#77Intermedio
Premium

Lettura della riflessione (prossimità) con TCRT5000

Con un piccolo sensore che manda luce invisibile e misura quanta ne torna indietro, rilevi la superficie che ha davanti. Più un oggetto o una superficie chiara si avvicina, più luce ritorna, e l'Ardui

4.6(33)
43
TCRT5000 Rilevamento di superfici bianche e nere
#78Intermedio
Premium

TCRT5000 Rilevamento di superfici bianche e nere

Con il piccolo sensore che fa da occhio ai robot seguilinea capisci se il pavimento è nero o bianco. Il TCRT5000 manda una luce invisibile verso il pavimento e controlla quanta ne torna indietro; sul

4.7(40)
49
ESP32 HC-SR04 Misuratore di distanza a ultrasuoni
#116Intermedio

ESP32 HC-SR04 Misuratore di distanza a ultrasuoni

Il sensore a ultrasuoni HC-SR04 calcola la distanza misurando il tempo di volo (Time of Flight) dell'onda sonora che emette dopo aver colpito un oggetto e essere rimbalzata indietro. In questo progetto invieremo un impulso di 10 microsecondi dall'ESP32 al pin TRIG del sensore e leggeremo il tempo di eco proveniente dal pin ECHO con `pulseIn()`. Visualizziamo la distanza calcolata in cm su un display LCD I2C 16x2. Poiché l'HC-SR04 funziona a 5V, è necessario un **divisore di tensione** sul pin EC

4.9(28)
44