Продолжаем программирование микроконтроллера ESP8266 с использованием операционной системы реального времени FREEFTOS и, также продолжая тему передачи данных по беспроводной сети, мы с использованием ОС попробуем теперь передать какие-то осознанные данные. Для этого мы воспользуемся транспортным протоколом UDP, так как для этого в SDK всё имеется в наличии и нам не потребуется самостоятельно формировать наши пакеты. На данном уроке мы попытаемся сочинить простенький UDP-клиент, который будет передавать в сеть на определённый адрес и порт постоянно инкрементирующиеся 16-разрядные знаковые величины.
Мы уже писали клиент UDP в уроке 15, только теперь будет ряд отличий. Во-первых, использование ОС, что даёт нам ряд преимуществ, я даже не буду называть — каких именно, это и так понятно. Во-вторых, мы будем передавать числа, а не строки. А в-третьих, я считаю это главным, мы будем использовать уже не библиотеку ESPCONN, а библиотеку LWIP. Из занятий по контроллерам STM32 мы прекрасно знаем данную библиотеку, мы прошли по ней очень много уроков, знаем, что в данной библиотеке существует три различных интерфейса — RAW, NETCONN и SOCKET, работали с ними со всеми. Поэтому нам в данном уроке потребуется лишь грамотно объединить наши знания для достижения цели. Использовать мы будем интерфейс SOCKET, который, как я считаю, программируется легче остальных, а также в нём уже всё настроено, что касается протокольных потоков, которые в других интерфейсах нужно распределять самостоятельно.
Режим мы будем использовать STA, а подключаться мы будем либо к роутеру, либо напрямую к смартфону, настроив в нём также точку доступа. Кстати, я набросал небольшую утилитку для мониторинга сетевого трафика, которая работает пока только как UDP-сервер и принимать и отображать на дисплее может только 16-разрядные цифры, что и требуется для нашего урока. Также подобная программка была написана и под OS Windows.
Схемой нашего урока также будет обычная отладочная плата ESP NodeMCU, подключенная к порту USB компьютера для последующей заливки ПО и мониторинга различных состояний в терминальной программе
А проект мы за основу возьмём из урока 22 с именем WIFI_AP_RTOS и назовём его WIFI_STA_UDP_CLIENT_TX_RTOS.
Постфикс TX обозначает то, что на данном уроке мы будем пока данные только передавать, приёмом мы займёмся позже.
Откроем наш проект в Eclipse и в файле user_config.h добавим ещё макросы с адресом и портом узла, на который мы будем передавать наши пакеты, а также с адресом порта нашего клиента. Сетевой адрес у нас будет динамический, тот, который нам присвоит точка доступа, поэтому его мы не дефайним
1 2 3 4 |
#define WIFI_CLIENTPASSWORD "12345678" #define SERVER_IP "192.168.1.207" #define SERVER_PORT 3333 #define CLIENT_PORT 4444 |
Далее в файле main.c в функции user_init закомментируем инициализацию точки доступа и вызовем функцию инициализации режима STA
//start_wifi_ap(WIFI_APSSID, WIFI_APPASSWORD);
start_wifi_station(WIFI_CLIENTSSID, WIFI_CLIENTPASSWORD);
В заголовочном файле wifi.h подключим нужные файлы библиотеки LWIP
1 2 3 4 5 6 7 |
#include <stdbool.h> #include "lwip/err.h" #include "lwip/sockets.h" #include "lwip/sys.h" #include "lwip/opt.h" #include "lwip/inet.h" #include <lwip/netdb.h> |
А чтобы данные файлы были хорошо видны и не подчёркивались как ошибочные, мы пропишем следующие пути в настройках проекта
Переходим в файл wifi.c, далее будем работать с ним.
В функции инициализации init_esp_wifi нам также надо будет настроить режим станции, а не точки доступа, поэтому следующие 2 строчки закомментируем и добавим 2 другие
//set_on_client_connect(on_wifi_client_connect);
//set_on_client_disconnect(on_wifi_client_disconnect);
set_on_station_connect(on_wifi_connect);
set_on_station_disconnect(on_wifi_disconnect);
Закомментируем вот эту строку и добавим другую
//stop_wifi_station();
stop_wifi_ap();
Мы так делаем для универсальности, чтобы в любой момент снова настроить точку доступа.
Добавим функцию для ещё одной задачи, в которой мы будем формировать и отправлять пакеты UDP. В теле данной функции мы сначала объявим переменную, требуемую для функции задержки, так как задержку мы будем использовать особенную, в которой будет учитываться начало этой задержки и время, прошедшее до следующей задержки, будет вычитаться из времени следующей задержки. Тем самым мы добиваемся точных периодов следования наших пакетов. С такой задержкой мы знакомы из уроков по FreeRTOS для контроллеров STM32. Также мы объявим символьный массив для строки, передаваемой в терминальную программу, символьный массив для строкового значения адреса и целочисленную переменную для дескриптора сокета
1 2 3 4 5 6 7 8 9 10 |
#define GPIO_LED 2 //------------------------------------------------ void ICACHE_FLASH_ATTR udp_task(void *pvParameters) { portTickType xLastWakeTime; char str1[21]; char addr_str[25]; int sockfd; } //------------------------------------------------ |
Отправим в терминальную программу сообщение о начале создания сокета
1 2 |
int sockfd; os_printf("Create socket...\n"); |
Объявим переменные структур для адреса клиента и сервера
1 2 |
os_printf("Create socket...\n"); struct sockaddr_in servaddr, cliaddr; |
Попытаемся создать наш сокет, если попытка не увенчается успехом, отправим соответствующее сообщение в терминальную программу и уничтожим нашу задачу
1 2 3 4 5 |
struct sockaddr_in servaddr, cliaddr; if ( (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0 ) { os_printf("socket not created\n"); vTaskDelete(NULL); } |
Обнулим полностью память, выделенную под переменные структур
1 2 3 4 |
vTaskDelete(NULL); } memset(&servaddr, 0, sizeof(servaddr)); memset(&cliaddr, 0, sizeof(cliaddr)); |
Заполним поля переменной структуры клиента
1 2 3 4 5 |
memset(&cliaddr, 0, sizeof(cliaddr)); //Заполнение информации о клиенте cliaddr.sin_family = AF_INET; // IPv4 cliaddr.sin_addr.s_addr = INADDR_ANY; cliaddr.sin_port = htons(CLIENT_PORT); |
Свяжем сокет с адресом клиента, при неудаче также выведем в терминал соответствующее сообщение и уничтожим задачу
1 2 3 4 5 6 7 |
cliaddr.sin_port = htons(4444); //Свяжем сокет с адресом сервера if (bind(sockfd, (const struct sockaddr *)&cliaddr, sizeof(struct sockaddr_in)) < 0 ) { os_printf("socket not binded\n"); vTaskDelete(NULL); } |
Затем отправим в терминальную программу сообщение об удачном связывании сокета с клиентом и заполним теперь в соответствующей переменной структуры информацию о сервере
1 2 3 4 5 6 7 |
vTaskDelete(NULL); } os_printf("socket binded\n", addr_str); //Заполнение информации о сервере servaddr.sin_family = AF_INET; // IPv4 servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); servaddr.sin_port = htons(SERVER_PORT); |
Запишем время в переменную для задержки
1 2 |
servaddr.sin_port = htons(3333); xLastWakeTime = xTaskGetTickCount(); |
А далее в цикле, который получится бесконечным, мы отправляем пакет с числом на сервер раз в 10 милисекунд
1 2 3 4 5 6 |
xLastWakeTime = xTaskGetTickCount(); for(short i=0;;i++) { sendto(sockfd, &i, 2, 0, (struct sockaddr*) &servaddr, sizeof(servaddr)); vTaskDelayUntil( &xLastWakeTime, ( 10 / portTICK_RATE_MS ) ); } |
А затем мы, как полагается, завершим наше соединение и уничтожим задачу, хотя мы вряд ли сюда когда-то попадём, но для порядка всё же добавим данный код
1 2 3 4 5 |
vTaskDelayUntil( &xLastWakeTime, ( 10 / portTICK_RATE_MS ) ); } shutdown(sockfd, 0); close(sockfd); vTaskDelete(NULL); |
Ну и, самое главное, не забываем создать нашу задачу в функции wifi_event_handler_cb вот здесь
1 2 3 4 |
case EVENT_STAMODE_GOT_IP: wifi_station_is_connected = true; GPIO_OUTPUT_SET(GPIO_LED, 0); xTaskCreate(udp_task, "udp_task", 4096, NULL, 5, NULL); |
Вот и готов наш клиент.
Соберём наш код, прошьём контроллер и посмотрим, что у нас в терминальной программе
Судя по сообщениям, к точке доступа мы подключились, сокет создан и связан с клиентом. Также у нас успешно работает параллельная задача.
То, что пакеты сейчас из нашего клиента передаются вникуда, это вполне нормально для UDP, он же не требует соединения.
Проверим сначала, как наши пакеты приходят в приложение в смартфоне.
Для этого устанавливаем приложение (ссылка и QR-код для установки внизу страницы) на смартфон (или планшет), запускаем его и жмём единственную кнопку Start. Соответственно, наше мобильное устройство должно быть подключено к той же сети и его адрес должен быть user_config.h.
Если всё нормально, то на дисплее мы увидим, как приходят и отображаются числа с клиента
Можно нажать кнопку Stop, в которую превратилась кнопка Start и прекратить приём чисел.
Теперь давайте запустим приложение на компьютере (ссылка также внизу страницы), которое достаточно просто запустить, установка не требуется. Далее вместо IP-адреса мобильного устройства мы заносим в макрос в user_config.h IP-адрес компьютера, собираем и прошиваем код, и также нажимаем кнопку Start
Мы видим, что циферки наши тут таким же образом бегут со скоростью 100 пакетов в секунду.
Запустим анализатор пакетов WireShark, чтобы также убедиться, что пакеты наши безошибочные (контрольная сумма в порядке).
Для этого отфильтруем пакеты по IP-адресу нашего ESP (а его мы видели в терминальной программе в момент соединения с точкой доступа)
Давайте раскроем какой-нибудь пакет
Мы видим, что контрольные суммы в нашем пакете корректные (заголовке IP и в заголовке UDP).
Итак, в данном занятии нам удалось создать простой UDP-клиент с использованием операционной системы FreeRTOS и библиотеки LWIP. Пусть этот клиент умеет пока только передавать данные, но самое главное, что мы с данного урока начали обмен данными по беспроводной сети с использованием контроллера ESP8266.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
TCP UDP client-server for Android
QR-код на скачивание приложения:
Модуль 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 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий