Продолжаем учиться писать код для микроконтроллера 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
Многофункциональный переходник JTAG UART FIFO SPI I2C можно приобрести здесь CJMCU FT232H USB к JTAG UART FIFO SPI I2C
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добавить комментарий