USB-CDC иногда теряет куски текста при передаче. ESP32-S3

Короче говоря, столкнулся с такой проблемой (которой раньше я не замечал): при выводе в терминал (обычный Serial.write(…) ) иногда рандомно теряет какие-то слова или по нескольку слов. Буквы по отдельности не теряются.

Пример: вывожу буфер, 1кб, состоит он из обычнх строчек, 80 символов длиной, \r\n на конце. Сами строчки - это текст на английском, help page. Иногда может потеряться строчка целиком, иногда - одно или несколько слов. Иногда - сиволы \r\n теряются.

Например, вместо

Hello dear Santa!
I am writing to you to say
that you are

Будет выведено

Santa!
I am writing to you to say
that are

Сделал тест:

Выводил один и тот же буфер просто в цикле for. После вывода стоит задержка и Serial.flush(). Не помогает. Из десяти попыток - 6 раз выводится без искажений, а 4 с рандомными искажениями. Всегда в разных местах.

С досады переписал функцию вывода - теперь, если буфер больше 128 байт, то он нарезается на кусочки:

Было:

// Send characters to user terminal
//
extern "C" int console_write_bytes(const void *buf, size_t len) {
  return Serial.write((const uint8_t *)buf, len);
}

Стало:

// Send characters to user terminal
//
extern "C" int console_write_bytes(const void *buf, size_t len) {
  size_t len0 = len;
  while (len > 128) {
    Serial.flush();
    Serial.write((const uint8_t *)buf, 128);
    buf += 128;
    len -= 128;
  }
  Serial.flush();
  Serial.write((const uint8_t *)buf, len);
  return len0;
}

Заработало. Но, извините, так же не должно быть. Никто с таким не сталкивался? Это какие-то ограничения буфера передачи? Выглядит как бага.

Именно. Он определён в недрах ядра. Вот человек менял размер под себя. Хирургическая операция по увеличению буфера последовательного порта у Arduino IDE / Хабр

2 лайка

Как то потребовался больший обьём буфера приёма и просто в сетапе прописал строку

Serial.setRxBufferSize(600);

В скобках размер в байтах. Попробуй, может поможет

тренируют лаконичность )))

По одному байту передавать можно

Да. Величина буфера в Ардуино 64 байта. Эта величина где-то довольно глубоко закопана т.к. разработчики Ардуино не предпролают, что кому-то эта величина может не подойти.

Недостаток буфера по-разному проявляется на передающем и на приемном конце.
На передающем - функция отправки (например, Serial.write()) блокирует работу программы, пока буфер не освободится. Т.е. есть место в буфере - данные помещаются в буфер и немедленный выход из функции. Нет места - функция ждет, пока состоится отправка имеющихся в буфере данных, и возвращает управление только после того, как данные поместились в буфер (по мере передачи буфер освобождается).
На приемном конце - хуже: данные попросту теряются.

То есть по внешним признакам проблема именно на приемном конце - принимающая программа не успевает вовремя вычитывать буфер.

а каким монитором смотришь, родным, может Putty попробуешь

Короче, пока все плохо. Устойчивой работы не добился. Ни в каком виде Serial.write() не работает, как положено.

Единственное, что не теряет куски текста - это fwrite(…, stdout). Но там другая засада - пока \n\r не передашь - текст не выводится.

Буферизует вывод, короче. И в примерах в ESP32 есть даже на эту тему код, который выключает буферизацию и читает\пишет через fwrite/fwrite, но.. Но пример я не запускал (он не для ардуины) но когда отключил буферизацию, как в примере - нифига не поменялось.

Горюю.

TeraTerm

кто виноват и что делать?

Я уже писал выше: вероятнее всего проблема не на передающем, а на приемном конце.
Вероятно Вы что-то делаете на приемном конце из-за чего буфер не успевает освободиться. Проверьте: если ничего не делать с принимаемыми данными, а только считать количество принятых байтов, количество совпадает?

На какой скорости? На какое расстояние?
А если в цикле, по одному байту(символу)? Можно свой буфер сделать, какой надо, и в цикле, небольшие задержки(если надо) для приёма.
Или скорость в приоритете?

Скорость там одна (игнорируется в HWCDC). Расстояние 60см.

Я делал буфер 63 байта и задержку 1 мс. Разбивал длинные строки на короткие. Не помогает.

Похоже на то, что строчки выводимые перетирают друг дружку. Как если бы посреди печати строчки драйвер вдруг начал печатать следующую.

Печатаю, например, в цикле текст через многократные вызовы Serial.write(). Процентов в 10-20 случаев теряются куски текста. Приемная сторона - лаптоп, ну он достаточно быстрый, чтобы есп32 его прям залил.

А раньше все работало.. Когда то. Откатил Arduino Core - не помогло.

Собственно, что я за этот USB взялся: ни с того ни с сего вдруг перестала прошиваться еспшка. После прошивки ее нельзя прошить второй раз - надо выдергивать и вставлять кабель назад. Если этого не сделать, то прошивание завершается с ошибкой о “unexpected byte <случайный байт>, possible corruption or line noise”. Перешел на USB-CDC - прошивается без ошибок, но теряются куски текста. Иногда.

Да, попробовать другой кабель наверно стоит, но.. Но тот же кабель прекрасно прошивает после сброса питания. При прошивке через USBCDC ошибок не возникает, передергивать кабель не нужно. Кнопка ресет, кстати, не помогает тоже.

Заказал пока себе еще одну плату такую же. Странно как-то, что проблемы возникли и с прошивкой через уарт и с работой через усб как-то одновременно

Вот. Если же использую fwrite(…, stdout) вместо Serial.write(), то ничего не теряется. Но есть другие нерешаемые пока проблемы и пока использовать просто fwrite не получается: символы не выводятся, пока \n не встретится, зато потом - все разом. А мне как раз надо и посимвольно бывает. Не всегда у меня вывод завершается на \n Ж(

Вот, “подкинуть” новую плату и кабель, сразу видно будет, железо виновато, или софт. Куда копать дальше.

P.S.

Вот, тоже бы проверить не помешало ИМХО

Нашел свою багу в трекере Espressif.Пишут, что вылечили, но по факту - нет. Написал им новоую туда :).

Бага в Arduino Core где-то, в HWCDC.cpp. Если вывод сделать посимвольный, вот так:

// Send characters to user terminal
//
extern "C" int console_write_bytes(const void *buf, size_t len) {
  int len0 = len;
  while( true ) {
    int space = 1/*Serial.availableForWrite()*/, w;
    if (space) {
      if (space >= len) {
        len = Serial.write((const uint8_t *)buf, len);
        Serial.flush();
        return len;
      }
      w = Serial.write((const uint8_t *)buf, space);
      Serial.flush();
      buf += w;
      len -= w;
    } else
      portYIELD();
  }
  return len0;
}

то ничего не теряется, но скорость примерно как у хорошей советской машинистки. Медленно вывод идет :).

Попробовать Serial.flush(); убрать?

Типичный симптом, что принимающая программа не успевает забирать данные из буфера.

Если на клетке слона написано “Буйвол”, не верь глазам своим.
Скорость обработки входного потока зависит, помимо прочего, от того, как написана принимающая программа. Ну а если эта принимающая программа еще и работает в многозадачной ОС…
Я, кстати, не нашел, где Вы писали, на какой скорости открывается порт. Попробуйте 4800.

Последовательный интерфейс никогда не шлет данные потоком без перерыва. Всегда имеются паузы. Возможно, в случае fwrite эти паузы больше, и этого достаточно, чтобы успевала приемная сторона.

1 лайк

Так посимвольный -посимвольному рознь. Можно ведь сделать, не хуже чем в библиотеке, на низком уровне(увы, знаком лишь с AVR)

Кстати, насколько я немного знаю, на EP32, чтобы программа быстрее выполнялась, надо указать, что она должна выполняться в оперативной памяти RAM.

Так только хуже. flush я туда воткнул, чтобы удостовериться в том, что бага. Понятное дело там ничего этого и рядом быть не должно. Обычный Serial.write() должен работать

PS:

если убрать flush, то теряется процентов 50 текста, сейчас попробовал. Но выводится быстро, конечно же