Подключение ESP32 к Wi-Fi: клиент, точка доступа и переподключение
Wi-Fi — главная причина, по которой берут ESP32 вместо обычной Arduino. Базовое
подключение умещается в пять строк, но скетч из первого примера в интернете обычно
намертво зависает в setup(), если роутер недоступен, и не восстанавливает связь
после разрыва. Ниже — оба режима работы, надёжная схема подключения и разбор
проблем, на которые уходит больше всего времени.
Примеры рассчитаны на пакет плат esp32 by Espressif Systems 3.x; если он ещё не установлен, начните с настройки Arduino IDE для ESP32.
Три режима работы
ESP32 умеет быть клиентом чужой сети, собственной точкой доступа или тем и другим одновременно.
| Режим | Константа | Что делает |
|---|---|---|
| Клиент | WIFI_STA | Подключается к роутеру, получает IP от него |
| Точка доступа | WIFI_AP | Раздаёт свою сеть, клиенты подключаются к плате |
| Совмещённый | WIFI_AP_STA | Одновременно клиент и точка доступа |
Режим клиента нужен, когда плата должна ходить в интернет или быть доступной в домашней сети. Точка доступа — когда роутера рядом нет: телефон подключается прямо к плате, например для первичной настройки устройства.
Подключение к сети: минимальный скетч
#include <WiFi.h>
const char* ssid = "MyNetwork";
const char* password = "MyPassword";
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Подключение");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("IP-адрес: ");
Serial.println(WiFi.localIP());
}
void loop() {
}
Такой скетч годится для проверки связи и первых экспериментов, но в готовом
устройстве его использовать нельзя: цикл while блокирует выполнение навсегда, если
роутер выключен или пароль неверный. Плата не мигнёт светодиодом, не ответит по
Serial и будет выглядеть зависшей.
Строго говоря, WiFi.begin() включает режим клиента сам, и для простого
подключения отдельный WiFi.mode(WIFI_STA) не обязателен. Но выключить точку
доступа он не умеет — только добавляет к текущему режиму роль клиента. Поэтому
если раньше в той же программе поднимался softAP(), плата окажется в режиме
WIFI_AP_STA и продолжит раздавать свою сеть. Явный mode() перед begin() эту
ситуацию закрывает и делает намерение очевидным для читателя кода.
Имя сети и пароль в примерах записаны прямо в коде — так удобно проверять, но не
стоит в таком виде коммитить проект в публичный репозиторий. Вынесите их в отдельный
заголовочный файл (secrets.h) и добавьте его в .gitignore, либо храните в
Preferences и задавайте через режим точки доступа при первой настройке.
Статусы подключения
WiFi.status() возвращает не только «подключено / не подключено» — по коду видно,
на каком шаге всё сломалось.
| Значение | Что означает |
|---|---|
WL_IDLE_STATUS | Подключение начато, идёт процесс |
WL_NO_SSID_AVAIL | Сеть с таким именем не найдена |
WL_CONNECT_FAILED | Найдена, но подключиться не удалось — обычно пароль |
WL_CONNECTED | Подключено, IP получен |
WL_CONNECTION_LOST | Связь была и пропала |
WL_DISCONNECTED | Не подключено |
Если в мониторе порта видно WL_NO_SSID_AVAIL при том, что сеть точно есть, —
почти наверняка она работает в диапазоне 5 ГГц (см. раздел о проблемах).
Подключение без блокировки loop
Рабочая схема выглядит иначе: setup() только запускает подключение, а loop()
продолжает работать и периодически проверяет связь. Устройство остаётся живым, даже
если сети нет.
#include <WiFi.h>
const char* ssid = "MyNetwork";
const char* password = "MyPassword";
const unsigned long reconnectInterval = 10000;
unsigned long lastReconnectAttempt = 0;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(ssid, password);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
unsigned long now = millis();
if (now - lastReconnectAttempt >= reconnectInterval) {
lastReconnectAttempt = now;
Serial.println("Нет связи, повторное подключение");
WiFi.disconnect();
WiFi.begin(ssid, password);
}
return;
}
// Основная работа устройства
}
Два важных момента. Повторные попытки идут по таймеру на millis(), а не через
delay() — иначе вернётся та же блокировка. И перед новым begin() вызывается
disconnect(): без него стек может остаться в промежуточном состоянии и игнорировать
попытки подключения.
События вместо опроса
Опрашивать WiFi.status() в цикле просто, но не всегда удобно: логика подключения
размазывается по loop(). Альтернатива — подписаться на события стека и реагировать
на них.
#include <WiFi.h>
void onWiFiEvent(arduino_event_id_t event, arduino_event_info_t info) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
Serial.println("Подключено к точке доступа");
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
// Адрес берём из самого события, а не через WiFi.localIP()
Serial.print("Получен IP: ");
Serial.println(IPAddress(info.got_ip.ip_info.ip.addr));
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
Serial.println("Связь потеряна");
break;
default:
break;
}
}
void setup() {
Serial.begin(115200);
WiFi.onEvent(onWiFiEvent);
WiFi.mode(WIFI_STA);
WiFi.begin("MyNetwork", "MyPassword");
}
void loop() {
}
Обратите внимание: подключение к точке доступа и получение IP — разные события.
Отправлять данные в сеть можно только после GOT_IP; сразу после STA_CONNECTED
адреса ещё нет. Адрес приходит прямо в структуре события — в примере он берётся
оттуда, а не отдельным запросом к стеку.
Обработчик выполняется в отдельной задаче стека Wi-Fi, а не в loop(). Держите его
коротким: никаких delay(), циклов ожидания и сетевых запросов — HTTP, MQTT, работы
с сокетами. Всё это блокирует стек и роняет соединение. Правильный приём — выставить
в обработчике флаг и сделать полезную работу в loop().
Статический IP
По умолчанию адрес выдаёт роутер по DHCP, и он может меняться. Если к плате нужно
обращаться по фиксированному адресу, задайте его до begin():
IPAddress localIp(192, 168, 1, 50);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDns(8, 8, 8, 8);
WiFi.config(localIp, gateway, subnet, primaryDns);
WiFi.begin(ssid, password);
Адрес должен лежать в подсети роутера и вне диапазона его DHCP-пула, иначе он рано или поздно достанется другому устройству. Часто удобнее не трогать плату, а закрепить адрес за её MAC на самом роутере.
Заодно стоит задать имя устройства в сети — тогда плату будет видно в списке
клиентов роутера под понятным именем. Порядок вызовов здесь принципиален:
setHostname() должен идти до WiFi.mode() и WiFi.begin(), иначе он молча не
подействует и в списке клиентов останется имя вида espressif.
void setup() {
Serial.begin(115200);
WiFi.setHostname("roboarm"); // строго до mode() и begin()
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
}
Несколько сетей: WiFiMulti
Если устройство переезжает между домом и мастерской, пропишите обе сети — плата сама выберет доступную с лучшим сигналом.
#include <WiFi.h>
#include <WiFiMulti.h>
WiFiMulti wifiMulti;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
wifiMulti.addAP("HomeNetwork", "homePassword");
wifiMulti.addAP("WorkshopNetwork", "workshopPassword");
if (wifiMulti.run(15000) == WL_CONNECTED) {
Serial.println(WiFi.localIP());
}
}
void loop() {
wifiMulti.run();
}
Аргумент run() — таймаут в миллисекундах. Вызов в loop() сам восстанавливает
связь при обрыве, поэтому отдельная логика переподключения не нужна.
Точка доступа
В режиме AP плата поднимает собственную сеть. Это стандартный способ настроить устройство, когда домашнего Wi-Fi ещё нет: пользователь подключается к сети платы с телефона и открывает страницу настроек.
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_AP);
WiFi.softAP("Robot-Setup", "12345678");
Serial.print("Адрес точки доступа: ");
Serial.println(WiFi.softAPIP());
}
void loop() {
}
Адрес по умолчанию — 192.168.4.1. Пароль должен быть не короче восьми символов:
при более коротком softAP() вернёт false и точка доступа не поднимется вообще —
ни защищённой, ни открытой. Проверять результат стоит всегда:
if (!WiFi.softAP("Robot-Setup", "12345678")) {
Serial.println("Не удалось поднять точку доступа");
}
Чтобы сделать сеть открытой намеренно, передайте только имя — параметр пароля по
умолчанию равен NULL, и режим шифрования останется открытым:
WiFi.softAP("Robot-Setup");
Совмещённый режим включается одной строкой и позволяет одновременно быть в домашней сети и держать свою:
WiFi.mode(WIFI_AP_STA);
WiFi.softAP("Robot-Setup", "12345678");
WiFi.begin("MyNetwork", "MyPassword");
У ESP32 один радиомодуль на оба режима, поэтому в WIFI_AP_STA точка доступа
работает на том же канале, что и подключение к роутеру. Скорость и стабильность
обеих сетей при этом заметно падают — режим хорош для настройки, но не для
постоянной работы.
Сканирование сетей
Список доступных сетей пригодится и для отладки, и для экрана выбора сети в интерфейсе устройства.
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
int networkCount = WiFi.scanNetworks();
for (int i = 0; i < networkCount; i++) {
Serial.printf("%s %d dBm %s\n",
WiFi.SSID(i).c_str(),
WiFi.RSSI(i),
WiFi.encryptionType(i) == WIFI_AUTH_OPEN ? "открытая" : "защищённая");
}
}
void loop() {
}
Уровень сигнала RSSI измеряется в дБм и всегда отрицательный: от −30 (плата рядом с
роутером) до −90 (на грани разрыва). Значения хуже −80 дБм означают, что связь будет
рваться, — двигайте плату или ставьте модуль с внешней антенной.
Скрытые сети в этот список по умолчанию не попадают. Чтобы их увидеть, включите
показ явно: WiFi.scanNetworks(false, true) — первый аргумент отвечает за
асинхронный режим, второй как раз за скрытые сети. У них будет пустой SSID(),
опознать точку можно только по BSSID.
Частые проблемы
Сеть не находится, хотя она есть. Классический ESP32, а также ESP32-S3, ESP32-C3 и ESP32-C6 работают только в диапазоне 2,4 ГГц. Если роутер раздаёт 2,4 и 5 ГГц под одним именем, отключите 5 ГГц на время проверки или создайте отдельную гостевую сеть на 2,4 ГГц. Двухдиапазонный только ESP32-C5.
Пароль верный, но подключения нет. Посмотрите на режим шифрования: WPA3 (SAE)
ESP32 поддерживает, но на связке «роутер в режиме WPA3-only + старая версия пакета
плат» подключение иногда не проходит. Если упёрлись — переключите роутер на смешанный
режим WPA2/WPA3 и обновите пакет esp32 до актуальной версии.
Имя сети на кириллице. Само по себе оно не ломается: SSID — это просто 32 байта, и стек работает с ними как с байтами. Но подводных камней два. Espressif поддерживает только кодировку UTF-8 и в своей документации прямо не рекомендует не-ASCII имена из-за неоднозначности кодировок. И лимит в 32 байта для кириллицы означает всего около 16 символов, потому что каждый занимает по два байта.
Скрытая сеть. Подключаться к ней можно обычным WiFi.begin(ssid, password) —
канал и BSSID указывать не обязательно, их значения по умолчанию (0 и NULL)
работают. А вот увидеть такую сеть в списке сканирования по умолчанию нельзя, для
этого нужен отдельный флаг (см. раздел про сканирование).
Плата перезагружается в момент подключения. В логе будет Brownout detector was triggered. Включение радиомодуля даёт скачок тока до 300–500 мА, и питания по
дешёвому USB-кабелю не хватает. Смените кабель на короткий и толстый, воткните в порт
компьютера напрямую или запитайте плату отдельным источником.
Перестали работать аналоговые входы. При активном Wi-Fi недоступен ADC2 — это пины GPIO 0, 2, 4, 12–15, 25–27 у классического ESP32. Переносите аналоговые датчики на ADC1: GPIO 32–39.
Связь рвётся раз в несколько минут. Отключите энергосбережение радиомодуля, если устройство должно отвечать мгновенно:
WiFi.setSleep(false);
Ток потребления при этом вырастет примерно на 30–40 мА — для устройств с питанием от сети это приемлемая плата за стабильность.
Источники
Примеры проверены на пакете плат esp32 by Espressif Systems 3.x. Имена событий и сигнатуры функций в версиях 2.x отличаются — если пакет старый, сверяйтесь с документацией своей версии.
- Arduino-ESP32: Wi-Fi API — режимы, события и методы класса
WiFi. - ESP-IDF: Wi-Fi Driver — поведение стека, события и режимы энергосбережения.
- ESP-FAQ: Wi-Fi — скрытые сети и оговорки про не-ASCII имена сетей.
Что дальше
- Если скетч не компилируется или не заливается — Ошибки Arduino IDE: компиляция, загрузка и порт.
- Установка пакета плат и первый скетч — Настройка Arduino IDE для работы с ESP32.
- Прошивка из командной строки — Установка esptool под Windows, macOS и Linux.