Értsd meg
Hardver, toolchain, GPIO és időzítés. Stabil mentális modell, veszélyes vakfoltok nélkül.
Tanuld meg az ESP32-t az első GPIO jeltől a Wi-Fi hálózatokon és szenzorokon át a valódi, kétmagos FreeRTOS alkalmazásokig.
Minden blokk az előzőre épül. Először megtanulod irányítani a hardvert, aztán összekötöd a világgal, végül feladatokra bontod és párhuzamosan futtatod.
Hardver, toolchain, GPIO és időzítés. Stabil mentális modell, veszélyes vakfoltok nélkül.
Szenzorbuszok, analóg jelek, Wi‑Fi, HTTP, MQTT és biztonságos konfiguráció.
Energia, hibakeresés, FreeRTOS feladatok, két mag és egy komplett záróprojekt.
Egy mikrovezérlő nem miniatűr PC: közvetlenül érintkezik a fizikai világgal, determinisztikusan reagál, és szűk erőforrásokból gazdálkodik.
Az „ESP32” egész család: ESP32, S2, S3, C3, C6, H2. Eltérhet a processzormag, a magok száma, az USB, a Bluetooth és a rádió. Mindig a pontos modul adatlapja a mérvadó.
| Terület | Szerep | Tipikus hiba |
|---|---|---|
| Flash | Program és tartós adatok | Túl nagy firmware |
| SRAM | Stack, heap, változók | Elfogyó heap / stack overflow |
| NVS | Kulcs–érték konfiguráció | Túl gyakori írás, kopás |
Miért kell tudnod a pontos ESP32 variánst?
Az ESP32 több nyelven programozható, de nem minden nyelv ad azonos sebességet, hardverhozzáférést vagy könyvtárkínálatot. A választás a projekt céljától függ.
C, C++, Python és Rust programozási nyelvek. Az Arduino Core és az ESP-IDF keretrendszerek/SDK-k. Az Arduino IDE, a VS Code és a PlatformIO fejlesztőeszközök. Például C++ kódot írhatsz Arduino IDE-ben vagy PlatformIO-ban, Arduino Core-ral vagy közvetlenül ESP-IDF-fel.
| Nyelv / környezet | Előny | Hátrány | Mikor válaszd? |
|---|---|---|---|
| C + ESP-IDF | Maximális kontroll, kis overhead, teljes Espressif API, kiváló hibakeresés és dokumentáció | Mutatók és kézi erőforrás-kezelés miatt könnyebb memóriabugot írni; meredekebb tanulás | Termék, alacsony késleltetés, speciális periféria, secure boot/OTA |
| C++ + Arduino Core | Nagyon sok könyvtár, rövid kód, könnyű kezdés, közben elérhető a FreeRTOS és sok ESP-IDF API | A könyvtárak minősége vegyes; absztrakciók elrejthetik az időzítést és memóriahasználatot | Tanulás, hobbi, prototípus és kis-közepes eszköz |
| C++ + ESP-IDF | Natív kontroll RAII-val, típusokkal és osztályokkal; C API-val jól együttműködik | Bonyolultabb build és hibakép; kivételek/RTTI gyakran tiltott vagy költséges | Nagyobb, moduláris firmware, tapasztalt C++ csapat |
| MicroPython | REPL, gyors kipróbálás, olvasható kód, nincs hosszú fordítási ciklus | Lassabb, jóval több RAM-ot használ, időzítése kevésbé determinisztikus, kisebb driverkészlet | Oktatás, szenzorprototípus, automatizálás, rövid fejlesztési idő |
| CircuitPython | Nagyon kezdőbarát; az eszköz USB-meghajtóként jelenhet meg; erős Adafruit ökoszisztéma | ESP32 boardtámogatása és hálózati képessége variánsonként eltér; teljesítmény/RAM korlátozott | Oktatás, maker projektek, támogatott S2/S3/C3/C6 panelek |
| Rust | Fordításkor kiszűrt memóriakezelési és konkurenciahibák; modern típusosság; natív sebesség | Meredek tanulás, hosszabb build, kisebb ökoszisztéma; chipenként eltérő támogatási érettség | Biztonságkritikusabb, hosszú életű vagy nagy párhuzamos rendszer |
| Lua / NodeMCU | Egyszerű scriptelés, interaktív fejlesztés, kis forráskód | Ma kisebb közösség és kevesebb friss könyvtár; RAM- és sebességkorlát | Régi NodeMCU rendszer karbantartása, nagyon kis script |
| JavaScript / Espruino | Webfejlesztőknek ismerős, REPL-es kísérletezés | Nem minden ESP32 variáns támogatott, szűkebb periféria-ökoszisztéma, interpreter-overhead | Gyors IoT demonstráció, meglévő JS tudás |
| Eszköz | Erősség |
|---|---|
| Arduino IDE | Leggyorsabb első feltöltés |
| PlatformIO | Reprodukálható függőségek, több környezet, tesztek |
| ESP-IDF CLI | Natív konfiguráció, monitor és debug |
| Thonny | Egyszerű MicroPython REPL és fájlkezelés |
| cargo-esp | Rust build és flash eszköztár |
A példák ugyanazt az elvet mutatják. A különbség nem csak szintaxis: eltér a futtatási modell, az indítás, a driver API és a hardver feletti kontroll.
constexpr int LED = 2;
void setup() { pinMode(LED, OUTPUT); }
void loop() {
digitalWrite(LED, !digitalRead(LED));
delay(500);
}from machine import Pin
from time import sleep
led = Pin(2, Pin.OUT)
while True:
led.value(not led.value())
sleep(0.5)// no_std firmware; a pontos inicializálás chip/crate verziófüggő
let mut led = Output::new(peripherals.GPIO2, Level::Low);
loop {
led.toggle();
delay.delay_millis(500);
}Nem csatlakozik? Tartsd nyomva a BOOT gombot az írás indulásakor; ellenőrizd a kábelt és a port jogosultságát.
A natív C/C++ és Rust gépi kódra fordul. A MicroPython, CircuitPython, Lua és JavaScript interpretert futtat, ezért kényelmesebb, de lassabb és kevésbé kiszámítható. Kemény időzítésű GPIO-t, motorvezérlést vagy nagy adatsebességet natív kóddal érdemes megoldani.
void setup() {
Serial.begin(115200);
while (!Serial) { delay(10); }
Serial.println("ESP32 elindult");
}
void loop() {
Serial.printf("Uptime: %lu ms, szabad heap: %u byte\n",
millis(), ESP.getFreeHeap());
delay(1000);
}A lábak villamos pontok, nem absztrakt változók. A 3,3 V-os határ, az áramkorlát és a boot-strapping pinek ismerete megóvja a panelt.
A kiosztás boardonként változhat; ellenőrizd a panel rajzát.
constexpr uint8_t LED = 2;
constexpr uint8_t BUTTON = 27;
uint32_t lastToggle = 0;
bool ledState = false;
void setup() {
pinMode(LED, OUTPUT);
pinMode(BUTTON, INPUT_PULLUP); // gomb a GND felé
}
void loop() {
uint32_t now = millis();
if (now - lastToggle >= 500) { // túlcsordulásbiztos
lastToggle = now;
ledState = !ledState;
digitalWrite(LED, ledState);
}
if (digitalRead(BUTTON) == LOW) { /* esemény */ }
}Mekkora logikai feszültségre készültek az ESP32 GPIO-k?
A jó firmware nem várakozik: eseményekre reagál. A delay() helyett állapotgépek, hardveres időzítők és rövid ISR-ek dolgoznak.
A PWM gyors ki-be kapcsolással állít elő átlagos teljesítményt LEDhez vagy motorvezérlőhöz. A frekvencia és felbontás kompromisszum: több bithez alacsonyabb maximális frekvencia társul.
const int pwmPin = 25;
void setup() {
ledcAttach(pwmPin, 5000, 8);
ledcWrite(pwmPin, 128); // kb. 50%
}delay()-t vagy hálózatot.volatile....FromISR API-t használj.loop()-ra vagy taskra.A perifériák nyelve négy alapmintára szűkíthető: analóg mérés, kétvezetékes címzett busz, gyors szinkron busz és soros adatfolyam.
| Busz | Vezeték | Jellemző |
|---|---|---|
| I²C | 2 + GND | Sok címzett eszköz, pull-up kell |
| SPI | 3 + CS/eszköz | Nagy sebesség, kijelző/SD |
| UART | TX/RX | Pont–pont, GPS/modem |
| ADC | jel + GND | Analóg, zajos és nem tökéletesen lineáris |
A klasszikus ESP32 ADC2 csatornáit a Wi‑Fi alrendszer is használja. Aktív Wi‑Fi mellett analóg méréshez válassz ADC1 lábat. Átlagolj több mintát, és valódi feszültséghez kalibrálj.
#include <Wire.h>
void setup() {
Serial.begin(115200); Wire.begin(21, 22);
for (uint8_t address = 1; address < 127; ++address) {
Wire.beginTransmission(address);
if (Wire.endTransmission() == 0)
Serial.printf("Eszköz: 0x%02X\n", address);
}
}
void loop() {}A hálózat nem állapot, hanem folyamat. Egy terméknek kezelnie kell a kimaradást, az újracsatlakozást és a konfiguráció változását.
Ne tedd a jelszót publikus forráskódba. Fejlesztéskor külön, gitignore-olt konfigurációt; termékben provisioninget és NVS-t használj. TLS nélkül a rádiós hálózat nem jelent alkalmazásszintű titkosítást.
#include <WiFi.h>
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
uint32_t started = millis();
while (WiFi.status() != WL_CONNECTED &&
millis() - started < 15000) {
delay(100);
}
if (WiFi.status() == WL_CONNECTED)
Serial.println(WiFi.localIP());
else Serial.println("Offline mód");
}Melyik megoldás a robusztus?
Az ESP32 lehet kliens, webszerver és üzenetküldő végpont. A protokollt az adat ritmusa és a rendszer felelőssége alapján válaszd.
| Megoldás | Jó választás |
|---|---|
| HTTP/REST | Ritka kérés–válasz, konfiguráció |
| WebSocket | Élő böngészős kétirányú adat |
| MQTT | Sok eszköz, publish/subscribe telemetria |
| OTA | Firmware-frissítés kábel nélkül |
#include <WebServer.h>
WebServer server(80);
void setupRoutes() {
server.on("/api/status", HTTP_GET, []() {
String json = "{\"uptime\":" + String(millis()) + "}";
server.send(200, "application/json", json);
});
server.onNotFound([](){ server.send(404, "text/plain", "Not found"); });
server.begin();
}
// loop(): server.handleClient(); — ne legyen hosszú blokkolás melletteA BLE nem „vezeték nélküli soros port”: szolgáltatásokból, karakterisztikákból és szabályozott adatcseréből álló GATT modellt használ.
A rövid advertising csomag segít felfedezni az eszközt; a kapcsolat több energiát kér.
Használj szabványos service UUID-t, ha létezik. Saját UUID esetén dokumentáld a byte-sorrendet, skálázást, mértékegységet és verziót. A BLE titkosítás nem helyettesíti automatikusan a jogosultságkezelést: érzékeny íráshoz párosítás és alkalmazásszintű ellenőrzés kell.
A tartós rendszer tudja, mit érdemes megőrizni és mikor érdemes elaludni. A flash írás véges, az akkumulátor energiája még inkább.
Az üzemidőt az átlagáram határozza meg: aktív mérés + rádiós adás + alvás súlyozott összege. A Wi‑Fi rövid áramcsúcsai miatt a stabil táp és a pufferkondenzátor kritikus.
Gyakori hiba: breadboardon működik, akkumulátorról resetel — a táp pillanatnyi áramot nem tud biztosítani.
constexpr uint64_t SECOND_US = 1000000ULL;
void setup() {
Serial.begin(115200);
// mérés → mentés → küldés, majd:
esp_sleep_enable_timer_wakeup(60 * SECOND_US);
Serial.flush();
esp_deep_sleep_start();
}
void loop() {}A soros log csak a kezdet. A reset ok, heap trend, stack vízjel és dekódolt backtrace együtt mondja el, mi történt.
| Tünet | Első ellenőrzés |
|---|---|
| Brownout reset | Tápfeszültség és áramcsúcs |
| Guru Meditation | Backtrace, null pointer, tömbhatár |
| Watchdog | Blokkoló ciklus, task starvation |
| Random fagyás | Heap, stack, race condition |
Serial.printf(
"heap=%u minHeap=%u core=%d reset=%d\n",
ESP.getFreeHeap(),
ESP.getMinFreeHeap(),
xPortGetCoreID(),
esp_reset_reason()
);A párhuzamosság célja nem az, hogy mindenből task legyen. Feladatokra bontjuk a felelősséget, üzenetekkel kommunikálunk, és mérhetően tartjuk a határidőket.
A klasszikus Arduino-ESP32 környezetben a rádiós rendszerfeladatok jellemzően a 0. magon, az Arduino setup/loop pedig az 1. magon fut. Ez implementációs részlet lehet; mérd és ellenőrizd a használt core verzióban.
xTaskCreate() szabad ütemezése sokszor jobb. Maghoz rögzítés csak mérhető indokkal.#include <Arduino.h>
struct Sample { uint32_t at; float value; };
QueueHandle_t samples;
void sensorTask(void*) {
TickType_t last = xTaskGetTickCount();
for (;;) {
Sample s{millis(), analogReadMilliVolts(34) / 1000.0f};
xQueueOverwrite(samples, &s); // queue hossza 1
vTaskDelayUntil(&last, pdMS_TO_TICKS(100));
}
}
void networkTask(void*) {
Sample s;
for (;;) {
if (xQueueReceive(samples, &s, portMAX_DELAY) == pdTRUE)
Serial.printf("t=%lu V=%.3f core=%d\n", s.at, s.value, xPortGetCoreID());
}
}
void setup() {
Serial.begin(115200);
samples = xQueueCreate(1, sizeof(Sample));
xTaskCreatePinnedToCore(sensorTask, "sensor", 3072, nullptr, 2, nullptr, 1);
xTaskCreatePinnedToCore(networkTask, "network", 4096, nullptr, 1, nullptr, 0);
}
void loop() { vTaskDelay(portMAX_DELAY); }Két task egyszerre olvas és ír közös adatot; az eredmény az időzítéstől függ. Első választásként tulajdonost és queue-t tervezz. Mutexet rövid kritikus szakaszra használj, és soha ne tartsd hálózati várakozás alatt.
Az ESP32 FreeRTOS stackméretei bájtban értendők az Arduino környezet gyakori API-jainál. Mérj uxTaskGetStackHighWaterMark()-kal. A túl magas prioritás kiéheztetheti a rendszerfeladatokat és watchdog resetet okozhat.
Mi a legjobb alapminta taskok közti szenzoradat-átadáshoz?
Építs környezeti állomást szenzorral, helyi kijelzővel, MQTT telemetriával, webes állapotoldallal és biztonságos frissítéssel.
Ha ez stabil, lépj ESP-IDF-re: komponensek, Kconfig, event loop, esp_timer, native HTTP/MQTT kliensek, secure boot, flash encryption, particionálás és Unity-alapú egységtesztek. A professzionális fejlődés kulcsa nem az újabb library, hanem a mérhető követelmény, tiszta felelősség és reprodukálható build.
Mitől lesz „kész” egy beágyazott projekt?
Három LED, egy nyomógomb és egy blokkolásmentes állapotgép. A projekt megtanítja, hogyan kezeld az időt delay() nélkül.
| ESP32 | Alkatrész | Másik pont |
|---|---|---|
| GPIO 25 | Piros LED anód | Katód → 330 Ω → GND |
| GPIO 26 | Sárga LED anód | Katód → 330 Ω → GND |
| GPIO 27 | Zöld LED anód | Katód → 330 Ω → GND |
| GPIO 32 | Nyomógomb egyik lába | Másik láb → GND |
Miért nincs külső gombellenállás? Az INPUT_PULLUP bekapcsolja a belső felhúzást, így megnyomva LOW értéket olvasunk.
constexpr uint8_t RED=25, YELLOW=26, GREEN=27, BUTTON=32;
enum class State { CarsGo, Prepare, Pedestrians };
State state = State::CarsGo;
uint32_t changedAt = 0, lastPress = 0;
void show(bool r, bool y, bool g) {
digitalWrite(RED,r); digitalWrite(YELLOW,y); digitalWrite(GREEN,g);
}
void setup() {
for (uint8_t pin : {RED,YELLOW,GREEN}) pinMode(pin,OUTPUT);
pinMode(BUTTON,INPUT_PULLUP); show(0,0,1);
}
void loop() {
uint32_t now=millis();
if (state==State::CarsGo && !digitalRead(BUTTON) && now-lastPress>250) {
lastPress=now; state=State::Prepare; changedAt=now; show(0,1,0);
}
if (state==State::Prepare && now-changedAt>=1500) {
state=State::Pedestrians; changedAt=now; show(1,0,0);
}
if (state==State::Pedestrians && now-changedAt>=5000) {
state=State::CarsGo; changedAt=now; show(0,0,1);
}
}Adj hozzá gyalogos zöld/piros LED-et és csipogót. Készíts külön enterState() függvényt, és a soros monitorra írd ki az állapotváltásokat.
Mérj hőmérsékletet és páratartalmat, majd jelenítsd meg egy 0,96″-os I²C OLED kijelzőn, hibás szenzorértékek kezelésével.
| Modul | ESP32 | Megjegyzés |
|---|---|---|
| DHT22 VCC | 3V3 | Ne 5 V-ról add az adatjelet |
| DHT22 DATA | GPIO 4 | 10 kΩ DATA–3V3, ha csupasz szenzor |
| DHT22 GND | GND | Közös föld |
| OLED VCC / GND | 3V3 / GND | Modul adatlapját ellenőrizd |
| OLED SDA / SCL | GPIO 21 / 22 | Tipikus I²C cím: 0x3C |
#include <Wire.h>
#include <DHT.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
constexpr uint8_t DHT_PIN=4;
DHT dht(DHT_PIN,DHT22);
Adafruit_SSD1306 display(128,64,&Wire,-1);
void setup() {
Serial.begin(115200); dht.begin(); Wire.begin(21,22);
if (!display.begin(SSD1306_SWITCHCAPVCC,0x3C)) {
Serial.println("OLED nem található"); while(true) delay(1000);
}
display.setTextColor(SSD1306_WHITE);
}
void loop() {
float humidity=dht.readHumidity(), temperature=dht.readTemperature();
display.clearDisplay(); display.setCursor(0,0); display.setTextSize(2);
if (isnan(humidity) || isnan(temperature)) {
display.println("Szenzorhiba"); Serial.println("DHT olvasasi hiba");
} else {
display.printf("%.1f C\n%.1f %%",temperature,humidity);
Serial.printf("T=%.1f C RH=%.1f %%\n",temperature,humidity);
}
display.display(); delay(2000); // DHT22-t ne olvasd túl gyorsan
}Ne közvetlenül az ESP32 vagy tápegység fölé tedd a DHT22-t, mert a saját hőjük torzít. Hasonlítsd referencia-hőmérőhöz, és dokumentáld az eltérést.
Kapcsolj kisfeszültségű terhelést telefonról, helyi weboldallal. Megtanulod a relémodul bekötését, az állapotvégpontot és a biztonságos alapállapotot.
Biztonság: ez a labor kizárólag érintésvédelmileg biztonságos kisfeszültséghez készült. Hálózati 230 V bekötését csak szakképzett villanyszerelő végezheti megfelelő házban és védelemmel.
| Relémodul | ESP32 / terhelés |
|---|---|
| IN | GPIO 26 |
| VCC | A modul előírt tápja; 3V3 csak ha támogatott |
| GND | ESP32 GND (közös referencia) |
| COM | Kisfeszültségű táp pozitív ága |
| NO | Terhelés pozitív pontja |
| Terhelés másik pontja | Kisfeszültségű táp negatív ága |
Sok modul aktív LOW. A példában ezt a RELAY_ON konstans kezeli.
#include <WiFi.h>
#include <WebServer.h>
constexpr uint8_t RELAY=26, RELAY_ON=LOW, RELAY_OFF=HIGH;
const char* SSID="HALOZAT_NEVE"; const char* PASS="JELSZO";
WebServer server(80); bool relayOn=false;
void setRelay(bool on) { relayOn=on; digitalWrite(RELAY,on?RELAY_ON:RELAY_OFF); }
void sendPage() {
String html="<!doctype html><meta name='viewport' content='width=device-width'>"
"<h1>ESP32 rele</h1><p>Allapot: " + String(relayOn?"BE":"KI") +
"</p><p><a href='/on'>BE</a> | <a href='/off'>KI</a></p>";
server.send(200,"text/html; charset=utf-8",html);
}
void setup() {
Serial.begin(115200); pinMode(RELAY,OUTPUT); setRelay(false);
WiFi.begin(SSID,PASS); uint32_t started=millis();
while (WiFi.status()!=WL_CONNECTED && millis()-started<15000) delay(100);
if (WiFi.status()!=WL_CONNECTED) { Serial.println("Wi-Fi hiba"); return; }
Serial.println(WiFi.localIP());
server.on("/",sendPage);
server.on("/on",[]{setRelay(true); server.sendHeader("Location","/"); server.send(303);});
server.on("/off",[]{setRelay(false); server.sendHeader("Location","/"); server.send(303);});
server.begin();
}
void loop() { server.handleClient(); delay(2); }A példa csak helyi, megbízható hálózatra való. Éles rendszerhez hitelesítés, CSRF-védelem, TLS vagy biztonságos átjáró, konfigurációs provisioning és watchdog szükséges.
A mérés az 1. magon, a hálózati kommunikáció a 0. magon fut. Queue választja szét a felelősségeket, a Wi‑Fi és MQTT pedig megszakadás után újracsatlakozik.
A queue hossza 1 és xQueueOverwrite() mindig a legfrissebb mintát tartja meg. Lassú hálózat nem torlaszolja fel a régi méréseket.
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
const char* SSID="HALOZAT"; const char* PASS="JELSZO";
const char* BROKER="192.168.1.10";
DHT dht(4,DHT22); WiFiClient tcp; PubSubClient mqtt(tcp); QueueHandle_t samples;
struct Sample { float temperature,humidity; uint32_t at; };
void sensorTask(void*) {
TickType_t last=xTaskGetTickCount();
for(;;) {
Sample s{dht.readTemperature(),dht.readHumidity(),millis()};
if(!isnan(s.temperature) && !isnan(s.humidity)) xQueueOverwrite(samples,&s);
vTaskDelayUntil(&last,pdMS_TO_TICKS(2000));
}
}
bool ensureNetwork() {
if(WiFi.status()!=WL_CONNECTED) {
WiFi.begin(SSID,PASS); for(int i=0;i<50 && WiFi.status()!=WL_CONNECTED;i++) vTaskDelay(pdMS_TO_TICKS(100));
}
if(WiFi.status()!=WL_CONNECTED) return false;
if(!mqtt.connected()) mqtt.connect("esp32-lab");
return mqtt.connected();
}
void mqttTask(void*) {
Sample s; char payload[96]; mqtt.setServer(BROKER,1883);
for(;;) {
if(ensureNetwork()) { mqtt.loop();
if(xQueueReceive(samples,&s,pdMS_TO_TICKS(100))==pdTRUE) {
snprintf(payload,sizeof(payload),"{\"temperature\":%.1f,\"humidity\":%.1f,\"uptime\":%lu}",s.temperature,s.humidity,s.at);
mqtt.publish("lab/esp32/environment",payload,true);
}
} else vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void setup() {
Serial.begin(115200); dht.begin(); WiFi.mode(WIFI_STA);
samples=xQueueCreate(1,sizeof(Sample)); if(!samples) ESP.restart();
xTaskCreatePinnedToCore(sensorTask,"sensor",3072,nullptr,2,nullptr,1);
xTaskCreatePinnedToCore(mqttTask,"mqtt",6144,nullptr,1,nullptr,0);
}
void loop() { vTaskDelay(portMAX_DELAY); }mosquitto_sub -h BROKER_IP -t lab/esp32/environment -v.Adj hozzá MQTT felhasználót/jelszót, TLS tanúsítvány-ellenőrzést, véletlenített exponenciális backoffot, egyedi kliensazonosítót és Last Will üzenetet. A Wi‑Fi stack feladataival való versengést mérd, ne csak feltételezd.
Miért használunk egyetlen elemű queue-t ebben a telemetria-projektben?