🔗 Masz ESP32? Sprawdź rozbudowaną wersję z trzema czujnikami HC-SR04 i regulacją prędkości PWM: Robocik-autko z AI na ESP32 →

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.

💡 Które piny wybrać? Szczególnie zalecane są GPIO 4 (D2) i GPIO 5 (D1) — nie mają żadnych ograniczeń bootstrapujących, więc są stabilne niezależnie od stanu czujnika podczas bootowania układu. Używaj ich priorytetowo dla linii ECHO i TRIG pierwszego czujnika.

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)
ℹ️ GPIO 0 i GPIO 15 przy starcie: GPIO 0 (D3) musi być HIGH podczas bootu — wewnętrzny pull-up ESP8266 to zapewnia, ponieważ wejście L298N IN ma wysoką impedancję i nie wymusza żadnego poziomu. GPIO 15 (D8) musi być LOW — wewnętrzny pull-down robi to samo. Po uruchomieniu oba działają normalnie jako wyjścia cyfrowe.

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

  1. Create impulse: Window size 1000 ms, Window increase 100 ms, 2 kanały wejściowe.
  2. Blok przetwarzania: Flatten — generuje mean, min, max i RMS dla każdego kanału.
  3. Blok uczący: Classification (Keras) — 50 epok, LR 0.0005, warstwa ukryta 32 neurony.
  4. Wygeneruj cechy, sprawdź UMAP — klasy powinny być wyraźnie odseparowane.
  5. 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.

🔗 Następny krok: Chcesz trzy czujniki, regulację prędkości PWM i bogatszy model z 6 klasami? Sprawdź rozbudowaną wersję projektu na ESP32 →