Здесь опубликован перевод на русский язык уникального документа USB in a NutShell, облегчающего разработчикам всего мира первое знакомство с протоколом USB. Наилучшая точка старта для новичков, и неплохое средство прочистить мозги более опытным разработчикам (которым кажется, что они что-то знают). Если какие-то термины и сокращения будут непонятны, пользуйтесь разделом [Термины] в конце статьи. [Глава 1: введение] Начало работы с протоколом USB выглядит устрашающе. Например, одна только документация стандарта USB 2.0 на 650 страниц может оттолкнуть любого новичка. И это только начало длинного списка связанных стандартов для USB. Стандарты классов USB, как например HID Class Specification с общим описанием работы устройств (клавиатур, мышей, и т. д.), попадающих в класс HID (Human Interface Devices) – занимают еще 97 страниц. Если Вы разрабатываете USB хост, то нужно выбрать один из трех стандартов интерфейса хоста (Host Controller Interface Standards). Ни один из них не детализирован в спецификации USB 2.0. Хорошие новости – нет необходимости читать весь стандарт USB. Некоторые главы вышли из стен маркетинга, другие посвящены нижнему уровню, о котором заботится Ваша микросхема USB контроллера и хост вместе с разработчиками хаба. Давайте немного попутешествуем по различным главам спецификации USB 2.0, и кратко рассмотрим основные моменты. Главы 1 Название Введение 2 Термины и аббревиатуры Основные вопросы 3 4 5 6 7 Описание Страниц Включает предназначение и область действия для USB. Самая 2 важная часть информации в этой части – ссылка на Universal Serial Bus Device Class Specifications. Нет необходимости читать эту главу. Это глава не требует пояснений – обычное зло любого стандарта. 8 Указывает цели USB, такие как Plug’n’Play и простота работы с ним конечного пользователя (не разработчика). Представляет скорости Low, Full and High Speed со списком возможностей, полученных для целей маркетинга. feature list straight from marketing. Нет необходимости читать эту главу. Обзор Тут можно начать чтение. Эта часть предоставляет основной обзор архитектуры системы USB включая топологию, скорости передачи данных, типы потоков данных, основные электрические параметры и т. д. Модель потока Эта глава начинает рассказ о том, как передаются данные по данных USB (Data Универсальной Последовательной Шине (Universal Serial Bus). Тут Flow Model) вводят и описывают такие термины как endpoints (конечные точки) пайпы или потоки (pipes). Большая часть главы посвящена каждому типу потока данных (Control, Interrupt, Isochronous и Bulk). Важно знать каждый тип передачи и его свойства, хотя это несколько тяжело для новичка. Механика Глава описывает два стандартных коннектора. Здесь важная информация – коннектор type A направлен на downstream (в сторону устройств USB) и коннектор type B направлен на upstream (в сторону хоста USB). Таким способом исключается возможность воткнуть кабель в два порта upstream. Все отсоединяемые кабели должны быть full/high speed, в то время как любой кабель low speed должен иметь подходящую цоколевку. Бегло просмотрев коннекторы, Вы можете пропустить эту часть, если не собираетесь заниматься производством коннекторов USB и/или кабелей. Разработчики PCB могут найти стандартные посадочные места (footprints) коннекторов. Электрика Глава рассматривает низкий уровень электрических сигналов, включая спротивление линии, интервалы времени нарастания/спада, спецификации драйвера (передатчика)/приемника и кодирование уровня бита, bit stuffing и т. д. Наиболее важные части этой главы описывают идентификацию скорости устройства путем подключения смещающего уровень резистора на одной из линий данных, а также сравнение bus powered devices и self powered devices. Можете проскочить через эту главу, если Вы не разрабатываете микросхемы приемопередатчиков USB. Хороший даташит на устройства USB (микросхему) описывает величину терминирующих шину резисторов, которые Вам понадобятся, чтобы соблюсти сопротивление шины. 4 10 60 33 75 8 9 10 11 Слой протокола Теперь мы начинаем рассматривать уровни протокола. Эта глава описывает пакеты USB на уровне отдельных байт, включая поля sync, pid, address, endpoint, CRC. После этого происходит переход на следующий слой протоколов, USB пакеты. Большинство разработчиков не обращают вниманния на эти низкоуровневые слои протокола, так как применяемые ими микросхемы для устройств USB сами заботятся об них. Однако здесь важны понимание процедуры получения информации о статусе (status reporting) и процедуры установления связи (handshaking). Фреймворк Это наиболее часто используемая глава во всей спецификации, и устройства USB только одна из всех, которую я потрудился распечатать и переплести. Она описывает энумерацию шины и коды запросов (request codes - set address, get descriptor и т. п), наиболее используемый в программировании слой протоколов USB, важный для программистов и дизайнеров. Эта глава должна быть тщательно прочитана. Аппаратура и Эта глава покрывает проблемы, касающиеся хоста. Описываются программное генерация фреймов и микрофреймов, требования к контроллеру обеспечение хоста хоста, программные механизмы и драйверная модельUSB. Можете USB пропустить эту главу, если Вы не разрабатываете хост USB. Описание хаба Описывает работу хабов USB включая конфигурацию хаба, разделение транзакций, стандартные дескрипторы для класса хаба и т. п. Можете пропустить эту главу, если Вы не разрабатываете хабы. 45 36 23 143 Теперь можно начать читать те части стандарта, которые нам действительно нужны. Если Вы разрабатываете драйвера (программное обеспечение) для периферии USB, то Вам понадобятся только следующие части: 4 - Обзор архитектуры 5 - Модель потока данных USB (Data Flow Model) 9 - Фреймворк устройства USB 10 - Аппаратура и программное обеспечение хоста USB. Разработчикам железа периферии (электроника) понадобятся только следующие главы: 4 5 6 7 - Обзор архитектуры Модель потока данных USB (Data Flow Model) Механика Электрика. USB in a NutShell для дизайнеров периферии Теперь допустим (и это наверняка правда) что (1) большинство из нас разрабатывают периферию USB и (2) прочитали стандарт, но после этого в голове никаких идей по поводу того, как все-таки реализовать разрабатываемое устройство. В следующих 7 главах мы сфокусируемся на необходимых в разработке устройства USB частях стандарта. Это позволит Вам «воткнуться» в USB и производить дальнейшие разработки в соответствии с Вашим приложением (назначением устройства USB). Стандарт USB 1.1 был недостаточно сложен для High Speed, и перерос в USB 2.0. Чтобы упростить понимание фундаментальных принципов USB, мы пропустим многое, касающееся непосредственно устройств USB High Speed. Введение в Universal Serial Bus (USB) USB версии 1.1 поддерживает две скорости – режим full speed 12 Mbits/s и режим low speed 1.5 Mbits/s. Режим 1.5 Mbits/s медленнее, и менее чуствителен к EMI (помехам), чем уменьшает стоимость ферритовых колец и снижает требования к качеству компонентов. Например, кварцы могут быть заменены на дешевые резонаторы. USB 2.0, который в настоящее время господствует для десктопов и ноутбуков, поднимает планку до 480Mbits/s. Эти 480Mbits/s обозначены как режимHigh Speed, и по этому параметру он может конкурировать с последовательной шиной Firewire. Скорости USB High Speed - 480Mbits/s Full Speed - 12Mbits/s Low Speed - 1.5Mbits/s Universal Serial Bus – шина, управляемая исключительно хостом. На шине допустим один и только один хост. Спецификация USB сама по себе не поддерживает любую форму мультихостинга. Однако в спецификации On-The-Go, появившейся в стандарте USB 2.0, введен протокол Host Negotiation Protocol, который позволяет двум устройствам USB договориться, кто будет выполнять роль хоста. Это предназначено и ограничено одиночными подключениями точка-точка, например мобильный телефон – персональный органайзер, и не распространяется на хабы и конфигурации компьютеров. Хост USB ответственен за то, что предпринял все транзакции и выбрал полосу пропускания. Данные могут быть посланы методами различных транзакций, используя token-based протокол (протокол, основанный на символах). По моему мнению, шинная топология USB несколько ограниченная. Одной из начальных целей USB было уменьшение количества кабелей, воткнутых в заднюю (или переднюю) стенку Вашего PC. Приверженцы Apple могут сказать, что эта идея пришла от Apple Desktop Bus, где клавиатура, мышь и другая периферия могут соединяться друг с другом (по топологии daisy chain), с использованием одного кабеля. Однако USB использует топологию «tiered star» (многоярусная звезда), похожую на топологию 10BaseT Ethernet. Это предполагает возможное использование хабов, что добавляет неудобства – больше коробочек на Вашем столе и большее количество кабелей. Но это не так плохо, как может показаться поначалу. Многие устройства имеют интегрированные в себя хабы. Например, Ваша клавиатура может содержать хаб, подключенный к компьютеру. Ваша мышь и другие устройства (цифровая камера и т. п.) могут быть просто воткнуты в клавиатуру. Многие мониторы также имеют встроенный хаб. Топология tiered star имеет некоторые преимущества перед простой топологией daisy chain. Первое – потребляемая мощность каждого устройства может отслеживаться, и переключения, перегрузки не влияют на работоспособность других устройств USB. Все high, full и low speed устройства могут поддерживаться одновременно – хаб отфильтровывает транзакции high speed и full speed, таким образом устройства с низкими скоростями не получают данные со слишком высокой скоростью. Одновременно к одной шине USB может быть подключено до 127 устройств. Нужно больше устройств? – просто добавьте другой порт/хост. Многие старые хосты USB имели 2 порта, и многие производители сочли это недостаточным, и начали производство 4 и 5 портовых карт хоста и материнских плат, с внутренними портами USB (например, для жестких дисков). Ранние хосты имели один контроллер USB, и два порта разделяли между собой доступную полосу пропускания USB. Поскольку требования к полосе пропускания росли, мы теперь видим многопортовые платы с двумя или больше контроллерами, позволяющими организовать отдельные каналы для данных. Контроллеры хоста USB имеют собственные спецификации. В стандарте USB 1.1 имеется две спецификации Host Controller Interface: UHCI (Universal Host Controller Interface) – спецификация, разработанная Intel, которая перекладывает большинство функций на программное обеспечение (Microsoft) и позволяет применить дешевую аппаратуру. OHCI (Open Host Controller Interface) – спецификация, разработанная Compaq, Microsoft и National Semiconductor, где больше функций возложено на аппаратуру (Intel), что упрощает программное обеспечение. Типичное взаимодействие инженеров железа / программ. . . С появлением USB 2.0 понадобилась новая спецификация Host Controller Interface Specification для описания деталей регистрового уровня, специфичного для USB 2.0. Родился EHCI (Enhanced Host Controller Interface). Известные поставщики включая Intel, Compaq, NEC, Lucent и Microsoft объединились вместе, чтобы предоставить нам один стандарт интерфейса, и таким образом, только один новый драйвер для реализации в операционных системах. Вовремя. USB носит имя, подразумевающее последовательную шину. Она использует 4 экранированных провода, из которых два передают питание (+5v & GND). Остальные два представляют витую пару (twisted pair) дифференциальных сигналов данных. Используется схема кодирования NRZI (Non Return to Zero Invert, без возврата к нулю с инверсией) для передачи данных с полем синхронизации для синхронизирования тактов хоста и приемника. USB поддерживает «горячее» (plug’n’play) соединение с динамически загружаемыми и выгружаемыми драйверами. Пользователь просто втыкает устройство, подключая его тем самым к шине. Хост детектирует присоединение, опрашивает свежевставленное устройство и загружает подходящий драйвер, индицируя песочными часами на экране момент загрузки (если драйвер для устройства USB уже установлен в системе). Конечный пользователь не заботится ни о терминировании, ни об IRQ (прерываниях) и адресах портов, ни о перезагрузке компьютера (перезагрузка не требуется). Когда пользователь закончил работу с USB-устройством, он просто вынимает его (или отсоединяет кабель), хост обнаружит отсутствие устройства и автоматически выгрузит драйвер. Загрузка (выбор) подходящего драйвера осуществляется по комбинации PID/VID (Product ID/Vendor ID). VID предоставляется организацией USB Implementor's forum за деньги, и это еще одна точка преткновения для USB. Последняя информация о ценах может быть найдена на сайтеhttp://www.usb.org/developers/vendor/. Другие организации по стандартам предоставляют дополнительные VID для некоммерческого использования, такого как обучение, разработка и прочее (радиолюбительство). USB Implementors forum все-таки должен оказать эту услугу. В этих случаях Вы можете использовать VID, назначенный поставщику системы разработки. Например, большинство производителей чипов имеют комбинации VID/PID, которые Вы можете использовать для Ваших чипов, и будет известно, что эти VID/PID не существуют в коммерческих устройствах. Другие производители чипов могут даже продать Вам для Вашего коммерческого устройства личный PID, чтобы использовать его с VID производителя. Другая более достойная внимания особенность USB - его режимы передачи. USB поддерживаетControl, Interrupt, Bulk и Isochronous передачи. В то время как мы будем смотреть на другие режимы передачи позже, Изохронный режим позволяет устройству резервировать определеную часть от полосы пропускания с гарантируемым временем ожидания (latency). Это идеально для аудио и видео приложений, где перегрузка канала может привести к заметной потере данных или понижению частоты кадров. Каждый режим передачи предоставляет разработчику компромиссы в области детектирования ошибок и восстановления, гарантированного времени ожидания и полосы пропускания. [Глава 2: Железо] Коннекторы Все устройства имеют upstream-соединение к хосту, и все хосты имеют downstream-соединение к устройству. Коннекторы upstream и downstream механически не взаимозаменяемы, что устраняет недопустимые петлевые соединения на хабах, такие как подсоединение downstream-порта в downstream-порт. Обычно используют два вида соединителей, называемые type A и type B, которые показаны ниже. Type A USB Connector (сокет, нумерация контактов при виде снаружи) Type B USB Connector (сокет, нумерация контактов при виде снаружи) Type A plug (т. е. папа) всегда обращен к upstream (т. е. к хосту). Type A socket (т. е мама) обычно можно найти на стенке хоста и хаба. Например, сокеты type A расположены на компьютерных материнских платах и хабах. Type B plug всегда соединяются с downstream и, следовательно, type B socket расположены на USB устройствах. На первый взгляд звучит довольно путано, но разобраться можно =). Интересно обнаружить в некоторых компьютерных магазинах кабели type A <--> type A с прямой разводкой проводов и многочисленные зарядники USB-типа. Это входит в противоречие со спецификацией USB. Устройства, являющиеся переходником между штеккером type A в штеккер type A являются мостом, используемым для соединения двух компьютеров друг с другом. Другие запрещенные кабели – удлинители USB, имеющие на одном конце штеккер (либо type A, либо type B) и сокет на другом конце (либо type A, либо type B). Эти кабели нарушают требования к длине кабелей USB. USB 2.0 включает в себя errata, которое представляет коннекторы mini-usb B. Подробная информация по этим коннекторам может быть найдена в Mini-B Connector Engineering Change Notice. Причина появления коннекторов mini появилась для применения USB в малогабаритных электронных устройствах типа мобильный телефоны и органайзеры. Обычный коннектор type B слишком большой, чтобы его можно было просто применить в этих устройствах. Совсем недавно была разработана спецификация On-The-Go, которая добавляет для USB функциональность peer-to-peer. Она представляет хосты USB в мобильных телефонах и электронных органайзерах, и таким образом включена спецификация для mini-A джеков, mini-A разъемов и mini-AB разъемов. Я предполагаю, что скоро получат широкое распространение кабели mini USB, и также набор кабелей-конвертеров mini в standard. Номер контакта 1 2 3 4 Цвет провода Красный Белый Зеленый Черный Функция VBUS (5 вольт) DD+ Земля В кабелях USB используется стандартная цветовая маркировка для изоляции внутренних проводников, что упрощает идентификацию проводов от производителя к производителю. Стандарт регламентирует различные электрические параметры для кабелей. Интересно подробно прочитать включенные оригинальные спецификации USB 1.0. Вы бы разобрались в указанных электрических атрибутах, но параграф 6.3.1.2 предлагает рекомендованный цвет для заполнения кабелей USB морозный белый – как скучно! USB 1.1 и USB 2.0 послабляют рекомендации на Черный, Серый или Натуральный. Разработчики PCB в части 6 стандарта найдут стандартные посадочные места и цоколевки коннекторов. Электрическая спецификация Вам не нужны электрические спецификации главы 7, за исключением если Вы не разрабатываете чипы USB устройств/трансиверов или USB хост/хаб. Мы бегло рассмотрим основные вопросы этой главы. Как мы уже упоминали, USB использует для передачи данных пару проводов. Для кодирования применяется принцип NRZI (Not Return Zero Invert – без возврата к нулю с инверсией), и применяется вставка бит (bit stuffing), чтобы обеспечить нужные изменения уровня в потоке данных. На низкоскоростных (low speed) и полноскоростных (full speed) устройствах дифференциальная ‘1’ передается путем повышения уровня D+ свыше +2.8V резистором 15КОм, подключенным к +3.6V и Dниже +0.3V резистором 1.5КОм, подключенным к земле. Дифференциальный ‘0’ передается тем же способом, только уровни меняются на противоположные - D- свыше 2.8V и D+ ниже 0.3V. Приемник распознает дифференциальную ‘1’, если уровень D+ больше на 200mV, чем D-, и дифференциальный ‘0’, если D+ на 200mV меньше, чем D-. Полярность сигнала инвертируется в зависимости от скорости шины. Термины состояний ‘J’ и ‘K’ используются для обозначения логических уровней. На low speed состояние ‘J’ – дифференциальный 0. На high speed состояние ‘J’ – дифференциальный 1. Трансиверы USB имеют одновременно дифференциальные и несимметричные одиночные (single ended) выходы. Определенные состояния шины обозначены несимметричными сигналами на D +, D- или обоими сразу. Например, сигнал SE0 несимметричный и может использоваться для обозначения сброса устройства, если он удерживается дольше 10 мс. Сигнал SE0 генерируется путем удержания и D- и D+ в низком уровне (< 0.3V). О реализации несимметричных и дифференциальных выходов важно знать, если Вы используете трансивер и FPGA в качестве устройства USB. Вы не можете осуществить работу только с помощью дифференциального выхода. Шина low speed/full speed имеет волновое сопротивление (characteristic impedance) 90 Ом +/- 15%. Таким образом, важно просмотреть даташит при выборе сопротивлений последовательных резисторов для D+ иD-. В любом хорошем даташите указаны величины сопротивлений и допуски на них. Режим High Speed (480Mbits/s) использует постоянный ток 17.78 mA для передачи сигналов – с целью уменьшения шума. Идентификация скорости Устройство USB должно показать свою скорость путем подвешивания линии D+ или D- к напряжению 3.3V. Устройство full speed, как показано на картинке ниже, использует pull up резистор, подключенный к D+ для указания хосту, что это устройство full speed device. Резисторы pull up на стороне устройства используются хостом или хабом для определения присутствия устройства на шине (подключено ли устройство в порт USB). Без pull up резистора считается, что к шине USB ничего не подключено. Некоторые устройства имеют этот резистор встроенным в чип (он может включаться и выключаться программно под управлением firmware), другие требуют наличия внешнего резистора. Возьмем для примера технологию SoftConnectTM Philips Semiconductor. При подклюючении к шине микроконтроллер инициализирует функцию устройства USB перед тем, как он разрешает pull up резистор для идентификации скорости, показывающий, что устройство подключено к шине. Если pull up резистор был подключен к Vbus, то это означает, что устройство сразу подключается к шине, как только оно воткнуто в порт USB. В этом случае хост может попытаться сбросить устройство и запросить дескриптор, когда микроконтроллер устройства еще не проинициализировал функцию устройства USB (не готов в обработке запросов USB). Другие вендоры, такие как Cypress Semiconductor, также используют программируемый резистор для технологии Re-NumerationTM в своих устройствах EzUSB, где устройство при подключении сначала определяется как программируемое устройство USB, а потом, после загрузки в устройство программного обеспечения, отключается от шины и под управлением загруженного firmware проходит энумерацию как другое устройсво USB (все это происходит незаметно для пользователя). Многие устойства EzUSB не имеют встроенной памяти Flash или OTP ROM для сохранения кода. Они загружают код через подключение по USB. Рисунок 2: USB устройство Full Speed имеет pull up резистор, подключенный к D+ Рисунок 3: USB устройство Low Speed имеет pull up резистор, подключенный к DОбратите внимание, что мы не рассматривали идентификацию скорости для режима High Speed. High speed начинают работу путем подсоединения на full speed (1.5k на 3.3V к сигналу D+). После установления соединения и сброса устройство переходит к соединению на high speed, если хаб или хост поддерживает это. Если резистор работает в режиме high speed, резистор pull up отключается для сохранения баланса линии. Для совместимости устройства с USB 2.0 не требуется поддержка режима high-speed. Это позволяет производить более дешевые устройства, если скорость не важна. Это также имеет место для USB 1.1 low speed устройства, которое не обязано поддерживать full speed. Однако высокоскоростное устройство не должно поддерживать режим низкой скорости. Оно должно только поддержать режим full speed для первого соединения, и затем перейти в режим high speed после успешного взаимодействия. USB 2.0 совместимое downstream устройство (хаб или хост) должен поддерживать все три режима - high speed, full speed и low speed. Питание (VBUS) Одна из выгод USB – устройства, питаемые от шины (bus-powered devices) – устройства, которые могут получать питание от шины и не требовать никаких дополнительных джеков или кабелей. Однако многие путаются в этой опции без рассмотрения нужных критериев. USB устройство указывает свое энергопотребление в единицах 2mA в дескрипторе конфигурации, который мы будем детально рассматривать далее. Устройство не может увеличить энергопотребление свыше величины, указанной при энумерации, даже если у него пропадет внешнее питание. Имеется 3 класса функций USB: Low-power питаемые от шины функции High-power питаемые от шины функции Self-powered функции (с собственным отдельным питанием) Low power питаемые от шины функции получают питание полностью только от VBUS и не могут потреблять больше, чем 1 юнит нагрузки (one unit load). Спецификация USB задает в качестве юнита нагрузки 100mA. Low power питаемые от шины функции должны быть также разработаны таким образом, чтобы могли работать при снижении напряжения на VBUS до 4.40V и повышении до 5.25V, замеренных на upstream коннекторе устройства. Для большинства устройств 3.3V обязательно применение LDO регуляторов. High power питаемые от шины функции получают питание только от USB и не могут потреблять больше, чем 1 юнит нагрузки, пока они не будут сконфигурированы хостом, после чего они могут потреблять до 5 юнитов нагрузки (500mA Max), предоставленных в соответствии с запрошенной величиной из дескриптора. High power питаемые от шины функции должны быть способны проходить детектирование и энумерацию при напряжении минимум 4.40V. Когда проходит работа с полной нагрузкой, то минимум для VBUS составляет 4.75 V, и максимум 5.25V. Здесь также измерения происходят с разъема upstream. Самопитаемые функции (self power functions) могут потреблять от шины до 1 юнита нагрузки и получать остаток мощности от внешнего источника. При пропадании внешнего источника питания должна быть обеспечена возможность потребления от шины USB не больше, чем 1 юнит нагрузки. Самопитаемые функции проще в разработке, поскольку отсутствуют проблемы с потребляемой мощностью. Одноюнитовая нагрузка на шину позволяет проводить детектирование и энумерацию без основного/дополнительного источника питания. Никакое устройство USB, независимо от типа питания не должно подавать питание на провод VBUS на своем upstream порту. Если VBUS пропадет, устройство должно в течение 10 секунд убрать питание с D+/D- pull-up резисторов, используемых для идентификации скорости. Другое условие использования VBUS - пусковой ток, который должен быть ограничен. Это выделено в параграфе 7.2.4.1 спецификации USB и обычно пропускается. Пусковой ток происходит из-за наличия электрической емкости в Вашем устройстве между VBUS и землей. Поэтому спецификация указывает максимально допустимую развязывающую емкость, которую Вы можете иметь в своем устройстве 10uF. Когда Вы отключаете устройство, возникает ЭДС самоиндукции от протекающего тока (за счет индуктивности кабеля USB), которая может достичь большой величины. Для защиты от этого должна присутствовать минимальная развязывающая емкость 1uF на VBUS. Типичное питаемое от шины устройство не может потреблять ток свыше весьма разумной величины 500mA. Вы можете спросить – какие осложнения тут могут быть? Может, Suspend Mode (режим приостановки)? Потребление в режиме приостановки (Suspend Mode) Suspend mode обязателен для всех устройств. Во время приостановки вступают в силу дополнительные ограничения. Максимальный ток приостановки пропорционален номинальной нагрузке в юнитах. Для устройства с током нагрузки в 1 юнит максимальный ток приостановки (по умолчанию) 500uA. Это включает ток от pull up резисторов на шине. В хабе имеются на обеих линиях D- и D+ pull down резисторы по 15 КОм. Эти pull down резисторы вместе с последовательно включенным резистором в устройстве (1.5 КОм pull up) создают общую нагрузку 16.5 КОм на VTERM обычно 3.3v. Таким образом, этот резистор потребляет 200uA еще до старта. Кроме того, нужно принимать во внимание для многих устройств регулятор 3.3V. Многие устройства USB работают от 3.3V, например - PDIUSBD11. Линейные регуляторы обычно весьма неэффективны при средних статических электрических токах порядка 600uA, поэтому требуются более эффективные и, таким образом, дорогие регуляторы. В большинстве случаев Вы должны также уменьшить тактовую частоту микроконтроллера или совсем остановить тактовую частоту микроконтроллера, чтобы уложиться в предел 500uA. Примечание переводчика: многие разработчики firmware не утруждают себя обработкой режима Suspend. Например, среди разработок, основанных на библиотеке V-USB, мне не попадались проекты с поддержкой режима Suspend. Многие разработчики на форуме USB Implementor's Forum спрашивают – какие проблемы произойдут, если превысить лимит тока Suspend? Само собой разумеется, что большинство хостов и хабов не могут обнаружить перегрузку такой величины, и следовательно Ваше устройство может потреблять ток 5 мА и даже 10 мА и при этом нормально работать – но все-таки Вы при этом нарушаете требования спецификации USB. Однако при нормальном функционировании (не в режиме Suspend), если Вы пытаетесь превысить на 100mA Вашу определенную допустимую нагружку, то хаб или хост наверняка обнаружат это и отсоединят Ваше устройство в интересах целостности шины. Конечно, этих вопросов проектирования можно избежать, если Вы хотите спроектировать устройство USB с собственным питанием. Токи Suspend не имеют значения для десктопов, но с введением спецификации On-The-Go хостами USB могут стать мобильные телефоны и мобильные органайзеры. Лишнее потребление энергии Вашими USB устройствами произведет неблагоприятный эффект на время работы мобильного устройства от батареи. Вход в Suspend Mode Устройство USB приостанавливается (переходит в режим Suspend), когда на шине нет активности более чем 3.0 мс. В течение следующих 7 мс устройство должно отключиться, и не потреблять ток больше, чем заданный ток suspend. Таким образом, через 10 мс после прекращения активности шины ток потребления от неё не должен превышать suspend current. Для поддержания состояния соединения к приостановленному хабу или хосту, устройство во время режима Suspend должно все еще предоставлять питание на pull up нагрузочный резистор, определяющий выбор скорости. У USB есть пакет start of frame (SOF), который строго периодично (каждые 1 мс для low speed и full speed) посылается на шину для поддержания её в активном состоянии. Это препятствует тому, чтобы шина стала неактивной (вошла режим приостановки) при отсутствии данных на шине. Шина high speed имеет микрофреймы, отсылаемые каждые 125.0 µs ±62.5 ns. Шина full speed отправляет фрейм каждые 1.000 ms ±500 ns. Шина low speed имеет keep alive EOP (End of Packet) каждые 1ms только в случае отсутствия любых low speed данных. Термин "Global Suspend" используется, когда вся шина USB входит в режим suspend целиком. Однако также может быть приостановлено выбранное устройство путем посылки команды хабу, куда подключено устройство. Это называют как "Selective Suspend." Устройство возобновит работу, как только примет любой «не idle» сигнал. Если устройство имеет возможность удаленного пробуждения (remote wakeup enabled), то оно может сигнализировать хосту о необходимости выхода из режима Suspend (resume). Скорость сигнала данных (Data Signalling Rate) Другая область, которая часто пропускается, является допуск на такты USB. Эта информация имеется в секции 7.1.11 спецификации USB. High speed data тактируются 480.00Mb/s с допуском на скорость ± 500ppm. Full speed data тактируются 12.000Mb/s с допуском на скорость ±0.25% или 2,500ppm. Low speed data тактируются 1.50Mb/s с допуском на скорость ±1.5% или 15,000ppm. Эти допуски позволяют для устройств low speed применять недорогие резонаторы, но исключает их для full speed или high speed устройств. [Глава 3: протоколы USB] В отличие от RS-232 и похожих последовательных интерфейсов, где формат отправляемых данных не задан, USB составлен из нескольких слоев протоколов. Звучит сложно, но не опускайте руки. Как только поймете, что происходит, нужно беспокоиться только о протоколах верхних уровней. Фактически большинство контроллеров USB берет на себя заботу о нижних уровнях протоколов, делая их невидимыми для конечного разработчика. Каждая транзакция USB состоит из: o o o Token Packet (заголовок, показывающий – что должно передаваться далее) необязательный Data Packet (содержит полезный payload) Status Packet (используется для подтверждения транзакций и предоставляет средство устранения ошибок) Как мы уже упоминали - USB является шиной, где главным является хост. Хост начинает все транзакции. Первый пакет, так называемый token, генерируется хостом для описания того, что проследует далее и будет ли это транзакция чтения или записи, и какой адрес устройства и определенная конечная точка (endpoint). Следующий пакет обычно пакет данных, несущий полезную нагрузку, за которым идет пакет рукопожатия (handshaking packet), сообщающий о том, что данные или token были приняты успешно, или конечная точка (endpoint) остановлена (stalled) или недоступна для принятия данных. Общие поля пакета USB Данные на шине USB передаются в таком порядке: первым идет LSB (Least Significant Bit, младший значащий бит). Пакеты USB состоят из следующих полей: o Sync Все пакеты должны начинаться с поля синхронизации (sync). Поле sync имеет длину 8 бит на low speed и full speed или 32 бита на high speed, и используется для синхронизации тактов приемника с тактами передатчика. Последние 2 бита показывают, где начинается поле PID. o PID PID означает Packet ID. Это поле используется для обозначения типа пакета, который сейчас отправляется. Следующая таблица показывает возможные значения этого поля. Группа Значение PID Идентификатор пакета Token 0001 OUT Token Data 1001 IN Token 0101 SOF Token 1101 SETUP Token 0011 DATA0 1011 Handshake Special DATA1 0111 DATA2 1111 MDATA 0010 ACK Handshake 1010 NAK Handshake 1110 STALL Handshake 0110 NYET (No Response Yet) 1100 PREamble 1100 ERR 1000 Split 0100 Ping У PID здесь 4 бита, однако чтобы обеспечить его правильный прием, 4 бита дополнены (complemented) и повторены, в результате получился 8-битный PID. Полученный формат показан ниже: PID0 o PID1 PID2 PID3 nPID0 nPID1 nPID2 nPID3 ADDR Поле адреса указывает, какому из устройств USB предназначен пакет. Адрес имеет длину 7 бит, что позволяет адресовать до 127 поддерживаемых одновременно устройств. Адрес 0 недопустим, он предназначен для устройств, адрес для которых пока не назначен. Любое устройство, которому не назначен адрес, должно отвечать на пакет с адресом 0. o ENDP Поле endpoint составлено из 4 бит, что позволяет 16 возможных конечных точек. Однако устройства low speed могут иметь только 2 дополнительные конечные точки сверх заданного по умолчанию канала (4 конечных точки максимум). o CRC Cyclic Redundancy Checks (Циклическая Избыточная проверка) выполняется над данными в пределах полезной нагрузки пакета. Все пакеты token имеют 5 бит CRC, а пакеты data имеют 16 бит CRC. o EOP End of packet, конец пакета. Сигнализируется несимметричным нулем (Single Ended Zero, SE0) на время примерно 2 бита, и далее следует состояние J длительностью 1 бит. Типы пакетов USB USB имеет 4 разных типа пакета. Пакеты token индицируют тип последующей транзакции, пакеты data содержат payload, пакеты handshake используются для подтвверждения данных или сообщений об ошибках, и пакеты start of frame (SOF) показывают начало нового фрейма. o пакеты Token Имеется 3 типа пакетов token: In – информируют устройство USB о том, что хост хочет прочитать информацию. Out - информируют устройство USB о том, что хост хочет отправить информацию. Setup – используется для начала управляющих передач (control transfers). Пакеты token должны удовлетворять следующему формату: Sync PID ADDR ENDP CRC5 EOP o пакеты Data Имеется 2 типа пакетов данных, каждый из которых может передать до 1024 байта данных. Data0 Data1 Режим High Speed задает два других data PID - DATA2 и MDATA. Пакеты Data имеют следующий формат: Sync PID Data CRC16 EOP Максимальный размер полезной нагрузки (payload) для low-speed устройств составляет 8 байт. Максимальный размер полезной нагрузки для full-speed устройств составляет 1023 байт. Максимальный размер полезной нагрузки для high-speed устройств составляет 1024 байт. Данные нужно послать в единицах байт. пакеты Handshake Имеется 3 типа пакетов handshake, которые состоят просто из PID ACK – подтверждение о том, что пакет успешно принят. NAK – сообщение о том, что устройство временно не может отправить или принять данные. Также используется в interrupt транзакциях для информирования хоста о том, что нет никаких данных для передачи. STALL – устройство находится в состоянии, которое требует вмешательства со стороны хоста. Пакеты handshake имеют следующий формат: Sync o PID EOP пакеты Start of Frame (SOF) Пакет SOF состоит из 11-битного номера фрейма, отсылаемого хостом каждые 1ms ± 500ns на full speed или каждые 125 µs ± 0.0625 µs на high speed. Sync PID Frame Number CRC5 EOP Функции USB Когда мы представляем себе устройство USB, мы думаем о нем как о периферийном устройстве USB, но устройство USB могло означать USB трансивер, используемый в хосте или периферии, USB хаб или микросхему контроллера хоста, или периферийное устройство USB. Поэтому стандарт ссылается на функции USB, которые можно видеть как USB устройства, которые обеспечивают определенный функционал наподобие принтера, Zip-драйва, сканера, модема или другой периферии. Сейчас мы уже должны знать ряд понятий, составляющих пакет USB. Нет? Вы уже забыли, сколько бит входит в поле PID? Не будьте слишком обеспокоены этим. К счастью, большинство функций USB уже в кремнии (специальных чипах) получают обработку низких уровней протокола USB (до слоя транзакций, который мы рассмотрим в следующей главе). Мы рассматриваем информацию о низких слоях протокола USB потому, что большинство контроллеров функций USB сообщают об ошибках, таких как PID Encoding Error. Не рассмотрев бегло низкий уровень, можно было бы спросить, что означает PID Encoding Error? Если бы Вы предположили, что последние четыре бита PID не соответствовали инверсии первых четырех битов, то Вы были бы правы. У большинства функций есть серия буферов, обычно 8 байт длиной. Каждый буфер будет принадлежать конечной точке (endpoint) - EP0 IN, EP0 OUT и т. п.. Например, хост отправляет запрос дескриптора устройства (device descriptor request). Функция аппаратно прочитает пакет setup и определит из поля адреса, предназначен ли пакет именно ей, и если да, то скопирует полезную нагрузку (payload) последующего пакета данных в соответствующий буфер конечной точки, заданный величиной в поле endpoint токена setup. Также отправится пакет handshake для подтверждения приема байта и сгенерируется внутреннее прерывание в микроконтроллере для соответствующей конечной точки, обозначающее, что пакет был принят. Все это делается обычно в «железе» (аппаратуре чипа контроллера USB устройства). Программное обеспечение (firmware микроконтроллера) получит прерывание, в котором оно должно прочитать содержимое буфера конечной точки и проанализировать запрос на дескриптор устройства (parse device descriptor request). Конечные точки (endpoints) Конечные точки могут быть описаны как источники или приемники данных. Поскольку шина является хосториентированной, конечные точки оказываются в конце канала связи, на функции USB. Например, на уровне программного обеспечения Ваш драйвер устройства может отправлять пакет в конечную точку EP1 устройства. Так как данные поступают от хоста, они попадут в OUT буфер EP1. Ваше firmware теперь может не спеша прочитать эти данные. Если устройство хочет вернуть данные, функция не может просто записать их на шину, так как шина полностью управляется хостом. Поэтому firmware помещает данные в буфер EP1 IN, и эти данные находятся в буфере до тех пор, пока хост не отправит пакет IN, которым он запрашивает данные конечной точки. Конечные точки можно также рассматривать как интерфейс между железом функции устройства и firmware, работающем на функции устройства. Все устройства должны поддерживать конечную точку 0. Это конечная точка, которая принимает все управляющие запросы и запросы статуса во время энумерации и в течение всего времени, когда устройство остается работоспособным на шине. Потоки, или каналы (Pipes, дословно - трубы) Когда устройство отправляет и принимает данные через несколько конечных точек, клиентское программное обеспечение передает данные через потоки. Поток – логическое соединение между хостом и конечной точкой (точками). Потоки также имеют набор параметров – тип передачи (Control, Bulk, Iso или Interrupt), направление потока данных и максимальные размеры пакета/буфера. Например, поток по умолчанию – двунаправленный поток, составленный из IN конечной точки 0 и OUT конечной точки 0 с типом передачи control. USB определяет два типа потоков (pipes) Stream Pipes не имеют предопределенного USB формата, поэтому Вы можете послать данные любого типа через stream pipe и восстановить данные на другом конце. Потоки данных последовательны и имеют предопределенную направленность – IN или OUT. Stream pipes поддерживают типы передач bulk, isochronous и interrupt. Stream pipes могут управляться либо от хоста, либо от устройства. Message Pipes имеют предопределенный USB формат. Они управляются хостом, инициируются запросом, отправляемым от хоста. Данные пересылаются в нужном направлении, заданном в запросе. Таким образом, message pipes позволяют передавать данные в обоих направлениях, но поддерживают только передачи control. [Глава 4: Типы конечных точек (Endpoint Types)] Стандарт USB определяет 4 типа конечных точек/передач: Control Interrupt Isochronous Bulk Передачи Control Control transfer обычно используются для команд и операций получения состояния. Основная настройка устройства USB со всеми функциями энумерации происходит с использованием передач control. Это обычно быстрые, случайные по времени пакеты, инициированные хостом, которые имеют наилучший приоритет по доставке (best effort). Длина пакета для устройств low speed должна быть 8 байт, для high speed разрешены размеры пакета 8, 16, 32 или 64 байт, и устройства full speed должны иметь размер пакета 64 байта. У передач control могут быть до 3 стадий. Setup Stage – стадия установки, определяет, куда посылаются данные. Состоит из трех пакетов. Токен setup отправляется первым, он содержит адрес и номер конечной точки. Пакет данных (data packet) отправляется следующим, и всегда имеет тип PID data0 и включает includes a setup packet, который детализирует тип запроса. Мы позже подробно рассмотрим setup packet. Последний пакет из трех – рукопожатие (handshake), используемое для подтверждения успешности приема или для сообщения об ошибке. Если функция успешно приняла setup data (CRC, PID и т. п. в порядке), она отвечает рукопожатием (handshake) ACK, иначе игнорирует данные, не отправляя пакет handshake. Функция не может послать пакетамы STALL или NAK в ответ на пакет setup. Data Stage – эта стадия необязательна. Она состоит из одной или нескольких передач IN или OUT. Запрос setup показывает, сколько данных будет передаваться на этой стадии. Если количество данных превышает максимальный размер пакета, данные будут переданы в нескольких передачах, каждая из которых (за исключением последней) имеет размер максимального пакета. Стадия data stage имеет два различных сценария, в зависимости от направления передачи данных. Сценарий IN: когда хост готов принять данные control data, он выпускает IN Token. Если функция получает IN token с ошибкой, например PID не соответствует инверитрованным битам PID, то она игнорирует пакет. Если токен принят успешно, устройство может либо ответить пакетом DATA, содержащим отправляемые control data, либо stall packet, индицирующий ошибку конечной точки, либо пакет NAK, показывающий хосту, что конечная точка работает, но пока не имеет данных для отправки. Сценарий OUT: когда хосту нужно передать устройству пакет с управляющими данными (control data packet), он выпускает OUT token, за которым идет пакет, содержащий control data в качестве полезной нагрузки. Если любая часть – или OUT token, или data packet ошибочны, функция игнорирует пакет. Если буфер конечной точки пуст, она заполняет буфер данными и передает ACK, информирующий хост о том, что данные получены успешно. Если буфер конечной точки не пуст, поскольку идет обработка предыдущего пакета, функция отвечает NAK. Однако, если у оконечной точки была ошибка, и ее бит останова (halt bit) был установлен, она возвращает STALL. o Status Stage сообщает состояние полного запроса, и меняется в зависимости от направления передачи. Функция всегда возвращает сообщение состояния. Стадия status stage также имеет два различных сценария, в зависимости от направления передачи данных. Сценарий IN: если хост отправил токен (токены) IN token(s) во время стадии data stage для приема данных, хост должен подтвердить успешный прием данных. Это делается путем отправки токена OUT, за которым идет пакет data нулевой длины. Функция может теперь сообщить свой статус на стадии handshaking. ACK указывает на то, что функция завершила команду и готова к приему следующей команды. Если во время обработки этой команды произошла ошибка, функция выдает STALL. Однако если пока продолжается обработка команды, функция выдает NAK, указывающий хосту повторить позже стадию status stage. Сценарий OUT: если хост отправил токен (токены) OUT на стадии data stage – чтобы отправить данные, функция подтверждает успешный прием данных отправкой пакета нулевой длины в ответ на токен IN. Однако если произошла ошибка, функция должна выдать STALL, или если пока обрабатываются данные, должна выдать NAK, указывая хосту повторить позже status phase. Передачи Control: общий обзор Теперь, как все это совмещается? Например, хост хочет запросить дескриптор устройства во время энумерации. Пакеты, которые посылают, будут следующими. Хост отправляет токен Setup, говорящий функции, что следующий пакет будет пакет Setup. Поле адреса будет содержать адрес устройства, от которого главный компьютер просит дескриптор. Номер конечной точки должен быть 0, что указывает канал по умолчанию (default pipe). Затем хост отправит пакет DATA0. Он будет содержать 8 байт полезной нагрузки, которая будет являться запросом Device Descriptor Request, описанным в Главе 9 спецификации USB. Функция USB подтверждает пакет setup, что он был прочитан корректно, без ошибок. Если пакет принят с ошибкой, устройство просто игнорирует этот пакет. Хост должен отправить пакет позже заново, после короткой задержки. Sync PID 2. Пакет Data0 Sync PID 3. Ack Handshake Sync PID 1. Токен Setup ADDR ENDP Data0 CRC5 EOP Адрес и номер конечной точки CRC16 EOP Device Descriptor Request Устройство подтверждает Setup Packet EOP Три вышеуказанных пакета представляют из себя первую транзакцию USB. Теперь устройство USB будет декодировать 8 принятых байт, и определять по ним, что это device descriptor request (запрос на дескриптор устройства). После этого устройство попытается отправить Device Descriptor, что произойдет в следующей транзакции USB. Sync PID 2. Пакет Data1 Sync PID 3. Ack Handshake Sync PID EOP 1. Токен In Sync PID ADDR 2. Пакет Data0 Sync PID 3. Ack Handshake Sync PID 1. Токен In ADDR ENDP Data1 EOP Адрес и номер конечной точки CRC16 EOP Первые 8 байт дескриптора устройства Хост подтверждает пакет ENDP Data0 EOP CRC5 CRC5 EOP Адрес и номер конечной точки CRC16 EOP Последние 4 байта + Padding Хост подтверждает пакет В этом случае мы подразумеваем, что максимальный размер полезной нагрузки 8 байт. Хост отправляет токен IN, чем говорит устройству, что он может теперь отправить данные для этой конечной точки. Поскольку максимальный размер пакета 8 байт, мы должны для отправки разделить 12 байт дескриптора устройства на кусочки. Каждый кусочек должен быть размером по 8 байт, за исключением последней транзакции. Хост подтверждает каждый пакет, который мы посылаем ему. Как только дескриптор устройства отправлен, начинается транзакция статуса. Если транзакции были успешны, хост отпраляет пакет нулевой длины, показывающий, что полная транзакция была успешна. Функция отвечает на это пакетом нулевой длины, индицирующим её состояние (status). Sync PID 2. Пакет Data1 Sync PID 3. Ack Handshake Sync PID 1. Токен Out ADDR ENDP Data1 CRC5 EOP Адрес и номер конечной точки CRC16 EOP Пакет нулевой длины EOP Устройство подтверждает всю транзакцию целиком Передачи Interrupt Любой, кто имел дело с запросами на прерывание у микроконтроллеров, знает, что прерывания генерирутся устройством (в микроконтроллере). Однако под управлением USB, если устройство требует внимания хоста, оно должно ждать, пока хост его не опросит, чтобы понять, что устройство нуждается в срочном обмене данными! Передачи Interrupt имеют следующие особенности: Гарантированная латентность (latency) Однонаправленный поток канала Определение ошибок и последующая повторная попытка передачи. Передачи Interrupt обычно непериодические, когда USB устройство "инициирует" связь, требующую ограниченного времени ожидания. Запрос на «прерывание» ставится устройством USB в очередь, пока компьютер не опросит устройство USB с целью получения данных. Максимальная полезная нагрузка для low-speed устройств - 8 байт. Максимальная полезная нагрузка для full-speed устройств - 64 байта. Максимальная полезная нагрузка для high-speed устройств - 1024 байта. На диаграмме показан формат транзакций Interrupt IN и Interrupt OUT. Interrupt IN: хост периодически опрашивает конечную точку interrupt. Скорость опроса указана в описателе конечной точки (endpoint descriptor), который мы рассмотрим позже. Каждый опрос сопровождается отправкой хостом токена IN. Если токен IN испорчен, функция игнорирует пакет, и продолжает мониторинг шины в ожидании новых токенов. Если interrupt поставлено в очередь устройством USB, функция отправит пакет data, содержащий данные, соответствующие interrupt приприеме токена IN. После успешного приема хостом, хост возвратит ACK. Однако, если данные были испорчены, хост не вернет status. Условие прерывания не присутствовало, когда главный компьютер опросил оконечную точку прерывания токеном IN, но функция ответила о своем состоянии NAK. Если на этой конечной точке произошла ошибка, то в ответ на токен IN будет отправлен STALL. Interrupt OUT: когда хост хочет отправить устройству данные interrupt, он выдает токен OUT, за которым идет пакет data, содержащий данные interrupt. Если любая часть - токен OUT или пакет data ошибочны, функция игнорирует пакет. Если буфер конечной точки функции был пуст, и функция вывела данные в буфер конечной точки, она выдает ACK, информируя хост об успешно принятых данных. Если буфер конечной точки не пуст (еще идет обработка предыдущего пакета), то функция выдает NAK. Однако, если ошибка произошла с конечной точкой и ее бит останова был установлен, то функция выдает STALL. Изохронные передачи (Isochronous Transfers) Изохронные передачи происходят периодически и продолжительное время. Они обычно содержат информацию, привязанную ко времени (чуствительную к времени доставки), например аудио или видео потоки. Если произойдет задержка или повторная передача данных в аудио потоке, то Вы должны в результате получить искаженный звук. Дополнительная неприятность – может пропасть синхронизация звука. Однако выпадение пакетов или фреймов могут случаться постоянно снова и снова, и это будет менее заметно для слушателя. Изохронные передачи обеспечивают: Гарантированное выделение полосы пропускания шины USB. Ограниченную латентность. Однонаправленный поток канала. Детектирование ошибок с помощью CRC, но без гарантии доставки (нет повторов при ошибках). Работают только на скоростях Full speed и high speed. Нет переключения данных. Для изохронной конечной точки максимальный размер полезной нагрузки по данным указывается в дескрипторе конечной точки (endpoint descriptor). Этот размер может быть максимум до 1023 байт для full speed и до 1024 байт для high speed. Поскольку максимальный размер полезной нагрузки влияет на требование к шине по полосе пропускания, следует внимательно отнестись к его выбору. Если Вы используете большую полезную нагрузку, то может оказаться хорошим выбором задать серию альтернативных интерфейсов (alternative interfaces) с различными изохронными размерами полезной нагрузки. Если во время энумерации хост не может предоставить Вашей изохронной конечной точке привилегированную полосу из-за ограничений полосы пропускания, то у него еще будут другие варианты полосы вместо того, чтобы потерпеть неудачу полностью. Данные, посылаемые через изохронную конечную точку, могут быть меньше предварительно оговоренного размера, и могут измениться по длине от транзакции к транзакции. На диаграмме показан формат изохронных транзакций IN и OUT. Изохронные транзакции не имеют стадии установления связи (рукопожатия, handshaking stage) и не могут сообщать об ошибках или событиях STALL/HALT. Передачи Bulk Bulk-передачи можно использовать для большого объема быстропередаваемых данных. В качестве примера можно привести задание по выводу на печать, посланное в принтер, или изображение, сгенерированное от сканера. Bulk-передачи предоставляют коррекцию ошибок полезной нагрузки с помощью поля CRC16 и механизмы детектирования ошибок и повторной передачи, гарантирующие отсутствие ошибок в передаваемых или принятых данных. Bulk-передачи будут использовать остаточную полосу пропускания шины после того, как все другие транзакции были распределены. Если шина занята данными isochronous и/или interrupt, то данные bulk могут прередаваться через шину медленно. Следовательно, передачи Bulk должны использоваться только для интенсивных коммуникаций с негарантированным временем доставки. Особенности передачи Bulk: Используются для передачи большого объема данных на высокой скорости за короткое время. Детектирование ошибок с помощью CRC, с гарантией доставки. Не гарантируется полоса пропускания или минимальная задержка (минимальная латентность). Однонаправленный поток данных канала. Работает только на скоростях Full speed и high speed. Bulk-передачи поддерживаются только full speed и high speed устройствами. Для конечных точек full speed максимальный размер пакета bulk бывает 8, 16, 32 или 64 байта длиной. Для конечных точек high speed, максимальный размер пакета может до 512 байт длиной. Если полезная нагрузка данных меньше максимального размера пакета, она не должна быть дополнена нулями. Bulk-передачу считают законченной, когда она передала точное количество запрошенных данных, передала пакет меньше, чем максимальный размер оконечной точки, передала пакет нулевой длины. На диаграмме показан формат транзакций bulk IN и bulk OUT. Bulk IN: когда хост готов принять данные bulk, он выдает токен IN. Если функция принимает токен IN с ошибкой, она игнорирует пакет. Если токен принят корректно, функция может ответить либо пакетом DATA (содержащим отправляемые данные bulk), либо пакетом stall, (показывающим, что конечная точка имеет ошибку), либо пакетом NAK (показывающим, что конечная точка в работе, но у нее пока нет данных для отправки). Bulk OUT: когда хост хочет отправить функции пакет bulk data, он выдает токен OUT, за которым идет data пакет, содержащий данные bulk. Если любая часть токена OUT или пакета data повреждена, функция игнорирует пакет. Если буфер конечной точки функции пуст, функция вдвигает данные в буфер конечной точки и выдает ACK, информируя хост об успешно принятых данных. Если буфер конечной точки функции не пуст по причине обработки предыдущего пакета, функция возвращает NAK. Однако если конечная точка в состоянии ошибки, и её halt-бит установлен, функция возвращает STALL. Управление полосой пропускания Хост отвечает за управление полосой пропускания шины. Это происходит при энумерации, и конфигурировании изохронных конечных точек и конечных точек interrupt через операции на шине. Спецификация накладывает ограничения на шину, которые разрешают выделение не более 90% всех фреймов для периодических передач (Interrupt или Isochronous) на шине full speed. На шине high speed это ограничение уменьшено - не более 80% микрофреймов можно выделить для периодических передач. Таким образом, очевидно - если у Вас есть шина, очень нагруженная периодическими передачами, то оставшиеся 10% оставлены для передач управления (control transfers), и как только они будут распределены (обработаны), передачи bulk получат оставшуюся часть полосы. [Глава 5: Дескрипторы USB] Все устройства USB имеют иерархию дескрипторов, которые описывают информацию для хоста – такую, как например что это за устройство, кто его изготовил, какую версию USB поддерживает устройство, какими способами устройство может быть сконфигурировано, количество конечных точек и их типы и т. д. Наиболее общие дескрипторы USB следующие: Дескрипторы устройства Дескрипторы конфигурации Дескрипторы интерфейса Дескрипторы конечной точки Строковые дескрипторы Устройства USB могут иметь только один дескриптор устройства (device descriptor). Дескриптор устройства включает в себя такую информацию, как поддерживаемую устройством ревизию USB, Product ID (PID, идентификатор продукта), Vendor ID (VID, идентификатор производителя), используемые для загрузки соответствующего устройству драйвера, и количество возможных конфигураций устройства. Число конфигураций указывает, сколько имеется ответвлений по дескрипторам конфигурации. Дескриптор конфигурации (configuration descriptor) указывает величину потребляемой мощности от шины, питается ли устройство от собственного источника (self powered) либо от шины USB (bus powered) и количество интерфейсов, которые есть у конфигурации. Когда устройство проходит энумерацию, хост читает дескриптор устройства и принимает решение, какую конфигурацию применить. Хост может разрешить только какую-то одну из конфигураций. Например, возможно существование конфигурации с высоким потреблением мощности от шины, и есть конфигурация с собственным источником питания. Если устройство подсоединено к хосту-десктопу с сетевым блоком питания, драйвер может выбрать конфигурацию с высоким потреблением мощности от шины, несмотря на то, что устройство подключено к собственному источнику, а в случае подсоединения к хосту-ноутбуку (при питании его от аккумултора) драйвер может принудительно выбрать конфигурацию с собственным блоком питания, что потребует от пользователя обязательного подключения внешнего блока питания для USB-устройства. Параметры настройки конфигураций не ограничены различиями мощности. Каждая конфигурация может быть настроена на одинаковое питание, но иметь разные интерфейсы или наборы конечных точек. Однако нужно отметить, что изменение конфигурации требует, чтобы вся деятельность по каждой конечной точке остановилась. В то время как USB предлагает эту возможность, у очень немногих устройств есть больше чем 1 конфигурация. Дескриптор интерфейса можно рассматривать как заголовок или группирование конечных точек в функциональную группу, выполняющую единственную особенность (feature) устройства. Например у Вас может быть многофункциональное устройство факса/сканера/принтера. Дескриптор интерфейса 1 может описывать конечные точки функции факса, дескриптор интерфейса 2 может описывать функцию сканера, и дескриптор интерфейса 3 может описывать функцию принтера. В отличие от дескриптора конфигурации, здесь нет ограничений на количество одновременно разрешенных интерфейсов. У устройства могут быть 1 или много интерфейсов, разрешенных одновременно. Дескрипторы интерфейса имеют поле bInterfaceNumber, указывающее номер интерфейса, и полеbAlternateSetting, которое разрешает интерфейсу поменять установки на лету. Например, у Вас есть устройство с двумя интерфейсами – интерфейс 1 и интерфейс 2. У интерфейса 1 поле bInterfaceNumber установлено в 0, что показывает – это первый дескриптор интерфейса, и поле bAlternativeSetting, установленное в 0. У интерфейса 2 поле bInterfaceNumber установлено в 1, что показывает - это второй интерфейс, и поле bAlternativeSetting установленное в 0 (значение по умолчанию). Мы могли бы добавить сюда другой дескриптор, у которого поле bInterfaceNumber также установлено в 1 (показывает, что это воторой интерфейс), но на этот раз поле bAlternativeSetting установлено в 1, что показывает – дескриптор интерфейса может иметь альтернативную настройку, взятую из другого описателя интерфейса 2. Когда эта конфигурация разрешена, используются первые два дескриптора интерфейса, у которых поле bAlternativeSettings равно 0. Однако во время работы хост может послать запрос SetInterface, направленный на интерфейс с номером 1 (второй интерфейс) с альтернативной настройкой 1, что разрешает другой дескриптор интерфейса. Это дает преимущество при использовании двух конфигураций – мы можем передавать данные через интерфейс 0, и в то же время менять настройки конечной точки, связанной с интерфейсом 1, не мешая работе интерфейса 0. Каждый дескриптор конечной точки используется для указания типа передачи, направления, интервала опроса (polling interval) и максимального размера пакета для каждой конечной точки. Конечная точка 0 всегда подразумевается по умолчанию точкой для управления, и поэтому она не имеет дескриптора. Композиция дескрипторов USB Все дескрипторы имеют общий формат. Первый байт указывает длину дескриптора в байтах, второй байт показывает тип дескриптора. Если длина описателя будет меньшей, чем определено в спецификации, то главный компьютер должен проигнорировать его. Однако, если размер больше ожидаемого, хост будет игнорировать дополнительные байты, и будет искать следующий дескриптор в конце возвращенной действительной длины. Смещ. Поле Размер Значение 0 bLength 1 1 bDescriptionType 1 2 ... n Число Описание Размер дескриптора в байтах Константа Тип дескриптора Начало параметров дескриптора Дескрипторы устройства Дескриптор устройства для устройства USB представляет само устройство в целом. Как следствие – устройство USB может иметь только один дескриптор устройства. Этот дескриптор указывает некоторые основные (несомненно важные) сведения об устройстве, такие как поддерживаемая версия USB, максимальный размер пакета, идентификаторы ID вендора и продукта (VID и PID) и количество возможных конфигураций, которые может иметь устройство. Формат дескриптора устройства показан далее. Смещ. Поле 0 bLength Размер Значение 1 1 bDescriptorType 1 2 bcdUSB 2 BCD 4 bDeviceClass 1 Class (класс) 5 bDeviceSubClass 1 6 bDeviceProtocol 1 Протокол 7 bMaxPacketSize 1 Число 8 idVendor 2 ID Vendor ID, VID (назначается организацией USB Org) 10 idProduct 2 ID Product ID, PID (назначается организацией производителем) 12 bcdDevice 2 BCD 14 iManufacturer 1 Индекс Индекс строки, описывающей производителя 15 iProduct 1 Индекс Индекс строки, описывающей продукт 16 iSerialNumber 1 Индекс Индекс строки, содержащей серийный номер 17 bNumConfigurations 1 Целое (Integer) Число Описание Размер дескриптора в байтах (для дескриптора устройства размер 18 байт) Константа Тип - Device Descriptor (0x01) Номер спецификации USB, с которой совместимо устройство. Код класса (назначается организацией USB Org) Если равно 0, то каждый интерфейс указывает свой собственный код класса. Если равен 0xFF, то код класса определяется вендором. Иначе поле содержит код стандартного класса. SubClass Код подкласса (назначается организацией USB (подкласс) Org) Код протокола (назначается организацией USB Org) Максимальный размер пакета для конечной точки 0. Допустимые размеры 8, 16, 32, 64 Device Release Number (номер версии устройства) Количество возможных конфигураций Поле bcdUSB сообщает о самой высокой версии USB, которая поддерживается устройством. Величина кодируется в формате binary coded decimal (BCD, десятичное число, закодированное в двоичном формате) 0xJJMN, где JJ старшая часть номера версии, M младшая часть версии, и N младшая часть подверсии.Например,USB 2.0 кодируется как 0x0200, USB 1.1 как 0x0110 и USB 1.0 как 0x0100. Поля bDeviceClass, bDeviceSubClass и bDeviceProtocol используются операционной системой для того, чтобы подобрать драйвер класса (class driver) для Вашего устройства. Обычно только bDeviceClass установлен на уровне устройства. Большинство спецификаций класса хочет идентифицировать себя на уровне интерфейса, в результате bDeviceClass устанавлен в 0x00. Это позволяет одному устройству поддерживать несколько классов. Поле bMaxPacketSize сообщает максимальный размер пакета для конечной точки 0. Все устройства USB должны поддерживать конечную точку 0. Поля idVendor и idProduct используются операционной системой для нахождения драйвера для устройства, их также использует программа (ПО хоста), работающая с USB-устройством. Vendor ID назначается организацией USB-IF. Поле bcdDevice имеет тот же формат, что и поле bcdUSB, и используется для предоставления номера версии устройства. Его значение присваивается разработчиком устройства USB. Три строковых дескриптора существуют для предоставления подробной информации о производителе, продукте и серийном номере. Нет обязатеоьных требований по наличию строковых дескрипторов. Если строковых дескрипторов нет, должен использоваться нулевой индекс. Поле bNumConfigurations задает количество конфигураций устройства, которые оно поддерживает на текущей скорости. Дескрипторы конфигурации Устройство USB может иметь несколько различных конфигураций, хотя большинство устройств просты и имеют только одну конфигурацию. Дескриптор конфигурации указывает, как устройство запитывается, какое у него максимальное энергопотребление, количество интерфейсов, которое имеет устройство. Поэтому для устройства возможно иметь 2 конфигурации – одну для питания от шины, другую для питания от основного (внешнего) источника. Поскольку это – заголовок к дескрипторам интерфейса, возможно также иметь разные конфигурации для различных режимов передачи. Как только все конфигурации прочитаны и проанализированы хостом, он посылает команду SetConfiguration с ненулевым значением, которое соответствует bConfigurationValue одной из конфигураций. Это используется для выбора нужной конфигурации. Смещ. Поле 0 bLength Размер Значение 1 1 bDescriptorType 1 2 wTotalLength 2 Число Полная длина возвращаемых данных в байтах 4 bNumInterfaces 1 Число Количество интерфейсов 5 bConfigurationValue 1 Число Величина, используемая как аргумент для выбора этой конфигурации 6 iConfiguration 1 Индекс Индекс строкового дескриптора, описывающего эту конфигурацию 7 bmAttributes 1 8 bMaxPower 1 Число Описание Размер дескриптора в байтах Константа Дескриптор конфигурации (0x02) Набор бит D7 зарезервировано, установлено в 1. (USB 1.0 Bus (Bitmap) Powered) D6 самозапитываемое (Self Powered) D5 удаленное пробуждение (Remote Wakeup) D4..0 зарезервировано, установлено в 0. мА Максимальное энергопотребление в единицах 2 мА Когда дескриптор конфигурации прочитан, он возвращает полное дерево иерархии, которое включает все используемые дескрипторы интерфейсов и дескрипторы конечных точек. Поле wTotalLengthотражает количество байт в иерархии. bNumInterfaces указывает количество интерфейсов, представленных для этой конфигурации. bConfigurationValue используется в запросе SetConfiguration для выбора этой конфигурации. iConfiguration – индекс строкового дескриптора, описывающего конфигурацию в виде, удобном для восприятия человеком. bmAttributes задает параметры питания для конфигурации. Если устройство имеет собственное питание, устанавливается бит D6. Бит D7 ранее использовался в USB 1.0 для указания, что устройство питается от шины, но теперь это делается с помощью поля bMaxPower. Если устройство использует любую мощность, потребляемую от шины, в любом случае – либо это устройство, питаемое от шины, либо имещее отдельный источник питания – устройство должно сообщить о своей максимальной потребляемой мощности в поле bMaxPower. Устройство может также поддержиавать удаленное пробуждение (remote wakeup), которое позволяет устройству пробуждать хост, когда он находится в режиме приостановки. bMaxPower задает максимальную потребляемую мощность устройством от шины USB. Величина указывается в единицах 2 мА. Таким образом, может быть указан максимум потребления примерно 500 мА. Спецификация позволяет устройству, питающемуся от шины, потреблять ток не более 500 мА от провода Vbus. Если устройство теряет внешнее питание (приходящее не от Vbus), оно не должно потреблять больше, чем указано в bMaxPower. Это должно прервать по ошибке любую операцию, которую устройство не может выполнить без внешнего источника питания. Дескрипторы интерфейса Дескриптор интерфейса можно рассматривать как заголовок собрание конечных точек, объединенных в функциональную группу для реализации (feature) устройства. Дескриптор интерфейса имеет следующий формат. Смещ. Поле Размер Значение Число Описание 0 bLength 1 1 bDescriptorType 1 Размер дескриптора в байтах (9 байт) 2 bInterfaceNumber 1 Число Количество интерфейсов 3 bAlternateSetting 1 Число Величина, используемая для выбора альтернативной установки 4 bNumEndpoints 1 Число Количество конечных точек, используемых в интерфейсе 5 bInterfaceClass 1 Класс Код класса (назначается организациейUSB Org) 6 bInterfaceSubClass 1 Подкласс Код подкласса (назначается организациейUSB Org) (SubClass) 7 bInterfaceProtocol 1 Протокол Код протокола (назначается организациейUSB Org) 8 iInterface 1 Константа Дескриптор интерфейса (0x04) Индекс Индекс строкового дескриптора, описывающего этот интерфейс bInterfaceNumber показывает индекс дескриптора интерфейса. Он указывается относительно нуля, и инкрементируется на 1 для каждого нового дескриптора интерфейса. bAlternativeSetting может использоваться для задания alternative interfaces (альтернативных интерфейсов). Эти альтернативные интерфейсы могут быть выбраны запросом Set Interface. bNumEndpoints показывает количество конечных точек, используемых в интерфейсе. Эта величина должна быть указана без учета конечной точки 0. Поле bNumEndpoints используется для указания количества идущих далее дескрипторов конечных точек. bInterfaceClass, bInterfaceSubClass и bInterfaceProtocol может использоваться для указания поддерживаемых классов - HID, коммуникационное устройство, mass storage (устройство хранения информации) и т. д. Это позволяет многим устройствам использовать драйверы класса (встроенные в операционную систему) – при этом отпадает необходимость в написании специального отдельного драйвера для Вашего устройства. iInterface используется для строкового описателя интерфейса. Дескрипторы конечной точки Дескрипторы конечной точки используются для описания конечных точек, отличных от конечной точки 0. Конечная точка 0 всегда используется как конечная точка управления, и она конфигурируется сразу автоматически, даже перед запросом информации всех дескрипторов. Хост использует информацию, полученную из описателей конечных точек, чтобы определить требования по полосе пропускания шины. Смещ. Поле 0 bLength Размер Значение 1 1 bDescriptorType 1 Константа Дескриптор конечной точки (0x05) 2 bEndpointAddress 1 Конечная Адрес конечной точки Число Описание Размер дескриптора в байтах (7 байт) точка биты 0..3 номер конечной точки биты 4..6 заразервированы, установлены в 0 бит 7 направление 0 = Out, 1 = In (для конечных точек игнорируется) 3 bmAttributes 1 Набор бит биты 0..1 тип передачи (Bitmap) 00 = Control 01 = Isochronous 10 = Bulk 11 = Interrupt биты 2..7 зарезервированы. Если конечная точка изохронная, то: биты 3..2 = тип синхронизации (режим Iso) 00 = No Synchonisation 01 = Asynchronous 10 = Adaptive 11 = Synchronous биты 5..4 = тип использования Usage Type (режим Iso) 00 = конечная точка данных 01 = конечная точка обратной связи (Feedback Endpoint) 10 = явная конечная точка обратной связи данных (Explicit Feedback Data Endpoint) 11 = зарезервировано 4 wMaxPacketSize 2 Число Максимальный размер пакета этой конечной точки, подходящий для отправки или приема 6 bInterval 1 Число Интервал для того, чтобы опросить передачи данных конечной точки. Указывается в количестве фреймов. Поле игнорируется для конечныйх точек Bulk и Control. Для конечных точек Isochronous должно быть равно 1 и для конечных точек interrupt может лежать в диапазоне 1..255. bEndpointAddress индицирует, какую конечную точку описывает этот дескриптор. bmAttributes указывает тип передачи. Это могут быть передачи Control, Interrupt, Isochronous илиBulk. Если указана контрольная точка изохронного типа, могут быть выбраны дополнительные атрибуты, такие как синхронизация и типы использования. wMaxPacketSize указывают максимальный размер полезной нагрузки в байтах для этой конечной точки. bInterval используется, чтобы определить интервал опроса определенных передач. Единицы измерения – фреймы, что для устройств low/full speed составляет 1 мс, и для high speed устройств – 125 мкс. Строковые дескрипторы Строковые дескрипторы предоставляют информацию в формате, удобном для чтения человеком, и их указывать необязательно. Если строковые дескрипторы не используются, поле индекса строки дескриптора должно быть установлено в 0, что указывает на отсутсвие стркового дескриптора. Строки закодированы в формате Unicode, и разрабатываемое устройство USB может быть изготовлено с поддержкой многих языков. Строка с индексом 0 должна вернуть список поддерживаемых языков. Список ID языков для USB может быть найден в документе Universal Serial Bus Language Identifiers (LANGIDs) version 1.0. Смещ. Поле 0 bLength Размер Значение 1 1 bDescriptorType 1 2 wLANGID[0] 2 Число Код поддерживаемого языка 0 (например 0x0409 Английский - United States) 4 wLANGID[1] 2 Число Код поддерживаемого языка 1 (например 0x0c09 Английский – Австралия) n wLANGID[x] 2 Число Код поддерживаемого языка x (например 0x0407 Немецкий - стандартный) Число Описание Размер дескриптора в байтах Константа Строковый дескриптор (0x03) Вышеупомянутый строковый дескриптор показывает формат строкового дескриптора 0. Хост должен прочитать этот дескриптор, чтобы определить, какие языки поддерживаются устройством. Если язык поддерживается, хост может запросом Get Descriptor(String) сослаться на него путем отсылки ID языка в поле wIndex. Все последующие строки имеют формат: Смещ. Поле Размер Значение Описание 0 bLength 1 Число Размер дескриптора в байтах 1 bDescriptorType 1 Constant Строковый дескриптор (0x03) 2 bString n Unicode Строка, закодированная в Unicode [Глава 6: запросы USB] Пакет Setup Любое устройство USB должно ответить на пакеты установки (setup) по каналу по умолчанию (default pipe, конечная точка 0). Пакеты setup используются для обнаружения и конфигурирования устройства, и выполняют общие функции, такие как установка адреса устройства USB, запрос описателя устройства или проверка состояния конечной точки. USB-совместимый хост предполагает, что все запросы будут обработаны в пределах максимального периода 5 секунд. Также определены более строгие лимиты времени для определенных запросов: Standard Device request (стандартный запрос к устройству) без стадии данных должен быть завершен в течение 50 мс. Standard Device request со стадией данных должен начать передавать данные не позже чем через 500 мс после запроса. Каждый пакет данных должен быть отправлен в течение 500 мс после успешного завершения передачи предыдущего пакета. Стадия состояния должна быть завершена в течение 50 мс после передачи последнего пакета данных. Команда SetAddress (которая содержит фазу данных) должна быть обработана и вернуть статус в течение 50 мс. Устройство тогда имеет 2 мс, чтобы изменить адрес прежде, чем будет послан следующий запрос. Эти периоды времени ожидания являются весьма приемлемыми для даже самого медленного из устройств, но могут быть ограничением во время отладки. Невозможно обеспечить 50 мс для многих отладчных символов, отправляемых на скорости 9600 bps через асинхронный последовательный порт, или во внутрисхемных отладчиках/эмуляторах при выполнении программы по шагам или при остановке по точке останова для просмотра внутренних регистров и переменных. Поэтому USB требует специальной техники отладки в отличие от других проектов на микроконтроллерах. Бегло просматривая XP DDK, я заметил, что Host Controller Driver теперь имеет команду USBUSER_OP_SEND_ONE_PACKET, которая прокомментирована так:"Это API используется для реализации 'пошаговой отладки' в инструменте разработки транзакций USB (USB transaction development tool)." Такой инструмент еще не был выпущен. Мы можем только надеяться, что скоро его увидим. Каждый запрос начинается с пакета Setup длиной 8 байт, который имеет следующий формат: Смещ. Поле 0 bmRequestType Размер Значение 1 Набор битов (BitMap) Описание D7 направление передачи фазы данных 0 = хост передает в устройство 1 = устройство передает на хост D6..5 тип 0 = Standard 1 = Class 2 = Vendor 3 = зарезервировано D4..0 получатель 0 = устройство 1 = интерфейс 2 = конечная точка 3 = другое 4..31 = зарезервировано 1 bRequest 1 Значение Запрос 2 wValue 2 Значение Значение 4 wIndex 2 6 wLength 2 Индекс или Индекс смещение Счетчик Если это фаза данных, то количество байт данных для передачи Поле bmRequestType определит направление запроса, тип запроса и назначенного получателя. ПолеbRequest определит делаемый запрос. Поле bmRequestType анализируется, и выполнение ответвляется на несколько обработчиков, таких как Standard Device request handler (стандартный обработчик запроса устройства), Standard Interface request handler (стандартный обработчик запроса интерфейса), Standard Endpoint request handler (стандартный обработчик запроса конечной точки), Class Device request handler (классовый обработчик запроса устройства) и т. п. То, как Вы проинализируете пакет setup, зависит только от Ваших предпочтений. Кто-то еще может захотеть сначала анализировать bRequest и потом определить тип запороса и его получателя на основании каждого запроса. Стандартные запросы являются общими для всех устройств USB и подробно рассмотрены на последующих страницах. Классовые запросы являются общими для классов драйверов. Например, все устройства, относящиеся к классу HID, будут иметь единый набор запросов, специфичных для класса. Они будут отличаться от запросов устройства, относящегося к классу communication или относящегося к классу mass storage. И наконец – запросы, определенные вендором (производителем). Это запросы, которые Вы как разработчик устройства USB, ножете назначить сами. Эти запросы отличаются от устройства к устройству (что нормально), но это все делается исключительно под Вашу реализацию алгоритма работы устройства. Общие запросы могут быть перенаправлены на различных получателей, и в зависимости от конкретного получателя выполнять разные функции. Например, стандартный запрос GetStatus может быть направлен на устройство, интерфейс или конечную точку. Когда он направлен на устройство, он возвращает флаги, показывающие статус удаленного пробуждения (remote wakeup), и является ли устройство самопитаемым. Однако тот же запрос, направленный к интерфейсу, всегда вернет 0, или если запрос будет направлен на конечную точку, то он вернет состояние флага halt (флаг останова) для конечной точки. Поля wValue и wIndex позволяют передавать параметры вместе с запросом, а поле wLength используется для указания количества байт, которые должны быть переданы на фазе данных. Примечание переводчика: иногда поля wValue и wIndex применяют для передачи данных без участия специальной фазы данных (в поле wLength указывают 0), в этом случае можно передать не больше 4 байт. Пример такого использования таких управляющих запросов для передачи данных в обе стороны можно найти в примере hid-custom-rq из библиотеки V-USB (http://ru.wikipedia.org/wiki/V-USB), или в статье «V-USB и libusb: обмен с устройством USB HID с помощью управляющих сообщений (USB control messages)»http://microsin.ru/content/view/1084/44/. Стандартные запросы Секция 9.4 спецификации USB подробно описывают запросы "Standard Device", которые необходимо реализовать для каждого устройства USB. Стандарт предоставляет одну таблицу, группирующую запросы. Приняв во внимание, что большинство firmware анализирует пакет setup packet по получателю, мы можем разбить запросы на основе получателя для более простой обработки и реализации. Стандартные запросы к устройству В настоящий момент имеется 8 стандартных запросов к устройству, все они указаны в таблице ниже. bmRequestType bRequest wValue wIndex wLength Data 1000 0000b 0000 0000b 0000 0000b 0000 0000b GET_STATUS (0x00) 0 CLEAR_FEATURE (0x01) Селектор фичи 0 2 Статус устройства 0 0 Нет SET_FEATURE (0x03) Селектор фичи 0 0 Нет SET_ADDRESS (0x05) Адрес устройства 0 0 Нет 1000 0000b Тип GET_DESCRIPTOR (0x06) дескриптора и 0 или ID языка индекс Длина дескриптора Дескриптор 0000 0000b Тип SET_DESCRIPTOR (0x07) дескриптора и 0 или ID языка индекс Длина дескриптора Дескриптор 1000 0000b GET_CONFIGURATION (0x08) 0 0 1 Значение конфигурации 0000 0000b SET_CONFIGURATION (0x09) Configuration Value 0 0 Нет Запрос Get Status, направленный на устройство, вернет 2 байта на стадии данных в следующем формате: D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 Зарезервировано D1 D0 Remote Self Wakeup Powered Если бит D0 (Self Powered) установлен, то это указывает на самопитаемое устройство (у устройства имеется собственный источник питания, и оно ничего не потребляет от шины USB), а если очищен, то устройство bus powered (питается от провода Vbus шины USB). Если бит D1 установлен, то у устройства разрешена возможность удаленного пробуждения хоста от спячки или приостановки (Remote Wakeup). Бит remote wakeup может быть изменен запросами SetFeature и ClearFeature с селектором фичи равным DEVICE_REMOTE_WAKEUP (0x01) Запросы Clear Feature и Set Feature могут использоваться для сброси или установки булевых (boolean) фич. Когда получатель – устройство, то возможны только два селектора фичи DEVICE_REMOTE_WAKEUP и TEST_MODE. Режим тестирования позволяет устройству выдавать (показывать) некоторые условия. Они позже были документированы в Ревизии 2.0 спецификации USB. Запрос Set Address используется во время энумерации для того, чтобы назначить уникальный адрес устройству USB. Адрес указывается в поле wValue и может принимать значение не более 127. Этот запрос уникален тем, что устройство не меняет свой адрес, пока не завершится стадия статуса (см.Control Transfers). Все другие запросы должны выполниться до стадии статуса. Запросы Set Descriptor/Get Descriptor используются для возврата дескриптора, указанного в wValue. Запрос для дескриптора конфигурации вернет дескриптор устройства и все дксерипторы интерфейса и конечной точки в одном запросе. Endpoint Descriptors (дескрипторы конечной точки) не могут быть запрошены напрямую запросами GetDescriptor/SetDescriptor. Interface Descriptors(дескрипторы интерфейса) не могут быть запрошены напрямую запросами GetDescriptor/SetDescriptor.String Descriptors (строковые дескрипторы) включают Language ID (идентификатор языка) в поле wIndex – для поддержки нескольких языков. Запросы Get Configuration/Set Configuration используются для запроса или установки текущей конфигурации устройства. В случае запроса Get Configuration будет возвращен байт на стадии данных, показывающий статус устройства. Нулевая величина показывает, что устройство не сконфигурировано, и ненулевое значение показывает, что устройство сконфигурировано. Set Configuration используется для разрешения (enable) устройства. Этот запрос должен содержать величину поля bConfigurationValue желаемого configuration descriptor (дескриптора конфигурации) в младшем байте wValue – для выбора разрешаемой конфигурации. Стандартные запросы к интерфейсу Спецификация в настоящий момент задает 5 стандартных запроса к интерфейсу, показанные в таблице ниже. Что интересно - только два запроса делают что-нибудь понятное. bmRequestType 1000 0001b 0000 0001b bRequest GET_STATUS (0x00) wValue 0 CLEAR_FEATURE (0x01) Селектор фичи wIndex wLength Data Интерфейс 2 Состояние интерфейса Интерфейс 0 Нет 0000 0001b SET_FEATURE (0x03) Селектор фичи Интерфейс 0 Нет 1000 0001b GET_INTERFACE (0x0A) 0 Интерфейс 1 Альтернативный интерфейс 0000 0001b SET_INTERFACE (0x11) Альтернативная установка Интерфейс 0 Нет Поле wIndex используется для указания ссылки на нужный интерфейс для запросов, направленных к интерфейсу. Формат поля следующий: D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 Зарезервировано D4 D3 D2 D1 D0 Номер интерфейса Запрос Get Status используется для возврата статуса (состояния интерфейса). Такой запрос к интерфейсу должен возвратить два байта 0x00, 0x00 (оба байта зарезервированы для использования в будущем). Запросы Clear Feature и Set Feature могут использоваться для очистки и сброса булевых фич. Когда назначенный получатель – интерфейс, спецификация USB ревизии 2 не указывает конкретных фич интерфейса. Запросы Get Interface и Set Interface получают и устанавливают Alternative Interface(альтернативный интерфейс), который более подробно описан Interface Descriptor (дескриптором интерфейса). Стандартные запросы к конечной точке Запросы Standard Endpoint бывают 4-х видов, перечисленных в таблице ниже. bmRequestType 1000 0010b wValue GET_STATUS (0x00) 0000 0010b bRequest 0 CLEAR_FEATURE (0x01) Селектор фичи Конечная точка 2 Конечная точка 0 Нет Селектор фичи Конечная точка 0 Нет 1000 0010b SYNCH_FRAME (0x12) 0 Конечная точка 2 Номер фрейма Поле wIndex в запросах к конечной точке используется для указания целевой конечной точки и направления запросов. Формат поля следующий: D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 Dir Зарезервировано Номер конечной точки (направление) Запрос Get Status возвращает 2 байта, в которых возвращается статус конечной точки (пока только бит Halted/Stalled, т. е. бит приостановки). Формат двух возвращаемых байт показан ниже: D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 Зарезервировано Data Состояние конечной точки SET_FEATURE (0x03) Зарезервировано wLength 0000 0010b D15 Windex D5 D4 D3 D2 D1 D0 Halt Запрос Clear Feature и Set Feature используется для установки фич конечной точки. Стандарт в настоящее время определяет один селектор фичи конечной точки ENDPOINT_HALT (0x00), который позволяет хосту приостановить и очистить конечную точку. Только оконечным точкам, кроме заданной по умолчанию конечной точки (конечная точка 0) рекомендуют иметь эти функциональные возможности. Запрос Synch Frame используется, чтобы сообщить о кадре синхронизации конечной точки. Глава 7 : обычный (Generic) драйвер USB Энумерация Энумерация – процесс определения факта, что устройство действительно подключено к шине USB и каких параметров это требует – потребляемая мощность, количество и тип конечной точки (или точек), класс устройства и т. д. В процессе энумерации хост назначает устройству адрес и разрешает конфигурацию, позволяющую устройству передавать данные по шине. Обычный процесс энумерации хорошо описан в секции 9.1.2 спецификации USB. Однако когда USB firmware пишется впервые, полезно знать не общий процесс энумерации, как он описан в стандарте, а то, как хост отвечает в процессе энумерации. Общий процесс энумерации под операционной системой Windows включает в себя следующие шаги: 1. Хост или хаб детектирует подключение нового устройства с помощью pull-up резисторов, которое устройство подключает к паре сигнальных проводов данных (D+ и D-). Хост делает задержку как минимум 100 мс, что позволяет вставить коннектор полностью и застабилизировать питание устройства. 2. Хост выдает на шину сброс, который выводит устройство в состояние по умолчанию. Устройство может теперь ответить на заданный по умолчанию нулевой адрес. 3. Хост MS Windows запрашивает первые 64 байта дескриптора устройства (Device Descriptor). 4. После приема первых 8 байт дескриптра устройства, хост немедленно выдает новый сброс шины. 5. Теперь хост выдает команду Set Address, чем переводит устройство в адресуемое состояние. 6. Хост запрошивает все 18 байт дескриптора устройства. 7. Затем он запрашивает 9 байт дескриптора конфигурации (Configuration Descriptor), чтобы определить полный её размер. 8. Хост запрашивает 255 байт дескриптора конфигурации. 9. Хост запрашивает все строковые дескрипторы (String Descriptors), если они имеются. По окончании шага 9 Windows запросит драйвер для Вашего устройства. Обычно он снова запросит все дескрипторы, перед тем как выдаст запрос Set Configuration. Вышеописанный процесс энумерации работает одинаково в Windows 2000, Windows XP and Windows 98 SE. При написании firmware в первый раз шаг 4 часто вводит новичков в затруднение. Хост запрашивает первые 64 байта дескриптора устройства, а затем когда хост сбрасывает Ваше устройство после получения первых 8 байт, естественно думать, что что-то не так с Вашим дескриптором устройства, или что firmware неправильно обрабатывает запрос. Однако если Вы осуществили команду Set Address, то она сработает, и затем будут запрошены полные 18 байт дескриптора устройства. Обычно если что-то неправильно с дескриптором или с тем, как он был отправлен, хост попытается прочитать его 3 раза с большой паузой между запросами. После третьей неудачной попытки хост «сдается» и сообщает об ошибке с Вашим устройством. [Глава 8: пример Firmware] Firmware - PIC16F876, управляющий PDIUSBD11 Мы начнем наши примеры с устройства фирмы Philips PDIUSBD11 (I2C Serial USB Device), подключенного к микроконтроллеру PIC16F876 компании MicroChip (показан на схеме) или Microchip PIC16F877 (чип в бОльшем корпусе на 40 ножек). Несмотря на то, что у Microchip есть два low speed USB микроконтроллера PIC16C745 и PIC16C765, у них память программ только OTP, и нет поддержки внутрисхемной отладки - In-Circuit Debugging (ICD), а это никак не способствует нормальной разработке firmware. Скоро должны появится 4 новых полноскоростных (full speed) устройства с поддержкой ICD. В настоящий момент микросхема Philips PDIUSBD11, подключенная к PIC16F876, дает те же возможности - Flash и внутрисхемную отладку. Схема необходимого железа показана выше на рисунке. Пример проходит энумерацию и позволяет прочитать аналоговое напряжение с пяти мультиплексированных входа ADC микроконтроллера PIC16F876. Код совместим с PIC16F877, позволяющим максимум 8 аналоговых каналов. Светодиод LED, подключенный к ножке порта RB3, горит, когда устройство сконфигурировано. Регулятор 3.3V не показан, но он необходим для PDIUSBD11. Если Вы запускаете этот пример схемы от внешнего источника питания, то можете использовать обычный регулятор 78L033 на 3.3V, однако если Вы хотите запустить устройство в режиме Bus Powered USB device (устройство, питаемое от шины USB), то Вам нужен регулятор с низким падением напряжения. Отладка может быть осуществлена путем соединения TXD (ножка 17) к драйверу RS-232 и подключения к компьютеру на скорости 115,200 бит/сек. Операторы printf добавлены для отображения процесса энумерации. Код написан на C и скомпилирован компилятор Hi-Tech PICC Compiler. Имеется для загрузки демонстрационная версия компилятора PICC demo version (7.86 PL4), которая работает 30 дней. Скомпилированный HEX-файл имеется в архиве. Он скомпилирован для использования с ICD (или без него). #include #include #include #include <pic.h> <stdio.h> <string.h> "usbfull.h" const USB_DEVICE_DESCRIPTOR DeviceDescriptor = { sizeof(USB_DEVICE_DESCRIPTOR), /* bLength */ TYPE_DEVICE_DESCRIPTOR, /* bDescriptorType */ 0x0110, /* bcdUSB USB Version 1.1 */ 0, /* bDeviceClass */ 0, /* bDeviceSubclass */ 0, /* bDeviceProtocol */ 8, /* bMaxPacketSize 8 Bytes */ 0x04B4, /* idVendor (Cypress Semi) */ 0x0002, /* idProduct (USB Thermometer Example) */ 0x0000, /* bcdDevice */ 1, /* iManufacturer String Index */ 0, 0, 1 /* iProduct String Index */ /* iSerialNumber String Index */ /* bNumberConfigurations */ }; Все структуры заданы в заголовочном файле (header, файл с расширением *.h). Мы получили этот пример на основе примера от Cypress - USB термометр, который Вы можете использовать вместе с USB Driver для Cypress USB Starter Kit. Новый generic-драйвер написан для поддержки этого и других примеров, которые могут также скоро появиться. Только одна строка представлена для отображения производителя. Это иллюстрирует, как реализовать строковые дескрипторы без переполнения всей кодовой памяти устройства. Описание дескриптора устройства и его полей могут быть найдено здесь. const USB_CONFIG_DATA ConfigurationDescriptor = { { /* configuration descriptor */ sizeof(USB_CONFIGURATION_DESCRIPTOR), /* bLength */ TYPE_CONFIGURATION_DESCRIPTOR, /* bDescriptorType */ sizeof(USB_CONFIG_DATA), /* wTotalLength */ 1, /* bNumInterfaces */ 1, /* bConfigurationValue */ 0, /* iConfiguration String Index */ 0x80, /* bmAttributes Bus Powered, No Remote Wakeup */ 0x32 /* bMaxPower, 100mA */ }, { /* interface descriptor */ sizeof(USB_INTERFACE_DESCRIPTOR), /* bLength */ TYPE_INTERFACE_DESCRIPTOR, /* bDescriptorType */ 0, /* bInterface Number */ 0, /* bAlternateSetting */ 2, /* bNumEndpoints */ 0xFF, /* bInterfaceClass (Vendor specific) */ 0xFF, /* bInterfaceSubClass */ 0xFF, /* bInterfaceProtocol */ 0 /* iInterface String Index */ }, { /* endpoint descriptor */ sizeof(USB_ENDPOINT_DESCRIPTOR), /* bLength */ TYPE_ENDPOINT_DESCRIPTOR, /* bDescriptorType */ 0x01, /* bEndpoint Address EP1 OUT */ 0x02, /* bmAttributes - Interrupt */ 0x0008, /* wMaxPacketSize */ 0x00 /* bInterval */ }, { /* endpoint descriptor */ sizeof(USB_ENDPOINT_DESCRIPTOR), /* bLength */ TYPE_ENDPOINT_DESCRIPTOR, /* bDescriptorType */ 0x81, /* bEndpoint Address EP1 IN */ 0x02, /* bmAttributes - Interrupt */ 0x0008, /* wMaxPacketSize */ 0x00 /* bInterval */ } }; Описание дескриптора конфигурации и их полей можно найти здесь. Мы сделали 2 дескриптора конечной точки в добавок к заданному по умолчанию каналу. Конечная точка EP1 OUT - 8 байт максимум Bulk OUT Endpoint, и конечная точка EP1 IN - 8 байт максимум Bulk IN Endpoint. Наш пример читает данные из конечной точки Bulk OUT и помещает их в кольцевой буфер из 80 байт. Посылка пакета IN к конечной точке EP1 читает 8-байтовые куски памяти из этого кольцевого буфера. LANGID_DESCRIPTOR LANGID_Descriptor = { /* LANGID String Descriptor Zero */ sizeof(LANGID_DESCRIPTOR), TYPE_STRING_DESCRIPTOR, /* bLenght */ /* bDescriptorType */ 0x0409 /* LANGID US English */ }; const MANUFACTURER_DESCRIPTOR Manufacturer_Descriptor = { /* ManufacturerString 1 */ sizeof(MANUFACTURER_DESCRIPTOR), TYPE_STRING_DESCRIPTOR, */ "B\0e\0y\0o\0n\0d\0 \0L\0o\0g\0i\0c\0" ManufacturerString in UNICODE */ }; /* bLenght */ /* bDescriptorType /* Строковый дескриптор с индексом 0 сделан для поддержки требований LANGID для строковых дескрипторов USB. Это указывает, что все описатели находятся на американском английском языке. Дескриптор производителя (Manufacturer Descriptor) может быть небольшим обманом, поскольку размер символьного массива фиксированно задан в заголовке и не динамичный. #define MAX_BUFFER_SIZE 80 bank1 unsigned char circularbuffer[MAX_BUFFER_SIZE]; unsigned char inpointer; unsigned char outpointer; unsigned unsigned unsigned unsigned unsigned char char char char char *pSendBuffer; BytesToSend; CtlTransferInProgress; DeviceAddress; DeviceConfigured; #define PROGRESS_IDLE #define PROGRESS_ADDRESS void main { TRISB RB3 = RB2 = 0 3 (void) = 0x03; 1; 0; /* Int & Suspend Inputs */ /* Device Not Configured (LED) */ /* Reset PDIUSBD11 */ InitUART(); printf("Initialising\n\r"); I2C_Init(); RB2 = 1; /* Bring PDIUSBD11 out of reset */ ADCON1 = 0x80; /* ADC Control - All 8 Channels Enabled, */ /* supporting upgrade to 16F877 */ USB_Init(); printf("PDIUSBD11 Ready for connection\n\r"); while(1) if (!RB0) D11GetIRQ(); /* If IRQ is Low, PDIUSBD11 has an Interrupt Condition */ } Функция main зависит от конкретного примера. Она отвечает за инициализацию входов и выходов портов ввода/вывода, инициализацию интерфейса I2C, ADC и PDIUSBD11. Как только все сконфигурировано, обработчик D11GetIRQ обрабатывает запросы на прерывание PDIUSBD11. void D11GetIRQ(void) { unsigned short Irq; unsigned char Buffer[1]; /* Read Interrupt Register to determine source of interrupt */ D11CmdDataRead(D11_READ_INTERRUPT_REGISTER, (unsigned char *)&Irq, 2); if (Irq) printf("Irq = 0x%X: ",Irq); Функция USB_Init инициализирует PDIUSBD11. Эта процедура инициализации опущена в даташите Philips на PDIUSBD11, но доступна в FAQ. Последняя команда разрешает мягкое подключение pull-up резистора на сигнале D+, что указывает хосту на подключение устройства USB на полной скорости (full speed), и представляет появление устройства USB на шине. void USB_Init(void) { unsigned char Buffer[2]; /* Disable Hub Function in PDIUSBD11 */ Buffer[0] = 0x00; D11CmdDataWrite(D11_SET_HUB_ADDRESS, Buffer, 1); /* Set Address to zero (default) and enable function */ Buffer[0] = 0x80; D11CmdDataWrite(D11_SET_ADDRESS_ENABLE, Buffer, 1); /* Enable function generic endpoints */ Buffer[0] = 0x02; D11CmdDataWrite(D11_SET_ENDPOINT_ENABLE, Buffer, 1); /* Set Mode - Enable SoftConnect */ Buffer[0] = 0x97; /* Embedded Function, SoftConnect, Clk Run, No LazyClk, Remote Wakeup */ Buffer[1] = 0x0B; /* CLKOut = 4MHz */ D11CmdDataWrite(D11_SET_MODE, Buffer, 2); } Main() осуществляет вызовы D11GetIRQ в цикле. Эта функция читает регистр прерывания PDIUSBD11, если какие-нибудь прерывания находятся в ожидании обработки. В этом случае они обрабатываются, иначе цикл продолжается. Другие устройства USB могут иметь несколько векторов прерывания, назначенных на каждую конечную точку. В этом случае каждый обработчик прерывания ISR будет обрабатывать отдельное прерывание, что позволяет убрать операторы if. if (Irq & D11_INT_BUS_RESET) { printf("Bus Reset\n\r"); USB_Init(); } if (Irq & D11_INT_EP0_OUT) { printf("EP0_Out: "); Process_EP0_OUT_Interrupt(); } if (Irq & D11_INT_EP0_IN) { printf("EP0_In: \n\r"); if (CtlTransferInProgress == PROGRESS_ADDRESS) { D11CmdDataWrite(D11_SET_ADDRESS_ENABLE,&DeviceAddress,1); D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP0_IN, Buffer, 1); CtlTransferInProgress = PROGRESS_IDLE; } else { D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP0_IN, Buffer, 1); WriteBufferToEndPoint(); } } Условные операторы работают вниз в порядке приоритета. Наивысший приоритет у прерывания bus reset (сброс шины). Оно просто вызывает функцию USB_Init, которая переинициализирует функцию USB. Следующий наивысший приоритет – канал по умолчанию, основанный на конечной точке ноль EP0 OUT и EP0 IN. Здесь обрабатываются энумерация и все управляющие запросы. Мы вызываем другую функцию для обработки запросов EP0_OUT. Когда запрос сделан хостом, и он хочет принять данные, PIC16F876 отправит чипу PDIUSBD11 пакет из 8 байт. Поскольку шина USB управляется хостом, то микросхема PDIUSBD11 не может отправить данные когда пожелает, она буферизирует данные и ждет токена IN, который будет отправлен хостом. Когда PDIUSBD11 примет токен IN, она генерирует прерывание. В этот момент нужно загрузить новый пакет данных для отправки, что и происходит с помощью дополнительной функции WriteBufferToEndpoint(). Секция CtlTransferInProgress == PROGRESS_ADDRESS обрабатывает установку адреса устройства. Это мы обсудим позднее. if (Irq & D11_INT_EP1_OUT) { printf("EP1_OUT\n\r"); D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP1_OUT, Buffer, 1); bytes = D11ReadEndpoint(D11_ENDPOINT_EP1_OUT, Buffer); for (count = 0; count < bytes; count++) { circularbuffer[inpointer++] = Buffer[count]; if (inpointer >= MAX_BUFFER_SIZE) inpointer = 0; } loadfromcircularbuffer(); //Kick Start } if (Irq & D11_INT_EP1_IN) { printf("EP1_IN\n\r"); D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP1_IN, Buffer, 1); loadfromcircularbuffer(); } Конечные точки EP1 OUT и EP1 IN реализованы для чтения и записи данных bulk в и из кольцевого буфера. Настройка позволяет использовать код в соединении с примером BulkUSB из Windows DDK. Кольцевой буфер задан ранее в коде как 80 байт, по длине занимающий весь bank1 PIC16F876 RAM. if (Irq & D11_INT_EP2_OUT) { printf("EP2_OUT\n\r"); D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP2_OUT, Buffer, 1); Buffer[0] = 0x01; /* Stall Endpoint */ D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP2_OUT, Buffer, 1); } if (Irq & D11_INT_EP2_IN) { printf("EP2_IN\n\r"); D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP2_IN, Buffer, 1); Buffer[0] = 0x01; /* Stall Endpoint */ D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP2_IN, Buffer, 1); } if (Irq & D11_INT_EP3_OUT) { printf("EP3_OUT\n\r"); D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP3_OUT, Buffer, 1); Buffer[0] = 0x01; /* Stall Endpoint */ D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP3_OUT, Buffer, 1); } if (Irq & D11_INT_EP3_IN) { printf("EP3_IN\n\r"); D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP3_IN, Buffer, 1); Buffer[0] = 0x01; /* Stall Endpoint */ D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP3_IN, Buffer, 1); } } Конечные точки 2 и 3 в настоящий момент не используются, поэтому мы их приостанавливаем (stall), если будут приняты любые данные. PDIUSBD11 имеет команду Set Endpoint Enable, которую можно использовать для разрешения или запрещения обычных конечных точек (т. е. любых конечных точек, отличных от нулевой, составляющей по умолчанию канал управления). Мы можем использовать эту команду, чтобы отключить обычные конечные точки, которые мы не хотим использовать. В настоящий момент код предоставляет фундамент для дальнейшего усовершенствования. void Process_EP0_OUT_Interrupt(void) { unsigned long a; unsigned char Buffer[2]; USB_SETUP_REQUEST SetupPacket; /* Check if packet received is Setup or Data - Also clears IRQ */ D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP0_OUT, &SetupPacket, 1); if (SetupPacket.bmRequestType & D11_LAST_TRAN_SETUP) { Первое, что мы должны проделать – определить, является ли пакет принятый на EP0 Out пакетом данных или пакетом настройки (Setup Packet). Setup Packet содержит запрос, такой как Get Descriptor, где пакет данных содержит данные для предыдущего запроса. Нам повезло, что большинство запросов не отправляют пакеты данных от хоста в устройство. Запрос, который это делает, является запросом SET_DESCRIPTOR, но он редко осуществляется. /* This is a setup Packet - Read Packet */ D11ReadEndpoint(D11_ENDPOINT_EP0_OUT, &SetupPacket); /* Acknowlegde Setup Packet to EP0_OUT & Clear Buffer*/ D11CmdDataWrite(D11_ACK_SETUP, NULL, 0); D11CmdDataWrite(D11_CLEAR_BUFFER, NULL, 0); /* Acknowlegde Setup Packet to EP0_IN */ D11CmdDataWrite(D11_ENDPOINT_EP0_IN, NULL, 0); D11CmdDataWrite(D11_ACK_SETUP, NULL, 0); /* Parse bmRequestType */ switch (SetupPacket.bmRequestType & 0x7F) { Как мы уже рассматривали в писании упраляющих передач (Control Transfers), пакет setup не может получать в ответ NAK или STALL. Когда микросхема PDIUSBD11 получает пакет Setup, она очищает (flush) буфер EP0 IN и запрещает команды Validate Buffer и Clear Buffer. Это гарантирует, что пакет setup будет подтвержден микроконтроллером путем отправки команды подтверждения настройки (Acknowledge Setup) в обе конечные точки EP0 IN и EP0 OUT до того, как эффективна команда Validate или Clear Buffer. Прием пакета setup также выводит управляющую контрольную точку 0 из состояния STALL, если она была остановлена. Как только пакет прочитался в память и пакет setup подтвержден, мы начинаем анализаровать запрос, начиная с определения типа запроса. Пока нас не интересует направление передачи, и мы операцией AND отключаем этот бит. Три запроса, на которые все устройства должны ответить - Standard Device Request, Standard Interface Request и Standard Endpoint Request. Мы реализовали наш функционал (чтение входов ADC) в запросе вендора (Vendor Request) – мы добавили оператор выбора (case) в запросы Standard Vendor. Если наше устройствао поддерживает спецификацию стандартного класса USB, нам также необходимо добавить операторы case для Class Device Request, Class Interface Request и/или Class Endpoint Request. case STANDARD_DEVICE_REQUEST: printf("Standard Device Request "); switch (SetupPacket.bRequest) { case GET_STATUS: /* Get Status Request to Device should return */ /* Remote Wakeup and Self Powered Status */ Buffer[0] = 0x01; Buffer[1] = 0x00; D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2); break; case CLEAR_FEATURE: case SET_FEATURE: /* We don't support DEVICE_REMOTE_WAKEUP or TEST_MODE */ ErrorStallControlEndPoint(); break; Запрос Get Status используется для сообщении о состоянии устройства в смысле питается ли устройство от шины или имеет собственный источник питания, и поддерживает ли устройство функцию удаленного пробуждения хоста (remote wakeup). В нашем устройстве мы сообщаем, что устройство имеет собственный источник питания (self powered, оно не питается от шины USB), и устройство не поддерживает remote wakeup. На запросы Device Feature это устройство ответит, что не поддерживает ни DEVICE_REMOTE_WAKEUP, ни TEST_MODE, и вернет ошибку запроса USB Request Error. case SET_ADDRESS: printf("Set Address\n\r"); DeviceAddress = SetupPacket.wValue | 0x80; D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0); CtlTransferInProgress = PROGRESS_ADDRESS; break; Только команда Set Address выполняет обработку после стадии status. Все другие команды должны завершить обработку перед стадией status. Адрес устройства читается, по операции OR на него накладывается константа 0x80, и результат сохраняется в переменной DeviceAddress. Операция OR 0x80 – специфична для микросхемы PDIUSBD11, у неё старший бит адреса задает, разрешено устройство или нет. В качестве статуса на хост возвращается пакет нулевой длины, что сигнализирует ему об успешном завершении команды. Однако хост должен отправить токен IN, получить пакет нулевой длины и выдать ACK перед тем, как мы можем поменять адрес. Иначе устройство может не увидеть токена IN, отправленный на адрес по умолчанию (нулевой адрес). Завершение стадии status сигнализируется прерыванием на конечной точке EP0 IN. Чтобы разделить ответ на установку адреса и обычное прерывание EP0_IN, мы установим переменную CtlTransferInProgress в значение PROGRESS_ADDRESS. В обработчике EP0 IN проверяется переменная CtlTransferInProgress. Если она равна PROGRESS_ADDRESS, то для PDIUSBD11 выдается команда Set Address Enable и CtlTransferInProgress устанавливается в значение PROGRESS_IDLE. Хост дает 2 мс на смену адреса устройством, перед тем, как отправить какую-нибудь другую команду. case GET_DESCRIPTOR: GetDescriptor(&SetupPacket); break; case GET_CONFIGURATION: D11WriteEndpoint(D11_ENDPOINT_EP0_IN, &DeviceConfigured, 1); break; case SET_CONFIGURATION: printf("Set Configuration\n\r"); DeviceConfigured = SetupPacket.wValue & 0xFF; D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0); if (DeviceConfigured) { RB3 = 0; printf("\n\r *** Device Configured *** \n\r"); } else { RB3 = 1; /* Device Not Configured */ printf("\n\r ** Device Not Configured *** \n\r"); } break; //case SET_DESCRIPTOR: default: /* Unsupported - Request Error - Stall */ ErrorStallControlEndPoint(); break; } break; Запросы Get Configuration и Set Configuration используются для того, чтобы "разрешить" устройство USB, что позволит передавать данные на конечные точки, отсличные от конечной точки 0. Set Configuration должен быть выдан с полем wValue, равным соответствующему значению поля bConfigurationValue нужной конфигурации, которую Вы хотите разрешить. В нашем случае имеется только одна конфигурация – конфигурация 1. Нулевое значение для конфигурации означает, что устройство не сконфигурировано, а ненулевое значение означает, что устройство сконфигурировано. Код не делает полную проверку типа значения конфигурации, он просто копирует значение в локальную переменную для сохранения - DeviceConfigured. Если поле wValue не соответствует полю bConfigurationValue конгфигурации, то необходимо возвратить USB Request Error. case STANDARD_INTERFACE_REQUEST: printf("Standard Interface Request\n\r"); switch (SetupPacket.bRequest) { case GET_STATUS: /* Get Status Request to Interface should return */ /* Zero, Zero (Reserved for future use) */ Buffer[0] = 0x00; Buffer[1] = 0x00; D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2); break; case SET_INTERFACE: /* Device Only supports default setting, Stall may be */ /* returned in the status stage of the request */ if (SetupPacket.wIndex == 0 && SetupPacket.wValue == 0) /* Interface Zero, Alternative Setting = 0 */ D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0); else ErrorStallControlEndPoint(); break; case GET_INTERFACE: if (SetupPacket.wIndex == 0) { /* Interface Zero */ Buffer[0] = 0; /* Alternative Setting */ D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 1); break; } /* else fall through as RequestError */ //case CLEAR_FEATURE: //case SET_FEATURE: /* Interface has no defined features. Return RequestError */ default: ErrorStallControlEndPoint(); break; } break; Для стандартных запросов к интерфейсу (Standard Interface Requests) не выполняется никаких реальных функций. Запорос Get Status должен вернуть нулевое слово – зарезервировано для использования в будущем. Запросы Set Interface и Get Interface используются с альтернативными дескрипторами интерфейса. У нас не заданы никакие альтернативные дескрипторы интерфейса, поэтому Get Interface возвращает 0 и любой запрос на установку интерфейса, не равного интерфейсу 0 с альтернативной установкой 0 обрабатывается с ошибкой запроса Request Error. case STANDARD_ENDPOINT_REQUEST: printf("Standard Endpoint Request\n\r"); switch (SetupPacket.bRequest) { case CLEAR_FEATURE: case SET_FEATURE: /* Halt(Stall) feature required to be implemented on all Interrupt and */ /* Bulk Endpoints. It is not required nor recommended on the Default Pipe */ if (SetupPacket.wValue == ENDPOINT_HALT) { if (SetupPacket.bRequest == CLEAR_FEATURE) Buffer[0] = 0x00; else Buffer[0] = 0x01; switch (SetupPacket.wIndex & 0xFF) { case 0x01: D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \ D11_ENDPOINT_EP1_OUT, Buffer, 1); break; case 0x81: D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \ D11_ENDPOINT_EP1_IN, Buffer, 1); break; case 0x02: D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \ D11_ENDPOINT_EP2_OUT, Buffer, 1); break; case 0x82: D11CmdDataWrite(D11_SET_ENDPOINT_STATUS D11_ENDPOINT_EP2_IN, Buffer, 1); break; case 0x03: D11CmdDataWrite(D11_SET_ENDPOINT_STATUS D11_ENDPOINT_EP3_OUT, Buffer, 1); break; case 0x83: D11CmdDataWrite(D11_SET_ENDPOINT_STATUS D11_ENDPOINT_EP3_IN, Buffer, 1); break; default: /* Invalid Endpoint - RequestError */ ErrorStallControlEndPoint(); break; } D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, + \ + \ + \ 0); } else { /* No other Features for Endpoint - Request Error */ ErrorStallControlEndPoint(); } break; Запросы Set Feature и Clear Feature используются для специфичных фич конечных точек. Стандарт задает один селектор фичи - ENDPOINT_HALT. Мы соответсвенно проверяем запрос на установку/сброс бита STALL. Фича HALT не требуется для конечной точки по умолчанию (конечной точки 0). case GET_STATUS: /* Get Status Request to Endpoint should return */ /* Halt Status in D0 for Interrupt and Bulk */ switch (SetupPacket.wIndex & 0xFF) { case 0x01: D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \ D11_ENDPOINT_EP1_OUT, Buffer, 1); break; case 0x81: D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \ D11_ENDPOINT_EP1_IN, Buffer, 1); break; case 0x02: D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \ D11_ENDPOINT_EP2_OUT, Buffer, 1); break; case 0x82: D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \ D11_ENDPOINT_EP2_IN, Buffer, 1); break; case 0x03: D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \ D11_ENDPOINT_EP3_OUT, Buffer, 1); break; case 0x83: D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \ D11_ENDPOINT_EP3_IN, Buffer, 1); break; default: /* Invalid Endpoint - RequestError */ ErrorStallControlEndPoint(); break; } if (Buffer[0] & 0x08) Buffer[0] = 0x01; else Buffer[0] = 0x00; Buffer[1] = 0x00; D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2); break; default: /* Unsupported - Request Error - Stall */ ErrorStallControlEndPoint(); break; } break; Запрос Get Status, направленный на конечную точку, возвращает статус конечной точки, т. е. остановлена ли она (halted), или нет. Как и в запросе Set/Clear feature ENDPOINT_HALT, нам нужно всего лишь сообщить о статусе обычных (не равных 0) конечных точек. Любые неопределенные стандартные запросы к конечным точкам (Standard Endpoint Request) обрабатываются с ошибкой запроса USB Request Error. case VENDOR_DEVICE_REQUEST: case VENDOR_ENDPOINT_REQUEST: printf("Vendor Device bRequest = 0x%X, wValue = 0x%X, wIndex = 0x%X\n\r", \ SetupPacket.bRequest, SetupPacket.wValue, SetupPacket.wIndex); switch (SetupPacket.bRequest) { case VENDOR_GET_ANALOG_VALUE: printf("Get Analog Value, Channel %x :",SetupPacket.wIndex & 0x07); ADCON0 = 0xC1 | (SetupPacket.wIndex & 0x07) << 3; /* Wait Acquistion time of Sample and Hold */ for (a = 0; a <= 255; a++); ADGO = 1; while(ADGO); Buffer[0] = ADRESL; Buffer[1] = ADRESH; a = (Buffer[1] << 8) + Buffer[0]; a = (a * 500) / 1024; printf(" Value = %d.%02d\n\r",(unsigned int)a/100,(unsigned int)a%100); D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2); break; Теперь мы подошли (наконец!) к функциональной части устройства USB. Запросы (Vendor Requests) выдумываются самим разработчиком. Мы придумали 2 запроса, VENDOR_GET_ANALOG_VALUE и VENDOR_SET_RB_HIGH_NIBBLE. VENDOR_GET_ANALOG_VALUE читает 10-битное значение напряжения ADC канала x, указанного в поле wIndex. На значение x мы накладываем по AND маску 0x07, что разрешает 8 возможных номеров каналов (что поддерживает более крупный чип PIC16F877, если это нужно). Аналоговая величина напряжения возвращается в 2 байтах пакета данных (примечание переводчика – от фазы данных можно было бы отказаться, передавая эти 2 байта в wValue или wIndex. В этом случае wLength нужно указать не 2, а 0). case VENDOR_SET_RB_HIGH_NIBBLE: printf("Write High Nibble of PORTB\n\r"); PORTB = (PORTB & 0x0F) | (SetupPacket.wIndex & 0xF0); D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0); break; default: ErrorStallControlEndPoint(); break; } break; VENDOR_SET_RB_HIGH_NIBBLE может быть использован для установки бит старшего ниббла PORTB[3:7]. default: printf("UnSupported Request Type 0x%X\n\r", SetupPacket.bmRequestType); ErrorStallControlEndPoint(); break; } } else { printf("Data Packet?\n\r"); /* This is a Data Packet */ } } Любые неподдерживаемые типы запросов, такие как class device request, class interface request и т. д. обрабатываются с ошибкой запроса USB Request Error. void GetDescriptor(PUSB_SETUP_REQUEST SetupPacket) { switch((SetupPacket->wValue & 0xFF00) >> 8) { case TYPE_DEVICE_DESCRIPTOR: printf("\n\rDevice Descriptor: Bytes Asked For %d, Size of Descriptor %d\n\r", \ SetupPacket->wLength,DeviceDescriptor.bLength); pSendBuffer = (const unsigned char *)&DeviceDescriptor; BytesToSend = DeviceDescriptor.bLength; if (BytesToSend > SetupPacket->wLength) BytesToSend = SetupPacket->wLength; WriteBufferToEndPoint(); break; case TYPE_CONFIGURATION_DESCRIPTOR: printf("\n\rConfiguration Descriptor: Bytes Asked For %d, Size of Descriptor %d\n\r", \ SetupPacket->wLength, sizeof(ConfigurationDescriptor)); pSendBuffer = (const unsigned char *)&ConfigurationDescriptor; BytesToSend = sizeof(ConfigurationDescriptor); if (BytesToSend > SetupPacket->wLength) BytesToSend = SetupPacket->wLength; WriteBufferToEndPoint(); break; Запросы Get Descriptor влекут за собой ответы размером больше, чем лимит на максимальный размер пакета 8 байт для конечной точки. Таким образом, ответ должен быть разбит на куски по 8 байт. Оба запроса Device и Configuration загружают адрес соответствующих дескрипторов в pSendBuffer, и устанавливают BytesToSend в значение длины дескриптора. Запрос будет также указывать длину дескриптора в поле wLength, что дает максимум данных для отправки. В любом случае мы проверяем действительную величину, запрашиваемую от хоста, и обрезаем размер по необходимости. Затем делается вызов WriteBuffertoEndpoint, который загружает первые 8 байт в буфер конечной точки и увеличивает указатель, чтобы быть готовым к следующему пакету из 8 байт. case TYPE_STRING_DESCRIPTOR: printf("\n\rString Descriptor: LANGID = 0x%04x, Index %d\n\r", \ SetupPacket->wIndex, SetupPacket->wValue & 0xFF); switch (SetupPacket->wValue & 0xFF) { case 0: pSendBuffer = (const unsigned char *)&LANGID_Descriptor; BytesToSend = sizeof(LANGID_Descriptor); break; case 1: pSendBuffer = (const unsigned char *)&Manufacturer_Descriptor; BytesToSend = sizeof(Manufacturer_Descriptor); break; default: pSendBuffer = NULL; BytesToSend = 0; } if (BytesToSend > SetupPacket->wLength) BytesToSend = SetupPacket->wLength; WriteBufferToEndPoint(); break; Если имеется любой строковый дескриптор, должен присутсвовать строковый дескриптор 0, описывающий языки, которые поддерживает устройство. Любые запросы строковых дескрипторов с ненулевыми номерами имеют LanguageID в поле wIndex, который говорит о том, какой язык поддерживается. В нашем случае мы немного обманываем и игнорируем значение wIndex (LANGID) возвращаемый в строке, независимо от того, какой язык запрашивался. default: ErrorStallControlEndPoint(); break; } } void ErrorStallControlEndPoint(void) { unsigned char Buffer[] = { 0x01 }; /* 9.2.7 RequestError - return STALL PID in response to next DATA Stage Transaction */ D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP0_IN, Buffer, 1); /* or in the status stage of the message. */ D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP0_OUT, Buffer, 1); } Когда мы сталкиваемся с неправильным запросом, параметром, или запросом, который устройство не поддерживает, мы должны сообщить об ошибке запроса (request error). Это задано в части 9.2.7 стандарта. Request error возвращает STALL PID в ответ на следующие данные стадии транзакции или стадии статуса сообщения. Однако чтобы предотвратить нежелательный трафик шины, сообщение должно возвращаться на следующей стадии данных, вместо того чтобы ждать до стадии состояния (status stage). unsigned char D11ReadEndpoint(unsigned char Endpoint, unsigned char *Buffer) { unsigned char D11Header[2]; unsigned char BufferStatus = 0; /* Select Endpoint */ D11CmdDataRead(Endpoint, &BufferStatus, 1); /* Check if Buffer is Full */ if(BufferStatus & 0x01) { /* Read dummy header - D11 buffer pointer is incremented on each read */ /* and is only reset by a Select Endpoint Command */ D11CmdDataRead(D11_READ_BUFFER, D11Header, 2); if(D11Header[1]) D11CmdDataRead(D11_READ_BUFFER, Buffer, D11Header[1]); /* Allow new packets to be accepted */ D11CmdDataWrite(D11_CLEAR_BUFFER, NULL, 0); } return D11Header[1]; } void D11WriteEndpoint(unsigned char Endpoint, const unsigned char *Buffer, unsigned char Bytes) { unsigned char D11Header[2]; unsigned char BufferStatus = 0; D11Header[0] = 0x00; D11Header[1] = Bytes; /* Select Endpoint */ D11CmdDataRead(Endpoint, &BufferStatus, 1); /* Write Header */ D11CmdDataWrite(D11_WRITE_BUFFER, D11Header, 2); /* Write Packet */ if (Bytes) D11CmdDataWrite(D11_WRITE_BUFFER, Buffer, Bytes); /* Validate Buffer */ D11CmdDataWrite(D11_VALIDATE_BUFFER, NULL, 0); } Функции D11ReadEndpoint и D11WriteEndpoint специфичны для микросхемы PDIUSBD11. PDIUSBD11 имеет два dummy – байта, которые являются префиксом к любой операции чтения или записи данных. Первый байт зарезервирован, а второй байт показывает количество принимаемых или передаваемых байт. Эти две функции обепечены своим заголовком. void WriteBufferToEndPoint(void) { if (BytesToSend == 0) { /* If BytesToSend is Zero and we get called again, assume buffer is smaller */ /* than Setup Request Size and indicate end by sending Zero Lenght packet */ D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0); } else if (BytesToSend >= 8) { /* Write another 8 Bytes to buffer and send */ D11WriteEndpoint(D11_ENDPOINT_EP0_IN, pSendBuffer, 8); pSendBuffer += 8; BytesToSend -= 8; } else { /* Buffer must have less than 8 bytes left */ D11WriteEndpoint(D11_ENDPOINT_EP0_IN, pSendBuffer, BytesToSend); BytesToSend = 0; } } Как мы уже упоминали, функция WriteBufferToEndPoint отвечает за загрузку данных в микросхему PDIUSBD11 порциями по 8 байт и подстройку указателей для готовности к следующему пакету. Функция вызывается один раз обработчиком запроса для загрузки превых 8 байт в буфер конечной точки. Затем хост посылает токен IN, читает эти данные и PDIUSBD11 генерирует прерывание. Обработчик конечной точки EP0 IN вызовет затем WriteBufferToEndpoint для загрузки следующего пакета в готовность к следующему токену IN от хоста. Трансфер считается завершенным, если все запрошенные байты прочитаны, если принят пакет с полезной нагрузкой меньше, чем bMaxPacketSize или если возвращен пакет нулевой длины (zero length packet). Таким образом, если счетчик BytesToSend достигает 0, мы предполагаем что данные были отправлены несколько раз по 8 байт, и отравляем zero length packet, что указывает – это были последние данные. Однако если у нас осталось меньше чем 8 байт для отправки, мы посылаем только оставшиеся байты. Здесь не требуется дополнять данные нулями. void loadfromcircularbuffer(void) { unsigned char Buffer[10]; unsigned char count; // Read Buffer Full Status D11CmdDataRead(D11_ENDPOINT_EP1_IN, Buffer, 1); if (Buffer[0] == 0) { // Buffer Empty if (inpointer != outpointer) { // We have bytes to send count = 0; do { Buffer[count++] = circularbuffer[outpointer++]; if (outpointer >= MAX_BUFFER_SIZE) outpointer = 0; if (outpointer == inpointer) break; // No more data }while (count < 8); // Maximum Buffer Size // Now load it into EP1_In D11WriteEndpoint(D11_ENDPOINT_EP1_IN, Buffer, count); } } } Процедура loadfromcircularbuffer() обрабатывает загрузку данных в буфер конечной точки EP1 IN. Процедура нормально вызывается после прерывания EP1 IN для перезагрузки буфера и для готовности к следующему токену IN на конечной точке EP1. Однако для того, чтобы отправить первый пакет, нам надо загрузить данные перед получением прерывания EP1 IN. Таким образом, процедура также вызывается после принятых данных в конечную точку EP1 OUT. Вызывая также процедуру из обработчика EP1 OUT, мы возможно перезапишем данные в буфере IN независимо от того, были ли уже посланы предыдущие данные буфера IN, или нет. Чтобы это предотвратить, мы проверяем – пуст ли буфер EP1 IN перед попыткой загрузить его новыми данными. void D11CmdDataWrite(unsigned char Command, const unsigned char *Buffer, unsigned char Count) { I2C_Write(D11_CMD_ADDR, &Command, 1); if(Count) I2C_Write(D11_DATA_ADDR_WRITE, Buffer, Count); } void D11CmdDataRead(unsigned char Command, unsigned char Buffer[], unsigned char Count) { I2C_Write(D11_CMD_ADDR, &Command, 1); if(Count) I2C_Read(D11_DATA_ADDR_READ, Buffer, Count); } D11CmdDataWrite и D11CmdDataRead – две функции, специфичные для микросхемы PDIUSBD11. Они отвечают за работу интерфейса I2C – за отправку сначала команд Address/Command и затем отправки принятых данных по шине I2C. Дополнительные функции нижнего уровня добавлены в исходный код, но здесь не прведены, чтобы лучше сфокусироваться именно на специфике протокола USB. Этот пример может использоваться вместе с примером bulkUSB.sys, который является частью Windows DDK. Для загрузки драйвера bulkUSB.sys либо поменяйте код для самоидентификации по VID 0x045E и PID 0x930A, либо поменяйте для bulkUSB.sys файл bulkUSB.inf в соответствии с применяемыми в этом примере VID/PID. Теперь можно использовать консольную программу пользовательского режима rwbulk.exe для отправки пакетов из кольцевого буфера. Используйте следующую команду для отправки порций по 80 байт от PIC16F876: rwbulk -r 80 -w 80 -c 1 -i 1 -o 0 Использование полезной нагрузки свыше 80 байт вызовет переполнение кольцевого буфера PIC в памяти BANK1. Этот пример был закодирован для лучшей удобочитаемости в ущерб размеру кода. Он компилируется в 3250 слов памяти FLASH (39% емкости PIC16F876). Благодарности Выражаем особую признательность Michael DeVault из компании DeVaSys Embedded Systems. Этот пример базируется на коде, написанным Michael. Код легко был разработан на плате для USB разработки USBLPT-PD11 компании DeVaSys перед портированием на PIC. Загрузка исходного кода версия 1.2, 14k байт История ревизий 6 апреля 2002 - версия 1.2 – увеличена скорость I2C для соответсвия комментариям. Улучшена обработка прерываний (IRQ) PDIUSBD11 7 января 2002 - версия 1.1 – добавлены процедуры Bulk обработки EP1 IN и EP1 OUT и сделаны дескрипторы, загружающиеся из FLASH 31 декабря 2001 - версия 1.0. [Термины] Endpoint(s) – конечная точка (точки). Специфическое понятие стандарта USB, символизирующее источник или приемник потока данных. Pipe(s) – буквальный перевод «труба» («трубы»). Означает специальным образом сформированные частные потоки данных через интерфейс USB. Иногда pipe называют каналом. Host – хост, главное устройство на шине. Обычно это компьютер. PCB – печатная плата. bit stuffing – вставка бит по специальному алгоритму в последовательном потоке бит на линии. Применяется для целей синхронизации или уменьшения/устранения постоянной составляющей в сигнале. bus powered devices – устройства USB, получающие питание +5V от шины USB (линия VBUS). Питающее напряжение на линии VBUS формирует хост. self powered devices - устройства USB, питающиеся от отдельного источника (не от шины USB). status reporting – получение информации о статусе устройства handshaking – «рукопожатие», процедура установления связи Frame Work – фреймворк, рабочая среда, рабочее окружение token - символ daisy chained – термин, относящийся к линейной (иногда кольцевой) топологии сети. Иногда такую топологию называют шлейфовой. Дословный перевод daisy chain – «цепочка маргариток», что означает венок из маргариток. tiered star – топология многоярусная звезда trade-off – компромисс upstream – восходящее соединение. Такое соединение имеет место для устройства USB по отношению к хосту. downstream – низходящее соединение. Такое соединение имеет место для хоста по отношению к устройству USB. plug – штеккер, коннектор на конце кабеля или на торце устройства USB (например, флешки). Этот коннектор втыкается в сокет, который обычно ставится на корпусе устройства (компьютера или периферии USB). socket – сокет, разъем на корпусе компьютера или периферии USB, куда втыкается plug (коннектор). errata – исправления, добавления к стандарту. peer-to-peer – возможность обмена данными между равноправными устройствами. pull up, pull-up – нагрузочный для сигнальной линии резистор, подключенный между линией сигнала и плюсом питания. pull down, pull-down – нагрузочный для сигнальной линии резистор, подключенный между линией сигнала и землей. OTP ROM – однократно программируемая память (обычно для программы) Flash – энергонезависимая память для хранения программ или данных LDO regulator – Low Dropout регулятор, имеющий маленькое падение напряжения, достаточное для нормальной работы (стабилизации) Token Packet – символ, показывающий, какие дальше идут данные payload – полезная нагрузка, передаваемые в потоке данные Padding – дополнение байтами до нужного количества (обычно нулевыми байтами) Latency – латентность, время ожидания на обработку, время задержки Feature – фича, какая-нибудь возможность (особенность) устройства Feature Selector – селектор фичи, число, от которого зависит выбор какой-нибудь возможности (особенности) устройства ADC – Analog-to-Digital Converter, АЦП, аналого-цифровой преобразователь ICD – In Circuit Debug, внутрисхемная отладка ISR – Interrupt Service Routine, подпрограмма обработки прерывания EPx – EndPoint (конечная точка) номер x