• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе 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. Но я не работал с ней.
 
Сверху Снизу