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