Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

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-узел сбора данных, шлюз протоколов или умный датчик — идеальный выбор.

User Feedback

Create an account or sign in to leave a review

There are no reviews to display.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.