Продолжаем тему передачи данных по беспроводной сети. На данном уроке мы попробуем теперь передать какие-то осознанные данные. Для этого мы воспользуемся транспортным протоколом UDP, так как для этого в IDF всё имеется в наличии и нам не потребуется самостоятельно формировать наши пакеты. На данном уроке мы попытаемся сочинить простенький UDP-клиент, который будет передавать в сеть на определённый адрес и порт постоянно инкрементирующиеся 16-разрядные знаковые величины. Причём сочинить даже громко сказано, так как мы это уже проделывали с контроллером ESP8266 в уроке 24.
Для создания UDP-клиента мы будем использовать библиотеку LWIP, с которой мы также очень хорошо знакомы и которая также входит в состав IDF.
На данном уроке мы пока будем только передавать данные, приёмом займёмся позже, чтобы идти поступательно и чтобы материал легче укладывался в сознании.
Схема у нас будет та же самая — отладочная плата, подключенная к USB компьютера
Проект мы создадим из проекта прошлого урока с именем WIFI_STA и назовём его WIFI_STA_UDP_CLIENT_TX.
Откроем наш проект в Espressif IDE и добавим в файл Kconfig.projbuild ещё три пункта, один для IP-адреса сервера, два других — для портов сервера и клиента
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
config SERVER_IP string "SERVER IPV4 Address" default "192.168.1.16" help SERVER IPV4 Address. config SERVER_PORT int "Server Port" range 0 65535 default 3333 help The remote port. config CLIENT_PORT int "Client Port" range 0 65535 default 4444 help The local port. |
Добавим также ещё один модуль для работы с протоколом UDP, состоящий из двух файлов следующего содержания
1 2 3 4 5 6 7 8 9 10 |
#ifndef MAIN_UDP_H_ #define MAIN_UDP_H_ //------------------------------------------------------------- #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" //------------------------------------------------------------- //------------------------------------------------------------- #endif /* MAIN_UDP_H_ */ |
1 2 |
#include "udp.h" //------------------------------------------------------------- |
Подключим нашу библиотеку в main.h
1 2 |
#include "wifi.h" #include "udp.h" |
Не забываем про CMakeLists.txt
set(COMPONENT_SRCS "main.c wifi.c udp.c")
Сохраним все файлы и попробуем собрать проект.
Если всё нормально собралось, то объявим и проинициализируем глобальный символьный массив для имени лога и добавим функцию для задачи обмена по протоколу UDP в файле udp.c
1 2 3 4 5 6 7 8 |
#include "udp.h" //------------------------------------------------------------- static const char *TAG = "udp"; //------------------------------------------------------------- void udp_task(void *pvParameters) { } //------------------------------------------------------------- |
Создадим для данной функции прототип и создадим такую задачу в функции app_main файла main.c
1 2 |
wifi_init_sta(); xTaskCreate(udp_task, "udp_task", 4096, NULL, 5, NULL); |
В конфигураторе проверим и при необходимости настроим адреса
В файле udp.h подключим библиотеки для работы с стеком протоколов LWIP
1 2 3 4 5 6 |
#include "esp_log.h" #include "lwip/err.h" #include "lwip/sockets.h" #include "lwip/sys.h" #include <lwip/netdb.h> |
Вернёмся в функцию udp_task файла udp.c и объявим несколько переменных, обычно используемых при работе с сокетами, а также переменную для работы с точной задержкой
1 2 3 4 5 |
void udp_task(void *pvParameters) { portTickType xLastWakeTime; int sockfd; struct sockaddr_in servaddr, cliaddr; |
Выведем соответствующее сообщение в лог и попытаемся создать сокет
1 2 3 4 5 6 |
struct sockaddr_in servaddr, cliaddr; ESP_LOGI(TAG, "Create socket...\n"); if ( (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0 ) { ESP_LOGE(TAG, "socket not created\n"); vTaskDelete(NULL); } |
Очистим память полей переменных типов структур и проинициализируем переменную для адреса клиента
1 2 3 4 5 6 7 8 |
vTaskDelete(NULL); } memset(&servaddr, 0, sizeof(servaddr)); memset(&cliaddr, 0, sizeof(cliaddr)); //Заполнение информации о клиенте cliaddr.sin_family = AF_INET; // IPv4 cliaddr.sin_addr.s_addr = INADDR_ANY; cliaddr.sin_port = htons(CONFIG_CLIENT_PORT); |
Попытаемся связать сокет с адресом клиента
1 2 3 4 5 6 7 8 |
cliaddr.sin_port = htons(CONFIG_CLIENT_PORT); //Свяжем сокет с адресом клиента if (bind(sockfd, (const struct sockaddr *)&cliaddr, sizeof(struct sockaddr_in)) < 0 ) { ESP_LOGE(TAG, "socket not binded\n"); vTaskDelete(NULL); } ESP_LOGI(TAG, "socket was binded\n"); |
Заполним информацию о сервере
1 2 3 4 5 |
ESP_LOGI(TAG, "socket was binded\n"); //Заполнение информации о сервере servaddr.sin_family = AF_INET; // IPv4 servaddr.sin_addr.s_addr = inet_addr(CONFIG_SERVER_IP); servaddr.sin_port = htons(CONFIG_SERVER_PORT); |
Запишем время в переменную для задержки
1 2 |
servaddr.sin_port = htons(CONFIG_SERVER_PORT); 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); |
Вот и готов наш клиент.
Соберём наш код, прошьём контроллер и посмотрим, что у нас в терминале
Судя по сообщениям, к точке доступа мы подключились, сокет создан и связан с клиентом. Также у нас успешно работает параллельная задача.
То, что пакеты сейчас из нашего клиента передаются вникуда, это вполне нормально для UDP, он же не требует соединения.
Проверим сначала, как наши пакеты приходят в приложение в смартфоне.
Для этого устанавливаем приложение (ссылка и QR-код для установки внизу страницы, возможно они могут не работать, тогда ищем в Play Market UDP TCP vlad_emb и приложение найдётся) на смартфон (или планшет), запускаем его и жмём единственную кнопку Start. Соответственно, наше мобильное устройство должно быть подключено к той же сети и его адрес должен быть конфигураторе.
Если всё нормально, то на дисплее мы увидим, как приходят и отображаются числа с клиента
Можно нажать кнопку Stop, в которую превратилась кнопка Start и прекратить приём чисел.
Теперь давайте запустим приложение на компьютере (ссылка также внизу страницы), которое достаточно просто запустить, установка не требуется. Далее вместо IP-адреса мобильного устройства мы заносим в конфигуратор IP-адрес компьютера, собираем и прошиваем код, и также нажимаем кнопку Start
Мы видим, что циферки наши тут таким же образом бегут со скоростью 100 пакетов в секунду.
Запустим анализатор пакетов WireShark, чтобы также убедиться, что пакеты наши безошибочные (контрольная сумма в порядке).
Для этого отфильтруем пакеты по IP-адресу нашего ESP32 (а его мы видели в терминальной программе в момент соединения с точкой доступа)
Итак, на данном занятии нам удалось создать простой UDP-клиент с использованием библиотеки LWIP. Пусть этот клиент умеет пока только передавать данные, но самое главное, что мы с данного урока начали обмен данными по беспроводной сети с использованием контроллера ESP32.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
TCP UDP client-server for Android
QR-код на скачивание приложения:
Недорогие отладочные платы ESP32 можно купить здесь: Недорогие отладочные платы ESP32
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
void udp_task(void *pvParameters)
{
// portTickType
uint32_t xLastWakeTime; ///<<<<<<<<
//
:
:
//
vTaskDelayUntil( &xLastWakeTime, ( 20 / 5)); //portTICK_RATE_MS ) );
}
:
: