Перейти к содержанию
    

STM32F0x2 и USB double-buffer

Имеется кит на базе 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), поэтому применение этого режима трудно назвать критичным. Но все же интересно, у кого-нибудь он работает правильно и согласно описанию производителя? :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я использовал DBL_BUF на F103, и всё работает нормально. В мануале, к сожалению, слишком куцое описание этого режима, а в HAL реализация двойных буферов, мне кажется, забагована. Баг в том, что обработчик прерывания должен уметь при необходимости обслуживать минимум два буфера за один вход в прерывание, если контроллер того требует. Отличие моего от вашего алгоритма в том, что я при инициализации заполнял SW_BUF/DTOG_TX нулями, и только потом, когда уже установлен бит DBL_BUF, переключал SW_BUF, чтобы начать передачу одного буфера, это различие может оказаться важно.

UPD: Могу предположить, что у вас случилось: вы инициализировали SW_BUF/DTOG_TX в 1/0, потом заполнили буфер данными и переключили SW_BUF(в нуль). В таком случае контроллер совершенно справедливо отправит два буфера, просто из куцого мануала этого не понять.

Изменено пользователем Timmy

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я использовал 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 соответственно. А у меня выходит, что эта логика начинает работать только спустя одну "лишнюю" транзакцию, которая по номеру идёт второй и передаёт неподтверждённый буфер. После первого круга по буферам все начинает работать правильно. Но вот как решить проблему первого круга? :crying:

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Только что попробовал изначально установить 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, если он появится. Потом лог-вектор срисовать в отладчике или выплюнуть через консоль.

 

В документации у них нет информации, что контроллер может запоминать переключения. У них написано, что анализируется только комбинация управляющих разрядов регистра и при сочетании 1/1 или 0/0 в хост отдаётся NAK до момента переключения в 0/1 или 1/0 соответственно. А у меня выходит, что эта логика начинает работать только спустя одну "лишнюю" транзакцию, которая по номеру идёт второй и передаёт неподтверждённый буфер. После первого круга по буферам все начинает работать правильно. Но вот как решить проблему первого круга? :crying:

В результате экспериментов я установил, что в USB контроллере есть скрытый бит, который определяет, как интерпретируются сочетания SW_BUF/DTOG 0/0 и 1/1 - либо как два пустых буфера, либо как два занятых. Этот бит приходится восстанавливать программно - после переключения SW_BUF - два занятых, после чтения единичного CTR_TX или начальной инициализации - два пустых.

То есть в исходном состоянии 1/0 || 0/1 можно набить данными один свободный буфер, переключить SW_BUF, и новое состояние окажется 0/0 || 1/1, однако оно будет означать два занятых и готовых к передаче буфера, а в мануале про это ничего не написано.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Было бы любопытно посмотреть на лог, описывающий последовательнось и времена переходов между состояниями регистра 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?

Как у Вас выглядит последовательность настройки конечной точки? Какие биты устанавливаются/сбрасываются и в каком порядке?

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Отладка идёт через последовательный порт и даже на скорости 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, могли бы в него теневой бит отображать:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...