Подписаться на получение новых статей на почту:

V-USB — поддержка протокола USB на AVR. Шаг №73

Всем привет. Давайте немного вспомним, что в прошлой статье мы с Вами рассмотрели  способ подключения микроконтроллера AVR к ОС Android, а также основные узлы для реализации этой цели. Сегодня мы познакомимся поближе с библиотекой V-USB, а именно рассмотрим на примере сам механизм взаимодействия и передачи данных avr – хост, сначала Windows, а потом Android.  Ссылка на ресурс была приведена ранее в прошлой записи. Документация на библиотеку есть как в английской версии так и в русской.

V-USB Итак поехали. Скачиваем, например, архив V-USB 2012-12-06. В данном архиве находится (рис. слева) драйвер для интерфейса USB, на стороне AVR (usbdrv), код для USB-устройства и хоста (libs-device, libs-host), примеры (examples), схемы подключения (circuits) и др. Документации по API драйвера, библиотек хоста и устройства: находятся в соответствующих заголовочных файлах (usbdrv.h, opendevice.h, osccal.h).   Примеры, которые даны в библиотеке, следующие: — (custom-class) — отправка и прием малых порций данных к (от) устройству; — (hid-data) —   применение функций usbFunctionWrite () и usbFunctionRead (), прием и отправка больших объемов данных с генерированных устройством на лету; — (hid-custom-rq) — демонстрация устройства HID-класса; — (hid-mouse) — реализация устройства мыши. Использование входящих конечных точек (interrupt-in endpoints);

AVR - хостВсе примеры содержат: одну папку (firmware) для программы на микроконтроллер, другую (commandline) — содержащую какое ни будь приложение или драйвер для хоста. Ну ведь как то они должны здороваться, общаться, будь то Windows или Android или другой хост.

Остановимся друзья более подробно на примере hid-data, где происходит обмен с устройством USB блоками данных, используя только функционал класса HID (HID-класс поддерживается всеми операционными системами Microsoft, а также другими, начиная с Windows 98SE). На стороне хоста не требуется DLL-драйвер и приложение не требует инсталляции. На рисунке ниже видно подключаемые файлы при подсоединении устройства. Попробуем разложить все по «полкам». Начнем с папки firmware: main.с –основная программа, точка входа, main.hex – прошивочный файл, Makefile – набор инструкций для компиляции программы, usbconfig — .конфигурация микроконтроллера. Начнем с основного файла main.с, разберем его:

 /*Первые пять библиотек для ввода-вывода, сторожевого таймера, прерываний, временных пауз и работы с памятью*/
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/eeprom.h>
/*Первая библиотека с низу — для использования строго типов данных, которые там описаны, а также для применения макроса PROGMEM, и использования библиотеки usbdrv.h*/
#include <avr/pgmspace.h>
#include «usbdrv.h»  /*Драйвер*/
#include «oddebug.h»  /*Реализует функции для отладочных логов.*/
Здесь как видите две библиотеки (две последние) из папки usbdrv. В принципе необходимо все файлы этой папки скопировать к себе в проект. Здесь прописаны все функции для работы с USB. Далее в программе идет дескриптор репорта USB. Что это? Когда мы присоединяемся к USB, хост должен получить хоть какую-то информацию об устройстве: что это, чем «кормить», чего от него ожидать и т.д.  Далее определить, какой же драйвер устройству «подсунуть» (рис. ниже).

HID-класс Всем этим заправляют дескрипторы — небольшие массивы данных жестко организованной структуры. Более подробно о них читайте в стандартах USB.  Устройство может содержать несколько конфигураций, конфигурация может содержать несколько интерфейсов, интерфейс может содержать несколько endpoints (конечные точки, эндпоинты) и несколько HID-дескрипторов. Эндпоинт — это минимальный канал, через который может передаваться информация.

Виртуальные каналы (конечные точки) бывают 4-х видов:
Control – тип канала используется хостом для управления периферийным устройством и передачи данных.
Bulk — для обмена данными. Механизм целостности и доставки данных для данного канала реализована «в железе». Скорость передачи ограничена.
Isochronous — для обмена потоковыми данными. Целостность и доставка данных не контролируются, зато скорость значительно выше чем для Bulk каналов.
Interrupt –  для реализации подобия «прерываний», которые являются логическими, и никак напрямую не связанны с аппаратными прерываниями МК или прерываниями ОС. Итак разберем дескриптор:

/*-------------------------------------------Интерфейс USB---------------------------------------------*/
PROGMEM char usbHidReportDescriptor[22] =           /*Дескриптор репорта USB*/
      {  0×06, 0×00, 0xff,                                                      /*USAGE_PAGE (Generic Desktop)*/
         0×09, 0×01,                                                              /*USAGE (Vendor Usage 1)*/
         0xa1, 0×01,                                                              /*COLLECTION (Application)*/
         0×15, 0×00,                                                              /*LOGICAL_MINIMUM (0)*/
         0×26, 0xff, 0×00,                                                      /*LOGICAL_MAXIMUM (255)*/
         0×75, 0×08,                                                              /*REPORT_SIZE (8)*/
         0×95, 0×80,                                                              /*REPORT_COUNT (128)*/
         0×09, 0×00,                                                              /*USAGE (Undefined)*/
         0xb2, 0×02, 0×01,                                                    /*FEATURE (Data,Var,Abs,Buf)*/
         0xc0          };                                                            /*END_COLLECTION*/

Да друзья -это и есть не понятный дескриптор. Давайте разберемся.  Атрибут PROGMEM используется в программах использующих ОЗУ и большой объем констант (при нехватки памяти), для сохранения этих данных во flash. Поскольку использование атрибута это есть двухэтапная процедура, то после сохранения данных, для их обработки необходимы специальные функции, определенные в библиотеке pgmspace.h. Отсюда и важность соблюдения типов данных. Мы уже один раз использовали данные функции, например в статье №13Теперь разберем немного сам массив:
— 0×06, 0×00, 0xff,   /*USAGE_PAGE (Generic Desktop)*/ - Все устройства HID привязаны к «страницам» — разделам стандарта. Набор цифр означает привязку к определенному устройству. Более подробно разобраться можно почитав про дескрипторы HID Usage Tables http://www.usb.org/developers/hidpage/Hut1_12v2.pdf. Например, где говорится, что 0×06 - это Generic Device Controls (Общие управляющие устройства), т.е. наше устройство является пользовательским устройством общего типа. От этого зависит, какой из стандартных HID-драйверов будет подцеплен к нашему устройству (На рис. выше). Остальные цифры могут обозначать какой-нибудь параметр устройства. Например 00 — неопределенно.
—  0×09, 0×01,           /*USAGE (Vendor Usage 1)*/ Задает начало конечной точки, ее назначение и описание.
 0xa1, 0×01,           /*COLLECTION (Application)*/ Объединяет группу используемых данных. В пакете должна быть всегда предопределенная коллекция — Application. Ее будет замыкать элемент END COLLECTION. 
- 0×15, 0×00,                    /*LOGICAL_MINIMUM (0)*/
0×26, 0xff, 0×00,            /*LOGICAL_MAXIMUM (255)*/  Интервал значений 
— 0×75, 0×08,                    /*REPORT_SIZE (8)*/  Число двоичных разрядов в элементе данных конечной точки
— 0×95, 0×80,                    /*REPORT_COUNT (128)*/ Число элементов такой размерности.
— 0×09, 0×00,                    /*USAGE (Undefined)*/
Данное описание указывает, что необходимо включить в пакет 128 элементов (REPORT_COUNT) длиной 8 бит (REPORT_SIZE), которые могут принимать значения от 0 (LOGICAL_MINIMUM) до 255 (LOGICAL_MAXIMUM). Здесь мы не использовали report-ID (идентификатор назначения пакета, который должен быть в первом байте репорта) т.к был задан только один feature-репорт.
         Как видите основной метод общения с HID — устройством происходит через репорты. Устройство  HID device может содержать до 255 репортов каждого типа (Input, Output, и Feature).  HID поставляет информацию для хоста (для приложения HID-клиента, в частности) через репорты Input Reports или Feature Reports. Обычно input reports содержат данные, которые образовались при работе пользователя с HID-устройством (например нажатия кнопок и т. п.). Feature-репорт отправляется только с помощью передачи управления USB на конечной точке ноль, не используя нормальные конечные точки.
Идем далее разбирать код.
/*Переменные, ниже, сохраняют состояние текущей передачи данных */
static uchar    currentAddress;
static uchar    bytesRemaining;

        Ниже две основные функции по передачи и приему данных. usbFunctionRead () вызывается, когда хост запрашивает кусок данных от устройства. usbFunctionWrite () - когда хост посылает кусок данных в устройство. Обе функции используются непосредственно в драйвере usbdrv и описаны в в usbdrv/usbdrv.h. Вызываются по 8 байт каждый для запроса и передач управления (control transfer's) данных полезной нагрузки (control-out). Как видите, обе работают с памятью EEPROM. Включение/отключение данных функций производится в файле usbconfig.h. Данные функции вызываются в основном цикле, непосредственно в функции usbPoll (). Ниже рисунок примера передачи и приема данных от устройства.

 /*Запрос хостом данных от устройства*/

uchar   usbFunctionRead (uchar *data, uchar len)
{    if (len > bytesRemaining)
           len = bytesRemaining;
     eeprom_read_block (data, (uchar *) 0 + currentAddress, len);
     currentAddress += len;
     bytesRemaining -= len;
     len;    }

/*Посылкахостомданныхвустройство.*/
uchar   usbFunctionWrite (uchar *data, uchar len)

{   if (bytesRemaining == 0)
         return 1;               /*Окончание передачи */
    if (len > bytesRemaining)
         len = bytesRemaining;
    eeprom_write_block (data, (uchar *) 0 + currentAddress, len);
    currentAddress += len;
    bytesRemaining -= len;
    return bytesRemaining == 0;   } /*Возврат 1, если это был последний кусок */

На рисунке видно что хост принял пакет из 128 элементов, как было описано в дескрипторе.

Прием / передача данных USB.

Рассмотрим следующую функцию. Вызывается для каждого сообщения принятой подпрограммой прерывания. Принимает данные от хоста и по принятой передаче определяет то ли это control-in и драйверу необходимо вызывать 'usbFunctionRead () то ли это control-out  — usbFunctionWrite (). Она также подробно описана в драйвере как и предыдущие функции.

usbMsgLen_t usbFunctionSetup (uchar data[8]){    
    usbRequest_t *rq = (void *) data;
    if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){ /*Запрос HID class*/
          if (rq->bRequest == USBRQ_HID_GET_REPORT){  /*wValue: ReportType (highbyte), ReportID (lowbyte)*/
          /*Поскольку мы имеем только один тип репорта, мы можем игнорировать репорт-ID*/
                bytesRemaining = 128;
                currentAddress = 0;
                return USB_NO_MSG; /*Использование usbFunctionRead () для получения данных хостом от устройства*/
           }else if (rq->bRequest == USBRQ_HID_SET_REPORT){
/*Поскольку мы имеем только один тип репорта, мы можем игнорировать репорт-ID */
                bytesRemaining = 128;
                currentAddress = 0;
                return USB_NO_MSG;  }  /*use usbFunctionWrite () для получения данных устройством от хоста */
        }else{
    /*игнорируем запросы типа вендора, мы их все равно не используем */ }
        return 0;   }

Приступим к рассмотрению основной функции main. Ниже я привел весь код этой функции:
int main (void)
{   uchar i;
     wdt_enable (WDTO_1S);   /*(wdt.h) Функция управления watchdog. Если его не использовать то необходимо все равно выключить. Здесь срабатывание сторожевого таймера стоит, примерно, 1с.*/
     DBG1 (0×00, 0, 0);     /*Отладочный вывод: Переопределен (в файле oddebug.h) от функции extern void odDebug (uchar prefix, uchar *data, uchar len), параметры которой состоят из метки ('префикса', 'prefix') для индикации номера создания лога отладки и блока памяти для дампа в формате hex ('данные' 'data' и 'длина' 'len'). Само тело функции в oddebug.c производит конвертацию символов в код ASCII и посимвольную загрузку в интерфейс UART.*/

ASCII (American standard code for information interchange) — название таблицы (кодировки, набора), в которой некоторым распространённым печатным и непечатным символам сопоставлены числовые коды. (Рисунок слева).

 

 

 

     odDebugInit ();  /*Функция выполняет переопределение и поиск регистров управления, а также разрешение передачи регистра UCSRB приема-передатчика uart задание значения регистру ubrr, т.е. определение скорости передачи. Мы с Вами уже рассматривали настройки данного интерфейса в статье №40. Также хочется сказать что функция использует спецификаторы static inline, где первый дает указания сохранить значение при выходе из функции, а использование встраивания inline дает указание для GCC сделать вызовы функции быстрее, что достигается вставкой кода тела функции в то место, где она вызывается. Более подробно смотри драйвер*/
     usbInit ();   /*Данная функция отвечает за начальные установки: внешние прерывания, их генерации, включения/выключения, конечные точки. В файле usbconfig.h необходимо сконфигурировать количество конечных точек. Мы  с Вами уже говорили что конечная точка это минимальный виртуальный канал. В нашей программе в файле конфигурации скомпилирована версия с двумя конечными точками (endpoints): default control endpoint 0 и interrupt-in endpoint (любой другой endpoint номер). В данной функции также передаются токены.*/ Рассмотрим, ниже, описанные константы и типы спецификации USB в usbdrv.h. 

Данные константы и типы различаются по пакетам.
1.  Token Packets (TP) — Маркерный пакет. Всегда посылается хостом и является заголовком транзакции, т.е. определяет кому и как будет передаваться информация в следующем пакете.
#define USBPID_SETUP    0x2d  /*Передача от хоста к конечной точке по каналу управления*/
#define USBPID_OUT      0xe1    /*Передача данных от хоста к конечной точке*/
#define USBPID_IN       0×69       /*Передача данных от конечной точки к хосту*/
Маркерный пакет USBГде на рисунке структуры пакета: PID — шеcтнадцатиричный код; ADDR число 1...127 -адрес устройства с которым будет работать хост в текущей транзакции; ENDP число 0...15 -адрес конечной точки с которой будет работать хост в текущей транзакции; CRC5 контрольная сумма полей ADDR; ENDP -вычисляется без учёта дополнительных синхробитов Stuffed Bit  -вычисляется побитовой сверткой данных с полиномом 0×25 (100101). CRC мы с Вам немного разобрали в статье №6.
2.Data Packets (DP). Пакет данных, которым обмениваются хост и конечная точка устройства. В транзакции он следует за маркерным пакетом. Ниже рис. структура пакета данных.
#define USBPID_DATA0    0xc3   /*Пакет данных с четным PID*/
#define USBPID_DATA1    0x4b   /*Пакет данных с нечетным PID*/
Data Packets USBDATA данные для обмена между хостом и конечной точкой в байтах.Число байтов данных называют полезной нагрузкой пакета (Payload). CRC16 контрольная сумма поля DATA: -вычисляется без учёта дополнительных синхробитов Stuffed Bit; -вычисляется побитовой сверткой данных с полиномом 0×18005 (11000000000000101).
3.Handshake Packets (HP) -пакет квитирования. Этим пакетом обычно заканчивается транзакция между хостом и конечной точкой. В нем сообщается о результатах приёма пакета данных.
Ниже описание полей PID, PID NAME и рис. Структуры пакета квитирования.
#define USBPID_ACK      0xd2 /*Выставляется хостом (IN-транзакция) или функцией(OUT) и потверждает, что обмен данными между ними прошел без ошибок*/
#define USBPID_NAK      0x5a /*Выставляется только функцией, подтверждает что данные OUT-транзакции приняты от хоста с ошибкой*/
#define USBPID_STALL    0x1e /*Выставляется только функцией, подтверждает что функция остановлена и не готова к обмену в IN/OUT транзакциях.*/
Handshake Packets USB
4.Start-of-Frame Packets (SOF) пакет определяющий начала кадра. Этот пакет посылается хостом каждую миллисекунду(1000Гц) и обозначает начало нового кадра. Этот пакет могут принимать все устройства сети, он без адресный. Это единственный пакет имеющий синхронизацию с часами реального времени. У нас такой код не встречается .
5.Preambule Packets (HP) - преамбула низко скоростного пакета. Этот пакет предписывает хабу работающему на полной скорости передать следующий за преамбулой пакет на низкой скорости.
После получения сигнала EOP в конце низко скоростного пакета хаб должен вернуться на полную скорость.
Теперь давайте немного здесь остановимся и разберемся как происходит передача. Итак передача данных осуществляется пакетами. Ниже на рисунке пакет Low Speed USB1.1 (1,5 Mb/s):
Пакет данных USBКак видите начало пакета определяется по сигналу SOP (Start of Packet) - переход из состояния Idle (свободное (незанятое) состояние линии связи) в состояние K. Где K, в свою очередь - сигнал K (Kill), сброс исходного (Idle) уровня состояния линии связи. Далее следует байт синхронизации SYNC (80hex), которой после NRZI кодирования имеет вид KJKJKJKK. Где J — сигнал J (Jump) (дифференциальный «0»), возврат линии связи к уровню исходного состояния (Idle). Последних два бита SYNC- KK являются маркерам начала блока данных. Блок данных состоит из полей разной длины, суммарная длина блока данных должна быть кратна 8 битам. Байты передаются в порядке очереди начиная с младшего бита и заканчивая старшим битом. Заканчивается пакет сигналом EOP (End of Packet) длительностью 3 бита, который является временным разделителем пакетов. Сигнал определяется:
На разъеме источника: SE0 в течении 2 бит с последующим J-сигналом в течений 1 бит. На разъёме приёмника:SE0 более 1 бит с последующим J-сигналом в течении 1 бит. Этот сигнал подаётся в линию связи в конце каждого пакета данных. EOP служит для разделения пакетов данных во времени (как стоп-бит в RS232). После состояния SE0, которое длится на протяжении двух битовых интервалов 160нс...175нс(Full Speed) или 1,25мкс...1,50мкс(Low Speed) передаётся сигнал J в течении 1 бита. После чего может быть передан новый пакет или шина перейти в состояния Idle.

Разобрав функции usbInit () и пакеты продолжаем далее разбирать код основной функции.

usbDeviceDisconnect (); /*Является макросом напоминающим функцию и предназначено (вместе с usbDeviceConnect ()) соединять и рас соединять устройство с шиной USB хоста. Здесь требуется конфигурация файла usbconfig.h. Где необходимо задать номер порта и биты D+ and D- соответственно USB_CFG_IOPORTNAME, USB_CFG_DMINUS_BIT и USB_CFG_DPLUS_BIT. Также важно дополнить, что прерывание USB должно быть запрещено, когда устройство переходит в отключенное состояние, или обработчик прерывания зависнет. В предыдущей функции (usbInit ()) у нас содержится селективное выключение прерывания USB: USB_INTR_ENABLE |= (1 << USB_INTR_ENABLE_BIT);. Также должна запускаться принудительная реэнумерация при запрете прерываний (повторная энумерация, или исчисление частей). Другими словами опознание устройства USB стеком USB ядра операционной системы, в процессе конфигурирования*/

while (--i){    /*Далее по коду идет функция сброса сторожевого таймера wdt_reset () (wdt.h) описывается как: Reset the watchdog timer.  When the watchdog timer is enabled, a call to this instruction is required before the timer expires, otherwise a watchdog-initiated device reset will occur. Т.е сбрасывает его несколько раз для эмуляции дисконекта USB.*/
 wdt_reset ();
         _delay_ms (1);  }
     usbDeviceConnect ();
     sei ();
     DBG1 (0×01, 0, 0);
     for (;;){
         DBG1 (0×02, 0, 0);
         wdt_reset ();
         usbPoll (); }  /*Данная функция является основным механизмом приема передачи данных. Эта функция должна быть вызвана через регулярные интервалы внутри главного цикла main. Максимальная задержка между вызовами должна быть несколько меньше 50 мс (таймаут USB для принятия сообщения Setup). Иначе устройство не будет распознано. Пожалуйста примите во внимание, что отладочный вывод через UART отнимает ~ 0.5 мс на байт при скорости 19200 bps. Я думаю более детально мы ее разберем в следующих статьях когда будем использовать в качестве хоста Android.*/
 return 0; }

Дабы не растягивать статью, то практическую часть мы перенесем в следующую запись. Рассмотрев программную часть реализации USB интерфейса, в следующей записи мы подключим МК к хосту в данном случае Windows XP и попробуем записать/считать данные. На этом на сегодня и остановимся. Всем пока.

 

Просмотрено 656 раз.

Я на Google+

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

Ваш e-mail не будет опубликован.

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting