Ну вот, наконец-то, дошли мы до той темы, которую, я думаю, многие ждали — это приём и передача данных при помощи возможностей ESP32 по беспроводной сети Wi-Fi.
С данной темой мы уже работали с применением контроллера ESP8266, причём используя различные протоколы передачи данных модели OSI, поэтому нам будет намного легче.
Мы также подробно не будем изучать протокол Wi-Fi, так как с ним работает наш контроллер аппаратно. Также помогает в этом комплект IDF. Режимы работы WiFi контроллера — это станция (или кратко STA) и точка доступа (AP). Также мы можем одновременно использовать четыре виртуальных интерфейса WiFi.
Самым простым из этих режимов является первый — режим станции.
Режим станции (STA) — это такой режим, в котором контроллер не создает собственную сеть, а подключается к любой существующей сети Wi-Fi, например, к существующей локальной сети или к другому устройству, работающему в режиме точки доступа (AP).
На данном уроке мы настроим режим станции и попробуем подключиться к существующей точке доступа, например к роутеру, который раздаёт Wi-Fi по дому. Роутер от просто точки доступа отличается тем, что трафик, идущий от станций, подключенных к нему, он транслирует во внешнюю сеть и наоборот.
Схема у нас будет простейшая — отладочная плата, подключенная к USB компьютера
А проект мы на этот раз создадим новый. Как создавать новый проект, мы знаем из урока 3.
Но тем не менее я этот процесс покажу.
Где-нибудь в районе дерева проекта вызовем контекстное меню (правый щелчок мыши) и выберем пункт New->Espressif IDF Project
Придумаем имя и жмём Finish
Как обычно, мы получим готовый проект с неким кодом в файле main.c.
Также если мы откроем файл Kconfig.projbuild, то мы также там увидим два пункта для конфигурирования имени точки доступа, к которой мы подключаемся, а также для пароля (или ключа) подключения к точке доступа. Выше пункта ESP_WIFI_SSID добавим пункт по настройке светодиода, так как он нам пригодится для сигнализации подключения к точке доступа
1 2 3 4 5 6 |
config LED_GPIO int "LED GPIO number" range 0 48 default 2 help LED GPIO. |
А ниже пункта ESP_WIFI_PASSWORD добавим пункт для настройке максимального количества попыток подключения к точке доступа
1 2 3 4 5 |
config ESP_MAXIMUM_RETRY int "Maximum retry" default 5 help Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent. |
Сохраним данный файл и добавим в дерево файл main.h следующего содержания
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef MAIN_MAIN_H_ #define MAIN_MAIN_H_ //------------------------------------------------------------- #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "esp_system.h" #include "esp_log.h" #include "nvs_flash.h" //------------------------------------------------------------- #endif /* MAIN_MAIN_H_ */ |
Откроем файл main.c, удалим оттуда подключения всех библиотек и подключим только что созданный заголовочный файл
1 2 |
#include "main.h" //------------------------------------------------------------- |
Удалим функцию event_handler вместе с телом а в функции app_main оставим только бесконечный цикл с задержкой, которую немного увеличим
1 2 3 4 5 6 |
void app_main(void) { while (true) { vTaskDelay(1000 / portTICK_PERIOD_MS); } } |
Попробуем собрать проект, он скорей всего соберётся. Также зайдём в конфигуратор, проверим, что у нас правильный порт для светодиода, а также сразу в данные поля можем занести имя точки доступа и пароль
Объявим глобальный указатель для имени в логах
1 2 3 4 |
#include "main.h" //------------------------------------------------------------- static const char *TAG = "main"; //------------------------------------------------------------- |
В функции app_main объявим переменную типа структуры для получения свойств соединения впоследствии
1 2 3 |
void app_main(void) { wifi_ap_record_t info; |
Код скорей всего не соберётся. Но так как мы основной код по работе с WiFi вынесем в отдельный модуль, то в нём и подключим необходимые библиотеки, которые будут видны и здесь. Поэтому давайте данный модуль создадим. Это будут два файла следующего содержания
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#ifndef MAIN_WIFI_H_ #define MAIN_WIFI_H_ //------------------------------------------------------------- #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "esp_log.h" #include "esp_event.h" #include "esp_wifi.h" //------------------------------------------------------------- //------------------------------------------------------------- #endif /* MAIN_WIFI_H_ */ |
1 2 |
#include "wifi.h" //------------------------------------------------------------- |
Не забываем также подключить модуль в файле CMakeLists.txt, находящемся в каталоге main
set(COMPONENT_SRCS "main.c wifi.c")
Также подключим наш модуль в файле main.h
1 2 |
#include "nvs_flash.h" #include "wifi.h" |
Теперь код скорей всего соберётся.
Вернёмся в файл main.c и в функции app_main произведём первичную инициализацию ножки GPIO для светодиода
1 2 3 4 |
wifi_ap_record_t info; gpio_reset_pin(CONFIG_LED_GPIO); gpio_set_direction(CONFIG_LED_GPIO, GPIO_MODE_OUTPUT); gpio_set_level(CONFIG_LED_GPIO, 0); |
Инициализируем память NVS, произведя её очистку в случае нехватки памяти или обнаружения новой версии
1 2 3 4 5 6 7 8 9 10 |
gpio_set_level(CONFIG_LED_GPIO, 0); //Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ret = nvs_flash_erase(); ESP_LOGI(TAG, "nvs_flash_erase: 0x%04x", ret); ret = nvs_flash_init(); ESP_LOGI(TAG, "nvs_flash_init: 0x%04x", ret); } ESP_LOGI(TAG, "nvs_flash_init: 0x%04x", ret); |
NVS — это такой раздел памяти контроллера, в котором хранятся некоторые настройки или свойства, так как это такая файловая система, основанная на конструкциях ключ-значение. Думаю, впоследствии будет смысл познакомиться с данной памятью поподробнее и посвятить ей отдельный урок.
Перейдём в файл wifi.c и добавим функцию инициализации WiFi, в которой произведём сначала первичную инициализацию стека TCP/IP при помощи специальной функции
1 2 3 4 5 6 7 |
#include "wifi.h" //------------------------------------------------------------- void wifi_init_sta(void) { ESP_ERROR_CHECK(esp_netif_init()); } //------------------------------------------------------------- |
Мы используем здесь макрос ESP_ERROR_CHECK вместо привычного возврата результата кода ошибки и отображения его в логе. Данный макрос проверяет возврат из функции кода ошибки, и при нехорошем результате приостанавливает работу программы, для чего внутри используется утверждение assert. Также данный макрос выведет соответствующее сообщение в терминал.
Объявим для данной функции прототип в заголовочном файле и вызовем её в функции app_main файла main.c
1 2 |
ESP_LOGI(TAG, "nvs_flash_init: 0x%04x", ret); wifi_init_sta(); |
Вернёмся в файл wifi.c в тело функции инициализации wifi_init_sta, в котором при помощи определённой функции IDF создадим цикл событий по умолчанию и с помощью другой функции произведём инициализацию станции по умолчанию
1 2 3 |
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); esp_netif_create_default_wifi_sta(); |
Затем объявим и проинициализируем переменную типа структуры управления настройками WiFi, такими так буфер RX/TX, структура WiFi NVS и т. д, после чего инициализируем WiFi данными настройками
1 2 3 4 |
esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); |
Далее объявим две переменных-идентификатора экземпляров зарегистрированных обработчиков событий. Один идентификатор нужен будет для обработки любого события, другой — для обработки события получения IP, а, следовательно, и удачного соединения с точкой доступа
1 2 3 4 |
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); esp_event_handler_instance_t instance_any_id; esp_event_handler_instance_t instance_got_ip; |
Выше функции wifi_init_sta добавим функцию-обработчик событий
1 2 3 4 5 6 7 |
#include "wifi.h" //------------------------------------------------------------- static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { } //------------------------------------------------------------- |
Пусть пока будет пустотелый. Вернёмся в функцию wifi_init_sta и зарегистрируем обработчики для наших событий
1 2 3 4 5 6 7 8 9 10 11 12 |
esp_event_handler_instance_t instance_got_ip; ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip)); |
А так как имя функции-обработчика в параметрах у нас одно и то же, то и функция будет одна.
Далее объявим и проинициализируем переменную типа структуры для свойств станции, полям которой мы присвоим SSID и пароль, которые мы добавили в конфигураторе, а также зададим режим шифрования
1 2 3 4 5 6 7 8 9 10 11 12 13 |
&instance_got_ip)); wifi_config_t wifi_config = { .sta = { .ssid = CONFIG_ESP_WIFI_SSID, .password = CONFIG_ESP_WIFI_PASSWORD, .threshold.authmode = WIFI_AUTH_WPA2_PSK, .pmf_cfg = { .capable = true, .required = false }, }, }; |
Установим режим работы WiFi как станции
1 2 3 4 5 6 |
.required = false }, }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); |
Установим нормальный режим работы WiFI, иначе по умолчанию это один из энергосберегающих режимов
1 2 |
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); |
Применяем конфигурационные установки для нашей станции
1 2 |
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); |
Добавим глобальный указатель для имени лога
1 2 3 4 |
#include "wifi.h" //------------------------------------------------------------- static const char *TAG = "wifi"; //------------------------------------------------------------- |
Вернёмся в функцию wifi_init_sta и запустим станцию, о чём сообщаем в логе
1 2 3 4 |
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); ESP_ERROR_CHECK(esp_wifi_start() ); ESP_LOGI(TAG, "wifi_init_sta finished."); |
А вот теперь нам пригодятся знания, полученные на прошлом уроке по работе с группами событий в системе реального времени FreeRTOS.
Для этого сначала в файле wifi.h подключим необходимый заголовочный файл
1 2 |
#include "freertos/task.h" #include "freertos/event_groups.h" |
Вернёмся в файл wifi.c и объявим глобальную переменную типа структуры для группы событий, а также макросы для двух событий
1 2 3 4 |
static const char *TAG = "wifi"; static EventGroupHandle_t s_wifi_event_group; #define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 |
Перейдём в пока ещё пустотелый обработчик событий event_handler и при условии, что мы попали в обработчик именно в момент старта WiFi, вызовем нужную функцию
1 2 3 4 5 6 |
static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } |
Объявим глобальную переменную для подсчёта количества попыток соединения
1 2 |
static const char *TAG = "wifi"; static int s_retry_num = 0; |
Вернёмся в обработчик и добавим ещё одну ветку в наше условие на случай, если мы сюда попадём в случае отсутствия соединения. Мы здесь будем наращивать счётчик попыток и сравнивать его с нашим максимальным числом из конфигуратора, если ещё не достигли, то будем заново пытаться соединиться, а если достигли, то установим соответствующий бит в группе событий
1 2 3 4 5 6 7 8 9 10 11 12 |
esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) { esp_wifi_connect(); s_retry_num++; ESP_LOGI(TAG, "retry to connect to the AP"); } else { xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); } ESP_LOGI(TAG,"connect to the AP fail"); } |
Добавим ещё одну ветку, в которую мы попадём в случае если у нас произошло удачное соединение и мы получили адрес IP. Если мы попадём в данную ветку, то получим указатель на структуру события, отобразим полученный адрес в терминале, сбросим количество попыток соединения, установим соответствующий бит в группе событий и зажжём светодиод
1 2 3 4 5 6 7 8 9 |
ESP_LOGI(TAG,"connect to the AP fail"); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); s_retry_num = 0; xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); gpio_set_level(CONFIG_LED_GPIO, 1); } |
Вернёмся в нашу функцию инициализации wifi_init_sta и создадим группу событий
1 2 3 |
void wifi_init_sta(void) { s_wifi_event_group = xEventGroupCreate(); |
Пока не тестируя наши биты, соберём код и прошьём контроллер. Если всё нормально, то у нас произойдёт успешное соединение с точкой доступа
Также мы видим, что светодиод загорелся
В случае потери соединения с точкой доступа произойдёт пять попыток повторного соединения
А вот если связь возобновится по истечении пяти попыток, то мы уже скорей всего не соединимся с точкой доступа. С этим мы сейчас и поработаем.
Но для начала вернёмся в функцию инициализации wifi_init_sta и протестируем наши биты
1 2 3 4 5 6 7 |
ESP_LOGI(TAG, "wifi_init_sta finished."); EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY); |
Именно протестируем, так как третий параметр у нас pdFalse, который, как мы уже прекрасно знаем отвечает за сброс битов и мы их не сбрасываем, а оставляем в том состоянии, в каком они есть. Также pdFalse в четвёртом параметре мы также хорошо знаем что означает. Благодаря ему мы ждём (наша задача находится в блокированном состоянии), когда будет установлен любой из битов, что означает, что наступило хотя бы одно событие.
Отобразим в логе смысл обнаруженного события
1 2 3 4 5 6 7 8 9 10 11 12 |
portMAX_DELAY); if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "connected to ap SSID:%s", CONFIG_ESP_WIFI_SSID); } else if (bits & WIFI_FAIL_BIT) { ESP_LOGI(TAG, "Failed to connect to SSID:%s", CONFIG_ESP_WIFI_SSID); } else { ESP_LOGE(TAG, "UNEXPECTED EVENT"); } |
Разрегистрируем наши обработчики и удалим группу событий
1 2 3 4 5 6 |
ESP_LOGE(TAG, "UNEXPECTED EVENT"); } ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip)); ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id)); vEventGroupDelete(s_wifi_event_group); |
Если мы проверим работу кода сейчас, то скорей всего при разрыве соединения у нас вообще не будет попыток соединения, так как произойдёт второе событие и мы провалимся вниз и уйдём из функции.
Теперь перейдём в функцию app_main файла main.c и в бесконечном цикле попробуем отследить состояние нашего соединения
1 2 3 |
while (true) { ret = esp_wifi_sta_get_ap_info(&info); ESP_LOGI(TAG, "esp_wifi_sta_get_ap_info: 0x%04x", ret); |
При нулевом статусе мы просто покажем SSID, а при другом — погасим светодиод и попробуем ещё раз запустить функцию инициализации для повторного соединения
1 2 3 4 5 6 |
ESP_LOGI(TAG, "esp_wifi_sta_get_ap_info: 0x%04x", ret); if(ret==0) ESP_LOGI(TAG, "SSID: %s", info.ssid); else{ gpio_set_level(CONFIG_LED_GPIO, 0); wifi_init_sta(); } |
Если кто-то испугается, что такой множественный способ инициализации будет отъедать кучу, то можно это проверить в самом начале бесконечного цикла
1 2 |
while (true) { ESP_LOGI(TAG, "Heap free size:%d", xPortGetFreeHeapSize()); |
Соберём код, прошьём контроллер.
Всё у нас также нормально соединится, если есть точка доступа и совпал пароль.
Запомним размер кучи
Посмотрим, что произойдёт при потере соединения.
Во-первых, у нас погаснет светодиод
Во-вторых, в терминале мы увидим, что у нас идут постоянные перезапуски инициализации и попытки соединения.
А при возобновлении соединения мы опять нормально соединимся и получим нормальный статус соединения
Размер кучи у нас также остался прежним (даже немного убавился).
Попробуем также обратиться к нашему узлу, так сказать, попинговать его
Итак, на данном уроке нам удалось соединиться с WiFi-роутером, создав соединение в режиме STA, что, надеюсь, в дальнейшем нам позволит работать с многими сетевыми протоколами и обеспечить беспроводную передачу данных с использованием контроллера ESP32.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь: Недорогие отладочные платы ESP32
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий