Продолжая тему передачи данных по беспроводной сети посредством протокола UDP, мы теперь попробуем принять данные с сервера. До сих пор мы пока только передавали пакеты с нашего клиента. Поэтому я добавил в мои программки для Android и для Windows ответы на наши пакеты. Программу для Windows я прикрепил внизу страницы, а приложение для Android достаточно будет просто обновить из Play Market. Сервер будет на 16-разрядное целое знаковое число отвечать подобным числом, но являющимся разностью числа 32767 и полученного числа. Также для того, чтобы нам нагляднее отследить пришедшие на наш клиент данные, мы к нашей плате подключим символьный дисплей, и схема наша примет следующий вид
Проект был сделан из проекта прошлого урока с именем WIFI_STA_UDP_CLIENT_TX и был назван WIFI_STA_UDP_CLIENT_RX.
Откроем наш проект в Espressif IDE, затем в соответствующий каталог нового проекта скопируем файлы lcd2004.h, lcd2004.c, i2c_user.h и i2c_user.c из проекта урока 18 с именем EVENT_GROUP_LCD.
Обновим дерево проектов и добавим данные модули в файле CMakeLists.txt
set(COMPONENT_SRCS «main.c wifi.c udp.c i2c_user.c lcd2004.c«)
Затем в файле Kconfig.projbuild добавим два пункта меню для ножек I2C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
LED GPIO. config SDA_GPIO int "SDA GPIO number" range 0 48 default 21 help GPIO number SDA. config SCL_GPIO int "SCL GPIO number" range 0 48 default 22 help GPIO number SCL. |
В файле main.h подключим библиотеку нашего дисплея
1 2 |
#include "udp.h" #include "lcd2004.h" |
В файле main.c произведём инициализацию дисплея
1 2 3 4 |
ESP_LOGI(TAG, "nvs_flash_init: 0x%04x", ret); ret = i2c_ini(); ESP_LOGI(TAG, "i2c_ini: %d", ret); LCD_ini(); |
В файле udp.h также подключим библиотеку для дисплея
1 2 3 |
#include <lwip/netdb.h> #include "lcd2004.h" |
В файле udp.c объявим структуру для данных, которые мы будем посылать в очередь на дисплей и саму очередь
1 2 3 4 5 6 7 8 9 10 11 |
static const char *TAG = "udp"; //------------------------------------------------------------- typedef struct { unsigned char y_pos; unsigned char x_pos; char *str; } qLCDData; //------------------------------------------------ xQueueHandle lcd_string_queue = NULL; //------------------------------------------------ |
Добавим также функцию для задачи, которая будет забирать эти данные из очереди и отправлять их на дисплей
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
xQueueHandle lcd_string_queue = NULL; //------------------------------------------------ void vLCDTask(void* arg) { BaseType_t xStatus; qLCDData xReceivedData; for(;;) { xStatus = xQueueReceive(lcd_string_queue, &xReceivedData, 10000 /portTICK_RATE_MS); if (xStatus == pdPASS) { LCD_SetPos(xReceivedData.x_pos,xReceivedData.y_pos); LCD_String(xReceivedData.str); } } } //------------------------------------------------ |
Ниже добавим функцию для задачи, которая будет принимать пакеты с сервера, так как практика показала, что приём пакетов лучше производить в отдельном потоке
1 2 3 4 5 |
//------------------------------------------------ static void recv_task(void *pvParameters) { } //------------------------------------------------ |
В функции udp_task объявим дескрипторы для задач, чтобы впоследствии мы могли ими воспользоваться для удаления этих задач
1 2 3 |
void udp_task(void *pvParameters) { TaskHandle_t xLCDTaskHandle = NULL, xRecvTask = NULL; |
Создадим очередь и задачу для дисплея
1 2 3 |
struct sockaddr_in servaddr, cliaddr; lcd_string_queue = xQueueCreate(10, sizeof(qLCDData)); xTaskCreate(vLCDTask, "vLCDTask", 2048, NULL, 2, &xLCDTaskHandle); |
Удалим их в случае невозможности связать сокет с адресом клиента
1 2 3 |
ESP_LOGE(TAG, "socket not binded\n"); vTaskDelete(xLCDTaskHandle); vQueueDelete(lcd_string_queue); |
Создадим задачу для приёма пакетов, в которую в качестве параметра передадим идентификатор сокета
1 2 |
servaddr.sin_port = htons(CONFIG_SERVER_PORT); xTaskCreate(recv_task, "recv_task", 4096, (void*)&sockfd, 5, &xRecvTask); |
Увеличим задержку в 10 раз, чтобы успевать принимать пакеты
1 2 |
sendto(sockfd, &i, 2, 0, (struct sockaddr*) &servaddr, sizeof(servaddr)); vTaskDelayUntil( &xLastWakeTime, ( 100 / portTICK_RATE_MS ) ); |
В конце тела функции удалим наши задачи и очередь, чтобы освободить память, хотя мы знаем, что мы туда вряд ли когда попадём, поэтому если мы захотим удалять задачу передачи пакетов и связи с сервером, то мы должны позаботиться об удалении всех очередей и задач, которые используются в модуле
1 2 3 4 |
close(sockfd); vTaskDelete(xRecvTask); vTaskDelete(xLCDTaskHandle); vQueueDelete(lcd_string_queue); |
Осталось нам придумать логику приёма пакетов, поэтому перейдём в функцию задачи приёма пакетов recv_task и объявим небольшой буфер, также создадим указатель целого типа на параметры, объявим переменную структуры очереди, небольшой строковый массив, а также проинициализируем позицию и указатель на строку, после чего добавим бесконечный цикл, в котором будем постоянно инкрементировать переменную i
1 2 3 4 5 6 7 8 9 10 11 |
static void recv_task(void *pvParameters) { char buf[10] = {}; int *sock = (int*) pvParameters; qLCDData xLCDData; char str1[10]; xLCDData.y_pos = 1; xLCDData.str = str1; for(short i=0;;i++) { } |
Попытаемся принять пакет в бесконечном цикле, возьмём из него данные, преобразуем их в строку и отправим в очередь для отображении на дисплее
1 2 3 4 5 6 |
for(short i=0;;i++) { recv(*sock, buf, sizeof(buf), 0); snprintf(str1, sizeof(str1), "%6d", *(short*)buf); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); } |
Соберём код, прошьём контроллер, убедившись, что имя и ключ точки доступа в конфигураторе правильные, также правильный IP и номер порта сервера, запустим сервер на смартфоне и, если всё правильно, то мы должны будем увидеть декриментирующиеся числа, пришедшие с сервера, на дисплее
Всё отлично работает.
Также мы можем теперь менять порт в приложении сервера (и не только, так как приложение время от времени обновляется). Попробуем его изменить и изменить его в заголовочном файле нашей программы. После этого также всё отлично передаётся и принимается.
Затем можно также проверить работу нашей программы и с сервером для Windows, в котором также теперь можно менять номер порта
Изменим IP-адрес и номер порта в программе и увидим, что у нас по-прежнему всё работает.
Итак, на данном занятии мы усовершенствовали свой клиент UDP, который теперь умеет также и принимать пакеты.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
TCP UDP client-server for Android
QR-код на скачивание приложения:
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий