• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Вопрос Проверка скорости передачи UDP

Garmin

Member
После того, как я добился устойчивого соединения по WiFi между двумя модулями ESP-12, я занялся проверкой качества и возможной скорости соединения.
Для этого я написал простенькую программу.
Вначале массив из 64 байт (16 слов) забивается константами. Первое слово назначается номером пакета и при каждой передаче оно увеличивается на 1. Это и будет тестовый пакет для передачи по UDP.
Код:
/*******************************************************************************
* Тестовые данные в буфер
*******************************************************************************/
void ICACHE_FLASH_ATTR set_udata (void)
{
    volatile u32 *p_buf = b.send;
    *p_buf++ = 0;            // index
    *p_buf++ = 0x05040302;
    *p_buf++ = 0x09080706;
    *p_buf++ = 0x0d0c0b0a;
    *p_buf++ = 0x11100f0e;
    *p_buf++ = 0x15141312;
    *p_buf++ = 0x19181716;
    *p_buf++ = 0x1d1c1b1a;
    *p_buf++ = 0x21201f1e;
    *p_buf++ = 0x25242322;
    *p_buf++ = 0x29282726;
    *p_buf++ = 0x2d2c2b2a;
    *p_buf++ = 0x31302f2e;
    *p_buf++ = 0x35343332;
    *p_buf++ = 0x39383736;
    *p_buf++ = 0x3d3c3b3a;
}
Затем по прерыванию от внешней ножки я передаю пакет. Предварительно убедившись, что модули соединились. Для передачи использую функцию espconn_send. После каждой передачи увеличиваю номер посылки.
Код:
static void __attribute__((optimize("O2"))) spi_int_handler (void *para)
{
    dport_isr_flag_t    dport_value = DPORT->isr_flag_bits;
    volatile u32        *buffer;
    __IO u32            *spi_reg;
  
    // прерывание от spi0
    if (dport_value.spi0_isr)
    {
        // стереть флаги разрешения прерывания и статуса прерывания
        SPI0->slave &= ~(SPI_SLAVE_INT_STT_MASK | SPI_SLAVE_INT_EN_MASK);
    }
    // прерывание от spi1
    if (dport_value.spi1_isr)
    {
        SPI1->slave &= ~(SPI_SLAVE_INT_STT_MASK);    // сотрём флаги прерываний
        espconn_send (&g.user_udp_struct, (u8 *)b.send, 64);    // struct espconn *espconn, uint8 *psent, uint16 length
                b.send[0]++;    // увеличить индекс
    }
    if (dport_value.i2s_isr)
    {
        // прерывание от i2s
    }
}
Во время приёма я использую коллбэк
user_udp_struct.recv_callback = user_udp_receive_cb;
Код:
/*******************************************************************************
* функция, вызываемая после приёма данных по UDP
*
*******************************************************************************/
void __attribute__((optimize("O2"))) user_udp_receive_cb (void *arg, char *pdata, unsigned short len)
{
    struct espconn    *parg = arg;
    u32 *data = (u32 *)pdata;

    g.num_err = 0;
    if (g.state == U_STATE_TRANSMIT)
    {
        // сравнить IP
        g.num_err += tcp_compare (parg->proto.udp->remote_ip, g.user_udp_struct.proto.udp->remote_ip);
        // сравнить порт
        g.num_err += port_compare (parg->proto.udp->remote_port, g.user_udp_struct.proto.udp->remote_port);
        // сравнить длину посылки == 64 байта
        g.num_err += len_compare (len);
// тест - сравнить номер посылки
        g.receive_index++;
        if (*data != g.receive_index)
        {
            g.num_err += 8;
            g.prev_index = g.receive_index;
            g.now_index = *data;

            g.receive_index = *data;
        }
      
        if (g.num_err == 0)
        {
            // передача по SPI
        }
        else
        {
            g.count_err++;
        }
    }
}
Во время приёма я проверяю IP адрес отправителя и порт отправителя, длину посылки и номер посылки. При несовпадении данных записываю сигнатуру посылки.
Проверка заключается в периодическом опросе данных сигнатур.

Так вот, когда я передаю данные с частотой 300 Гц, у меня проходят почти все пакеты. На 1-10 тыс. посылок приходится 2-10 потерянных пакетов.
Ошибок IP, MAC, длины не происходит.
А только я поднимаю скорость передачи посылок до 3 кГц (1.5Мбод), то получаю 25% потерянных пакетов.
Повышение частоты процессора до 180 МГц уменьшает процент потерь до 10%.
Я так понимаю, что процедура отправки слишком медленная.

Как тогда добиваются передачи данных на скорости до 10 Мбод в веб-свалке?
 
Последнее редактирование:

pvvx

Активный участник сообщества
Как тогда добиваются передачи данных на скорости до 10 Мбод в веб-свалке?
Ваши вопросы не ясны. Чип должен давать трансфер в одну сторону к 5-ти Мегабайтам в секунду. Но китайский SDK = тормоз.
Пример передачи по UDP данных с ADC с оцифровкой 192 кГц (передается 2 байта на замер, блоками по 1024 байт), т.е. 384 кило/сек. Роутер показывает загрузку по WiFi 400к/cек.
esp8266web/ovls/wdrv at master · pvvx/esp8266web · GitHub
В данном релиз основная нагрузка на CPU приходится на прием по прерываниям с SAR (обработка аппаратного прерывания в 192 килогерца).
Анализом предельного трансфера ESP8266 по UDP не занимался. Интересовал только предел по TCP с использованием LwIP. Стабильный итог на который можно выйти на TCP выходит 1.2 Мегабайта в сек в одну сторону. C Web немного менее, но:
HTTP Web (в web-свалке), стабильно отдает виртуальные данные (чтение адресной шины), со скорость более 1 Mегабайта в сек. Но следует обязательно отключить вывод отладки в UART, т.к. она сильно тормозит процесс... и при установке 'частота CPU' на 160MHz.

ЗЫЖ
К тому, почему ваши вопросы не ясны, т.к. я думаю, что вы понимаете, что:
1) Чем больше размер блока данных, тем меньше накладные расходы передачи на заголовки и обработку всякими процедурами.
2) Уже много раз описывал: в последних версиях SDK, при установке параметра мощности передачи на максимум, роутеры и прочие устройства очень плохо принимают пакеты от модуля. Что-то там намудрили и наверно сигнал передачи искажен (дома серьезного анализатора спектра WiFiи т.д. пока нет, а носиться на работу модулем – лень – он всего то игрушка и не более :)).
 
Последнее редактирование:

Garmin

Member
Благодарю за подсказку.
Я рассчитывал на то, что 2Мбит в одну сторону этот чип должен вытянуть.
Для пояснения, с помощью каких программ SDK я передаю и принимаю, я написал имена программ.
Попробую разобраться в вашем примере и поиграться мощностью передачи. Как что-то прояснится, напишу.
 

pvvx

Активный участник сообщества
Я рассчитывал на то, что 2Мбит в одну сторону этот чип должен вытянуть.
Он вытянет и больше, если будете передавать UDP пакеты с блоком данных в 1024 байта. Кол-во передачь в секунду ограничена скоростью обработки CPU функций передачи в SDK. espconn - это нашлепка над LwIP и в случае UDP не сильно увеличивает исполняемый код. В некоторых случаях c TCP там возникает доп. копирование данных и требуется больше памяти и процесс тормознее, чем работать напрямую с LwIP...
Для пояснения, с помощью каких программ SDK я передаю и принимаю, я написал имена программ.
Как у вас при использование espconn_send() в аппаратном прерывании система то живет? :) Я уж на это не стал обращать внимания... Пишите, что это тест... Ну думал что блаж такая именно для теста... :) Повторности вхождения espconn и прочие процедуры SDK не поддерживают...
 
Последнее редактирование:

Garmin

Member
Как у вас при использование espconn_send() в аппаратном прерывании система то живет?
Где в описании функций SDK есть ссылка на ограничение работы этой функции? Откуда я могу всё знать?
Если глюкавая SDK и глюкавая документация, кому пенять надо?
Именно для прояснения я задаю здесь вопросы.
Вопрос по сути:
Вы используете udp_sendto в коллбеке. Её можно в прерывании SPI вызывать, или необходимо создавать задачу для передачи UDP, и передавать ей сообщение?
 

pvvx

Активный участник сообщества
Где в описании функций SDK есть ссылка на ограничение работы этой функции? Откуда я могу всё знать?
Если глюкавая SDK и глюкавая документация, кому пенять надо?
Именно для прояснения я задаю здесь вопросы.
Вопрос по сути:
Вы используете udp_sendto в коллбеке. Её можно в прерывании SPI вызывать, или необходимо создавать задачу для передачи UDP, и передавать ей сообщение?
В аппаратных прерываниях обычно не используют неатомарные операции, тем более процедуры с десятками уровнями вложенности и задействующие всю аппаратуру...
В RTOS вам придется использовать обрамлять всякие critical section и городить разделяемые ресурсы, семафоры...
В NON_OS_SDK придется помучиться, но проще оперировать через ets_post()/ets_task().
Иначе при обработке приема/передаче или даже выводе какого printf() у вас возникнет прерывание и побегут приколы... ets_task-и исполняются по очереди в NON_OS_SDK. Полные описаний получаете путем disasm SDK к примеру в IDA.
Обучение написания на СИ и всяким программистским приемам, а также переводу названий спец.сленгу не входят в данный сайт и с вашими требованиями обычно осуществляются только через платное обучение в течении многих лет :)
Тут можно ответить только на конкретный вопрос, умещающийся в скромное окошко форума... :)
 

Garmin

Member
Да ладно, вы на мой конкретный вопрос уже ответили... :)
Буду огород городить. Делать отдельную задачу и передавать через [inline]ets_post()/ets_task()[/inline] :)
 

pvvx

Активный участник сообщества
Да ладно, вы на мой конкретный вопрос уже ответили... :)
Буду огород городить. Делать отдельную задачу и передавать через [inline]ets_post()/ets_task()[/inline] :)
Они в SDK вроде по другому называются... Китайцы к ним сделали нашлепки типа sysytem_post(), чтобы отсечь приоритет пользователя от системного...
В NON_OS SDK системный менеджер тасков и програмных таймеров = ets_run(). Он и вызывает их по очереди с учетом приоритета. Т.е. система работает только по событиям post и софт-таймеров. Аппаратные прерывания идут сами по себе....
Т.е. если запущен какой таск, то 'прерывния' софт-таймера или другие ждут завершения того 'таска'. По этому длинная по времени процедура убивает работу системы... А уж апп.прерывания - пилят всё и не отслеживается даже переполнение стека...
 
Последнее редактирование:

Garmin

Member
Немного разобрался с функциями [inline]ets_post()/ets_task()[/inline].
Поднял UDP соединение с передачей данных через таск.
Есть один простой вопрос и один сложный.
Простой:
Я устанавливаю параметры UDP соединения с указанием своего IP адреса и порта и адреса удалённого устройства. Инициализация UDP вызывается в колбеке после установки соединения:
Код:
int ICACHE_FLASH_ATTR user_udp_init (void)
{
    g.user_pcbv = udp_new ();    // (struct udp_pcb *)
    g.user_pcbv->local_ip.addr = g.local_ip.ip.addr;
    g.user_pcbv->local_port = g.local_port;

    udp_connect (g.user_pcbv, &g.remote_ip, (u16)g.remote_port);    // соединиться

    udp_recv (g.user_pcbv, user_udp_receive_cb, g.user_pcbv);    // зарегистрировать функцию приёма

    return 1;
}
При передаче я также указываю порт получателя:
Код:
/******************************************************************************
* Передача данных из буфера b.spi_receive по UDP
******************************************************************************/
void ICACHE_FLASH_ATTR user_tx (void)
{
    struct pbuf *z;

    z = pbuf_alloc (PBUF_TRANSPORT, sizeof (b.spi_receive), PBUF_RAM);
    if (z != NULL)
    {
        err_t err = pbuf_take (z, b.spi_receive, sizeof (b.spi_receive));
        if (err == ERR_OK)
        {
            udp_sendto (g.user_pcbv, z, &g.remote_ip, g.remote_port);
        }
        pbuf_free (z);
    }
}
А при приёме я получаю не порт получателя, а порт отправителя:
Код:
/*******************************************************************************
* функция, вызываемая после приёма данных по UDP
*
*******************************************************************************/
void ICACHE_FLASH_ATTR user_udp_receive_cb (void *arg, struct udp_pcb *upcb, struct pbuf *p, ip_addr_t *addr, u16_t port)
{
    u32 length;
    u32 *data;

    g.num_err = 0;

    if (p == NULL) return;
    if (p->tot_len < 1)
    {
        pbuf_free (p);
        return;
    }
    length = p->tot_len;
    data = (u32 *)p->payload;
    // сравнить IP
    g.num_err += tcp_compare ((u8*)addr, (u8*)&g.remote_ip.addr);
    // сравнить порт
    g.num_err += port_compare (port, g.remote_port);
    // сравнить длину
    g.num_err += len_compare (length);
    // тест - сравнить номер посылки
    g.num_err += index_compare (*data);
    // конец теста
    if (g.num_err == 0)
    {
        ...
        }
    }
}
Так вот, проверка на порт срабатывает только, если указан порт ремоут, а для приёмника это порт отправителя. Я так понимаю, что при передаче на приёмник он должен получать посылки на свой порт.
 

Garmin

Member
Второй вопрос сложнее.
Я тестировал качество соединения UDP. Вначале я уменьшил мощность передатчика[inline]system_phy_set_max_tpw (10);[/inline] , по показаниям сниффера уровень WiFi снизился с -10дБ до -45дБ, что выше на 10-20дБ, чем уровень остальных точек WiFi в месте прослушивания, но не перегружает близко расположенный приёмник.
Тестирование проводилось при передаче пакетов в одну и обе стороны, при частоте процессора 80 и 160МГц и при различной длине UDP посылки: 64, 128, 256, 512, 1024 байт.
Скорость передачи в одну сторону 205 кбайт/с.
Результаты тестирования:
Блок 64 байт 80МГц: в одну сторону потери 6,4-17%
в две стороны потери 15,8%
Блок 128 байт: в две стороны потери 3-6%
Блок 256 байт: в одну сторону потери 0,03-0,04%
в две стороны потери 0,8-1,8%, количество ошибок 122, количество потерянных блоков 756
Блок 512 байт 160 МГц: в одну сторону потери 0,01-0,13%,
в две стороны потери 0,02-0,3%, количество ошибок 8, количество потерянных блоков 20
Блок 1024 байт: в одну сторону потери 0%
в две стороны потери 0,01-0,11%, количество ошибок 6, количество потерянных блоков 16.

Анализируя данные тестов, я вижу, что потери происходят пачками, и есть при любых двусторонних передачах.
Это могут быть либо коллизии двух передатчиков, либо в некоторые моменты времени блок WiFi занимается своими делами.
Добавление в инит команды [inline]wifi_set_sleep_type (NONE_SLEEP_T);[/inline] ситуацию не изменило.
Есть ли у кого-нибудь мысли по этому поводу?
Как добиться. чтобы передача WiFi не начиналась одновременно при несинхронных источниках данных?
 
Последнее редактирование:

pvvx

Активный участник сообщества
Анализируя данные тестов, я вижу, что потери происходят пачками, и есть при любых двусторонних передачах.
Это могут быть либо коллизии двух передатчиков, либо в некоторые моменты времени блок WiFi занимается своими делами.
А помех разве не бывает и нет других WiFi рядом?
 

Garmin

Member
Уровень сигнала от ESP8266 выше посторонних сигналов не меньше, чем на 20 дБ. Я не думаю, что это связано с помехами.
Неужели никто не проверял ESP8266 на характеристики передачи UDP?
Если нет никакого ответа, придётся пробовать TCP, он с проверкой доставки. Однако в этом случае вырастут накладные расходы на передачу и я не знаю задержки передачи между пакетами в этом случае. Посоветуйте пример, из которого можно сварганить простую передачу по TCP/IP.
 

pvvx

Активный участник сообщества
Уровень сигнала от ESP8266 выше посторонних сигналов не меньше, чем на 20 дБ. Я не думаю, что это связано с помехами.
Неужели никто не проверял ESP8266 на характеристики передачи UDP?
А смысл? Трафик примерно одинаковый с TCP, если клиент-сервер в той-же сети. Но на WiFi всегда потери UDP.
Вчера тестировал на UDP и TCP модуль RTL00 V1.0 (RTL8710AF): прием и передача болтаются в области 1.0..1.2 Мегабайта в сек (передача потока длиной от 10MBytes) и одинаково на UDP и TCP...
Если нет никакого ответа, придётся пробовать TCP, он с проверкой доставки. Однако в этом случае вырастут накладные расходы на передачу и я не знаю задержки передачи между пакетами в этом случае. Посоветуйте пример, из которого можно сварганить простую передачу по TCP/IP.
Есть в UDK - wifi-ap-tcp-client / wifi-sta-tcp-client и на сайте Espressif ESP8266 as TCP server - ESP8266 Developer Zone ESP8266 as TCP client - ESP8266 Developer Zone
 
Последнее редактирование:

pvvx

Активный участник сообщества
Интересно, какие потери у вас при передаче UDP?
Встроенный в AT тест для RTL не подсчитывает потери на UDP.
Что на ESP, что на RTL надо самому писать счетчики контроль потерь... А это не интересно, т.к. достаточно включить второй модуль ESP и нажимать ему RESET с удержанием. Вся связь по WiFi в округе и в большинстве случаев нажатий RESET будет повалена :p ESP - универсальная глушилка WiFi. Что-то там намудрили Espressif в чипе с WiFi частью и она не отключается по RESET.
Так-же мне не интересно это дело, т.к. вокруг обычно работает не менее десятка AP и 'ползают' десятки ST с запросами у пользователей в каждом устройстве. Всё это, включая микроволновки у соседей дает выпадение пакетов UDP.
 
Последнее редактирование:

Garmin

Member
Благодарю за развёрнутый ответ.
Для того, чтобы убрать запросы внешних AP, я попробовал использовать опцию [inline]ssid_hidden = 1[/inline] в параметрах wifi соединения. Но второй модуль - станция не смог соединиться со скрытой точкой доступа. Есть ли у вас опыт использования этой опции?
Пока я думаю перейти на TCP и проверить качество соединения и стабильность задержки передачи сигналов.
 

nikolz

Well-known member
С TCP должны быть задержки при многих ESP так как придется постоянно устанавливать и терять соединение.
 

Garmin

Member
Я хочу использовать ESP в двух режимах:
1) Соединение двух ESP и двухсторонняя передача данных;
2) Односторонняя передача от одной ESP access point нескольким stations (изначально думал использовать широковещательные пакеты).
 

nikolz

Well-known member
Я хочу использовать ESP в двух режимах:
1) Соединение двух ESP и двухсторонняя передача данных;
2) Односторонняя передача от одной ESP access point нескольким stations (изначально думал использовать широковещательные пакеты).
Я пока серьезно не погружался в общение ESP между собой, так как у меня умные измерительные приборы и они общаются с главным компом.
Но есть две идеи в плане создания внутренних сетей.
1) Реализация на ESP ethrnet обмена . т.е это то, что идет до UDP и TCP, так как на внутренних сетях eSP UDP и тем более TCP это излишество.
2) Использовать для создания внутренней сети NRF
Если кто-то это уже делал интересно было бы послушать о результатах.
 

Garmin

Member
Свои Ethernet пакеты можно отправлять, есть соответствующая функция SDK. Но я не работал с ней.
 
Сверху Снизу