Autonomiczny robot jeżdżący z lokalnym AI — bez chmury, bez internetu — zbudowany na układzie ESP8266. To prostszy i tańszy wariant projektu, idealny jako pierwszy krok w świat TinyML na mikrokontrolerach. Przy odpowiednio lekkim modelu Edge Impulse ESP8266 (NodeMCU lub D1 Mini) obsłuży dwa czujniki HC-SR04 i w czasie rzeczywistym będzie podejmował decyzje ruchowe: jedź prosto, skręć, cofnij.
Co budujemy?
Robot 2WD z dwoma ultradźwiękowymi czujnikami HC-SR04: jednym skierowanym w przód i jednym na prawy bok. Model Machine Learning nauczy się rozpoznawać cztery sytuacje drogowe. Całe sterowanie odbywa się lokalnie na chipie — zero zależności od zewnętrznych usług.
Lista materiałów
- ESP8266 NodeMCU v3 lub Wemos D1 Mini — ~12 zł
- Podwozie 2WD z silnikami DC, przekładnia ≥ 1:48 — ~25 zł
- Sterownik L298N (moduł z radiatorem) — ~10 zł
- HC-SR04 × 2 — ~6 zł/szt.
- Zasilanie: powerbank 5V/2A lub 4× AA w holderze — ~15 zł
- Przewody dupont żeńsko-męskie, płytka stykowa — ~8 zł
Łączny koszt: ok. 65–90 zł. Wszystkie części dostępne na Allegro lub AliExpress.
HC-SR04 na GPIO — bez I²C, nawet kilka naraz
Standardowy czujnik HC-SR04 komunikuje się przez dwie zwykłe linie cyfrowe: TRIG (wejście czujnika — wyzwolenie pomiaru) i ECHO (wyjście czujnika — czas echa). To nie I²C ani SPI — podłączasz go bezpośrednio do dowolnych pinów GPIO, dokładnie tak jak przycisk. Na jednym ESP8266 możesz podłączyć kilka HC-SR04 jednocześnie — każdy do osobnej pary pinów.
Schemat połączeń
Poniższe przypisanie sprawdzono na NodeMCU v3. Na D1 Mini numery GPIO są identyczne, tylko etykiety na płytce się różnią.
HC-SR04 #1 — PRZÓD
VCC → 5V (z szyny zasilającej)
GND → GND
TRIG → D1 / GPIO 5 ← zalecana stabilna para
ECHO → D2 / GPIO 4 ←
HC-SR04 #2 — PRAWY BOK
VCC → 5V
GND → GND
TRIG → D6 / GPIO 12
ECHO → D7 / GPIO 13
L298N (ENA i ENB na jumperze = pełna prędkość, brak PWM)
IN1 → D5 / GPIO 14 (lewy silnik — do przodu)
IN2 → D0 / GPIO 16 (lewy silnik — wstecz)
IN3 → D3 / GPIO 0 (prawy silnik — do przodu)
IN4 → D8 / GPIO 15 (prawy silnik — wstecz)
GND → GND wspólny z ESP8266
12V → zasilanie silników (6–12V)
Zbieranie danych treningowych
Zaloguj się na edgeimpulse.com (konto free wystarczy) i utwórz projekt. Zainstaluj CLI: npm install -g edge-impulse-data-forwarder.
Wgraj poniższy szkic — wysyła odczyty obu czujników przez Serial co 100 ms:
// Zbieranie danych treningowych — ESP8266
const int TRIG1 = 5, ECHO1 = 4; // D1, D2 (przód)
const int TRIG2 = 12, ECHO2 = 13; // D6, D7 (prawy bok)
long zmierz(int trig, int echo) {
digitalWrite(trig, LOW); delayMicroseconds(2);
digitalWrite(trig, HIGH); delayMicroseconds(10);
digitalWrite(trig, LOW);
long t = pulseIn(echo, HIGH, 25000);
return t == 0 ? 200 : t / 58;
}
void setup() {
Serial.begin(115200);
pinMode(TRIG1, OUTPUT); pinMode(ECHO1, INPUT);
pinMode(TRIG2, OUTPUT); pinMode(ECHO2, INPUT);
}
void loop() {
Serial.print(zmierz(TRIG1, ECHO1));
Serial.print(",");
Serial.println(zmierz(TRIG2, ECHO2));
delay(100);
}
Uruchom edge-impulse-data-forwarder --frequency 10 i zbierz po 60–100 próbek na klasę:
- wolna_droga — oba czujniki > 35 cm
- przeszkoda_przod — przód < 20 cm
- przeszkoda_prawo — prawy bok < 15 cm
- przeszkoda_lewo — trzymaj rękę z lewej strony robota (model wnioskuje z braku echa po prawej przy zbliżeniu z lewej)
Konfiguracja impulsu w Edge Impulse Studio
- Create impulse: Window size 1000 ms, Window increase 100 ms, 2 kanały wejściowe.
- Blok przetwarzania: Flatten — generuje mean, min, max i RMS dla każdego kanału.
- Blok uczący: Classification (Keras) — 50 epok, LR 0.0005, warstwa ukryta 32 neurony.
- Wygeneruj cechy, sprawdź UMAP — klasy powinny być wyraźnie odseparowane.
- Przed eksportem sprawdź zużycie RAM: dla ESP8266 musi być poniżej 20 kB. Jeśli więcej — zmniejsz warstwę ukrytą do 16 neuronów.
Eksport biblioteki do Arduino IDE
Zakładka Deployment → Arduino library, EON Compiler: ON (mniejszy Flash) → Build. Pobierz ZIP i dodaj przez Szkic → Dołącz bibliotekę → Dodaj bibliotekę .ZIP. W Menedżerze płytek zainstaluj esp8266 by ESP8266 Community (URL: https://arduino.esp8266.com/stable/package_esp8266com_index.json).
Pełny kod robota
#include <Arduino.h>
#include <moj_robot_inferencing.h> // Twoja biblioteka z Edge Impulse
// HC-SR04
const int TRIG1 = 5, ECHO1 = 4; // D1, D2 — przód
const int TRIG2 = 12, ECHO2 = 13; // D6, D7 — prawy bok
// L298N — kierunek (ENA/ENB jumper ON)
const int IN1 = 14, IN2 = 16; // lewy silnik (D5, D0)
const int IN3 = 0, IN4 = 15; // prawy silnik (D3, D8)
long zmierz(int trig, int echo) {
digitalWrite(trig, LOW); delayMicroseconds(2);
digitalWrite(trig, HIGH); delayMicroseconds(10);
digitalWrite(trig, LOW);
long t = pulseIn(echo, HIGH, 25000);
return t == 0 ? 200 : t / 58;
}
void jedz() { digitalWrite(IN1,HIGH);digitalWrite(IN2,LOW);
digitalWrite(IN3,HIGH);digitalWrite(IN4,LOW); }
void cofnij() { digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH);
digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH); }
void skrecL() { digitalWrite(IN1,LOW); digitalWrite(IN2,LOW);
digitalWrite(IN3,HIGH);digitalWrite(IN4,LOW); }
void skrecP() { digitalWrite(IN1,HIGH);digitalWrite(IN2,LOW);
digitalWrite(IN3,LOW); digitalWrite(IN4,LOW); }
void stoj() { digitalWrite(IN1,LOW); digitalWrite(IN2,LOW);
digitalWrite(IN3,LOW); digitalWrite(IN4,LOW); }
float bufor[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];
int idx = 0;
void setup() {
Serial.begin(115200);
pinMode(TRIG1,OUTPUT); pinMode(ECHO1,INPUT);
pinMode(TRIG2,OUTPUT); pinMode(ECHO2,INPUT);
for (int p : {IN1,IN2,IN3,IN4}) pinMode(p,OUTPUT);
stoj();
delay(2000); // czas na ustawienie robota
}
void loop() {
if (idx + 2 <= EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
bufor[idx++] = (float)zmierz(TRIG1, ECHO1);
bufor[idx++] = (float)zmierz(TRIG2, ECHO2);
}
if (idx >= EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
idx = 0;
signal_t sig;
numpy::signal_from_buffer(bufor, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &sig);
ei_impulse_result_t wynik;
if (run_classifier(&sig, &wynik, false) != EI_IMPULSE_OK) return;
int best = 0;
for (int i = 1; i < EI_CLASSIFIER_LABEL_COUNT; i++)
if (wynik.classification[i].value > wynik.classification[best].value) best = i;
String k = wynik.classification[best].label;
Serial.printf("[AI] %s (%.0f%%)\n", k.c_str(), wynik.classification[best].value * 100);
if (k == "wolna_droga") jedz();
else if (k == "przeszkoda_przod") { cofnij(); delay(350); skrecP(); delay(280); }
else if (k == "przeszkoda_prawo") skrecL();
else if (k == "przeszkoda_lewo") skrecP();
else stoj();
}
delay(100);
}
Typowe problemy i rozwiązania
ESP8266 nie startuje — pętla resetów
Coś aktywnie trzyma GPIO 0 (D3) w stanie LOW podczas bootu. Odłącz kabel od D3 i sprawdź, czy problem znika. Jeśli tak — dodaj rezystor 10 kΩ podciągający do 3.3V (pull-up) na linii D3 między ESP a L298N IN3.
Model zawsze zwraca tę samą klasę
Klasy treningowe są niezbalansowane. Wejdź do Edge Impulse → Data acquisition i porównaj liczebność klas. Uzupełnij te z mniejszą liczbą próbek.
Robot skręca nie w tę stronę
Zamień przewody jednego silnika lub zamień pary IN1↔IN3 i IN2↔IN4 w kodzie. Kierunek obrotu zależy od orientacji silnika na podwoziu — nie ma jednego standardu.
HC-SR04 zwraca 200 cm (timeout)
Czujnik wymaga zasilania 5V na VCC — nie 3.3V. Na NodeMCU pin VIN daje 5V z USB. Upewnij się, że GND ESP8266 i GND czujnika są ze sobą połączone.