ESP32: почему он стал стандартом IoT
ESP32 от Espressif Systems вышел в 2016 году и быстро стал самым популярным Wi-Fi/BT чипом для IoT. Причины:
240 МГц Xtensa LX6 (два ядра!) — серьёзная вычислительная мощь
Wi-Fi 802.11 b/g/n + Bluetooth 4.2/BLE — встроено в один чип
520 КБ SRAM + внешняя Flash — достаточно для реальных приложений
Богатая периферия: 18 каналов ADC, 2 DAC, 3 UART, 2 SPI, 2 I2C, I2S, CAN, Touch, Hall
Цена $2–5 (модуль ESP32-WROOM-32)
FreeRTOS в основе SDK — готовая RTOS "из коробки"
Семейство ESP32 сегодня:
ESP32 — оригинал, Xtensa LX6 240 МГц, Wi-Fi + BT Classic + BLE
ESP32-S2 — одно ядро, USB OTG, нет BT, дешевле
ESP32-S3 — два ядра, USB OTG, AI-расширения (ML)
ESP32-C3 — RISC-V одно ядро, Wi-Fi + BLE, ультрадешёвый (~$1)
ESP32-C6 — RISC-V, Wi-Fi 6, BLE 5, Thread/Zigbee (Matter)
ESP32-H2 — только BLE 5 + Thread (802.15.4), без Wi-Fi
Архитектура: два ядра и их назначение
ESP32 имеет два ядра Xtensa LX6 с именами PRO_CPU (ядро 0) и APP_CPU (ядро 1):
PRO_CPU (Protocol CPU, Core 0):
- Wi-Fi/Bluetooth стек (работает здесь)
- Системные задачи FreeRTOS
- Обработка прерываний от периферии
APP_CPU (Application CPU, Core 1):
- Ваш прикладной код
- Бизнес-логика
- Задачи реального времени приложения
При использовании Arduino framework — код в loop() выполняется на APP_CPU.
В ESP-IDF — вы явно указываете ядро при создании задачи:
// ESP-IDF: создание задач с привязкой к ядру
void wifi_task(void *pvParam)
{
// Эта задача работает на PRO_CPU — ближе к WiFi-стеку
while (1) {
// Сетевые операции
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void sensor_task(void *pvParam)
{
// Эта задача на APP_CPU — изолирована от WiFi-шумов
while (1) {
float adc_val = read_adc();
run_pid(adc_val);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void app_main(void)
{
// PRO_CPU (Core 0) — WiFi, сетевые задачи
xTaskCreatePinnedToCore(wifi_task, "WiFi", 4096, NULL, 5, NULL, 0);
// APP_CPU (Core 1) — приложение
xTaskCreatePinnedToCore(sensor_task, "Sensor", 4096, NULL, 4, NULL, 1);
}
Важно: WiFi-стек использует PRO_CPU интенсивно во время передачи. Задачи реального времени лучше держать на APP_CPU чтобы WiFi не вызывал джиттер.
WiFi: три режима работы
Station Mode (STA) — подключение к роутеру
// Arduino framework
#include <WiFi.h>
const char* SSID = "MyNetwork";
const char* PASSWORD = "MyPassword";
void wifi_connect() {
WiFi.mode(WIFI_STA);
WiFi.begin(SSID, PASSWORD);
// Статический IP (не DHCP) — обязательно для production!
IPAddress local_ip(192, 168, 1, 200);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress dns1(192, 168, 1, 1);
WiFi.config(local_ip, gateway, subnet, dns1);
Serial.print("Подключение к WiFi");
uint32_t timeout = millis() + 30000; // 30 секунд таймаут
while (WiFi.status() != WL_CONNECTED) {
if (millis() > timeout) {
Serial.println("\nОшибка подключения! Перезагрузка...");
ESP.restart();
}
delay(500);
Serial.print(".");
}
Serial.printf("\nПодключено! IP: %s, RSSI: %d dBm\n",
WiFi.localIP().toString().c_str(),
WiFi.RSSI());
}
// Мониторинг соединения в loop():
void check_wifi_reconnect() {
static uint32_t lastCheck = 0;
if (millis() - lastCheck < 5000) return;
lastCheck = millis();
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi потерян, переподключение...");
WiFi.disconnect();
WiFi.begin(SSID, PASSWORD);
}
}
Access Point Mode (AP) — ESP32 как точка доступа
void start_access_point() {
WiFi.mode(WIFI_AP);
// SSID, пароль, канал, скрытый?, макс клиентов
WiFi.softAP("ESP32-Config", "setup12345", 6, false, 4);
// Настройка IP точки доступа
IPAddress ap_ip(192, 168, 4, 1);
IPAddress ap_netmask(255, 255, 255, 0);
WiFi.softAPConfig(ap_ip, ap_ip, ap_netmask);
Serial.printf("AP запущен: %s, IP: %s\n",
WiFi.softAPSSID().c_str(),
WiFi.softAPIP().toString().c_str());
}
STA+AP (одновременно!) — для конфигурации устройства
void start_sta_ap_mode() {
WiFi.mode(WIFI_AP_STA);
// AP для настройки (пока устройство не настроено)
WiFi.softAP("ESP32-Setup");
// STA для рабочего подключения
if (has_credentials()) {
WiFi.begin(saved_ssid, saved_password);
}
}
BLE: Bluetooth Low Energy в деталях
BLE в ESP32 реализован через NimBLE (более лёгкий стек, рекомендуется) или Bluedroid.
GATT Server — ESP32 как BLE периферия
#include <NimBLEDevice.h>
// UUID сервисов и характеристик (генерируйте свои на uuidgenerator.net)
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define TEMPERATURE_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
#define CONTROL_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a9"
NimBLEServer* pServer = nullptr;
NimBLECharacteristic* pTempChar = nullptr;
NimBLECharacteristic* pCtrlChar = nullptr;
bool deviceConnected = false;
// Callbacks для событий подключения
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) override {
deviceConnected = true;
Serial.printf("BLE: клиент подключён, addr: %s\n",
NimBLEAddress(desc->peer_ota_addr).toString().c_str());
// Обновляем параметры соединения для лучшей производительности
pServer->updateConnParams(desc->conn_handle, 6, 6, 0, 100);
}
void onDisconnect(NimBLEServer* pServer) override {
deviceConnected = false;
Serial.println("BLE: клиент отключился");
// Перезапускаем рекламу
NimBLEDevice::startAdvertising();
}
};
// Callbacks для записи характеристики управления
class ControlCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic* pChar) override {
std::string value = pChar->getValue();
if (value.length() > 0) {
uint8_t command = value[0];
Serial.printf("BLE: команда получена: 0x%02X\n", command);
switch (command) {
case 0x01: relay_on(); break;
case 0x00: relay_off(); break;
}
}
}
};
void ble_init() {
NimBLEDevice::init("ESP32-Sensor");
NimBLEDevice::setMTU(185); // Увеличиваем MTU для больших пакетов
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
// Создаём сервис
NimBLEService* pService = pServer->createService(SERVICE_UUID);
// Характеристика температуры (только чтение + уведомления)
pTempChar = pService->createCharacteristic(
TEMPERATURE_UUID,
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
);
pTempChar->setValue(0.0f);
// Характеристика управления (запись)
pCtrlChar = pService->createCharacteristic(
CONTROL_UUID,
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR // NR = No Response (быстрее)
);
pCtrlChar->setCallbacks(new ControlCallbacks());
pService->start();
// Настройка рекламы
NimBLEAdvertising* pAdv = NimBLEDevice::getAdvertising();
pAdv->addServiceUUID(SERVICE_UUID);
pAdv->setScanResponse(true);
pAdv->setMinPreferred(0x06);
pAdv->start();
Serial.println("BLE запущен, ожидаем подключения...");
}
// Обновление данных температуры (вызывать периодически)
void ble_update_temperature(float temperature) {
if (!deviceConnected) return;
// Отправляем float как 4 байта
uint8_t data[4];
memcpy(data, &temperature, 4);
pTempChar->setValue(data, 4);
pTempChar->notify(); // Push уведомление подключённому клиенту
}
NVS: хранение настроек во Flash
NVS (Non-Volatile Storage) — key-value хранилище в Flash памяти ESP32. Пережи вает перезагрузки и обновления прошивки:
#include <Preferences.h> // Arduino framework
Preferences prefs;
struct DeviceConfig {
char mqtt_host[64];
uint16_t mqtt_port;
char device_id[32];
float setpoint;
bool auto_mode;
};
DeviceConfig config;
void config_load_defaults() {
strlcpy(config.mqtt_host, "192.168.1.100", sizeof(config.mqtt_host));
config.mqtt_port = 1883;
strlcpy(config.device_id, "esp32_001", sizeof(config.device_id));
config.setpoint = 25.0f;
config.auto_mode = true;
}
bool config_load() {
prefs.begin("config", true); // true = read-only
if (!prefs.isKey("mqtt_host")) {
prefs.end();
return false; // Первый запуск — нет сохранённых настроек
}
prefs.getString("mqtt_host", config.mqtt_host, sizeof(config.mqtt_host));
config.mqtt_port = prefs.getUShort("mqtt_port", 1883);
prefs.getString("device_id", config.device_id, sizeof(config.device_id));
config.setpoint = prefs.getFloat("setpoint", 25.0f);
config.auto_mode = prefs.getBool("auto_mode", true);
prefs.end();
return true;
}
void config_save() {
prefs.begin("config", false); // false = read-write
prefs.putString("mqtt_host", config.mqtt_host);
prefs.putUShort("mqtt_port", config.mqtt_port);
prefs.putString("device_id", config.device_id);
prefs.putFloat("setpoint", config.setpoint);
prefs.putBool("auto_mode", config.auto_mode);
prefs.end();
Serial.println("Конфигурация сохранена в NVS");
}
void config_reset() {
prefs.begin("config", false);
prefs.clear();
prefs.end();
Serial.println("NVS очищен, перезагрузка...");
ESP.restart();
}
// Использование:
void setup() {
if (!config_load()) {
Serial.println("Первый запуск, загружаем дефолты");
config_load_defaults();
config_save();
}
Serial.printf("MQTT: %s:%d\n", config.mqtt_host, config.mqtt_port);
}
OTA: обновление прошивки по воздухуESP32: глубокое погружение
#include <ArduinoOTA.h>
#include <Update.h>
// ===== ПРОСТОЕ OTA ЧЕРЕЗ Arduino IDE =====
void ota_init_arduino() {
ArduinoOTA.setHostname("esp32-gateway-001");
ArduinoOTA.setPassword("ota_secret_password");
ArduinoOTA.onStart([]() {
String type = ArduinoOTA.getCommand() == U_FLASH ? "прошивку" : "файловую систему";
Serial.printf("OTA: начало обновления %s\n", type.c_str());
// Останавливаем критичные задачи перед обновлением
mqtt_stop();
modbus_stop();
});
ArduinoOTA.onEnd([]() {
Serial.println("OTA: обновление завершено, перезагрузка...");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
static uint8_t last_pct = 0;
uint8_t pct = progress * 100 / total;
if (pct != last_pct && pct % 10 == 0) {
Serial.printf("OTA: %u%%\n", pct);
last_pct = pct;
}
});
ArduinoOTA.onError([](ota_error_t error) {
const char* errors[] = {"Auth Failed", "Begin Failed", "Connect Failed",
"Receive Failed", "End Failed"};
Serial.printf("OTA Ошибка[%u]: %s\n", error,
error <= 4 ? errors[error] : "Unknown");
});
ArduinoOTA.begin();
}
// В loop() добавить:
// ArduinoOTA.handle();
// ===== HTTP OTA: скачивание прошивки с сервера =====
#include <HTTPUpdate.h>
#include <WiFiClientSecure.h>
void ota_update_from_server(const char* server_url) {
Serial.printf("OTA: загрузка с %s\n", server_url);
WiFiClient client;
// Или WiFiClientSecure для HTTPS (настройте сертификат!)
httpUpdate.setLedPin(LED_BUILTIN, LOW);
// Callback прогресса
httpUpdate.onProgress([](int current, int total) {
Serial.printf("OTA: %d/%d bytes (%d%%)\n",
current, total, current * 100 / total);
});
t_httpUpdate_return ret = httpUpdate.update(client, server_url);
switch (ret) {
case HTTP_UPDATE_FAILED:
Serial.printf("OTA ошибка (%d): %s\n",
httpUpdate.getLastError(),
httpUpdate.getLastErrorString().c_str());
break;
case HTTP_UPDATE_NO_UPDATES:
Serial.println("OTA: нет обновлений");
break;
case HTTP_UPDATE_OK:
Serial.println("OTA: успех, перезагрузка...");
break;
}
}
// Проверка обновлений по расписанию:
void check_for_updates() {
static uint32_t lastCheck = 0;
const uint32_t CHECK_INTERVAL = 3600000UL; // 1 час
if (millis() - lastCheck < CHECK_INTERVAL) return;
lastCheck = millis();
// Проверяем версию на сервере
String server_version = http_get_json("/api/firmware/version")["version"];
if (server_version != FIRMWARE_VERSION) {
Serial.printf("Доступна новая версия: %s (текущая: %s)\n",
server_version.c_str(), FIRMWARE_VERSION);
ota_update_from_server("http://server/firmware/latest.bin");
}
}
Deep Sleep: энергосбережение
ESP32 в active mode потребляет ~80–240 мА. В deep sleep — 10 мкА!
#include <esp_sleep.h>
#include <esp_wifi.h>
// Типы пробуждения:
// - Таймер (RTC таймер)
// - GPIO (кнопка, сигнал)
// - Touch (сенсорные входы)
// - ULP (Ultra-Low Power co-processor)
// - UART (RXD0)
// - BT (в режиме light sleep)
void go_to_deep_sleep(uint32_t sleep_seconds) {
Serial.printf("Уходим в сон на %u секунд...\n", sleep_seconds);
Serial.flush();
// Закрываем WiFi перед сном (экономит время пробуждения)
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
// Настраиваем пробуждение по таймеру
esp_sleep_enable_timer_wakeup((uint64_t)sleep_seconds * 1000000ULL);
// Пробуждение от GPIO4 (нажатие кнопки)
esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, 0); // 0 = LOW уровень
// Входим в deep sleep
esp_deep_sleep_start();
// Код после этой строки не выполнится!
}
void setup() {
Serial.begin(115200);
// Определяем причину пробуждения
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
switch (cause) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Пробуждение: таймер");
send_sensor_data(); // Отправляем данные и снова в сон
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Пробуждение: кнопка");
// Пользователь нажал кнопку — полная работа
full_operation_mode();
return;
case ESP_SLEEP_WAKEUP_UNDEFINED:
Serial.println("Первый запуск или reset");
first_boot_setup();
break;
default:
Serial.printf("Причина: %d\n", cause);
}
// Снова в сон через 60 секунд
go_to_deep_sleep(60);
}
// RTC Memory: данные переживают deep sleep!
RTC_DATA_ATTR int boot_count = 0;
RTC_DATA_ATTR float last_temperature = 0.0f;
RTC_DATA_ATTR uint32_t error_count = 0;
void setup_with_rtc_memory() {
boot_count++;
Serial.printf("Загрузка #%d, последняя T=%.1f°C\n",
boot_count, last_temperature);
// Читаем датчик, сохраняем в RTC memory
last_temperature = read_temperature();
go_to_deep_sleep(300); // 5 минут
}
ADC: правильная работа с АЦП
АЦП ESP32 имеет репутацию "неточного". Это правда — и вот почему и как с этим работать:
#include <esp_adc/adc_oneshot.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
// Калиброванный ADC на ESP-IDF (точность ±5мВ вместо ±50мВ)
adc_oneshot_unit_handle_t adc1_handle;
adc_cali_handle_t cali_handle;
void adc_init_calibrated() {
// Инициализация ADC
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc1_handle));
// Настройка канала (GPIO34 = ADC1 Channel 6)
adc_oneshot_chan_cfg_t chan_config = {
.bitwidth = ADC_BITWIDTH_12,
.atten = ADC_ATTEN_DB_12, // 0-3.3В диапазон
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle,
ADC_CHANNEL_6, &chan_config));
// Калибровка (Line Fitting или Curve Fitting)
adc_cali_line_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_config, &cali_handle));
}
float adc_read_voltage_mv() {
int raw;
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, ADC_CHANNEL_6, &raw));
int voltage_mv;
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(cali_handle, raw, &voltage_mv));
return (float)voltage_mv;
}
// Oversampling для повышения точности (16x → +2 бита)
float adc_read_averaged(int samples = 64) {
int64_t sum = 0;
for (int i = 0; i < samples; i++) {
int raw;
adc_oneshot_read(adc1_handle, ADC_CHANNEL_6, &raw);
sum += raw;
delayMicroseconds(100);
}
int avg_raw = sum / samples;
int voltage_mv;
adc_cali_raw_to_voltage(cali_handle, avg_raw, &voltage_mv);
return (float)voltage_mv;
}
// Важные ограничения ADC ESP32:
// - GPIO36, 37, 38, 39: только вход, без pullup/pulldown в кристалле
// - ADC2 нельзя использовать одновременно с WiFi!
// - Нелинейность вблизи 0В и 3.3В — оставайтесь в диапазоне 100мВ..3.1В
// - Для точных измерений: внешний АЦП MCP3208 по SPI
Практический проект: промышленный IoT узел
// Полная архитектура ESP32 IoT узла
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Preferences.h>
// ===== КОНФИГУРАЦИЯ =====
#define FIRMWARE_VERSION "1.2.3"
#define DEVICE_MODEL "ESP32-IoT-Node"
#define PUBLISH_INTERVAL_MS 5000
#define WATCHDOG_TIMEOUT_MS 30000
// ===== ГЛОБАЛЬНОЕ СОСТОЯНИЕ =====
struct State {
bool wifi_connected = false;
bool mqtt_connected = false;
float temperature = 0;
float humidity = 0;
float pressure = 0;
uint32_t uptime_sec = 0;
uint32_t publish_count = 0;
uint32_t error_count = 0;
};
State state;
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
// ===== МНОГОЗАДАЧНОСТЬ =====
QueueHandle_t sensorQueue;
SemaphoreHandle_t stateMutex;
// Задача: чтение датчиков (Core 1)
void task_sensors(void *pv) {
for (;;) {
// Здесь: читаем датчики
float t = 25.0 + random(-10, 10) / 10.0; // Имитация
float h = 50.0 + random(-5, 5) / 10.0;
// Обновляем состояние через мьютекс
xSemaphoreTake(stateMutex, portMAX_DELAY);
state.temperature = t;
state.humidity = h;
xSemaphoreGive(stateMutex);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// Задача: MQTT публикация (Core 0, рядом с WiFi)
void task_mqtt(void *pv) {
for (;;) {
if (!mqtt.connected()) {
if (WiFi.isConnected()) {
if (mqtt.connect("esp32-node", "user", "pass",
"nodes/esp32-001/status", 1, true, "{\"online\":false}")) {
mqtt.publish("nodes/esp32-001/status", "{\"online\":true}", true);
mqtt.subscribe("nodes/esp32-001/commands");
}
}
}
mqtt.loop();
// Публикация данных
static uint32_t lastPublish = 0;
if (millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
lastPublish = millis();
StaticJsonDocument<256> doc;
xSemaphoreTake(stateMutex, portMAX_DELAY);
doc["temperature"] = state.temperature;
doc["humidity"] = state.humidity;
doc["uptime"] = state.uptime_sec;
doc["errors"] = state.error_count;
doc["version"] = FIRMWARE_VERSION;
doc["rssi"] = WiFi.RSSI();
doc["free_heap"] = ESP.getFreeHeap();
xSemaphoreGive(stateMutex);
char payload[256];
serializeJson(doc, payload);
mqtt.publish("nodes/esp32-001/telemetry", payload);
state.publish_count++;
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// Задача: watchdog и uptime (Core 1)
void task_system(void *pv) {
for (;;) {
state.uptime_sec++;
// Проверка heap (memory leak detection)
if (ESP.getFreeHeap() < 10000) {
Serial.println("КРИТИЧНО: мало памяти! Перезагрузка...");
ESP.restart();
}
// Heartbeat LED
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
Serial.begin(115200);
Serial.printf("\nESP32 IoT Node v%s\n", FIRMWARE_VERSION);
Serial.printf("Chip: %s, Rev: %d, Cores: %d\n",
ESP.getChipModel(), ESP.getChipRevision(), ESP.getChipCores());
// Инициализация
stateMutex = xSemaphoreCreateMutex();
sensorQueue = xQueueCreate(10, sizeof(float));
// WiFi
WiFi.mode(WIFI_STA);
WiFi.begin("SSID", "PASSWORD");
// MQTT
mqtt.setServer("192.168.1.100", 1883);
mqtt.setBufferSize(1024);
// Запускаем задачи на разных ядрах
xTaskCreatePinnedToCore(task_sensors, "Sensors", 4096, NULL, 3, NULL, 1);
xTaskCreatePinnedToCore(task_mqtt, "MQTT", 8192, NULL, 4, NULL, 0);
xTaskCreatePinnedToCore(task_system, "System", 2048, NULL, 1, NULL, 1);
Serial.println("Задачи запущены");
}
void loop() {
// loop() работает на Core 1 с низким приоритетом
// Можно использовать для некритичных задач или оставить пустым
vTaskDelay(pdMS_TO_TICKS(1000));
}
Выбор инструментария: Arduino vs ESP-IDF
Критерий | Arduino Framework | ESP-IDF (native) |
|---|---|---|
Порог входа | Низкий | Высокий |
Документация | Обширная, много примеров | Официальная, полная |
Производительность | Достаточная | Максимальная |
Доступ к периферии | Через библиотеки | Прямой |
Размер бинарника | Больше | Меньше |
RTOS | Доступен (FreeRTOS через задачи) | Нативный |
Время разработки | Быстрее | Медленнее |
Production-ready | Да (если делать правильно) | Да |
Рекомендация | Прототипы, несложные задачи | Серийное производство |
Заключение
ESP32 — один из лучших выборов для промышленных IoT-узлов с умеренными требованиями к реальному времени. Двухъядерность позволяет изолировать WiFi-стек от прикладного кода, богатая периферия закрывает большинство интерфейсных задач, встроенный FreeRTOS — для многозадачности.
Ключевые принципы надёжного ESP32-устройства: статический IP вместо DHCP, watchdog timer, NVS для конфигурации, OTA для обновлений, RTC memory для данных через sleep, мониторинг heap и перезагрузка при критичном уровне.
ESP32 — это не замена промышленному ПЛК. Но как edge-узел сбора данных, шлюз протоколов или умный датчик — идеальный выбор.
Create an account or sign in to leave a review
There are no reviews to display.