Atelier.

atelier · lecții

Lecția 07 45 min începător 3 întrebări quiz

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).

  1. Deschizi Arduino IDE
  2. Click pe icoana de cărți din bara stângă (sau Sketch → Include Library → Manage Libraries)
  3. În căsuța de căutare, caută pe rând:
CautăCine o faceApeși
Adafruit NeoPixelAdafruitInstall
Adafruit GFX LibraryAdafruitInstall
Adafruit SSD1306AdafruitInstall (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

  1. 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”.
  2. În Arduino IDE: Tools → Board → esp32 → ESP32 Dev Module
  3. 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.
  4. 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:

  1. Lipești codul în Arduino IDE (sau îl scrii)
  2. Apeși icoana cu bifă verde (sus stânga) → Verify. Așteaptă „Done compiling”.
  3. Apeși icoana cu săgeată → (lângă bifă) → Upload. Așteaptă „Done uploading”.
  4. Pe placă, LED-ul roșu mic clipește scurt cât se transferă codul (~5 secunde).
  5. 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”

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, ...) sau digitalWrite(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:

SimptomCauzaFix
Eroare „Adafruit_NeoPixel.h: No such file”Librăria nu-i instalatăLibrary Manager → caută NeoPixel → Install
Toate 5 LED-urile se aprind random colorateNUM_LEDS = 1 dar codul a tot scris pe toate la run-uri anterioareApeși EN și flashezi din nou
LED [0] aprins dar culoarea-i albastru sau roșu, nu verdeOrdinea de culori e GRB vs RGB diferită pe modulul tăuSchimbă NEO_GRB în NEO_RGB și reflashezi
Nimic nu se aprindeCablajul: VCC sau GND nu-s pe rail-uri, sau DIN nu-i pe GPIO 5Verifică 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_intrat nu-i resetat. Verifică linia timp_intrat = millis(); din setup() și după schimbarea de stare.
  • LED-urile se schimbă prea repede → probabil ai uitat să convertești int la unsigned long pentru DURATA. Adaugă UL la 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:

SimptomCauzaFix
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 stinsCablaj VCC sau GND întreruptVerifică wire-urile alimentare
OLED arată dungi sau zgomotAdresa 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