LINUX.ORG.RU

Как работает UART over USB, если получатель тормозит?

 , ,


1

2

Предположим, у нас отправитель шлёт данные по 20 байтов тысячу раз в секунду. При этом получатель тормоз и не всегда успевает с такой частотой принимать данные (но если бы они где-то буферизовались, то проблем бы не было).

У меня есть девайс, который на компьютере принимает ровнёхонько 20 000 байтов в секунду, вообще без видимых флюктуаций. А вот на смартфоне не успевает и выходит на процентов 10-20 меньше. Я в протоколах этих не разбираюсь и предполагал, что там как-то автоматически будет буферизоваться всё где-то (в разумных пределах), а я там уже буду вычитывать, к примеру 100 раз в секунду по 200 байтов за раз, ну или как получится.

Я посмотрел исходники драйвера на смартфоне и там запускается поток с высоким приоритетом, который просто в цикле читает данные и передаёт их дальше. То бишь если у этого потока цикл будет не такой быстрый (а ведь андроид это не ОС реального времени и совершенно нельзя гарантировать, что поток, даже с высоким приоритетом, будет висеть на процессоре без перерыва) и иногда будут перерывы, то пакеты будут теряться.

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

★★★

Не совсем понял про какой цикл вы пишите, но поток внутри ядра живёт по своим законам, ЕМНИП, при прерывании он ставится в очередь первым.

USB как-бы синхронный протокол, обмен иницирует хост и он аппаратно должен забирать всё из буферов USB-UART. Далее со стороны хоста драйвер вытаскивает из usb-пакетов нужную информацию и передаёт драйверу tty, у которого свой буфер.

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

А вобще, такие скорости (>115200) критичны к качеству/длине кабеля.

mky ★★★★★
()
Ответ на: комментарий от mky

Не совсем понял про какой цикл вы пишите

Тут

но поток внутри ядра живёт по своим законам

Я извиняюсь, неясно написал. В андроиде драйвер в юзерспейсе, это просто библиотека на жаве. Там нет доступа к ядерному драйверу, ядро даёт доступ к «сырому» USB (что бы это ни значило), а драйвер этот уже его разбирает и взаимодействует.

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

Нисколько не читается. Если быть точным, пакеты по 28 байтов. Вот 28 байтов и приходит в мой коллбэк, даже 56 не приходит. Насколько я понял, там 64 байта некий внутренний usb read buffer, видимо к этим 28 байтам ещё заголовки добавляются и в 64 уже больше одного пакета не влезает. Если я добавляю sleep(1) (1 мс), то скорость падает примерно в 2 раза (т.е. вместо примерно 1000 чтений в секунду получается примерно 500 чтений в секунду), если sleep(10), то падает в 10 раз. На компьютере такой же «алгоритм» с такими же sleep-ами пакеты вообще не теряет, они там буферизуются, пока читающий поток «спит» и потом сразу всем скопом отдаются. Но предполагаю, что буферизуются они где-то в ядре, ну или где-то в библиотеке, куда я доступа не имею (проверял на chrome/webserial и java/jserial).

А вобще, такие скорости (>115200) критичны к качеству/длине кабеля.

Кабель обычный качественный USB-C.

vbr ★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 3)
Ответ на: комментарий от MKuznetsov

для начала у него там 220кБод :)

ну а так, надеяться на потороха любой операционки при работе с уартом - плохая затея. И приходится в юзерспейсе организовывать потоки и буферы ;)

Morin ★★★★
()
Ответ на: комментарий от vbr

Кабель обычный качественный USB-C

То есть физического uart нигде нет, кабель сразу идёт в микроконтроллер или куда ещё?

В компьютере с линуксом драйвер в ядре, там прерывания и куча буферов ОЗУ, поэтом там подобных проблем и нет, а не из-за того, что CPU быстрее.

Андройд я не знаю, в FAQ этой библиотеки так и написано, что проблемы на скоростях выше 115200. Ещё там написано про port.getReadEndpoint().getMaxPacketSize() и вроде они так и делают:

mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize());
Только я не знаю, тот ли это размер буфера, который с USB-связан.

Ещё там что-то написано, что в андройде:

A smaller buffer size is ok, as long as the received messages fits into the buffer. If it doesn't fit into the buffer, read returns an empty result. 
То есть если буфер слишком маленький, то вобще ничего. А здесь написано:

The Android accessory protocol supports packet buffers up to 16384 bytes, so you can choose to always declare your buffer to be of this size for simplicity.

Note: At a lower level, the packets are 64 bytes for USB full-speed accessories and 512 bytes for USB high-speed accessories. The Android accessory protocol bundles the packets together for both speeds into one logical packet for simplicity.

Ну то есть проверьте какой буфер ставит эта библиотека, получается, что когда данных приходит много, андройд их копит (до 16 кбайт) и хочет отдать разом все, если не получается, наверное они теряются.

mky ★★★★★
()

Прежде всего в условиях задачи не указано, осуществляется ли flow control приемником (обычно пины CTS/RTS). Если нет то, соответственно, у передатчика нет возможности узнать, все ли данные успевают обрабатываться приемником.

Minoru ★★★
()
Ответ на: комментарий от mky

То есть физического uart нигде нет, кабель сразу идёт в микроконтроллер или куда ещё?

Физического нет. Кабель идет сразу в микроконтроллер. Там китайский аналог STM32. В прошивке я к сожалению не разбираюсь, как именно сделано. Ну как-то сделано. Отдельного чипа для этого вроде бы нет.

Андройд я не знаю, в FAQ этой библиотеки так и написано, что проблемы на скоростях выше 115200. Ещё там написано про port.getReadEndpoint().getMaxPacketSize() и вроде они так и делают:

Как я понял, Endpoint.getMaxPacketSize() это просто read only значение, его задаёт микроконтролер и со стороны приёмника его изменять нельзя.

Ну то есть проверьте какой буфер ставит эта библиотека, получается, что когда данных приходит много, андройд их копит (до 16 кбайт) и хочет отдать разом все, если не получается, наверное они теряются.

Там 64 байта возвращает этот метод сейчас. Я попробую погуглить, можно ли это менять на стороне приёмника, спасибо.

vbr ★★★
() автор топика

Насколько критично чтобы поток был ровным?

Обычно на UART делаеют протокол так, чтобы сейчас принял 1 байт, через время 15 байт, затем еще 7 байт, и протокол собирается все равно в верные пакеты

Полагаться на 20 байт каждую миллисекунду я бы не стал даже на ПК, даже если оно работает сейчас

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от I-Love-Microsoft

Насколько критично чтобы поток был ровным?

Это девайс, который делает 1000 измерений в секунду и отдаёт эти данные. Я пока не очень понимаю, что происходит, когда он отдаёт меньше, предполагаю, что какие-то измерения он пропускает. Сами пакеты на проводе не теряются (там счётчик и в нём пропусков нет), видимо в прошивке где-то приостанавливается работа при забитом USB.

В целом хотелось бы, чтобы все 1000 в секунду приходили. Ровно не обязательно. Это дело сохраняется (тут вообще не важно) и отрисовывается в реалтайме (тут желательно хотя бы 20 раз в секунду принимать, а лучше 100).

Полагаться на 20 байт каждую миллисекунду я бы не стал даже на ПК, даже если оно работает сейчас

На самом деле там 28 байтов (осмысленных, по всяким служебным заголовкам и тд не знаю).

В целом на текущий момент на андроиде я этот цикл максимально оптимизировал, убрал всё лишнее, поставил приоритет -19 (высший). Стабильно 1000 пакетов всё равно не держит, но потери уже минимальные, наверное пока на этом остановлюсь. В перспективе, вероятно, надо с uart на «сырой» usb переходить, похоже с ним проще будет. По крайней мере с API без всяких библиотек можно работать.

vbr ★★★
() автор топика
Ответ на: комментарий от mky

Насколько я понял по исходникам библиотеки, она передаёт тот же буфер, который я передаю ей, в USB API андроида. Мой буфер я ставил 16384, ничего не поменялось (он изначально был 256).

vbr ★★★
() автор топика
Ответ на: комментарий от vbr

Я извиняюсь, неясно написал. В андроиде драйвер в юзерспейсе, это просто библиотека на жаве. Там нет доступа к ядерному драйверу, ядро даёт доступ к «сырому» USB (что бы это ни значило), а драйвер этот уже его разбирает и взаимодействует.

а вы не измеряли какая скорость получается без двух synchronized и копирования массива в каждом цикле (и оnNewData и Log.d )? то есть по возможности убрать ВСЁ и просто посчитать сколько байт/пакетов типично прилетает, как часто и что из этого выходит..сравнить с вариантом для пк.

померить скорость чтения альтернативным тулзом, терминалом или на C набросать эталонный «цикл чтения в никуда». Вполне могут быть приколы с java и мультитред.

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)
Ответ на: комментарий от vbr

Возможно недостаточный размер буфера, ведь в ОС Linux дестопной у UART даже с USB буфер и на десятки килобайт может быть, а тут на Android да с такой бешеной частотой, может быть недостаточным и лишь представлен крошечным входным FIFO аппаратным в самом конвертере

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от MKuznetsov

а вы не измеряли какая скорость получается без двух synchronized и копирования массива в каждом цикле (и оnNewData и Log.d )? то есть по возможности убрать ВСЁ и просто посчитать сколько байт/пакетов типично прилетает, как часто и что из этого выходит..сравнить с вариантом для пк.

Сравнивал. Иногда ровно 28 000, иногда чуть меньше. По сути на этом остановился. Не знаю, от чего зависит, от запуска к запуску меняется почему-то.

померить скорость чтения альтернативным тулзом, терминалом или на C набросать эталонный «цикл чтения в никуда». Вполне могут быть приколы с java и мультитред.

Там доступа нет к ttyACM0, рутовать надо, для приложения это не очень подходит. В целом думаю, на C с драйвером в ядре всё будет работать нормально. Без драйвера в ядре с libftdi (драйвер для FTDI-чипов в юзерспейсе поверх libusb) не знаю, но это очень уж сложно, пока без этого попробую обойтись.

vbr ★★★
() автор топика
Ответ на: комментарий от vbr

Там доступа нет к ttyACM0, рутовать надо, для приложения это не очень подходит. В целом думаю, на C с драйвером в ядре всё будет работать нормально. Без драйвера в ядре с libftdi (драйвер для FTDI-чипов в юзерспейсе поверх libusb) не знаю, но это очень уж сложно, пока без этого попробую обойтись.

что бы я сделал:

  1. перестал искать соломинку в чужом глазу (выискивать отмазки про USB), а взялся-бы за свой код :-)

  2. убрал запись в порт из цикла опроса. У тебя измерительный прибор, он льёт гору показаний, а обратно пишешь в него крайне редко и мало. Но на каждые 28 байт проверяешь мутекс

  3. посчитал и поставил таймер в чтение (там где mSerialPort.read(buffer, mReadTimeout)). Чуть больше чем надо на приём 28 байт. Просто не очень хорошо постоянно долбить read() впустую

  4. посмотрел что происходит в OnNewData() - там вполне может втормаживать. Скорее какую-то часть PAD (та фигнь которая режет поток на пакеты) вынес оттуда непосредственно ближе к чтению

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)