LABORATORUL 02

Stările de consum și autonomia unui nod ESP32-C6

Durată: 3 ore Suport: Capitolele 4, 5 și 10 Platformă: ESP32-C6 DevKitC-1 Carte capitolul de referință EN English version

Un senzor care trebuie să reziste un an pe o baterie lucrează sub o secundă pe oră. Restul timpului doarme. În această lucrare măsurăm cât consumă fiecare stare a unui ESP32-C6, construim bugetul energetic al unui nod real și descoperim că cea mai mare economie nu vine din a dormi mai adânc, ci din a te trezi mai puțin timp.

1Obiectivele lucrării

  • Identificarea stărilor de consum ale unui microcontroler modern și a ceea ce rămâne pornit în fiecare
  • Măsurarea curentului în fiecare stare, de la sute de miliamperi la câțiva microamperi
  • Programarea somnului adânc și a surselor de trezire: temporizator și pin de intrare
  • Păstrarea informației peste repornire folosind memoria din domeniul mereu alimentat
  • Calculul curentului mediu, al bugetului energetic și al autonomiei pe baterie
  • Reducerea duratei fazei active - de departe cea mai eficientă optimizare
  • Înțelegerea diferenței dintre consumul cipului și consumul plăcii de dezvoltare

2Obiectul lucrării

Construim un nod senzor autonom: se trezește la un interval fix, citește temperatura și umiditatea, trimite valorile prin Wi-Fi și adoarme la loc. Apoi îl optimizăm pas cu pas și urmărim cum autonomia calculată crește de la câteva zile la peste un an - fără să schimbăm bateria și fără să schimbăm cipul.

microcontroler
ESP32-C6
RISC-V pe 32 de biți, până la 160 MHz
particularitate
nucleu de consum redus
un al doilea nucleu la 20 MHz, care poate lucra cât timp cel principal doarme
comunicații
Wi-Fi 6 · BLE 5 · 802.15.4
Thread și Zigbee, pe lângă Wi-Fi
domeniu mereu alimentat
memorie și temporizator
supraviețuiesc somnului adânc
100 mA 1 mA 10 µA activ somn adânc activ somn adânc perioada ciclului - aici se decide autonomia 3 s …
Fig. 1 - Profilul de curent al unui nod pe baterie, cu axa verticală logaritmică. Vârfurile scurte sunt emisiile Wi-Fi. Suprafața de sub curbă este sarcina consumată - și, în ciuda aparențelor, cea mai mare parte din ea se află în vârfurile înguste, nu în platoul lung de jos.

3Materiale necesare

  • 1 ESP32-C6-DevKitC-1
  • 1 Cablu USB-C pentru date
  • 1 Modul INA219
  • 1 Multimetru cu domeniu de µA
  • 1 LCD 16×2 cu adaptor I²C (PCF8574)
  • 1 Convertor de nivel I²C 3,3 V ↔ 5 V
  • 1 Sursă de laborator 5 V
  • 1 Senzor BME280 (I²C) sau DHT22
  • 1 Buton tactil + rezistor 10 kΩ
  • 1 Breadboard
  • 20 Fire jumper
  • 1 Suport 2×AA sau acumulator LiPo (opțional)
Despre măsurarea curenților foarte mici Un multimetru obișnuit are, pe domeniul de µA, o rezistență internă suficient de mare încât să provoace o cădere de tensiune care resetează microcontrolerul la fiecare vârf de curent. Dacă plăcuța repornește ciudat în timpul măsurătorilor, aceasta este cauza. INA219 nu are această problemă, pentru că șuntul lui este de doar 0,1 Ω.

Schema de montaj

sursă de laborator 5 V max 1 A + − INA219 Vin+ → șunt → Vin− 0x40 · VCC ← 3V3 SDA→GPIO6 SCL→GPIO7 GND comun LCD 16×2 PCF8574 · 0x27 VCC ← 5 V sursă GND comun convertor de nivel I²C LV 3,3 V · HV 5 V SDA→GPIO6 SCL→GPIO7 ESP32-C6 DevKitC-1 5V GND 3V3 GPIO6 · SDA GPIO7 · SCL GND GPIO4 USB deconectat BME280 0x76 VCC SDA SCL GND 10 kΩ buton
Fig. 2 - Montajul. Sursa de 5 V intră pe pinul 5V al plăcii prin șuntul INA219 (roșu): tot curentul plăcii, inclusiv al regulatorului ei, trece prin modul. LCD-ul se alimentează direct de la sursă, înainte de șunt, deci consumul lui (și al luminii de fundal) nu intră în măsurătoare. BME280, INA219 și LCD-ul stau pe aceeași magistrală I²C a plăcii (GPIO6 = SDA, GPIO7 = SCL), la adrese diferite; LCD-ul trece printr-un convertor de nivel, pentru că lucrează la 5 V. Butonul trage GPIO4 la masă, iar rezistorul de 10 kΩ ține pinul sus și în somn adânc.
Înainte de a porni alimentarea 1. Deconectați cablul USB. Placa nu se alimentează niciodată din două surse în același timp.
2. Reglați sursa la 5 V și limitați curentul la 1 A înainte de a o lega. Pinul 5V trece prin regulatorul plăcii, deci INA219 vede consumul plăcii așa cum este, nu doar al cipului.
3. LCD-ul nu se leagă direct pe GPIO6/GPIO7. Adaptorul PCF8574 are rezistențe de pull-up spre 5 V, iar pinii ESP32-C6 nu suportă 5 V. Folosiți convertorul de nivel (partea LV la 3V3-ul plăcii, partea HV la 5 V) sau scoateți cele două rezistențe de pull-up de pe adaptor.
4. Masa este comună: minusul sursei, GND-ul plăcii, al lui INA219, al convertorului și al LCD-ului.

Pentru stările de somn, multimetrul pe domeniul de µA se pune în serie pe același fir roșu, între sursă și INA219.

4Stările de consum

Un microcontroler modern nu are două stări (pornit / oprit), ci o scară întreagă. Fiecare treaptă oprește ceva în plus și, în schimb, are nevoie de mai mult timp ca să revină la lucru.

StareCe rămâne pornitOrdin de mărimeRevenire
Activ, radio pornitprocesor, memorie, Wi-Fi în emisiezeci–sute de mA-
Modem-sleepprocesor și memorie; radioul se stinge între cadrecâteva zeci de mAimediată
Light-sleepmemoria își păstrează conținutul, ceasurile se oprescsute de µAmicrosecunde, programul continuă de unde a rămas
Deep-sleepdoar domeniul mereu alimentat: temporizatorul și o memorie micăcâțiva µAmilisecunde, dar programul o ia de la capăt
Hibernarepractic nimic în afară de circuitul de treziresub 1 µAca la o pornire completă
Diferența care contează cel mai mult La light-sleep execuția continuă din punctul în care a fost întreruptă: variabilele sunt intacte, conexiunea Wi-Fi poate fi păstrată. La deep-sleep, cipul repornește: setup() se execută din nou, iar toate variabilele obișnuite sunt pierdute. În schimb consumul este de aproximativ o sută de ori mai mic. Alegerea între ele este primul compromis de proiectare al oricărui nod pe baterie.

Capcana plăcii de dezvoltare

Citiți acest paragraf înainte de a vă mira de rezultate Fișa tehnică promite câțiva microamperi în somn adânc. O placă de dezvoltare va consuma, cel mai probabil, de o mie de ori mai mult. Vinovații sunt pe placă, nu în cip:
  • convertorul USB–serial - rămâne alimentat și consumă permanent
  • regulatorul de tensiune - are un curent propriu de repaus, uneori de ordinul miliamperilor
  • LED-ul de alimentare - 2–5 mA, adică singur cât de o mie de ori cipul adormit
  • rezistoarele de forțare de pe liniile de programare
Un produs real folosește modulul gol, nu placa de dezvoltare. Măsurați totuși placa așa cum este: diferența dintre cifra din fișa tehnică și cifra măsurată este cea mai valoroasă lecție a acestei lucrări.

5Calculatorul de buget energetic

Introduceți curenții măsurați și perioada dorită. Calculatorul arată curentul mediu, repartiția sarcinii între cele două faze și autonomia estimată.

Curent mediu, repartiția sarcinii și autonomia
Experimentul de gândire care schimbă totul Porniți de la valorile implicite (80 mA timp de 4 s, la fiecare 15 minute) și încercați două optimizări:
  • Somn de zece ori mai bun: coborâți curentul de somn de la 15 µA la 1,5 µA. Priviți autonomia.
  • Trezire de patru ori mai scurtă: readuceți somnul la 15 µA și coborâți durata activă de la 4 s la 1 s.
A doua optimizare câștigă detașat, deși pare mai modestă. Motivul: la aceste valori faza activă domină bugetul. De aceea efortul de inginerie trebuie pus în a te trezi mai puțin timp - și exact asta facem la secțiunea 9.

6Pregătirea mediului de lucru

  1. Instalați suportul pentru ESP32 în Arduino IDE

    În File → Preferences → Additional Boards Manager URLs adăugați:

    adresă
    https://espressif.github.io/arduino-esp32/package_esp32_index.json

    Apoi Tools → Board → Boards Manager, căutați „esp32” și instalați pachetul esp32 by Espressif Systems, versiunea 3.0 sau mai nouă. Versiunile 2.x nu cunosc ESP32-C6.

  2. Selectați placa și portul

    Tools → Board → ESP32 Arduino → ESP32C6 Dev Module. La Port alegeți portul care apare după conectarea plăcii.

    Dacă nu apare niciun portCablul USB-C este, în multe cazuri, doar pentru încărcare. Încercați alt cablu înainte de a bănui driverele. Plăcuța C6 are două conectoare USB - folosiți-l pe cel marcat USB (nu UART) pentru portul serial nativ.
  3. Verificați lanțul de compilare cu un program minimal
    test.ino
    void setup() {
      Serial.begin(115200);
      delay(1000);                      // lasa portul serial nativ sa se initializeze
      Serial.println("ESP32-C6 functional");
    }
    
    void loop() {
      Serial.printf("timp: %lu ms\n", millis());
      delay(1000);
    }

    Încărcați și deschideți monitorul serial la 115200 baud. Dacă vedeți mesajele, mediul este gata.

  4. Instalați biblioteca pentru senzor

    Tools → Manage Libraries, căutați Adafruit BME280 și instalați-o împreună cu dependențele propuse (Adafruit Unified Sensor).

7Somnul adânc și memoria care supraviețuiește

Primul program demonstrează comportamentul care îi surprinde pe toți: după somnul adânc, microcontrolerul nu continuă, ci repornește. Singurul lucru care supraviețuiește este ce am pus în memoria din domeniul mereu alimentat.

somn_01_baza.ino
#include <esp_sleep.h>

#define uS_PER_S   1000000ULL
#define SOMN_S     10             // cate secunde dormim

// Atributul RTC_DATA_ATTR aseaza variabila in memoria din domeniul mereu
// alimentat. Ea supravietuieste somnului adanc; o variabila obisnuita nu.
RTC_DATA_ATTR int numar_treziri = 0;
RTC_DATA_ATTR uint32_t timp_total_activ_ms = 0;

int contor_obisnuit = 0;          // acesta va fi mereu 0 dupa trezire

void afiseaza_motivul() {
  switch (esp_sleep_get_wakeup_cause()) {
    case ESP_SLEEP_WAKEUP_TIMER:  Serial.println("trezit de temporizator"); break;
    case ESP_SLEEP_WAKEUP_GPIO:   Serial.println("trezit de un pin");       break;
    case ESP_SLEEP_WAKEUP_EXT1:   Serial.println("trezit de EXT1");         break;
    default:                      Serial.println("pornire normala (nu din somn)");
  }
}

void setup() {
  uint32_t t0 = millis();
  Serial.begin(115200);
  delay(200);                     // fara aceasta pauza pierdem primele mesaje

  numar_treziri++;
  contor_obisnuit++;

  Serial.println("-----------------------------");
  afiseaza_motivul();
  Serial.printf("treziri (memorie RTC) : %d\n", numar_treziri);
  Serial.printf("contor obisnuit       : %d   <- mereu 1\n", contor_obisnuit);
  Serial.printf("timp activ cumulat    : %lu ms\n", timp_total_activ_ms);

  // Aici ar veni treaba propriu-zisa: citire senzor, transmisie.
  delay(500);

  timp_total_activ_ms += millis() - t0;

  Serial.printf("adorm %d secunde...\n", SOMN_S);
  Serial.flush();                 // golim tamponul serial INAINTE de a adormi

  esp_sleep_enable_timer_wakeup(SOMN_S * uS_PER_S);
  esp_deep_sleep_start();         // de aici incolo nu se mai executa nimic
}

void loop() {
  // Nu se ajunge niciodata aici: esp_deep_sleep_start() nu se intoarce.
}
Trei lucruri de observat
  1. numar_treziri crește la fiecare ciclu, dar contor_obisnuit rămâne mereu 1 - memoria obișnuită se pierde.
  2. Instrucțiunile de după esp_deep_sleep_start() nu se execută niciodată; funcția nu se întoarce.
  3. Fără Serial.flush() ultimele caractere se pierd: cipul adoarme înainte ca ele să apuce să plece pe fir. Este o greșeală care produce mesaje trunchiate și multă confuzie.
Memoria RTC nu este nelimitată Sunt câțiva kiloocteți. Puneți acolo doar strictul necesar: contoare, ultima valoare citită, canalul Wi-Fi. Nu încercați să stocați istoricul măsurătorilor.

8Sursele de trezire

Temporizatorul nu este singura cale de a ieși din somn. Un nod real trebuie să reacționeze și la evenimente externe: un buton, un senzor de mișcare, un contact de ușă.

somn_02_trezire.ino
#include <esp_sleep.h>

#define uS_PER_S    1000000ULL
#define SOMN_S      30
#define PIN_BUTON   GPIO_NUM_4    // pe C6, trezirea prin pin cere GPIO0..GPIO7

RTC_DATA_ATTR int treziri_temporizator = 0;
RTC_DATA_ATTR int treziri_buton = 0;

void setup() {
  Serial.begin(115200);
  delay(200);

  esp_sleep_wakeup_cause_t motiv = esp_sleep_get_wakeup_cause();
  if (motiv == ESP_SLEEP_WAKEUP_TIMER) {
    treziri_temporizator++;
    Serial.println("temporizator - masuratoare programata");
  } else if (motiv == ESP_SLEEP_WAKEUP_GPIO) {
    treziri_buton++;
    Serial.println("BUTON - eveniment neasteptat!");
  } else {
    Serial.println("pornire de la zero");
  }
  Serial.printf("temporizator: %d   buton: %d\n",
                treziri_temporizator, treziri_buton);

  // Butonul leaga pinul la masa cand este apasat, deci il tinem sus printr-o
  // rezistenta interna si ne trezim pe front descendent.
  pinMode(PIN_BUTON, INPUT_PULLUP);

  // Doua surse de trezire active simultan.
  esp_sleep_enable_timer_wakeup(SOMN_S * uS_PER_S);
  esp_deep_sleep_enable_gpio_wakeup(BIT(PIN_BUTON), ESP_GPIO_WAKEUP_GPIO_LOW);

  Serial.println("adorm - apasati butonul sau asteptati 30 s");
  Serial.flush();
  esp_deep_sleep_start();
}

void loop() {}
API-ul de trezire diferă între variantele ESP32 Aceasta este una dintre cele mai frecvente surse de erori la trecerea de la un cip la altul:
CipFuncția de trezire prin pinPini permiși
ESP32 clasicesp_sleep_enable_ext0_wakeup() / ext1pinii RTC
ESP32-C3 / C6 / H2esp_deep_sleep_enable_gpio_wakeup()GPIO0–GPIO7
ESP32-S3ambele variantepinii RTC
Verificați întotdeauna documentația pentru cipul concret. Un program scris pentru ESP32 clasic nu se compilează pe C6, iar mesajul de eroare nu spune limpede de ce.

Trezirea din light-sleep

Când aveți nevoie ca programul să continue de unde a rămas - de exemplu pentru a păstra o conexiune deschisă - folosiți somnul ușor. Diferența în cod este minimă, dar comportamentul este cu totul altul:

light sleep - programul continuă
esp_sleep_enable_timer_wakeup(5 * uS_PER_S);
esp_light_sleep_start();          // aceasta functie SE INTOARCE

Serial.println("m-am trezit si continui de aici");
Serial.printf("variabilele sunt intacte: %d\n", contor_obisnuit);

9Scurtarea fazei active

Calculatorul de la secțiunea 5 ne-a arătat unde se duce energia. Acum atacăm faza activă. Un nod neoptimizat petrece cea mai mare parte a timpului de veghe așteptând să se conecteze la Wi-Fi, nu citind senzorul.

Ce durează o conectare

EtapăDurată tipicăSe poate elimina?
Scanarea tuturor canalelor1–3 sda - dacă memorăm canalul și adresa punctului de acces
Asocierea și autentificarea100–300 msparțial
Obținerea adresei prin DHCP0,5–2 sda - cu adresă IP fixă
Transmisia propriu-zisă10–50 msnu, dar este oricum neglijabilă
Observația centrală Datele utile ocupă sub 5 % din timpul de veghe. Restul este pregătire - și aproape toată poate fi memorată de la ciclul anterior în memoria RTC.
wifi_rapid.ino - conectare cu date memorate
#include <WiFi.h>
#include <esp_wifi.h>

const char* SSID = "reteaua_laboratorului";
const char* PAROLA = "parola";

// Tot ce merita memorat de la ciclul anterior, ca sa nu-l refacem.
RTC_DATA_ATTR uint8_t  canal_salvat = 0;
RTC_DATA_ATTR uint8_t  bssid_salvat[6];
RTC_DATA_ATTR bool     avem_date_salvate = false;
RTC_DATA_ATTR uint32_t esecuri_la_rand = 0;

// Adresa fixa elimina complet negocierea DHCP.
IPAddress ip_local(192, 168, 1, 77);
IPAddress poarta   (192, 168, 1, 1);
IPAddress masca    (255, 255, 255, 0);
IPAddress dns      (192, 168, 1, 1);

bool conecteaza(uint32_t limita_ms = 8000) {
  uint32_t t0 = millis();

  WiFi.mode(WIFI_STA);
  WiFi.config(ip_local, poarta, masca, dns);   // fara DHCP
  WiFi.setSleep(true);                          // radioul se stinge intre cadre

  if (avem_date_salvate) {
    // Stim exact pe ce canal si la ce punct de acces: fara scanare.
    WiFi.begin(SSID, PAROLA, canal_salvat, bssid_salvat);
  } else {
    WiFi.begin(SSID, PAROLA);                   // prima data, cautam
  }

  while (WiFi.status() != WL_CONNECTED && millis() - t0 < limita_ms) {
    delay(10);
  }

  if (WiFi.status() != WL_CONNECTED) {
    esecuri_la_rand++;
    // Dupa trei esecuri banuim ca punctul de acces s-a mutat pe alt canal
    // si stergem datele memorate, ca sa reluam cu scanare completa.
    if (esecuri_la_rand >= 3) avem_date_salvate = false;
    return false;
  }

  canal_salvat = WiFi.channel();
  memcpy(bssid_salvat, WiFi.BSSID(), 6);
  avem_date_salvate = true;
  esecuri_la_rand = 0;

  Serial.printf("conectat in %lu ms  (canal %d, IP %s)\n",
                millis() - t0, canal_salvat, WiFi.localIP().toString().c_str());
  return true;
}
De ce contorul de eșecuri este obligatoriu Punctele de acces își schimbă canalul singure, ca să evite interferențele. Dacă nodul insistă pe canalul memorat, rămâne deconectat pentru totdeauna. Trei încercări eșuate declanșează o scanare completă. Fără această plasă de siguranță, optimizarea transformă nodul într-un dispozitiv care moare silențios după câteva săptămâni - un tip de defect foarte greu de diagnosticat pe teren.

Măsurați câștigul

Rulați conectarea de zece ori cu avem_date_salvate = false forțat, apoi de zece ori normal. Notați timpul mediu în fiecare caz și recalculați autonomia cu widgetul de la secțiunea 5. Diferența este, de obicei, un factor de trei până la cinci.

10Nodul complet

Punem totul cap la cap: trezire, citire senzor, transmisie rapidă, adormire. Programul își măsoară singur durata fazei active și o raportează, ca să putem urmări efectul optimizărilor.

nod_autonom.ino
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <esp_sleep.h>

#define uS_PER_S      1000000ULL
#define PERIOADA_S    900              // 15 minute
#define SDA_PIN       6
#define SCL_PIN       7

const char* SSID    = "reteaua_laboratorului";
const char* PAROLA  = "parola";
const char* SERVER  = "http://192.168.1.10:8000/masuratori";

RTC_DATA_ATTR uint32_t ciclu = 0;
RTC_DATA_ATTR uint32_t timp_activ_total_ms = 0;
RTC_DATA_ATTR uint8_t  canal_salvat = 0;
RTC_DATA_ATTR uint8_t  bssid_salvat[6];
RTC_DATA_ATTR bool     avem_date_salvate = false;

Adafruit_BME280 bme;

void adormi(const char* motiv) {
  timp_activ_total_ms += millis();
  Serial.printf("[%s] activ %lu ms  (cumulat %lu ms in %lu cicluri)\n",
                motiv, millis(), timp_activ_total_ms, ciclu);
  Serial.flush();

  WiFi.disconnect(true);               // oprim radioul explicit
  WiFi.mode(WIFI_OFF);

  esp_sleep_enable_timer_wakeup((uint64_t) PERIOADA_S * uS_PER_S);
  esp_deep_sleep_start();
}

void setup() {
  Serial.begin(115200);
  delay(150);
  ciclu++;

  // --- 1. senzorul, cat mai devreme: se poate citi fara Wi-Fi pornit ---
  Wire.begin(SDA_PIN, SCL_PIN);
  if (!bme.begin(0x76)) {              // unele module raspund la 0x77
    Serial.println("senzorul nu raspunde");
    adormi("fara senzor");
  }

  // Modul fortat: senzorul face o singura masuratoare la cerere si se opreste.
  // Este exact ce ne trebuie - un senzor care masoara continuu consuma degeaba.
  bme.setSampling(Adafruit_BME280::MODE_FORCED,
                  Adafruit_BME280::SAMPLING_X1,     // temperatura
                  Adafruit_BME280::SAMPLING_X1,     // presiune
                  Adafruit_BME280::SAMPLING_X1,     // umiditate
                  Adafruit_BME280::FILTER_OFF);
  bme.takeForcedMeasurement();

  float temperatura = bme.readTemperature();
  float umiditate   = bme.readHumidity();
  float presiune    = bme.readPressure() / 100.0F;

  Serial.printf("ciclul %lu: %.2f gradeC  %.1f%%  %.1f hPa\n",
                ciclu, temperatura, umiditate, presiune);

  // --- 2. reteaua, cat mai tarziu si cat mai scurt ---
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(true);
  if (avem_date_salvate) WiFi.begin(SSID, PAROLA, canal_salvat, bssid_salvat);
  else                   WiFi.begin(SSID, PAROLA);

  uint32_t t0 = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - t0 < 8000) delay(10);
  if (WiFi.status() != WL_CONNECTED) {
    avem_date_salvate = false;         // reluam cu scanare data viitoare
    adormi("fara retea");
  }
  canal_salvat = WiFi.channel();
  memcpy(bssid_salvat, WiFi.BSSID(), 6);
  avem_date_salvate = true;

  // --- 3. transmisia ---
  char corp[160];
  snprintf(corp, sizeof(corp),
           "{\"ciclu\":%lu,\"temp\":%.2f,\"umid\":%.1f,\"pres\":%.1f,\"activ_ms\":%lu}",
           ciclu, temperatura, umiditate, presiune, millis());

  HTTPClient http;
  http.begin(SERVER);
  http.addHeader("Content-Type", "application/json");
  int cod = http.POST(corp);
  Serial.printf("server: %d\n", cod);
  http.end();

  adormi("ok");
}

void loop() {}
Ordinea operațiilor nu este întâmplătoare Senzorul se citește înainte de a porni Wi-Fi-ul. Dacă am face invers, radioul ar sta pornit degeaba în timpul celor 10–20 ms de măsurătoare - o risipă mică luată separat, dar care se adună de zeci de mii de ori pe durata de viață a bateriei. Aceeași logică explică modul „forțat” al senzorului: măsoară o singură dată, la comandă, apoi se oprește singur.

11Protocolul de măsurare

Completați acest tabel cu propriile valori. Coloana din dreapta este cea care contează pentru referat.

Stare măsuratăCum o provocațiCurent măsurat
Activ, Wi-Fi conectat și transmiteîn timpul apelului http.POST()………… mA
Activ, Wi-Fi pornit dar inactivdupă conectare, în buclă de delay()………… mA
Activ, Wi-Fi opritWiFi.mode(WIFI_OFF) apoi buclă goală………… mA
Light-sleepesp_light_sleep_start() cu trezire la 30 s………… µA
Deep-sleepesp_deep_sleep_start()………… µA
Deep-sleep, LED de alimentare dezlipitopțional, doar cu acordul cadrului didactic………… µA

Citirea pe LCD

Programul de mai jos parcurge singur stările active, ține fiecare stare 10 secunde și afișează pe LCD curentul mediu măsurat de INA219. Placa nu are nevoie de calculator: după încărcarea programului scoateți cablul USB și porniți sursa. Bibliotecile necesare, din Library Manager: Adafruit INA219 și LiquidCrystal I2C.

masurare_lcd.ino
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <LiquidCrystal_I2C.h>
#include <esp_sleep.h>

#define SDA_PIN     6
#define SCL_PIN     7
#define DURATA_MS   10000UL            // cat timp tinem fiecare stare
#define SOMN_S      20                 // cat dureaza fiecare stare de somn
#define uS_PER_S    1000000ULL

const char* SSID   = "reteaua_laboratorului";
const char* PAROLA = "parola";

Adafruit_INA219   ina(0x40);
LiquidCrystal_I2C lcd(0x27, 16, 2);    // unele adaptoare raspund la 0x3F
WiFiUDP           udp;

// Emisie continua: pachete UDP de difuzare, nu e nevoie de server.
void emite() {
  static uint8_t buf[1000];
  udp.beginPacket(IPAddress(255, 255, 255, 255), 9999);
  udp.write(buf, sizeof(buf));
  udp.endPacket();
}

// Tine starea DURATA_MS, mediaza curentul si il afiseaza o data pe secunda.
float masoara(const char* nume, void (*lucru)() = nullptr) {
  lcd.clear();
  lcd.print(nume);
  double suma = 0;
  uint32_t n = 0;
  unsigned long t0 = millis(), afisat = t0;
  while (millis() - t0 < DURATA_MS) {
    if (lucru) lucru(); else delay(2);
    suma += ina.getCurrent_mA();
    n++;
    if (millis() - afisat >= 1000) {
      afisat = millis();
      lcd.setCursor(0, 1);
      lcd.printf("I=%7.1f mA    ", suma / n);
    }
  }
  return suma / n;
}

// LCD-ul e alimentat inainte de sunt: textul ramane pe ecran cat doarme placa.
void anuntaSomn(const char* nume) {
  lcd.clear();
  lcd.print(nume);
  lcd.setCursor(0, 1);
  lcd.print("cititi multimetr");
  ina.powerSave(true);                 // INA219 coboara la cativa uA
  delay(50);
}

void setup() {
  Wire.begin(SDA_PIN, SCL_PIN);
  lcd.init();
  lcd.backlight();
  if (!ina.begin(&Wire)) {
    lcd.print("INA219 lipsa!");
    while (true) delay(1000);
  }
  ina.setCalibration_32V_1A();         // pas de 0,04 mA, suporta varfurile Wi-Fi

  WiFi.mode(WIFI_OFF);
  masoara("WiFi oprit");

  lcd.clear();
  lcd.print("conectare...");
  WiFi.begin(SSID, PAROLA);
  while (WiFi.status() != WL_CONNECTED) delay(100);
  masoara("WiFi inactiv");
  masoara("WiFi emisie", emite);

  WiFi.mode(WIFI_OFF);
  anuntaSomn("Light-sleep 20 s");
  esp_sleep_enable_timer_wakeup(SOMN_S * uS_PER_S);
  esp_light_sleep_start();             // programul continua de aici
  ina.powerSave(false);

  anuntaSomn("Deep-sleep 20 s");
  esp_deep_sleep_start();              // la trezire, setup() o ia de la capat
}

void loop() {}
De ce stările de somn se citesc pe multimetru Cât timp placa doarme, nu rulează niciun program care să citească INA219. În plus, INA219 cu șunt de 0,1 Ω are un pas de aproximativ 0,1 mA, prea mare pentru câțiva microamperi. De aceea, înainte de fiecare somn, programul scrie pe LCD ce stare urmează și trece INA219 în regim de economie. LCD-ul fiind alimentat înainte de șunt, mesajul rămâne pe ecran, iar curentul îl citiți pe multimetrul pus în serie. După 20 de secunde de deep-sleep placa repornește și ciclul se reia.

Cum obțineți o măsurătoare corectă

  • Alimentați plăcuța de la baterie sau de la o sursă separată, nu prin USB de la calculator
  • Deconectați cablul de programare în timpul măsurătorii - convertorul USB adaugă un consum propriu
  • Așteptați cel puțin 5 secunde după intrarea în somn înainte de a citi: primul interval conține tranzitorii
  • Pentru faza activă folosiți valoarea medie pe toată durata, nu vârful
  • Repetați fiecare măsurătoare de trei ori și notați dispersia
Măsurarea vârfurilor de curent Emisia Wi-Fi durează câteva milisecunde și poate depăși 300 mA. Un multimetru obișnuit, care face o citire la fiecare câteva sute de milisecunde, nu va vedea niciodată acest vârf. Dacă aveți acces la un osciloscop, măsurați căderea de tensiune pe un rezistor de 1 Ω montat în serie - imaginea vârfurilor este spectaculoasă și explică de ce unele baterii cu rezistență internă mare nu pot alimenta un nod Wi-Fi, deși au capacitate suficientă.

12Sarcini de lucru

  1. Încărcați somn_01_baza.ino și lăsați-l să ruleze zece cicluri. Explicați în referat de ce numar_treziri crește iar contor_obisnuit nu.
  2. Completați integral tabelul de măsurători de la secțiunea 11.
  3. Calculați, cu widgetul de la secțiunea 5, autonomia nodului pe două acumulatoare AA (aproximativ 2600 mAh) pentru perioade de 1, 15 și 60 de minute. Realizați un grafic autonomie–perioadă.
  4. Măsurați durata fazei active înainte și după optimizările de la secțiunea 9 (canal memorat și adresă IP fixă). Recalculați autonomia în ambele cazuri și exprimați câștigul în procente.
  5. Adăugați trezirea prin buton la nodul complet, astfel încât o apăsare să provoace o măsurătoare imediată, în afara programului obișnuit. Verificați, prin numărătorul din memoria RTC, că ambele surse funcționează.
  6. Comparați light-sleep cu deep-sleep pentru o perioadă de 5 secunde. La ce perioadă devine deep-sleep mai avantajos, ținând cont că repornirea completă durează și ea, și consumă?

13Aplicație de aprofundare

Nodul care își alege singur ritmul Un senzor de temperatură care raportează la fiecare 15 minute risipește energie când temperatura nu se schimbă și pierde informație când aceasta se schimbă brusc. Implementați un interval adaptiv:
  • păstrați în memoria RTC ultima valoare transmisă;
  • dacă valoarea nouă diferă cu mai puțin de 0,3 °C, nu transmiteți deloc și dublați intervalul de somn (până la maximum o oră);
  • dacă diferă cu peste 1 °C, transmiteți și reduceți intervalul la minimum (un minut);
  • transmiteți totuși obligatoriu cel puțin o dată pe oră, ca serverul să știe că nodul este viu.
Măsurați consumul mediu pe o oră, o dată într-o încăpere cu temperatură stabilă și o dată cu senzorul ținut în mână. Discutați în referat compromisul dintre autonomie și rezoluția temporală a datelor, și de ce regula „cel puțin o dată pe oră” este obligatorie într-un sistem real.

14Întrebări de verificare

15Resurse