Программирование ARM: работа с USB AT91SAM7X256 - пишем USB HID и ПО хоста для него Fri, March 24 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

AT91SAM7X256 - пишем USB HID и ПО хоста для него Печать
Добавил(а) microsin   

Как сделать USB HID и ПО хоста, работающего с ним, продемонстрировано на сайте Atmel в примере из пакета AT91SAM7X-EK Software Package for IAR 5.2, Keil and GNU (40 MB, revision 1.5, updated 12/08). Статья предназначена для упрощения разборок с примерами от Atmel и документацией, потому что в них без стакана не разберешься. Если найдете в статье неясности или ошибки, оставьте пожалуйста комментарий ниже.

USB-HID-buzzer.jpg

[firmware]

1. Смотрите его пример в проекте usb-device-hid-transfer-project-at91sam7x-ek, находится в файле at91sam7x-ek. (на сайте Atmel см. Products -> AT91SAM 32-bit ARM-based Microcontrollers -> AT91 Software Package -> AT91SAM7X-EK Software Package for IAR 5.2, Keil and GNU (40 MB, revision 1.5, updated 12/08) This package provides software drivers and libraries to build any application for AT91SAM7X devices.). Выбрал пакет AT91SAM7X-EK потому, что у меня была макетная плата с микроконтроллером AT91SAM7X256 - AT91SAM7X.

AT91SAM7X-kit-IMG 8396

Из архива взял файл packages\usb-device-hid-transfer-project-at91sam7x-ek-iar.zip - это проект для IAR EWB ARM, но есть также отдельно проекты для Keil и GNU средств разработки. Распаковал содержимое в отдельную папку. Проект для firmware USB HID находится в файле at91sam7x-ek\usb-device-hid-transfer-project\usb-device-hid-transfer-project.eww. Откроем его в IAR. Как скомпилировать и запустить проект, рассказано в комментариях файла main.c, все делается традиционными методами. Для вывода отладочной информации о работе firmware полезно подключить порт DBGU макетной платы к RS-232 (COM-порт) компьютера и запустить программу - терминал с параметрами 115200 бод, 8 бит, без четности, 1 стоп-бит, нет flow-контроля.

2. USB HID в примере используется со следующими параметрами:
64 байта - размер буфера передачи (main.c\unsigned char oBuffer[64]), хотя в примере используется только половина буфера (размер конечной точки выбран 32 байта - в макроопределении HIDDTransferDriver_REPORTSIZE).
64 байта - размер буфера приема (main.c\unsigned char iBuffer[64]), хотя в примере также используется только половина буфера.
at91lib\usb\device\hid-transfer\HIDDTransferDriverDesc.c: VID - manufacturerDescriptor (USB_CFG_VENDOR_NAME), PID - productDescriptor (USB_CFG_DEVICE_NAME).
at91lib\usb\device\hid-transfer\HIDDTransferDriverDesc.h: HIDDTransferDriver_REPORTSIZE (==32), HIDDTransferDriverDescriptors_REPORTSIZE (==32), HIDDTransferDriverDescriptors_INTERRUPTOUT_POLLING, HIDDTransferDriverDescriptors_INTERRUPTIN_POLLING (время опроса конечных точек хостом, в мс, ==50), HIDDTransferDriverDescriptors_INTERRUPTOUT (Interrupt OUT endpoint ==2), HIDDTransferDriverDescriptors_INTERRUPTIN (Interrupt IN endpoint number ==1).

3. Инициализируется драйвер вызовом HIDDTransferDriver_Initialize(). Данные от ПО хоста (от компьютера) читаются вызовом HIDDTransferDriver_Read (iBuffer, 64) - эта процедура крутится в бесконечном цикле main. Первый параметр - указатель на буфер, а второй - размер буфера. Процедура возвращает количество принятых байт. Если указатель на буфер NULL, то функция просто возвращает количество байт, которое можно прочитать. Репорт от ПО хоста (от компьютера) читается вызовом HIDDTransferDriver_ReadReport (iBuffer, 64) - эта процедура крутится в бесконечном цикле main. Функция работает аналогично HIDDTransferDriver_Read. Данные передаются к ПО хоста (к компьютеру) вызовом HIDDTransferDriver_Write(oBuffer, 64, 0, 0). Первые 2 параметра - указатель на буфер и его длина, а 3 и 4 параметры относятся к функции обратного вызова (callback function), которая здесь не используется (поэтому эти параметры равны 0). Функция может вернуть либо USBD_STATUS_SUCCESS (что означает - передача данных успешно начата), либо USBD_STATUS_LOCKED (означает, что пока конечная точка/устройство занято и не может начать операцию по передаче данных).

4. Внешние события USB интерфейса отрабатывает обработчик прерываний USBD_UDP.c\void USBD_InterruptHandler (void). Обрабатываемые события - resume, suspend, end of bus reset (изменения состояния USB-устройства). В частности, когда отсоединяется кабель USB, происходит событие Suspend. Обработчик прерывания конечной точки USB (Endpoint interrupt handler) находится в usb\device\core\USBD_UDP.c -> UDP_EndpointHandler. Обработчик поддерживает события транзакций IN, OUT и SETUP, а также STALL condition.

5. По умолчанию в проекте задано макроопределение TRACE_LEVEL=4 (вывод в DBGU информации), но лучше задать TRACE_LEVEL=0, иначе в режиме ожидания к консоль постоянно вываливаются пустые строки (происходят при вызовах HIDDTransferDriver_Write(oBuffer, 64, 0, 0), но как поправить - я не разобрался). Все возможности по трассировке через DBGU описаны в файле trace.h.

6. Программа firmware делает следующее:
- читает приходящие от ПО хоста репорты и отображает их в консоль DBGU (с помощью подпрограммы ShowBuffer).
- читает приходящие от ПО хоста данные и отображает их в консоль DBGU (с помощью подпрограммы ShowBuffer).
- читает состояние кнопок и помещает их в первый байт oBuffer[0]. В oBuffer также впечатывается hex и dec значение счетчика успешных передач cnt. Эта информация в бесконечном цикле main отправляется к ПО хоста (вызовами HIDDTransferDriver_Write(oBuffer, 64, 0, 0)).
- обновляет состояние светодиодов данными из iBuffer[0].

7. Контроллер UDP вроде как пишет данные в "конфигурируемую" память FIFO размером 1352 байта, но где и как эта память конфигурируется, нигде не написано.

Теперь несколько замечаний по коду firmware. Для работы нужны только 4 функции (все они неблокирующие) - HIDDTransferDriver_Initialize - инициализирует устройство, а HIDDTransferDriver_ReadReport, HIDDTransferDriver_Write, HIDDTransferDriver_Read - предназначены для периодического вызова из основного цикла main (вызовы HIDDTransferDriver_Write должны быть обязательно, а вызовов остальных функций может и не быть). Код этих функций и всей библиотеки, к сожалению, урезанный, и не реализует того функционала, который разработчики задумали в него заложить. Например, процедуры HIDDTransferDriver_Read и HIDDTransferDriver_Write не поддерживают буферизированной передачи блоков произвольного размера (размер искусственно ограничен размером буфера конечной точки), что создает большие неудобства при использовании этих функций, если нужно передавать данные с максимальной скоростью. То есть в сторону устройства USB HID невозможно передавать данные с максимальной скоростью, которую может дать ПО хоста, так как пакеты будут теряться. Библиотека страдает также глюками, которые трудно логически объяснить - например функцию HIDDTransferDriver_Read нельзя использовать без вызовов HIDDTransferDriver_Write, иначе ПО хоста виснет (при вызове функций usb_set_configuration или usb_claim_interface из libusb ПО хоста виснет наглухо, да так, что процесс программы даже невозможно убить средствами системы и требуется перезагрузка. Походу виснет также подсистема драйвера USB HID Windows XP, так как отсоединение и присоединение устройства USB HID на ARM не дает никаких изменений в дереве Диспетчера Устройств - устройства HID от Atmel всегда остаются в системе до полной перезагрузки). Кроме того, нет функций для проверки наличия данных для приема, для определения факта окончания текущей передачи, для сброса буферов и т. п. Поэтому для полноценного использования библиотеки она требует обработки напильником. Вот что стоит, как минимум, изменить:

1. Уменьшить время опроса конечных точек с 50 мс до 2 мс (макроопределения HIDDTransferDriverDescriptors_INTERRUPTOUT_POLLING, HIDDTransferDriverDescriptors_INTERRUPTIN_POLLING), это позволит передавать данные с бОльшей скоростью. Однако быстрее, чем 32000 байт/сек, мне все равно передавать не удавалось (это максимальная скорость и в ту, и в другую сторону. Передача на скорости 32000 байт/сек поддерживается одновременно в обе стороны). Теоретически максимальная скорость для Full Speed USB HID, напомню - 64000 байт/сек (64 байта каждые 1 мс).

2. Назначить размер конечной точки 64 байта (в примере задано 32, макроопределение HIDDTransferDriver_REPORTSIZE). Это позволит разгрузить процессор при большой скорости передачи.

3. Добавить буферизированный прием данных в устройстве USB. Делается кольцевой буфер, который заполняется по прерыванию конечной точки, а опустошается в теле цикла main. В этом случае вызовы HIDDTransferDriver_Read уже не нужны. Устройство USB теперь всегда успеет принять данные. Заполнение кольцевого буфера я добавил в функцию USBD_UDP.c -> UDP_WritePayload (она вызывается из обработчика прерывания конечной точки, хотя это не лучшее решение, так как не фильтруются данные репортов. Вариантов реализации много).

[ПО хоста]

1. Скомпилированный пример ПО хоста находится в том же архиве at91sam7x-ek.zip, файл at91sam7x-ek\usb-device-hid-transfer-project\hidTest.exe, а исходный код - в папке at91sam7x-ek\usb-device-hid-transfer-project\HIDTest\. Как скомпилировать и описание примера приведено в файле at91sam7x-ek\usb-device-hid-transfer-project\HIDTest\hclient.htm (русский перевод см. в статье "Пример работы с USB HID из Windows DDK" [6]). Приложение ПО хоста - немного переделанный пример из Windows DDK - HClient. Для того, чтобы скомпилировать ПО хоста (HIDTest), нужно сначала установить Windows DDK. Я скачал Windows XP DDK ver. 2600 по ссылке [7]. Установка прошла без проблем, при установке я поставил галочки на интересующих меня частях DDK. Запустил Win XP Free Build Environment (C:\WINDOWS\system32\cmd.exe /k C:\WINDDK\2600\bin\setenv.bat C:\WINDDK\2600 fre), перешел в папку с исходниками at91sam7x-ek\usb-device-hid-transfer-project\HIDTest\, ввел команду build (как было указано в hclient.htm). При компиляции выскочило 3 ошибки на отсутствие доступа к файлу strsafe.h. Файл этот нашелся в папке "c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include\", я его просто скопировал в папку HIDTest и запустил build заново. На этот раз все прошло без ошибок. Размер исполняемого файла hidTest.exe оказался 26624 байт! При компиляции в Win XP Checked Build Environment размер файла получается несколько больше - 35328 байт, так как в файл попадает отладочная информация.

2. ПО хоста от Atmel (точнее даже, пример от Microsoft, несколько переделанный программистами Atmel) использует функции hid.dll и вполне себе работает, так что можно взять за основу его. Мне же, как новичку, было интересно прикрутить библиотеку libusb и посмотреть, как будут работать её функции с устройством USB HID от Atmel. К сожалению, примеров в Internet оказалось совсем мало, но кое-что все же нашлось. Принцип работы следующий.

3. Втыкаем устройство USB HID от Atmel, и записываем его параметры, которые можно и нужно узнать с помощью тестовой программы "C:\Program Files\LibUSB-Win32\bin\testlibusb-win.exe" (Вы уже догадались, она входит в пакет libusb). Интересуют нас следующие параметры:
VID, PID (они задаются в макроопределениях USB_CFG_VENDOR_NAME, USB_CFG_DEVICE_NAME файла HIDDTransferDriverDesc.c firmware)
iConfiguration - номер конфигурации USB HID
iInterface - номер интерфейса USB HID
wMaxPacketSize - размер пакета (задается макроопределением HIDDTransferDriver_REPORTSIZE firmware)

4. Создаем код ПО хоста. В нем надо загрузить в память библиотеку libusb и получить указатели на её функции. Делается все это стандартным образом (см. код в примере по ссылке 4 или статью "Как использовать библиотеку libusb в Visual Studio..." [8]). Потом нужно проинициализировать доступ к устройству, для чего надо вызвать функции в такой последовательности:

/* Функция usbOpenDevice реализована в opendevice.c, остальные функции взяты 
 из библиотеки libusb. */
if(0 != usbOpenDevice(&handle, vid, vendor, pid, product, NULL, NULL, NULL))
{
   msg.Format("Could not find USB device "%s" with vid=0x%x pid=0x%xn", product, vid, pid);
   MessageBox(msg, "Error", MB_ICONEXCLAMATION);
   exit(1);
}
else
{
   msg.Format("O.K. usbOpenDevice "%s" with vid=0x%x pid=0x%x", product, vid, pid);
   WLog(msg);
}
if (0 != usb_set_configuration(handle, iConfiguration))
{
   msg.Format("Cannot set configuration %in", CONFIGURATION);
   MessageBox(msg, "Error", MB_ICONEXCLAMATION);
   exit(1);
}
else
{
   msg.Format("O.K. usb_set_configuration %i", iConfiguration);
   WLog(msg);
}
if (0 != usb_claim_interface(handle, HID_INTERFACE))
{
   msg.Format("Cannot claim interface %in", iInterface);
   MessageBox(msg, "Error", MB_ICONEXCLAMATION);
   exit(1);
}
else
{
   msg.Format("O.K. usb_claim_interface %i", iInterface);
   WLog(msg);
}

Обратите внимание, что функции вызываем с параметрами, которые мы определили на шаге 3.

5. Для очистки буфера приема используется код наподобие такого:

while (USB_BLOCK_SIZE == usb_bulk_read(handle, IN_ENDPOINT, dummybuf, USB_BLOCK_SIZE, 2))
{
}

Здесь USB_BLOCK_SIZE равен 64 - это минимальная порция данных, которую передает firmware USB HID за один раз (равно размеру буфера конечной точки). Для обращения к USB-устройству используется предварительно полученный хендл handle, и задан таймаут операции 2 мс. Значение IN_ENDPOINT (номер конечной точки устройства USB) мы получили на шаге 3. Буфер dunnybuf имеет размер 64 байта.

6. Для передачи данных используем такой код:

if (USB_BLOCK_SIZE*9 != usb_bulk_write(handle, OUT_ENDPOINT, (char*)data, USB_BLOCK_SIZE*9, 5000))
{
   msg.Format("usb_bulk_write timeout...");
   WLog(msg);
}

В буфере data (размером в USB_BLOCK_SIZE*9 байт) лежат предварительно подготовленные для отправки данные вместе с контрольной суммой. Для отправки используется предварительно полученный хендл USB-устройства handle, и задан таймаут операции 5000 мс. Значение OUT_ENDPOINT (номер конечной точки устройства USB) мы получили на шаге 3.

Вместо usb_bulk_write с равным успехом можно использовать usb_interrupt_write (они работают почему-то абсолютно одинаково). Размер передаваемых данных может быть разумно-произвольным (firmware с буферизацией все равно успеет), желательно только, чтобы размер передаваемого блока был кратен размеру конечной точки HIDDTransferDriver_REPORTSIZE (wMaxPacketSize). Так как функция usb_bulk_write (и usb_interrupt_write) блокирующая, то передавать данные лучше в отдельном потоке, чтобы не блокировалась работа всего приложения.

7. Для приема данных используем такой код:

if (USB_BLOCK_SIZE*9 == usb_bulk_read(handle, IN_ENDPOINT, (char*)RBUFF, USB_BLOCK_SIZE*9, 500))
{
   msg.Format("usb_bulk_read timeout...");
   WLog(msg);
}

Параметры функции аналогичные, таймаут задан 500 мс (за это время должны пройти все данные объемом USB_BLOCK_SIZE*9).

Вместо usb_bulk_read с равным успехом можно использовать usb_interrupt_read (они работают почему-то абсолютно одинаково). Размер принимаемых данных может быть разумно-произвольным, желательно только, чтобы размер принимаемого блока был кратен размеру конечной точки HIDDTransferDriver_REPORTSIZE (wMaxPacketSize). Так как функция usb_bulk_read (и usb_interrupt_read) блокирующая, то принимать данные лучше в отдельном потоке, чтобы не блокировалась работа всего приложения. Между вызовами функций usb_bulk_read (или usb_interrupt_read) возможны потери данных в размере до 2 * HIDDTransferDriver_REPORTSIZE, и с этим ничего поделать, к сожалению, нельзя. Единственное, что можно сделать, чтобы обеспечить безошибочность передачи данных - это их контроль (например, с помощью CRC) и повторная передача, либо синхронизация по времени передач и приема, чтобы промежутки между вызовами usb_bulk_read совпадали с паузами в передаче. И тот и другой способы связаны с уменьшением максимально возможной скорости передачи данных.

[Протокол обмена данными]

Пример firmware и ПО хоста дают базовые функции, которые нужны для обмена данными между Вашим USB HID на микроконтроллере AT91SAM7X256 (или аналогичном) и программой, работающей на компьютере. Функции обмена предоставляют только способ переноса сырых данных. Ваше программное обеспечение должно реализовать алгоритм обмена этими данными, и этот алгоритм будет зависеть уже от задач программного обеспечения. Целесообразно за минимальный размер передаваемого блока принять размер буфера конечной точки (в примере от Atmel 32 байта, но легко можно увеличить до 64). Возможны 2 самых распространенных сценария:

1. Обмен запрос-ответ. Например, ПО хоста посылает с заданной периодичностью пакет данных фиксированного размера, а firmware должно в ответ что-то ответить. Если за определенное время (таймаут) firmware не ответило, то ПО хоста посылает данные заново. Такая схема предоставляет определенные преимущества - легко определить начало цикла и начало полезного блока данных, можно исключить проверку на CRC и поиск начала данных в потоке, не нужно для данных делать кольцевой буфер, так как потери пакетов исключаются сами собой. Недостаток единственный - средняя скорость обмена как минимум в 2 раза меньше максимально возможной (из-за пауз в передаче). Для детектирования начала цикла обмена в firmware можно использовать функцию HIDDTransferDriver_ReadReport (в ПО хоста необходимо при этом посылать соответствующий репорт).

2. Обмен постоянным потоком данных. И ПО хоста, и firmware постоянно что-то передают и принимают без перерыва. В этом случае скорость максимальная (до 32000 байт/сек в обе стороны для Full Speed USB HID в данном примере), но необходимо вставлять служебные данные для того, чтобы понять, где у данных начало и конец, а также (если требуется доставка блока размером больше, чем размер буфера конечной точки - в нашем примере размер этого блока можно довести до 64 байт) средство для контроля этих данных (счетчики пакетов и CRC). Это увеличивает сложность программного обеспечения как в firmware, так и в ПО хоста - необходимо реализовывать кольцевой буфер приема, отслеживать потери пакетов, проверять данные на целостность.

Какой сценарий выбрать - зависит от функций программного обеспечения.

[Ссылки]

1. Проект HIDTest (пример ПО хоста) и проект usb-device-hid-transfer-project-at91sam7x-ek (firmware для USB HID на ARM процессоре Atmel AT91SAM7X256, которое работает вместе с HIDTest).
2. AT91 USB Framework site:atmel.com - документация по примерам USB (см. AT91 USB HID Driver Implementation).
3. AT91SAM7X-EK Software Package site:atmel.com.
4. Пример работы с устройством USB HID на AT91SAM7X256 с использованием библиотеки libusb. Комментарии кода Atmel USB HID переведены на русский язык, добавлен на прием кольцевой буфер, позволяющий принимать данные без потерь пакетов.
5. SniffUSB USB Sniffer for Windows site:pcausa.com - программа, которая мне очень помогла при отладке протокола обмена.
6. Пример работы с USB HID из Windows DDK
7. Windows XP DDK ver. 2600.
8. Как использовать библиотеку libusb в Visual Studio

 

Комментарии  

 
0 #1 J3d1 27.01.2010 00:45
Статья толковая, давно хотел попробовать поработать с Atmel USB. Автору большое спасибо :-)
Цитировать
 

Добавить комментарий


Защитный код
Обновить

Top of Page