Продолжаем учиться писать код для микроконтроллера ESP8266 с использованием операционной системы реального времени FREEFTOS.
На данном уроке мы попробуем создать соединение по Wi-Fi в режиме STA (станции), то есть соединиться с готовой точкой доступа для передачи данных по беспроводной сети.
С таким режимом мы уже работали в уроке 14, но только без использования операционной системы, в автономном режиме.
А с использованием операционной системы работа с таким режимом несколько отличается, так как нам нужно учитывать определённые требования FREERTOS, а также работу мы ведём с использованием другого комплекта SDK.
Схема у нас остаётся прежней — отладочная плата, подключенная к USB компьютера
А проект мы за основу возьмём из урока 19 с именем UART_TX_RTOS и дадим ему имя WIFI_STA_RTOS.
Откроем наш проект в Eclipse и первым делом в файле main.c исправим в функции user_rf_cal_sector_set вот эту циферку
rf_cal_sec = 128 - 5;
Удалим объявление структуры с переменными
typedef struct
{
unsigned char num_task;
unsigned short del;
} pData;
pData dt1, dt2;
В функции user_init удалим инициализацию переменных и создание двух задач
dt1.del = 1000; dt1.num_task = 1;
dt2.del = 900; dt2.num_task = 2;
xTaskCreate(task1, «task1», 256, (void *) &dt1, 1, NULL);
xTaskCreate(task1, «task2», 256, (void *) &dt2, 1, NULL);
Вместо этого произведём инициализацию ножки, к которой подключен синий светодиод, и создадим задачу без параметров
1 2 3 |
os_printf("\r\n\r\n"); PIN_FUNC_SELECT (PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2); xTaskCreate(task1, "task1", 256, NULL, 1, NULL); |
В функции задачи task1 удалим код инициализации, оставим только бесконечный цикл, в котором будем лишь время от времени выводить версию SDK для того, чтобы видеть, что у нас работает операционная система и ничего не повисло. После этого функция примет вот такой вид
1 2 3 4 5 6 7 8 |
void ICACHE_FLASH_ATTR task1(void *pvParameters) { while(1) { os_printf("SDK version:%s\n", system_get_sdk_version()); vTaskDelay(1000 / portTICK_RATE_MS); } } |
В заголовочном файле user_config.h объявим две константы с именем точки доступа, к которой мы будем подключаться и с паролем
1 2 3 4 5 |
#define __USER_CONFIG_H__ //---------------------------------------------------------- #define WIFI_CLIENTSSID "ASUS333" #define WIFI_CLIENTPASSWORD "12345678" //---------------------------------------------------------- |
Так как работа с режимом станции Wi-Fi не такая простая, давайте для этого создадим ещё один модуль в виде двух файлов wifi.c и wifi.h, которые будут иметь пока вот такое содержимое
wifi.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef WIFI_H_ #define WIFI_H_ //------------------------------------------------ #include "esp_common.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include <stdbool.h> #include "user_config.h" #include "gpio.h" //------------------------------------------------ #endif /* WIFI_H_ */ |
wifi.c:
1 2 |
#include "wifi.h" //------------------------------------------------ |
Подключим также данный модуль в main.h
1 2 |
#include "gpio.h" #include "wifi.h" |
В файле wifi.c добавим функцию инициализации wifi
1 2 3 4 5 |
//------------------------------------------------ WIFI_MODE ICACHE_FLASH_ATTR init_esp_wifi() { } //------------------------------------------------ |
Создадим на данную функцию прототип в заголовочном файле wifi.h и вызовем её в main.c
1 2 |
PIN_FUNC_SELECT (PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2); init_esp_wifi(); |
В файле wifi.h объявим два типа указателей на функции (с указателями на функции мы знакомы из уроков по C, а именно из урока 30)
1 2 3 4 |
#include "gpio.h" //------------------------------------------------ typedef void (* wifi_state_cb_t)(); typedef void (* wifi_disco_cb_t)(uint8 reason); |
В файле wifi.c создадим несколько указателей таких типов и объявим, что они пока никуда не указывают
1 2 3 4 5 6 7 8 |
#include "wifi.h" //------------------------------------------------ wifi_state_cb_t on_station_connect = NULL; wifi_disco_cb_t on_station_disconnect = NULL; wifi_state_cb_t on_station_first_connect = NULL; wifi_state_cb_t on_client_connect = NULL; wifi_state_cb_t on_client_disconnect = NULL; //------------------------------------------------ |
Объявим также две глобальные переменные булевого типа и пока проинициализируем их в false
1 2 3 4 |
wifi_state_cb_t on_client_disconnect = NULL; //------------------------------------------------ volatile bool wifi_station_is_connected = false; volatile bool wifi_station_static_ip = false; |
Создадим функцию, которая будет назначать реальный указатель на функцию обратного вызова по событию соединения с точкой доступа
1 2 3 4 5 6 7 |
volatile bool wifi_station_static_ip = false; //------------------------------------------------------------------ void ICACHE_FLASH_ATTR set_on_station_connect(wifi_state_cb_t cb) { on_station_connect = cb; } //------------------------------------------------------------------ |
И ниже добавим такую функцию, в которой пока выведем в терминальную программу соответствующее сообщение
1 2 3 4 5 6 |
//------------------------------------------------ LOCAL void ICACHE_FLASH_ATTR on_wifi_connect() { os_printf("on_wifi_connect();\n"); } //------------------------------------------------ |
В функции инициализации вызовем функцию назначения указателя функции и передадим ей в параметре имя функции
1 2 3 |
WIFI_MODE ICACHE_FLASH_ATTR init_esp_wifi() { set_on_station_connect(on_wifi_connect); |
Добавим статическую переменную для программного таймера
1 2 3 |
volatile bool wifi_station_static_ip = false; //------------------------------------------------ static os_timer_t timer; |
Добавим функцию, которую мы будем вызывать при соединении с точкой доступа, в которой выйдем из функции, если переменная состояния соединения ещё не получила значения true
1 2 3 4 5 6 7 8 9 |
static os_timer_t timer; //------------------------------------------------ bool ICACHE_FLASH_ATTR wifi_station_connected() { if(!wifi_station_is_connected){ return false; } } //------------------------------------------------ |
Затем узнаем текущий режим
1 2 3 |
return false; } WIFI_MODE mode = wifi_get_opmode(); |
Если там не присутствует режим STA, то также выйдем из функции с результатом false
1 2 3 4 |
WIFI_MODE mode = wifi_get_opmode(); if((mode & STATION_MODE) == 0){ return false; } |
Ну а дальше, если мы не вышли из функции, узнаем состояние соединения
1 2 3 |
return false; } STATION_STATUS wifistate = wifi_station_get_connect_status(); |
Путём некоторого логического сбора информации о состояниях соединения присвоим нашей булевской переменной новое значение и вернём его
1 2 3 4 |
STATION_STATUS wifistate = wifi_station_get_connect_status(); wifi_station_is_connected = (wifistate == STATION_GOT_IP || (wifi_station_static_ip && wifistate == STATION_CONNECTING)); return wifi_station_is_connected; |
Ниже добавим функцию ожидания соединения, которая будет также являться и процедурой обработчика события таймера, в которой пока произведём деинициализацию нашего таймера
1 2 3 4 5 6 |
//------------------------------------------------ LOCAL void ICACHE_FLASH_ATTR wait_for_connection_ready(uint8 flag) { os_timer_disarm(&timer); } //------------------------------------------------ |
Далее в условии вызовем нашу функцию, которую мы только что написали, и если результат будет true, то выведем в терминальную программу соответствующее сообщение
1 2 3 4 |
os_timer_disarm(&timer); if(wifi_station_connected()) { os_printf("connected\n"); } |
В противном случае отправим в терминальную программу сообещние об ожидании 2 секунд и вызовем инициализацию таймера повторно и данная функция вызовется рекурсивно, так как в параметре инициализации таймера будет её имя, а затем запустим таймер
1 2 3 4 5 6 |
os_printf("connected\n"); } else { os_printf("reconnect after 2s\n"); os_timer_setfn(&timer, (os_timer_func_t *)wait_for_connection_ready, NULL); os_timer_arm(&timer, 2000, 0); } |
Также нам надо запустить таймер первоначально. Это мы проделаем в функции on_wifi_connect
1 2 3 4 |
os_printf("on_wifi_connect();\n"); os_timer_disarm(&timer); os_timer_setfn(&timer, (os_timer_func_t *)wait_for_connection_ready, NULL); os_timer_arm(&timer, 100, 0); |
Далее подобные мероприятия нам нужно провести для события разрыва соединения.
Для этого мы также добавим функцию для присвоения реального указателя функции ниже функции set_on_station_connect
1 2 3 4 5 6 |
//------------------------------------------------ void ICACHE_FLASH_ATTR set_on_station_disconnect(wifi_disco_cb_t cb) { on_station_disconnect = cb; } //------------------------------------------------ |
А ниже функции on_wifi_connect добавим функцию, в которой мы будем только выводить сообщение в терминальную программу о том, что у нас пропало соединение, с указанием идентификатора причины разъединения, которые можно посмотреть в библиотеке в соответствующем перечисляемом типе, больше мы в данной функции ничего делать не будем
1 2 3 4 5 6 |
//------------------------------------------------ LOCAL void ICACHE_FLASH_ATTR on_wifi_disconnect(uint8_t reason) { os_printf("disconnect %d\n", reason); } //------------------------------------------------ |
Вызовем функцию для присвоения указателя в функции инициализации init_esp_wifi
1 2 |
set_on_station_connect(on_wifi_connect); set_on_station_disconnect(on_wifi_disconnect); |
Добавим обработчик событий соединения Wi-Fi, в котором сбросим переменную состояния в false
1 2 3 4 5 6 7 |
static os_timer_t timer; //------------------------------------------------ void ICACHE_FLASH_ATTR wifi_event_handler_cb(System_Event_t *event) { static bool station_was_connected = false; } //------------------------------------------------ |
В функции инициализации init_esp_wifi с помощью специальной библиотечной функции назначим данную функцию обработчиком событий
1 2 |
set_on_station_disconnect(on_wifi_disconnect); wifi_set_event_handler_cb(wifi_event_handler_cb); |
Добавим глобальный макрос для ножки светодиода
1 2 3 |
static os_timer_t timer; //------------------------------------------------ #define GPIO_LED 2 |
В обработчике wifi_event_handler_cb, если вдруг событие ведёт вникуда, то вернёмся из функции
1 2 3 4 5 |
static bool station_was_connected = false; if (event == NULL) { return; } |
Покажем в терминальной программе идентификатор события
1 2 3 |
return; } os_printf("[WiFi] event %u\n", event->event_id); |
Затем, используя оператор вариантов, подготовим ветви для обработки различных событий соединения Wi-Fi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
os_printf("[WiFi] event %u\n", event->event_id); switch (event->event_id) { case EVENT_STAMODE_DISCONNECTED: break; case EVENT_STAMODE_CONNECTED: break; case EVENT_STAMODE_DHCP_TIMEOUT: break; case EVENT_STAMODE_GOT_IP: break; case EVENT_SOFTAPMODE_STACONNECTED: break; case EVENT_SOFTAPMODE_STADISCONNECTED: break; default: break; } |
Начнём с события разрыва соединения, в котором мы также сбросим в false переменную состояния соединения и погасим светодиод, установив на его ножке высокий уровень
1 2 3 |
case EVENT_STAMODE_DISCONNECTED: wifi_station_is_connected = false; GPIO_OUTPUT_SET(GPIO_LED, 1); |
Создадим указатель на тип структуры состояния разрыва и присвоим ей адрес соответствующего поля указателя на переменную структуры из параметра функции
1 2 |
GPIO_OUTPUT_SET(GPIO_LED, 1); Event_StaMode_Disconnected_t *ev = (Event_StaMode_Disconnected_t *)&event->event_info; |
Если функция события разъединения не ведёт вникуда, то вызовем её, передав ей в параметре идентификатор причины разъединения
1 2 3 4 5 |
Event_StaMode_Disconnected_t *ev = (Event_StaMode_Disconnected_t *)&event->event_info; if(on_station_disconnect) { on_station_disconnect(ev->reason); } |
Далее обработаем событие соединения, в соответствующем кейсе которого выведем в терминальную программу сообщение о том, что у нас статическая адресация, если это действительно так
1 2 3 4 5 |
case EVENT_STAMODE_CONNECTED: if(wifi_station_static_ip) { os_printf("STA IP is static\n"); } |
Установим в true нашу переменную состояния соединения
1 2 3 |
os_printf("STA IP is static\n"); } wifi_station_is_connected = true; |
Если подобная переменная, только локальная, не установлена в true, то мы её тоже установим
1 2 3 4 5 |
wifi_station_is_connected = true; if(!station_was_connected) { station_was_connected = true; } |
В ветке этого же условия вызовем функцию обработки первого соединения, но так как мы данной функции не присваивали реальный адрес, то вызова никакого не произойдёт и в тело следующего вложенного условия мы не попадём, мы не хотим никак обрабатывать первое соединение
1 2 3 4 5 |
station_was_connected = true; if(on_station_first_connect) { on_station_first_connect(); } |
Выйдем из тел обоих условий и добавим следующее условие, которое будет также при наличии функции соединения вызывать её, мы такую функцию назначали в инициализации, поэтому она у нас вызовется и это будет функция с именем on_wifi_connect
1 2 3 4 5 6 |
on_station_first_connect(); } } if(on_station_connect){ on_station_connect(); } |
Далее обработаем условие ожидания присвоения адреса от сервера DHCP, в котором мы сбросим нашу переменную состояния в false, если она в true
1 2 3 4 5 |
case EVENT_STAMODE_DHCP_TIMEOUT: if(wifi_station_is_connected) { wifi_station_is_connected = false; } |
Затем вызовем функцию разрыва соединения при условии её существования, а она у нас есть, передав ей идентификатор причины UNSPECIFIED
1 2 3 4 5 |
wifi_station_is_connected = false; if(on_station_disconnect) { on_station_disconnect(REASON_UNSPECIFIED); } |
Следующее событие — событие успешного присвоения адреса DHCP-сервером нашему модулю, в котором мы установим нашу переменную в true
1 2 |
case EVENT_STAMODE_GOT_IP: wifi_station_is_connected = true; |
Зажжем наш светодиод
1 2 |
wifi_station_is_connected = true; GPIO_OUTPUT_SET(GPIO_LED, 0); |
Также в true установим и локальную переменную
1 2 3 4 5 |
GPIO_OUTPUT_SET(GPIO_LED, 0); if(!station_was_connected) { station_was_connected = true; } |
Также здесь обработаем и первое соединение, если у нас такой обработчик присутствует (у нас его, как мы знаем, нет)
1 2 3 4 5 |
station_was_connected = true; if(on_station_first_connect) { on_station_first_connect(); } |
Выйдя из обоих условий, вызовем функцию соединения
1 2 3 4 5 6 |
on_station_first_connect(); } } if(on_station_connect){ on_station_connect(); } |
Обработаем также событие соединения в случае настройки нашего модуля в режиме точки доступа
1 2 3 4 5 |
case EVENT_SOFTAPMODE_STACONNECTED: if(on_client_connect) { on_client_connect(); } |
Мы работаем в режиме станции и точку доступа мы не включаем, следовательно у нас нет указателя на функцию, вернее он в null, и мы ничего не вызовем.
Аналогично обработаем событие разъединения с клиентом в случае настройки в режиме точки доступа
1 2 3 4 5 |
case EVENT_SOFTAPMODE_STADISCONNECTED: if(on_client_disconnect) { on_client_disconnect(); } |
Ниже функции on_wifi_disconnect добавим функцию установки режима работы модуля Wi-Fi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//------------------------------------------------ bool ICACHE_FLASH_ATTR wifi_set_mode(WIFI_MODE mode) { if(!mode){ bool s = wifi_set_opmode(mode); wifi_fpm_open(); wifi_fpm_set_sleep_type(MODEM_SLEEP_T); wifi_fpm_do_sleep(0xFFFFFFFF); return s; } wifi_fpm_close(); return wifi_set_opmode(mode); } //------------------------------------------------ |
В теле данной функции, в случае, если мы хотим установить режим NULL (такой режим, когда вообще отключены все соединения), вызовем функцию установки режима, затем в форсированном режиме усыпим наш модуль Wi-Fi и установим ему период сна практически бесконечный и выйдем из функции со статусом вызова функции установки режима. Затем, если мы не вышли и у нас режим не NULL, то мы отключаем форсированный режим и устанавливаем режим из параметра с возвратом статуса.
В функции инициализации init_esp_wifi установим режим по умолчанию, узнав его предварительно с помощью определённой библиотечной функции
1 2 3 |
wifi_set_event_handler_cb(wifi_event_handler_cb); WIFI_MODE mode = wifi_get_opmode_default(); wifi_set_mode(mode); |
Ниже функции wifi_set_mode добавим функцию отключения режима точки доступа
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//------------------------------------------------ bool ICACHE_FLASH_ATTR stop_wifi_ap() { WIFI_MODE mode = wifi_get_opmode(); mode &= ~SOFTAP_MODE; if(!wifi_set_mode(mode)) { os_printf("Failed to disable AP mode!\n"); return false; } return true; } //------------------------------------------------ |
В теле данной функции мы сначала узнаем режим работы модуля и занесём его в соответствующую переменную, в которой впоследствии сбросим бит точки доступа, и затем установим режим со сброшенным битом. В случае неудачи установки выведем соответствующее сообщение в терминальную программу и возвратимся со статусом false, а в случае удачи — true.
Вызовем данную функцию в функции инициализации init_esp_wifi и вернём режим
1 2 3 |
wifi_set_mode(mode); stop_wifi_ap(); return mode; |
Ниже функции wifi_set_mode добавим функцию старта соединения с параметрами имени точки доступа и пароля доступа к ней, в теле которой мы сначала узнаем текущий режим работы модуля Wi-Fi
1 2 3 4 5 6 |
//------------------------------------------------ bool ICACHE_FLASH_ATTR start_wifi_station(const char * ssid, const char * pass) { WIFI_MODE mode = wifi_get_opmode(); } //------------------------------------------------ |
Затем в теле данной функции, если в режиме бит станции не установлен, то установим его сначала в переменной
1 2 3 4 5 |
WIFI_MODE mode = wifi_get_opmode(); if((mode & STATION_MODE) == 0) { mode |= STATION_MODE; } |
Попытаемся установить новый режим, если будет неудача, то выведем соответствующее сообщение в терминальную программу и выйдем из функции с статусом false
1 2 3 4 5 6 |
mode |= STATION_MODE; if(!wifi_set_mode(mode)) { os_printf("Failed to enable Station mode!\n"); return false; } |
Выйдя из тел обоих условий, проверим условие наличия переданного SSID, если он пустой то также выведем соответствующее сообщение и выйдем из функции со статусом false
1 2 3 4 5 6 7 8 |
return false; } } if(!ssid) { os_printf("No SSID Given. Will connect to the station saved in flash\n"); return true; } |
Выйдя из этого условия, создадим переменную структуры конфигурации соединения, в которой сначала память, выделенную под её поля, очистим полностью, а затем занесём в соответствующее поле имя точки доступа
1 2 3 4 5 |
return true; } struct station_config config; memset(&config, 0, sizeof(struct station_config)); strcpy(config.ssid, ssid); |
Если пароль непустой, также присвоим его соответствующему полю переменной структуры
1 2 3 4 5 |
strcpy(config.ssid, ssid); if(pass) { strcpy(config.password, pass); } |
Вызовем функцию занесения полей в модуль, если неудача, то возвратимся с результатом false
1 2 3 4 5 6 |
strcpy(config.password, pass); } if(!wifi_station_set_config(&config)){ os_printf("Failed to set Station config!\n"); return false; } |
Включим режим полноправной работы модуля (режим без ограничения энергопотребления)
1 2 3 |
return false; } wifi_set_sleep_type(NONE_SLEEP_T); |
Включим режим DHCP Client, при неудаче выходим из функции со статусом false
1 2 3 4 5 6 7 8 |
wifi_set_sleep_type(NONE_SLEEP_T); if(!wifi_station_dhcpc_status()) { os_printf("DHCP is not started. Starting it...\n"); if(!wifi_station_dhcpc_start()) { os_printf("DHCP start failed!\n"); return false; } } |
Включим самую большую поддерживаемую скорость (протокол 802.11N)
1 2 3 4 5 |
return false; } } if(wifi_get_phy_mode() != PHY_MODE_11N) wifi_set_phy_mode(PHY_MODE_11N); |
Вызовем функцию соединения с точкой доступа и вернём статус
1 2 |
wifi_set_phy_mode(PHY_MODE_11N); return wifi_station_connect(); |
В функции user_init файла main.c вызовем данную функцию, не забыв перед этим добавить её прототип в заголовочном файле
1 2 |
init_esp_wifi(); start_wifi_station(WIFI_CLIENTSSID, WIFI_CLIENTPASSWORD); |
Ну и вот наконец-то настало время проверить нашу программу. Соберём код, прошьём контроллер, и если всё правильно, то мы должны будем через некоторое время наблюдать зажженный синий светодиод
В терминальной программе мы также видим, что у нас всё нормально соединилось
Также попробуем послать пинги нашему модулю
Модуль отлично откликается.
Итак, на данном уроке нам удалось настроить модуль Wi-Fi нашего контроллера ESP8266 в режиме станции, используя при этом операционную систему реального времени FREERTOS.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Многофункциональный переходник CJMCU FT232H USB к JTAG UART FIFO SPI I2C можно приобрести здесь ftdi ft232rl
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий