|
STM32F0x2 и USB double-buffer, ошибки flow-control на double-buffered bulk-in endpoint |
|
|
|
Feb 22 2017, 19:36
|

Гуру
     
Группа: Админы
Сообщений: 3 621
Регистрация: 18-10-04
Из: Москва
Пользователь №: 904

|
Имеется кит на базе STM32F072 (на STM32F042 те же проблемы), на котором отлаживается прошивка USB-device с двумя bulk endpoint'ами (in/out). В режиме single-buffer все работает штатно: передача идет только при установке RX_VALID/TX_VALID в соответствующих битах регистров USB_EPnR. Переключение управляющих битов в этих регистрах происходит в соответствии с их описанием в RM0091 (toggling). После успешной отладки в этом режиме перешли к использованию режима double-buffer и с ним начались неожиданные проблемы: при первой передаче по Bulk-in endpoint происходит неконтролируемая отправка второго пакета. Суть эксперимента в следующем: 1. устройство подключается к хосту, успешно проходит энумерацию, чтение дескрипторов и т.п. (lsusb -vvv показывает все правильно); 2. в устройстве активируется bulk-in endpoint в режиме double buffer и SW_BUF/DTOG_TX биты ставятся в положение 1/0, настраиваются адреса буферов для передачи в памяти пакетов (два буфера), данные для передачи записываются в область памяти первого буфера и его счетчик передачи устанавливается равным 60. При этом второй буфер в памяти пакетов заполняется характерной сигнатурой и его размер устанавливается равным 11 (это несущественно). Статус endpoint устанавливается равным TX_VALID; 3. на хосте программа в цикле с помощью libusb пытается выполнить передачи блоков данных размером 64 байта (чтение из устройства, bulk in); 4. запускаем программу и получаем два буфера данных, вместо одного (60 и 11 байт). При этом второй буфер передается несмотря на то, что после передачи первого буфера SW_BUF/DTOG_TX == 11, т.е. согласно описанию передача должна приостановиться до момента, когда SW_BUF/DTOG_TX станут равны 01 соответственно. Получается, что контроллер отправляет второй буфер "авансом", несмотря на состояние специальных разрядов управления потоком передачи. На больших блоках данных, например, при работе с mass storage это незаметно, т.к. второй буфер используется сразу "по делу" и эта паразитная передача не мешает. Однако если, например, выполнять поллинг состояния устройства с помощью bulk-in endpoint, то второй пакет будет содержать неожиданные данные, которые не были еще сформированы программой в микроконтроллере. Интересно то, что после еще одной отправки, т.е. когда состояние SW_BUF/DTOG в вышеописанном примере перейдет из 01 в 00, передача честно остановится, контроллер будет отправлять NAK при попытке передачи данных через bulk-in и описанный в документации механизм flow control начинает работать согласно описанию. Но это не дает ответа, что делать со вторым блоком данных, который отправляется вопреки всему. В errata на этот счет никаких данных нет. В STM32CubeMX драйвер HAL PCD реализован из рук вон плохо в области работы с double-buffered endpoint и поэтому как источник информации о правильном методе работы в этом режиме служить не может. В других открытых библиотеках этого режима нет. С другой стороны опыт показывает, что разница в скорости передачи между single-buffered bulk-in endpoint и double-buffered не превышает 10% по нашим замерам (800 кБайт/с против 870 кБайт/с в тесте передачи блоков внутреннего флеша без использования DMA), поэтому применение этого режима трудно назвать критичным. Но все же интересно, у кого-нибудь он работает правильно и согласно описанию производителя?
--------------------
BR, Makc В недуге рождены, вскормлены тленом, подлежим распаду. (с) У.Фолкнер.
|
|
|
|
|
 |
Ответов
(1 - 5)
|
Feb 27 2017, 07:15
|
Знающий
   
Группа: Участник
Сообщений: 835
Регистрация: 9-08-08
Из: Санкт-Петербург
Пользователь №: 39 515

|
Я использовал DBL_BUF на F103, и всё работает нормально. В мануале, к сожалению, слишком куцое описание этого режима, а в HAL реализация двойных буферов, мне кажется, забагована. Баг в том, что обработчик прерывания должен уметь при необходимости обслуживать минимум два буфера за один вход в прерывание, если контроллер того требует. Отличие моего от вашего алгоритма в том, что я при инициализации заполнял SW_BUF/DTOG_TX нулями, и только потом, когда уже установлен бит DBL_BUF, переключал SW_BUF, чтобы начать передачу одного буфера, это различие может оказаться важно. UPD: Могу предположить, что у вас случилось: вы инициализировали SW_BUF/DTOG_TX в 1/0, потом заполнили буфер данными и переключили SW_BUF(в нуль). В таком случае контроллер совершенно справедливо отправит два буфера, просто из куцого мануала этого не понять.
Сообщение отредактировал Timmy - Feb 27 2017, 07:46
|
|
|
|
|
Feb 27 2017, 10:32
|

Гуру
     
Группа: Админы
Сообщений: 3 621
Регистрация: 18-10-04
Из: Москва
Пользователь №: 904

|
Цитата(Timmy @ Feb 27 2017, 10:15)  Я использовал DBL_BUF на F103, и всё работает нормально. В мануале, к сожалению, слишком куцое описание этого режима, а в HAL реализация двойных буферов, мне кажется, забагована. Баг в том, что обработчик прерывания должен уметь при необходимости обслуживать минимум два буфера за один вход в прерывание, если контроллер того требует. Да, там с этим проблема и по сути вместо подсовывания следующего буфера они там еще раз кладут тот же, что только что успешно передали. В этом нет смысла, только дополнительная задержка. Цитата Отличие моего от вашего алгоритма в том, что я при инициализации заполнял SW_BUF/DTOG_TX нулями, и только потом, когда уже установлен бит DBL_BUF, переключал SW_BUF, чтобы начать передачу одного буфера, это различие может оказаться важно. Только что попробовал изначально установить SW_BUF/DTOG_TX в 0/0, после чего DBL_BUF устанавливается в 1, записывается буфер размером 64 байта и переключается SW_BUF (из 0 в 1) для начала передачи. Со стороны хоста даётся запрос на 128 байт для Bulk-in endpoint и вместо ожидания приёма данных с вылетом по таймауту (нет короткого блока данных, завершающего передачу) я получаю 64 + содержимое из второго буфера, которого не ждал. Т.е. складывается впечатление, что начальное состояние этих бит не играет роли. Цитата UPD: Могу предположить, что у вас случилось: вы инициализировали SW_BUF/DTOG_TX в 1/0, потом заполнили буфер данными и переключили SW_BUF(в нуль). В таком случае контроллер совершенно справедливо отправит два буфера, просто из куцого мануала этого не понять. В документации у них нет информации, что контроллер может запоминать переключения. У них написано, что анализируется только комбинация управляющих разрядов регистра и при сочетании 1/1 или 0/0 в хост отдаётся NAK до момента переключения в 0/1 или 1/0 соответственно. А у меня выходит, что эта логика начинает работать только спустя одну "лишнюю" транзакцию, которая по номеру идёт второй и передаёт неподтверждённый буфер. После первого круга по буферам все начинает работать правильно. Но вот как решить проблему первого круга?
--------------------
BR, Makc В недуге рождены, вскормлены тленом, подлежим распаду. (с) У.Фолкнер.
|
|
|
|
|
Feb 28 2017, 04:49
|
Знающий
   
Группа: Участник
Сообщений: 835
Регистрация: 9-08-08
Из: Санкт-Петербург
Пользователь №: 39 515

|
Цитата(makc @ Feb 27 2017, 13:32)  Только что попробовал изначально установить SW_BUF/DTOG_TX в 0/0, после чего DBL_BUF устанавливается в 1, записывается буфер размером 64 байта и переключается SW_BUF (из 0 в 1) для начала передачи. Со стороны хоста даётся запрос на 128 байт для Bulk-in endpoint и вместо ожидания приёма данных с вылетом по таймауту (нет короткого блока данных, завершающего передачу) я получаю 64 + содержимое из второго буфера, которого не ждал. Т.е. складывается впечатление, что начальное состояние этих бит не играет роли. Было бы любопытно посмотреть на лог, описывающий последовательнось и времена переходов между состояниями регистра USB_EPxR. То есть программируете старт передачи, все прерывания должны быть всё время теста запрещены, и опрашиваете где-нибудь полсекунды USB_EPxR, при любых изменениях записывая в лог-вектор новое состояние и время изменения из системного таймера. А также сбрасывая CTR_TX, если он появится. Потом лог-вектор срисовать в отладчике или выплюнуть через консоль. Цитата(makc @ Feb 27 2017, 13:32)  В документации у них нет информации, что контроллер может запоминать переключения. У них написано, что анализируется только комбинация управляющих разрядов регистра и при сочетании 1/1 или 0/0 в хост отдаётся NAK до момента переключения в 0/1 или 1/0 соответственно. А у меня выходит, что эта логика начинает работать только спустя одну "лишнюю" транзакцию, которая по номеру идёт второй и передаёт неподтверждённый буфер. После первого круга по буферам все начинает работать правильно. Но вот как решить проблему первого круга?  В результате экспериментов я установил, что в USB контроллере есть скрытый бит, который определяет, как интерпретируются сочетания SW_BUF/DTOG 0/0 и 1/1 - либо как два пустых буфера, либо как два занятых. Этот бит приходится восстанавливать программно - после переключения SW_BUF - два занятых, после чтения единичного CTR_TX или начальной инициализации - два пустых. То есть в исходном состоянии 1/0 || 0/1 можно набить данными один свободный буфер, переключить SW_BUF, и новое состояние окажется 0/0 || 1/1, однако оно будет означать два занятых и готовых к передаче буфера, а в мануале про это ничего не написано.
|
|
|
|
|
Feb 28 2017, 07:48
|

Гуру
     
Группа: Админы
Сообщений: 3 621
Регистрация: 18-10-04
Из: Москва
Пользователь №: 904

|
Цитата(Timmy @ Feb 28 2017, 07:49)  Было бы любопытно посмотреть на лог, описывающий последовательнось и времена переходов между состояниями регистра USB_EPxR. То есть программируете старт передачи, все прерывания должны быть всё время теста запрещены, и опрашиваете где-нибудь полсекунды USB_EPxR, при любых изменениях записывая в лог-вектор новое состояние и время изменения из системного таймера. А также сбрасывая CTR_TX, если он появится. Потом лог-вектор срисовать в отладчике или выплюнуть через консоль. Отладка идёт через последовательный порт и даже на скорости 430 кБит/с сильно тормозит процесс передачи через bulk, по эксперимент попробую устроить. Цитата В результате экспериментов я установил, что в USB контроллере есть скрытый бит, который определяет, как интерпретируются сочетания SW_BUF/DTOG 0/0 и 1/1 - либо как два пустых буфера, либо как два занятых. Этот бит приходится восстанавливать программно - после переключения SW_BUF - два занятых, после чтения единичного CTR_TX или начальной инициализации - два пустых. То есть в исходном состоянии 1/0 || 0/1 можно набить данными один свободный буфер, переключить SW_BUF, и новое состояние окажется 0/0 || 1/1, однако оно будет означать два занятых и готовых к передаче буфера, а в мануале про это ничего не написано. Честно говоря я не понял, как можно инициализировать этот теневой бит, о котором Вы говорите. Как контроллер отличает переключения SW_BUF (DTOG_RX) в режиме инициализации от рабочего режима? По значению бита DBL_BUF? Как у Вас выглядит последовательность настройки конечной точки? Какие биты устанавливаются/сбрасываются и в каком порядке?
--------------------
BR, Makc В недуге рождены, вскормлены тленом, подлежим распаду. (с) У.Фолкнер.
|
|
|
|
|
Feb 28 2017, 10:08
|
Знающий
   
Группа: Участник
Сообщений: 835
Регистрация: 9-08-08
Из: Санкт-Петербург
Пользователь №: 39 515

|
Цитата(makc @ Feb 28 2017, 10:48)  Отладка идёт через последовательный порт и даже на скорости 430 кБит/с сильно тормозит процесс передачи через bulk, по эксперимент попробую устроить. Последовательный порт не помешает, промежуточный результат можно в сыром виде в памяти сложить, там же не больше десяти записей должно получиться. Цитата Честно говоря я не понял, как можно инициализировать этот теневой бит, о котором Вы говорите. Как контроллер отличает переключения SW_BUF (DTOG_RX) в режиме инициализации от рабочего режима? По значению бита DBL_BUF? Как у Вас выглядит последовательность настройки конечной точки? Какие биты устанавливаются/сбрасываются и в каком порядке? Я не пытался инициализировать теневой бит, достаточно знать, что при включении двойной буферизации он находится в состоянии "пусто при равных битах". При простейшем запуске конечной точки 1 после сброса делаем так: usb->EPR[1] = 1|USB_EPxR_EP_TYPE_BULK|USB_EPxR_STAT_TX_VALID|USB_EPxR_EP_KIND; тут исходное состояние было нулевое, поэтому можно опустить специальное управление t битами USB_EPxR_STAT_TX После этого точка будет возвращать NAK. Далее можно заполнить буфер данными, записать в COUNT_TX длину пакета и записать usb->EPR[1] = 1|USB_EPxR_EP_TYPE_BULK|USB_EPxR_EP_KIND|USB_EPxR_CTR_TX|USB_EPxR_DTOG_RX, чтобы переключить SW_BUF в единицу, и больше ничего не делать. По идее должен уйти один пакет, когда попросят, после чего установятся биты USB_EPxR_CTR_TX и USB_EPxR_DTOG_TX. Я слишком дотошно не тестировал протокол на стороне PC, и может быть что-то упустил и на самом деле оно не может работать правильно. Но начинал тоже с попытки начальной инициализации SW_BUF/DTOG в 1/0, получилась ерунда. Кстати, в EPR пропадает зря как минимум CTR_RX, могли бы в него теневой бит отображать
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|