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.
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)
Schema de montaj
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.
| Stare | Ce rămâne pornit | Ordin de mărime | Revenire |
|---|---|---|---|
| Activ, radio pornit | procesor, memorie, Wi-Fi în emisie | zeci–sute de mA | - |
| Modem-sleep | procesor și memorie; radioul se stinge între cadre | câteva zeci de mA | imediată |
| Light-sleep | memoria își păstrează conținutul, ceasurile se opresc | sute de µA | microsecunde, programul continuă de unde a rămas |
| Deep-sleep | doar domeniul mereu alimentat: temporizatorul și o memorie mică | câțiva µA | milisecunde, dar programul o ia de la capăt |
| Hibernare | practic nimic în afară de circuitul de trezire | sub 1 µA | ca la o pornire completă |
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
- 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
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ă.
- 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.
6Pregătirea mediului de lucru
- Instalați suportul pentru ESP32 în Arduino IDE
În
File → Preferences → Additional Boards Manager URLsadă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. - Selectați placa și portul
Tools → Board → ESP32 Arduino → ESP32C6 Dev Module. LaPortalegeț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 marcatUSB(nuUART) pentru portul serial nativ. - Verificați lanțul de compilare cu un program minimaltest.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.
- 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.
#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.
}numar_treziricrește la fiecare ciclu, darcontor_obisnuitrămâne mereu 1 - memoria obișnuită se pierde.- Instrucțiunile de după
esp_deep_sleep_start()nu se execută niciodată; funcția nu se întoarce. - 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.
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șă.
#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() {}| Cip | Funcția de trezire prin pin | Pini permiși |
|---|---|---|
| ESP32 clasic | esp_sleep_enable_ext0_wakeup() / ext1 | pinii RTC |
| ESP32-C3 / C6 / H2 | esp_deep_sleep_enable_gpio_wakeup() | GPIO0–GPIO7 |
| ESP32-S3 | ambele variante | pinii RTC |
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:
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 canalelor | 1–3 s | da - dacă memorăm canalul și adresa punctului de acces |
| Asocierea și autentificarea | 100–300 ms | parțial |
| Obținerea adresei prin DHCP | 0,5–2 s | da - cu adresă IP fixă |
| Transmisia propriu-zisă | 10–50 ms | nu, dar este oricum neglijabilă |
#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;
}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.
#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() {}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ți | Curent măsurat |
|---|---|---|
| Activ, Wi-Fi conectat și transmite | în timpul apelului http.POST() | ………… mA |
| Activ, Wi-Fi pornit dar inactiv | după conectare, în buclă de delay() | ………… mA |
| Activ, Wi-Fi oprit | WiFi.mode(WIFI_OFF) apoi buclă goală | ………… mA |
| Light-sleep | esp_light_sleep_start() cu trezire la 30 s | ………… µA |
| Deep-sleep | esp_deep_sleep_start() | ………… µA |
| Deep-sleep, LED de alimentare dezlipit | opț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.
#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() {}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
12Sarcini de lucru
- Încărcați
somn_01_baza.inoși lăsați-l să ruleze zece cicluri. Explicați în referat de cenumar_treziricrește iarcontor_obisnuitnu. - Completați integral tabelul de măsurători de la secțiunea 11.
- 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ă.
- 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.
- 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ă.
- 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
- 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.