Время чтения: 25 минут | Технология: IoT Development | Платформы: ESP8266, ESP32
Разработка проектов для нескольких платформ (ESP8266 и ESP32) в одном проекте может быть сложной задачей, но с правильными подходами это становится управляемым процессом. В этой статье мы рассмотрим, как эффективно использовать обе платформы в одном проекте с помощью Arduino IDE, избегая распространенных ошибок.

Файл <span class="token operator">></span> Параметры (File > Preferences)Additional Boards Manager URLs добавьте:
http://arduino.esp8266.com/stable/package_esp8266com_index.json

Tools > Board > Boards Manageresp8266 и установите последнюю версиюAdditional Boards Manager URLs добавьте:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
Инструменты > Плата > Менеджер плат… (Tools > Board > Boards Manager)esp32 и установите последнюю версию
#ifdef (if defined) — это директива препроцессора, которая позволяет включать или исключать части кода в зависимости от определенных макросов. Это полезно для разделения кода между разными платформами.
#ifdef ESP32
#include <WiFi.h>
#include <SPI.h>
#else
#include <ESP8266WiFi.h>
#include <SPI.h>
#endif
void setup() {
Serial.begin(115200);
#ifdef ESP32
Serial.println("Running on ESP32");
#else
Serial.println("Running on ESP8266");
#endif
// Общий код для обеих платформ
setupWiFi();
}
void loop() {
// Общий код для обеих платформ
mainLoop();
}
#ifdef ESP32
#define LED_PIN 2
#define BUTTON_PIN 4
#else
#define LED_PIN 0
#define BUTTON_PIN 14
#endif
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) {
digitalWrite(LED_PIN, HIGH);
delay(1000);
digitalWrite(LED_PIN, LOW);
}
}
main.ino — основной файл с общей логикойesp32_specific.cpp — код, специфичный для ESP32esp8266_specific.cpp — код, специфичный для ESP8266common.cpp — общие функции и реализации#pragma once #include "config.h" // Объявляем функции, доступные везде void setupWiFi(); void loopCommon();
#pragma once // Современная замена #ifndef/#define для защиты от двойного включения // ИСПРАВЛЕНИЕ ОШИБКИ: Подключаем Arduino API для всех .cpp файлов #include <Arduino.h> // --- Абстракция железа --- #if defined(ESP32) #define BOARD_NAME "ESP32" #define LED_PIN 2 #define BTN_PIN 4 #define ANALOG_PIN 34 // Логика светодиода (Прямая) #define LED_ON HIGH #define LED_OFF LOW #elif defined(ESP8266) #define BOARD_NAME "ESP8266" #define LED_PIN LED_BUILTIN // Обычно GPIO 2 #define BTN_PIN 0 // Кнопка FLASH #define ANALOG_PIN A0 // Только один АЦП // Логика светодиода (Инвертированная на большинстве NodeMCU/Wemos) #define LED_ON LOW #define LED_OFF HIGH #else #error "Неподдерживаемая плата! Выберите ESP32 или ESP8266 в меню Tools." #endif // Общие константы #define VERSION "1.0.0" #define WIFI_SSID "Your_SSID" #define WIFI_PASS "Your_Password"
WiFi.h для ESP32 и ESP8266WiFi.h для ESP8266.#include "common.h"
// Условное подключение нужной библиотеки Wi-Fi
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
void setupWiFi() {
Serial.printf("[%s] Connecting to %s", BOARD_NAME, WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Используем printf вместо String() для экономии памяти (важно для ESP8266)
Serial.printf("\nWiFi connected! IP: %s\n", WiFi.localIP().toString().c_str());
}
void loopCommon() {
// Мигаем светодиодом, используя наши абстракции LED_ON / LED_OFF
digitalWrite(LED_PIN, LED_ON);
delay(500);
digitalWrite(LED_PIN, LED_OFF);
delay(500);
}
#ifdef ESP8266 #define MAX_DATA_SIZE 1024 #else #define MAX_DATA_SIZE 4096 #endif char dataBuffer[MAX_DATA_SIZE];
void setup() {</p><p> Serial.begin(115200);
delay(1000); // Даем время на инициализацию Serial
Serial.printf("\n--- Booting %s (v%s) ---\n", BOARD_NAME, VERSION);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LED_OFF);
// Вызываем платформо-зависимую инициализацию
setupSpecific();
// Вызываем общую инициализацию
setupWiFi();
}
void loop() {
// Выполняем общую логику (мигание LED, поддержание связи)
loopCommon();
}
Первое правило мультиплатформенности: никогда не пишите всё в одном .ino файле. Разделяйте логику.
Создайте папку проекта (например, main) со следующей структурой:
CrossPlatformIoT/ │ ├── main.ino # Главный файл (точка входа) ├── config.h # Абстракция железа (пины, макросы) ├── common.h # Объявления общих функций ├── common.cpp # Реализация общей логики (Wi-Fi, основной цикл) ├── esp32_specific.cpp # Код, который скомпилируется ТОЛЬКО для ESP32 ├── esp32_specific.h ├── esp8266_specific.cpp # Код, который скомпилируется ТОЛЬКО для ESP8266 └── esp8266_specific.h
config.h и решение проблемы SerialСамая частая ошибка при выносе кода в .cpp файлы — error: 'Serial' was not declared in this scope.
Решение: Явно подключать #include <Arduino.h> в заголовочный файл конфигурации.

Также мы сразу абстрагируем пины. Обратите внимание: на многих платах ESP8266 встроенный светодиод включается при подаче LOW, а на ESP32 — при HIGH. Мы решим это макросами LED_ON и LED_OFF.
config.h#pragma once // Современная замена #ifndef/#define для защиты от двойного включения // ИСПРАВЛЕНИЕ ОШИБКИ: Подключаем Arduino API для всех .cpp файлов #include <Arduino.h> // --- Абстракция железа --- #if defined(ESP32) #define BOARD_NAME "ESP32" #define LED_PIN 2 #define BTN_PIN 4 #define ANALOG_PIN 34 // Логика светодиода (Прямая) #define LED_ON HIGH #define LED_OFF LOW #elif defined(ESP8266) #define BOARD_NAME "ESP8266" #define LED_PIN LED_BUILTIN // Обычно GPIO 2 #define BTN_PIN 0 // Кнопка FLASH #define ANALOG_PIN A0 // Только один АЦП // Логика светодиода (Инвертированная на большинстве NodeMCU/Wemos) #define LED_ON LOW #define LED_OFF HIGH #else #error "Неподдерживаемая плата! Выберите ESP32 или ESP8266 в меню Tools." #endif // Общие константы #define VERSION "1.0.0" #define WIFI_SSID "Your_SSID" #define WIFI_PASS "Your_Password"
Библиотеки Wi-Fi называются по-разному, но их API (методы WiFi.begin, WiFi.status) почти идентичны.
common.h#pragma once #include "config.h" // Объявляем функции, доступные везде void setupWiFi(); void loopCommon();
common.cpp#include "common.h"
// Условное подключение нужной библиотеки Wi-Fi
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
void setupWiFi() {
Serial.printf("[%s] Connecting to %s", BOARD_NAME, WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Используем printf вместо String() для экономии памяти (важно для ESP8266)
Serial.printf("\nWiFi connected! IP: %s\n", WiFi.localIP().toString().c_str());
}
void loopCommon() {
// Мигаем светодиодом, используя наши абстракции LED_ON / LED_OFF
digitalWrite(LED_PIN, LED_ON);
delay(500);
digitalWrite(LED_PIN, LED_OFF);
delay(500);
}
Допустим, нам нужно читать АЦП. У ESP32 он 12-битный (0-4095), а у ESP8266 — 10-битный (0-1023). Мы можем написать специфичные функции, которые приведут данные к общему виду, и разнести их по разным файлам.
Лайфхак: Благодаря директивам#ifdefвнутри самих файлов, мы можем даже не использовать#include "esp32_specific.h"в главном файле. Компилятор просто проигнорирует "чужой" файл.
esp32_specific.cpp
#include "config.h"
#ifdef ESP32 // Этот код скомпилируется только если выбрана ESP32
void setupSpecific() {
Serial.println("Инициализация специфичных модулей ESP32 (например, Hall sensor)...");
pinMode(BTN_PIN, INPUT_PULLUP);
}
int readAnalogSpecific() {
int raw = analogRead(ANALOG_PIN);
// Приводим 12-бит (4095) к 10-бит (1023) для совместимости
return map(raw, 0, 4095, 0, 1023);
}
#endifesp8266_specific.cpp#include "config.h"
#ifdef ESP8266 // Этот код скомпилируется только если выбрана ESP8266
void setupSpecific() {
Serial.println("Инициализация специфичных модулей ESP8266...");
pinMode(BTN_PIN, INPUT_PULLUP);
}
int readAnalogSpecific() {
// На ESP8266 АЦП сразу выдает 10-бит (0-1023)
return analogRead(ANALOG_PIN);
}
#endifesp32_specific.h и esp8266_specific.hВ обоих файлах мы просто декларируем функции, чтобы main.ino их "видел".
#pragma once void setupSpecific(); int readAnalogSpecific();
Теперь собираем всё вместе. Код выглядит максимально чисто, а вся грязная работа с #ifdef спрятана внутри .cpp файлов и заголовков.
main.ino#include "config.h"
#include "common.h"
// Подключаем заголовки.
// "Лишние" функции просто не скомпилируются благодаря #ifdef внутри .cpp файлов
#include "esp32_specific.h"
#include "esp8266_specific.h"
void setup() {
Serial.begin(115200);
delay(1000); // Даем время на инициализацию Serial
Serial.printf("\n--- Booting %s (v%s) ---\n", BOARD_NAME, VERSION);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LED_OFF);
// Вызываем платформо-зависимую инициализацию
setupSpecific();
// Вызываем общую инициализацию
setupWiFi();
}
void loop() {
// Выполняем общую логику (мигание LED, поддержание связи)
loopCommon();
// Читаем АЦП (каждая плата использует свою логику преобразования)
int sensorValue = readAnalogSpecific();
// Выводим раз в 5 секунд
static unsigned long lastPrint = 0;
if (millis() - lastPrint > 5000) {
Serial.printf("Sensor value (normalized 10-bit): %d\n", sensorValue);
lastPrint = millis();
}
}
String в Serial.print.Как видно в примере выше, вместо:
Serial.println("Starting " + String(BOARD_NAME) + " project");
Используйте:
Serial.printf("Starting %s project\n", BOARD_NAME);
Это критически важно для ESP8266, так как динамическое выделение памяти под String быстро фрагментирует кучу и приводит к перезагрузкам (WDT Reset).
Если вам нужно хранить большие массивы данных (HTML-страницы, сертификаты), используйте макрос PROGMEM.
На ESP8266 чтение из PROGMEM требует функции pgm_read_byte, а ESP32 читает её как обычную память. Чтобы код был универсальным, используйте PSTR() для строк или храните статические данные во LittleFS.
Никогда не вызывайте специфичные функции (например, ledcSetup на ESP32) напрямую в .ino файле. Всегда создавайте "обертки" (wrapper functions) в config.h или специфичных .cpp файлах, как мы сделали с readAnalogSpecific().
API для сна различаются радикально. Используйте макросы:
void goToSleep(uint32_t seconds) {
#ifdef ESP32
esp_sleep_enable_timer_wakeup(seconds * 1000000ULL);
esp_deep_sleep_start();
#elif defined(ESP8266)
ESP.deepSleep(seconds * 1000000ULL);
#endif
}#error для защитыЕсли ваш код предназначен только для ESP-семейства, добавьте в config.h блок #else #error .... Если вы случайно скомпилируете проект для Arduino Uno, компилятор выдаст понятную ошибку, а не сотни строк непонятного спама об отсутствии WiFi.h.

Написание кроссплатформенного кода требует дисциплины. Главный секрет — абстракция.
Выделите всё, что зависит от "железа" (номера пинов, библиотеки, разрядность АЦП), в заголовочный файл config.h и специфичные .cpp файлы. А в главном .ino файле пишите только бизнес-логику вашего устройства.
Такой подход позволит вам в будущем легко добавить поддержку, например, ESP32-C3 или Raspberry Pi Pico W, просто дописав еще один блок #elif defined(...) в конфигурации, не переписывая при этом основной код проекта!
Дополнительные материалы:
Понравилась статья Введение в мультиплатформенную разработку для ESP8266 и ESP32? Не забудь поделиться с друзьями в соц. сетях.
А также подписаться на наш канал на VK Видео, вступить в группу Вконтакте.
Спасибо за внимание!
Технологии начинаются с простого!
Фотографии к статье
Файлы для скачивания
| Код ESP8266 + ESP32.zip | 4 Kb | 23 | Скачать |
Статьи про ESP32, ESP8266
31 июля , 2026
Комментариев:0
Файлов для скачивания:1
Фото:7
Понравилась статья? Нажми
Читайте также
Мы в соц сетях
Комментарии