Firmware Test 1 pas-cu-pas — de la blink la semafor complet
5 micro-pași A→E. Pornești cu LED-ul intern care clipește, ajungi la semafor complet cu OLED și buton. Fiecare pas e ~10 linii și se testează separat — nu compilezi 80 de linii odată și speri să meargă.
Ce înveți
- Cum instalezi librării noi din Library Manager (Adafruit GFX, SSD1306, NeoPixel)
- Cum verifici că flashing-ul merge înainte să adaugi orice cod nou
- Cum spargi un program mare în pași mici pe care-i testezi pe rând
- Cum debug-uiești când un pas nu merge — fără să dai vina pe placa fizică
Înainte de această lecție: ai parcurs deja 06-arduino-ide (Arduino IDE instalat + suport ESP32 + driverul CP2102 + blink integrat ai văzut) și 06b-citesti-cod (cum citești cod fără să te pierzi). Dacă LED-ul intern al ESP32 nu clipește încă, întoarce-te la lecția 06.
De ce facem 5 micro-pași?
Pentru că un program de 80 de linii care nu merge te lasă fără idee unde-i bug-ul. Cu 5 pași mici, fiecare verificat separat, știi exact unde se strică:
- Pasul A merge → IDE-ul + placa + cablul USB sunt ok
- Pasul B merge → librăria NeoPixel + cablajul LED-urilor sunt ok
- Pasul C merge → ciclul de stări (state machine) e ok
- Pasul D merge → librăria OLED + cablajul I2C sunt ok
- Pasul E merge → butonul + INPUT_PULLUP sunt ok
La final ai exact codul de la Test 1 din plan VIA, dar înțeles, nu copiat orb.
Pregătire — instalează librăriile
Avem nevoie de 3 librării noi (LED-urile WS2812 și OLED-ul nu vin built-in în ESP32 SDK).
- Deschizi Arduino IDE
- Click pe icoana de cărți din bara stângă (sau Sketch → Include Library → Manage Libraries)
- În căsuța de căutare, caută pe rând:
| Caută | Cine o face | Apeși |
|---|---|---|
Adafruit NeoPixel | Adafruit | Install |
Adafruit GFX Library | Adafruit | Install |
Adafruit SSD1306 | Adafruit | Install (zice „va instala și dependențe” — accepți toate) |
După instalare, librăriile sunt salvate local. Nu mai trebuie să faci asta a doua oară.
Conectare la PC
- Cablu USB-C între placa ESP32 și PC. Cablul trebuie să fie de date, nu doar de încărcare — uite-te pe el să nu scrie „CHARGE ONLY” sau „POWER”.
- În Arduino IDE: Tools → Board → esp32 → ESP32 Dev Module
- Tools → Port → alegi portul nou (Windows:
COM3-COM10; Linux:/dev/ttyUSB0).- Nu apare niciun port? Scoate cablul, vezi care port dispare. Dacă tot niciunul → driverul CP2102 nu-i instalat. Întoarce-te la lecția 06.
- Pe placă: lasă butonul EN și BOOT în paragră, nu apăsa nimic încă.
Cum se flashează (același pentru toate cele 5 micro-pași)
Toți pașii A→E urmează ACEEAȘI rutină de upload:
- Lipești codul în Arduino IDE (sau îl scrii)
- Apeși icoana cu bifă verde (sus stânga) → Verify. Așteaptă „Done compiling”.
- Apeși icoana cu săgeată → (lângă bifă) → Upload. Așteaptă „Done uploading”.
- Pe placă, LED-ul roșu mic clipește scurt cât se transferă codul (~5 secunde).
- După „Done uploading”, apeși butonul EN (reset) o dată ca să pornească noul cod curat.
Dacă vezi eroare „failed uploading” sau „connecting…”:
- Apeși și ții apăsat butonul BOOT pe placă în timp ce dai Upload
- După ce începe transferul (vezi procentaj crește), eliberezi BOOT
- ESP32 are uneori nevoie să-l forțezi în mod „flashing”
Pas A — Blink integrat (5 linii)
Ce face: LED-ul mic albastru de pe placa ESP32 (lângă cipul wifi) clipește o dată pe secundă. Niciun fir extern. Doar placa + cablul USB.
void setup() {
pinMode(2, OUTPUT); // pin 2 = LED intern pe majoritatea ESP32 DevKit
}
void loop() {
digitalWrite(2, HIGH); // pune 3.3V pe pin 2 → LED aprins
delay(500); // așteaptă 500ms
digitalWrite(2, LOW); // pune 0V pe pin 2 → LED stins
delay(500); // așteaptă 500ms → ciclu 1Hz
}
Cum flashezi: copiezi codul, Verify, Upload, apeși EN.
Ce verifici: LED-ul albastru mic de pe placă clipește, jumate de secundă aprins, jumate stins.
Dacă nu merge:
- LED-ul nu clipește deloc → poate pinul tău nu-i 2. Încearcă cu
digitalWrite(5, ...)saudigitalWrite(13, ...)— variază între plăci. - Upload eșuează → vezi secțiunea „Dacă vezi failed uploading” de mai sus.
Când merge, treci la Pas B. Ai dovedit că IDE + cablu + placă merg.
Pas B — Un singur WS2812 verde (10 linii)
Ce face: primul LED din lanțul tău (cel din stânga, marcat [0] în schematic) se aprinde verde solid. Nimic mai mult.
Înainte: trebuie să ai LED-ul [0] cablat conform schiței Test 1 PAS 2 (VCC pe rail 5V, GND pe rail GND, DIN conectat la GPIO 5).
#include <Adafruit_NeoPixel.h> // librăria pentru WS2812 (instalată la „Pregătire")
const int PIN_LED = 5; // GPIO 5 → DIN al LED-ului [0]
const int NUM_LEDS = 1; // pentru pasul B testăm doar 1 LED
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
void setup() {
leduri.begin(); // inițializează lanțul
leduri.setPixelColor(0, leduri.Color(0, 80, 0)); // LED [0] verde (R=0, G=80, B=0)
leduri.show(); // trimite culoarea spre LED-uri
}
void loop() {
// nimic în loop — LED-ul rămâne verde constant
}
Cum flashezi: Upload, EN.
Ce verifici: LED-ul [0] (primul din lanț) e verde solid. Restul de 4 LED-uri sunt stinse.
Dacă nu merge:
| Simptom | Cauza | Fix |
|---|---|---|
| Eroare „Adafruit_NeoPixel.h: No such file” | Librăria nu-i instalată | Library Manager → caută NeoPixel → Install |
| Toate 5 LED-urile se aprind random colorate | NUM_LEDS = 1 dar codul a tot scris pe toate la run-uri anterioare | Apeși EN și flashezi din nou |
| LED [0] aprins dar culoarea-i albastru sau roșu, nu verde | Ordinea de culori e GRB vs RGB diferită pe modulul tău | Schimbă NEO_GRB în NEO_RGB și reflashezi |
| Nimic nu se aprinde | Cablajul: VCC sau GND nu-s pe rail-uri, sau DIN nu-i pe GPIO 5 | Verifică wire-urile cu schița PAS 2 |
Când merge, treci la Pas C. Ai dovedit că librăria NeoPixel + cablajul fizic LED-uri sunt ok.
Pas C — Cele 5 LED-uri în ciclu semafor REAL (25 linii)
Ce face: semafor real cu 3 stări în care TOATE LED-urile fac sens. Mașinile au verde → galben → roșu (3 culori). Pietonul are roșu → verde (2 culori). Aprinse simultan, ca un semafor adevărat.
Layout-ul fizic (cum ai lipit LED-urile pe breadboard):
LED[0] = roșu pieton LED[1] = verde pieton LED[2] = verde mașini LED[3] = galben LED[4] = roșu mașini
#include <Adafruit_NeoPixel.h>
const int PIN_LED = 5;
const int NUM_LEDS = 5;
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
// 3 stări de semafor, durata fiecăreia în milisecunde
const int VERDE_MASINI = 0, GALBEN = 1, ROSU_MASINI = 2;
const unsigned long DURATA[] = { 5000, 1500, 4000 };
// ^verde ^galb ^roșu maș + verde pieton
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
void setup() {
leduri.begin();
timp_intrat = millis();
}
void loop() {
// Trec la următoarea stare după DURATA[stare]
if (millis() - timp_intrat >= DURATA[stare]) {
stare = (stare + 1) % 3; // 0→1→2→0
timp_intrat = millis();
}
// Aprind 2 LED-uri simultan: unul pentru mașini, unul pentru pieton
leduri.clear();
if (stare == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0)); // LED[2] verde mașini
leduri.setPixelColor(0, leduri.Color(80, 0, 0)); // LED[0] roșu pieton (așteaptă)
}
if (stare == GALBEN) {
leduri.setPixelColor(3, leduri.Color(80, 80, 0)); // LED[3] galben mașini
leduri.setPixelColor(0, leduri.Color(80, 0, 0)); // LED[0] roșu pieton (continuă)
}
if (stare == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0)); // LED[4] roșu mașini
leduri.setPixelColor(1, leduri.Color(0, 80, 0)); // LED[1] verde pieton (trece)
}
leduri.show();
}
Cum flashezi: Upload, EN.
Ce verifici: LED-urile se aprind în perechi, simultan:
- 5 secunde: LED[2] verde + LED[0] roșu pieton (mașinile trec, pietonul așteaptă)
- 1.5 secunde: LED[3] galben + LED[0] roșu pieton (avertizare, pietonul așteaptă)
- 4 secunde: LED[4] roșu + LED[1] verde pieton (mașinile așteaptă, pietonul trece)
- repetă
Dacă nu merge:
- LED-urile nu se schimbă deloc → poate
timp_intratnu-i resetat. Verifică liniatimp_intrat = millis();dinsetup()și după schimbarea de stare. - LED-urile se schimbă prea repede → probabil ai uitat să convertești
intlaunsigned longpentruDURATA. AdaugăULla final:5000UL. - Culorile greșite pe LED-uri → verifică ordinea fizică din lanț. LED[0] e PRIMUL din chain (cel mai aproape de ESP32 GPIO 5). Dacă ai lipit altfel, schimbă indexurile în cod sau mută LED-urile.
Când merge, treci la Pas D. Ai dovedit că state machine + millis() pentru timing merg.
Pas D — Adaugă OLED-ul (cod existent + ~15 linii)
Ce face: OLED-ul arată „Nod N1” + numele stării curente.
Înainte: OLED-ul trebuie cablat conform schiței Test 1 PAS 3 (SDA→GPIO 21, SCL→GPIO 22, VCC→3.3V, GND→GND).
#include <Adafruit_NeoPixel.h>
#include <Wire.h> // I2C pentru OLED
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int PIN_LED = 5;
const int NUM_LEDS = 5;
Adafruit_NeoPixel leduri(NUM_LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 oled(128, 64, &Wire, -1); // dimensiune ecran 128x64
const int VERDE_MASINI = 0, GALBEN = 1, ROSU_MASINI = 2;
const unsigned long DURATA[] = { 5000, 1500, 4000 };
const char* NUME[] = { "VERDE MASINI", "GALBEN", "VERDE PIETON" };
int stare = VERDE_MASINI;
unsigned long timp_intrat = 0;
void setup() {
leduri.begin();
// Pornesc OLED la adresa I2C standard 0x3C
if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
// Dacă eșuează, blochez aici — utilizatorul vede LED-urile nemișcate
while (true) { delay(1000); }
}
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
timp_intrat = millis();
}
void loop() {
if (millis() - timp_intrat >= DURATA[stare]) {
stare = (stare + 1) % 3;
timp_intrat = millis();
}
leduri.clear();
if (stare == VERDE_MASINI) {
leduri.setPixelColor(2, leduri.Color(0, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == GALBEN) {
leduri.setPixelColor(3, leduri.Color(80, 80, 0));
leduri.setPixelColor(0, leduri.Color(80, 0, 0));
}
if (stare == ROSU_MASINI) {
leduri.setPixelColor(4, leduri.Color(80, 0, 0));
leduri.setPixelColor(1, leduri.Color(0, 80, 0));
}
leduri.show();
// Afișează pe OLED
oled.clearDisplay();
oled.setCursor(0, 0);
oled.println("Nod N1");
oled.println();
oled.println(NUME[stare]);
oled.display();
}
Cum flashezi: Upload, EN.
Ce verifici: OLED-ul afișează „Nod N1” pe primul rând și numele stării curente pe al treilea rând. LED-urile continuă să cicleze.
Dacă nu merge:
| Simptom | Cauza | Fix |
|---|---|---|
| Placă blocată după upload (LED-urile nu se mai mișcă) | oled.begin() a returnat false, codul rămâne în while (true) | Verifică wire-urile I2C: SDA→GPIO 21, SCL→GPIO 22, VCC→3.3V (nu 5V), GND→GND |
| OLED rămâne complet stins | Cablaj VCC sau GND întrerupt | Verifică wire-urile alimentare |
| OLED arată dungi sau zgomot | Adresa I2C diferită (unele module au 0x3D) | Schimbă 0x3C în 0x3D și reflashează |
| Eroare „Adafruit_SSD1306.h: No such file” | Librăria nu-i instalată | Library Manager → caută SSD1306 → Install |
Când merge, treci la Pas E. Ai dovedit că OLED + I2C merg.
Pas E — Adaugă butonul pieton (+8 linii)
Ce face: apeși butonul → contor pieton crește → vezi pe OLED.
Înainte: butonul cablat conform schiței Test 1 PAS 4 (S→GPIO 4, GND→GND, VCC neconectat).
Schimbări față de Pasul D:
// (toate include-urile și constantele de la Pas D)
const int PIN_BUTON = 4;
int pietoni = 0;
unsigned long ultima_apasare = 0;
const unsigned long ANTI_BOUNCE = 2000; // ignoră apăsări mai dese de 2s
În setup() adaugi:
pinMode(PIN_BUTON, INPUT_PULLUP);
În loop() adaugi (înainte de OLED-ul de la pasul D):
// Citește butonul
if (digitalRead(PIN_BUTON) == LOW) { // LOW = apăsat (pull-up)
if (millis() - ultima_apasare >= ANTI_BOUNCE) {
pietoni++;
ultima_apasare = millis();
}
}
Și în OLED-ul existent, adaugi o linie:
oled.print("Pietoni: ");
oled.println(pietoni);
Cum flashezi: Upload, EN.
Ce verifici:
- Pe OLED apare „Pietoni: 0”.
- Apeși butonul → „Pietoni: 1” apare în maxim 1 secundă.
- Apeși butonul de 2 ori în 1 secundă → tot „Pietoni: 1” (anti-bounce funcționează).
- Apeși după 3 secunde → „Pietoni: 2”.
Dacă nu merge:
- Contorul nu crește deloc → verifică wire S→GPIO 4. Sau butonul are pin VCC conectat (NU trebuie — vezi schiță PAS 4).
- Contorul sare prea repede → micșorează
ANTI_BOUNCE(poate butonul tău e ok și 50ms ajung). Sau crește dacă încă duble-apasă.
Când merge, ai Test 1 complet. Felicitări — ai construit primul semafor empatic.
Recap
Ai pornit cu LED-ul intern și 5 linii. Ai ajuns la semafor complet cu ~80 de linii. Diferența: acum știi exact ce face fiecare bloc. Dacă mâine adaugi microfonul (Test 2), știi că tot ce-i sub el e ok.
Pas următor
Plan Test 2 (microfon INMP441) → — adaugi senzorul de sunet care detectează sirena.
După lecția asta, poți
- Spargi orice program nou în micro-pași A→E în loc să flashezi 80 linii odată
- Debug-uiești un sketch care nu merge fără să-l rebuilduiești de la zero
- Instalezi librării noi din Library Manager în 10 secunde
- Recunoști când placa-i blocată în boot mode și folosești butonul EN/BOOT corect