Общие сведения:
В этом проекте мы соединим две Piranha ESP32 по WiFi и будем передавать массивы данных с одной на другую при помощи протокола UDP.
UDP (англ. User Datagram Protocol) — протокол пользовательских датаграмм. UDP предоставляет ненадёжный сервис, и датаграммы могут прийти не по порядку, дублироваться или вовсе исчезнуть без следа. UDP подразумевает, что проверка ошибок и исправление либо не нужны, либо должны исполняться в приложении.
Фомат массива
При передаче и получении информации используются байты. Одним байтом возможно передать число от 0 до 255, что соответствует типу данных byte и uint8_t, а также от -128 до 127, что соответствует типам char и int8_t. При передаче б_о_льших чисел используется трюк с преобразованием типов адресов (указателей). Например, если это массив с типом данных int (что соответствует четырём байтам для esp32), то каждый элемент массива отсылается побайтово. Таким образом число типа int i = -1 будет передано как четыре байта со значениями 0xff. Далее принимающая сторона преобразует байты обратно в int. Этот приём работает только при приёме/передаче данных представляющих непрерывные структуры в памяти, таких как массивы. Со связанными списками это приём не работает. При таком подходе очень легко выйти за границы массива, поэтому при передаче данных передаётся и их размер в байтах полученный при помощи оператора sizeof. Принимающая сторона вычисляет количество элементов приводя количество полученных байтов к размеру типа ожидаемых данных, так же при помощи оператора sizeof.
При передаче и приёме данных используйте одинаковые типы данных! В примерах используется проверка размера данных, но всё равно что-то может пойти не так. Если принимающая сторона будет ожидать int, а передающая сторона передаст char, то может произойти чтение или запись в ненадлежащую область памяти, от чего ESP32 может зависнуть, перезагружаться или работать непредсказуемо.
Видео:
редактируется ...
Нам понадобится:
Подключение:

Скетчи проекта для прямого подключения (esp32 - esp32):
Скетч для ESP32 в качестве UDP сервера
В данном скетче отправка данных осуществляется при помощи функции printf, а получение при помощи преобразования указателей. Это сделано для демонстрации двух способов. В первом случае получатель должен парсить строку, во втором отправитель должен отсылать только байты необходимых данных.
Копировать12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576`// Подключаем библиотеки #include <AsyncUDP.h> #include <WiFi.h> #include <WiFiAP.h>
// Определяем название и пароль точки доступа const char ssid = "esp32asAP"; const char password = "password12345";
// Создаём объект UDP соединения AsyncUDP udp;
// Определяем порт const uint16_t PORT = 49152;
// Массив данных для отправки int SDATA[2] {1984, 2021};
// Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) {
// Записываем адрес начала данных в памяти int pdata = (int)packet.data();
// Вычисляем размер данных const size_t len = packet.length() / sizeof(&pdata);
// Если адрес данных не равен нулю и размер данных больше нуля... if (pdata != NULL && len > 0) {
// Проходим по элементам массива for (size_t i = 0; i < len; i++) {
// Выводим каждый элемент в последовательный порт Serial.print(pdata[i]); Serial.print(", "); }
Serial.println();
// Отправляем данные клиенту packet.printf("Пакет получен. Ответ: %d, %d\n", SDATA[0], SDATA[1]); } }
void setup() {
// Инициируем последовательный порт Serial.begin(115200); Serial.println(); Serial.println("Инициируем точку доступа WiFi");
// Инициируем точку доступа WiFi WiFi.softAP(ssid, password); IPAddress myIP = WiFi.softAPIP();
// Выводим IP-адрес Serial.print("IP-адрес точки доступа: "); Serial.println(myIP);
// Инициируем сервер if(udp.listen(PORT)) {
// При получении пакета вызываем callback функцию udp.onPacket(parsePacket); }
Serial.println("Сервер запущен."); }
void loop() { delay(10); }`
Скетч для ESP32 в качестве UDP клиента
В скетче клиента отправка данных осуществляется побайтово, а приём при помощи парсинга строки.
Копировать123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384`// Подключаем библиотеки #include "WiFi.h" #include "AsyncUDP.h"
// Создаём переменную состояния кнопки bool keyState = false;
// Массив данных для отправки int DATA[2] {11276, 1969};
// Определяем название и пароль точки доступа const char ssid = "esp32asAP"; const char password = "password12345";
// Определяем порт const uint16_t PORT = 49152;
// Создаём объект с IP-адресом точки доступа IPAddress ADDR(192, 168, 4, 1);
// Создаём объект UDP соединения AsyncUDP udp;
// Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) { String msg = packet.readStringUntil(':'); int first = packet.readStringUntil(',').toInt(); int second = packet.readStringUntil('\n').toInt();
// Выводим преобразованные данные в последовательный порт Serial.printf("%s %d, %d\n", msg.c_str(), first, second); }
void setup() { // Инициируем последовательный порт Serial.begin(115200);
// Устанавливаем режим работы в качестве клиента WiFi.mode(WIFI_STA); WiFi.begin(ssid, password);
// Ждём подключения WiFi while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.print("."); delay(100); }
// Если удалось подключиться по UDP if (udp.connect(ADDR, PORT)) {
Serial.println("UDP подключён");
// вызываем callback функцию при получении пакета udp.onPacket(parsePacket); }
// Если подключение не удалось else {
Serial.println("UDP не подключён");
// Входим в бесконечный цикл while(1) { delay(1000); } } }
void loop() { // Отправляем данные серверу побайтово udp.broadcastTo((uint8_t*)&DATA, sizeof(DATA), PORT);
// Если соединение WiFi прервано if (WiFi.status() != WL_CONNECTED) {
// Вызываем функцию setup(), для повторного подключения setup(); }
delay(1000); }`
Скетчи проекта для подключения через WiFi роутер (esp32 - роутер - esp32):
При таком подключении необходимо сначала загрузить скетч с сервером UDP. В последовательный порт будет выведен IP-адрес сервера, который необходимо указать в скетче клиента. Так же в обоих скетчах необходимо указать название точки доступа и пароль. Обе ESP32 должны находиться в одной сети. При таком подключении передача данных немного медленнее, чем прямое подключение (оверхед роутера на маршрутизацию пакетов).
Скетч для ESP32 в качестве UDP сервера
Копировать1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889`// Подключаем библиотеки #include <AsyncUDP.h> #include <WiFi.h>
// Определяем название и пароль точки доступа const char ssid = "название WiFi"; const char password = "пароль WiFi";
// Создаём объект UDP соединения AsyncUDP udp;
// Определяем порт const uint16_t PORT = 49152;
// Массив данных для отправки int SDATA[2] {1984, 2021};
// Массив для получения int rdata[2] {0};
// Кол-во элементов массива для получения const size_t glen = sizeof(rdata) / sizeof(rdata[0]);
// Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) {
// Записываем адрес начала данных в памяти const int tmp = (int)packet.data();
// Вычисляем размер данных const size_t len = packet.length() / sizeof(&rdata);
// Если адрес данных не равен нулю и размер данных больше нуля... if (tmp != nullptr && len > 0) {
// Проходим по элементам массива for (size_t i = 0; i < len && i < glen; i++) {
// Записываем в глобальную переменную rdata[i] = tmp[i]; }
// Отправляем данные клиенту packet.printf("Пакет получен. Ответ: %d, %d\n", SDATA[0], SDATA[1]); } }
void setup() {
// Инициируем последовательный порт Serial.begin(115200);
// Устанавливаем режим работы в качестве клиента WiFi.mode(WIFI_STA); WiFi.begin(ssid, password);
// Ждём подключения WiFi while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.print("."); delay(100); }
// Выводим IP-адрес IPAddress myIP = WiFi.localIP(); Serial.print("IP-адрес точки доступа: "); Serial.println(myIP);
// Инициируем сервер if(udp.listen(PORT)) {
// При получении пакета вызываем callback функцию udp.onPacket(parsePacket); }
Serial.println("Сервер запущен."); }
void loop() {
// Выводим значение элементов массива for (size_t i = 0; i < glen; i++) { Serial.println(rdata[i]); }
delay(1000); }`
Скетч для ESP32 в качестве UDP клиента
Копировать123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293`// Подключаем библиотеки #include "WiFi.h" #include "AsyncUDP.h"
// Создаём переменную состояния кнопки bool keyState = false;
// Массив данных для отправки int DATA[2] {11276, 1969};
// Определяем название и пароль точки доступа const char ssid = "название WiFi"; const char password = "пароль WiFi";
// Определяем порт const uint16_t PORT = 49152;
// Определяем адрес сервера (например "192.168.1.3") const char* SER_IP = "n.n.n.n";
// Создаём объект UDP соединения AsyncUDP udp;
// Переменные для получения данных String msg = ""; int first = 0; int second = 0;
// Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) { msg = packet.readStringUntil(':'); first = packet.readStringUntil(',').toInt(); second = packet.readStringUntil('\n').toInt(); }
void setup() { // Инициируем последовательный порт Serial.begin(115200);
// Устанавливаем режим работы в качестве клиента WiFi.mode(WIFI_STA); WiFi.begin(ssid, password);
// Ждём подключения WiFi while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.print("."); delay(100); }
// Инициируем объект IP-адреса IPAddress addr; // Вычисляем IP-адрес из строки addr.fromString(SER_IP); // Если удалось подключиться по UDP if (udp.connect(addr, PORT)) {
Serial.println("UDP подключён");
// вызываем callback функцию при получении пакета udp.onPacket(parsePacket); }
// Если подключение не удалось else {
Serial.println("UDP не подключён");
// Входим в бесконечный цикл while(1) { delay(1000); } } }
void loop() { // Отправляем данные серверу побайтово udp.broadcastTo((uint8_t*)&DATA, sizeof(DATA), PORT);
// Выводим полученные данные в последовательный порт Serial.printf("%s %d, %d\n", msg.c_str(), first, second);
// Если соединение WiFi прервано if (WiFi.status() != WL_CONNECTED) {
// Вызываем функцию setup(), для повторного подключения setup(); }
delay(1000); }`
Ссылки
Комплект для повторения проекта
Что понадобится
Это быстрый ориентир по основным модулям и деталям. Полный список для повторения проекта смотрите в самой статье.
2 позиции