Интерфейс USB. Еще немного теории.



Появилось немного свободного времени, и я решил написать небольшую «внеплановую» статью.

Итак, из предыдущей статьи, мы знаем, что для обмена данными используются некие виртуальные каналы – «конечные точки». Давайте рассмотрим, как происходит обмен.



Обмен данными по USB

Нужно помнить, что интерфейс USB предусматривает использование разветвлителей – хабов. Более того, допускается каскадное включение хабов. Следовательно, необходимо как-то идентифицировать конкретное USB устройство в «гирлянде» из хабов и USB устройств. Для этого каждому устройству присваивается адрес.

Здесь остановимся немного подробнее. Адрес кодируется 7 битами. Изначально (в момент подключения), устройство, грубо говоря, само себе назначает адрес 0. Этот адрес зарезервирован стандартом как раз для вновь подключаемых устройств. Далее, в процессе инициализации (об этом поговорим позже), хост присваивает устройству уникальный адрес отличный от 0, а адрес 0 «освобождается» для вновь подключаемых устройств.

И так, для того чтобы передать данные конкретному устройству, нужно знать адрес устройства и номер виртуального канала «внутри» устройства (адрес «конечной точки»).

Как мы уже выяснили, сразу после включения, устройство имеет особый, «нулевой» адрес. Каждое устройство, согласно стандарту, имеет «нулевую конечную точку» типа Control. Соответственно, сразу после подключения, хост может начинать обмениваться данными с новым устройством (адрес = 0, номер конечной точки = 0).

Рассмотрим, как происходит обмен данными.

Инициатором обмена всегда выступает хост. Устройство не может начать передачу данных по собственной инициативе. Хост поочередно (емнип, с интервалом в 1 мс, но «низкоскоростные» устройства могут опрашиваться не каждый раз, а, например, раз за 10 циклов опроса) опрашивает все подученные устройства. Каждый раз, при опросе, устройство может получить данные от хоста и отправить данные хосту.

Сам обмен осуществляется пакетами. Стандартом предусмотрено несколько типов пакетов. «Побайтно» мы пока разбирать пакеты не будем, но коснемся этого вопроса в практической части.

Дело в том, что часть работы по формированию и передаче пакетов (например, вопросы синхронизации, расчет контрольных сумм и т. д.) возьмет на себя USB периферия МК. Для тех, кто хочет сразу углубиться в биты и байты могу порекомендовать ознакомиться с разделом 8 официальной спецификации USB 2.0

Пока нам достаточно знать, что существуют «пакеты данных» и несколько типов «служебных пакетов».

USB и Plug and Play

Давайте рассмотрим, что с нашим устройством будет происходить дальше, после того как хост определил подключение нового устройства и готов начать обмен данными. Нам нужно ненадолго подняться на «высокий» уровень – уровень ОС.

Дело в том, что в стандарт USB поддерживает концепцию Plug and Play (подключи и играй). Данная концепция подразумевает, что пользователю достаточно «воткнуть» устройство в соответствующий порт ПК. Дальше ОС автоматически определит тип подключенного устройства, найдет подходящий для данного устройства драйвер, сконфигурирует устройство и т. д. (правда, это конечно в идеале :))

Для того чтобы вся эта красота работала, стандартом USB предусмотрены некие общие требования для всех устройств:

1. Каждое устройство содержит «собственное описание» (дескриптор устройства).

2. Есть некий, общий для всех USB устройств, механизм который позволяет ОС прочитать дескриптор устройства для того, чтобы идентифицировать устройство, узнать его характеристики.

3. Есть некий, общий для всех USB устройств, механизм который позволяет ОС выполнить первичную конфигурацию устройства (например, присвоить устройству новый адрес, о чем мы говорили выше).

Данными вещами (чтение дескриптора устройства, идентификация устройства) занимается некая служба ОС, которая отвечает за базовую поддержку USB.
После того как устройство будет идентифицировано и проведена некая первичная инициализация, данная служба передаст управление устройством драйверу, который «закреплен» за данным типом устройств (или конкретно за этим устройством).

Что будет, если служба не сможет найти «подходящий» драйвер для данного устройства знают все :)

Теперь возвращаемся на наш «низкий» уровень.

Начало работы с устройством. Стандартные заросы.

На практике, для чтения дескриптора устройства и первичной инициализации используются та самая «нулевая конечная точка». Есть несколько предусмотренных стандартом запросов (Standard Device Requests), которые должны обрабатываться всеми USB устройствами. Пока приведу несколько примеров таких запросов:

GET_DESCRIPTOR – запрос на получения дескриптора устройства. Данный запрос содержит дополнительную информацию о том, какой именно дескриптор должно вернуть (в устройстве «хранится» несколько разных дескрипторов, но об этом позже).

GET_CONFIGURATION – запрос на получение текущей конфигурации устройства.

SET_ADDRESS – данный запрос используется для присвоения устройству «нормального» (отличного от 0) адреса. Сам адрес содержится в запросе.

Нужно понимать, что запрос — это не более чем стандартизированная структура данных, которая содержит код запроса (bRequest) и дополнительные данные. Ответы на каждый из запросов тоже, естественно, стандартизированы.

Кроме стандартных запросов, которые устройство «обязано» поддерживать, можно определить «свои» запросы, специфические для конкретного устройства (класса устройств).

Детально со всей «кухней» запросов и ответов мы познакомимся в «практической» части.

Заодно, для того чтобы показать как выглядит тот самый дескриптор устройства приведу пример:


static const BYTE devDescriptor[] = {
	/* Device descriptor */
	0x12,   // длина дескриптора в байтах;
	0x01,   // тип дескриптора: 1-Device descriptor
	0x00,   // 2 байта - версия USB: 2.0
	0x02,   //
	0x02,   // класс устройства: 2-CDC class code
	0x00,   // подкласс: 0-CDC class sub code
	0x00,   // протокол: 0-CDC Device protocol
	0x08,   // максимальный размер пакета для "нулевой конечной точки"
	0xEB,   // 2 байта - код производителя VID
	0x03,   //
	0x26,   // 2 байта - код устройства PID
	0x61,   //
	0x10,   // 2 байта - версия (ревизия) устройства
	0x01,   //
	0x01,   // индекс строки с названием производителя
	0x02,   // индекс строки с названием устройства
	0x03,   // индекс строки с серийным номером устройства
	0x01    // количество поддерживаемых конфигураций
};


Пока это просто иллюстрация того, как выглядит дескриптор, вникать в значение полей не стоит, этим мы займемся в следующей статье.

На этом, предлагаю завязывать с голой теорией и постепенно переходить к практике. В следующей статье начнем потихоньку писать код.

Извиняюсь за отсутствие в статье наглядности (иллюстраций, схем и т. д.) о чем было справедливо замечено в комментариях к предыдущей статье. В следующей статье попытаюсь исправиться.
  • +7
  • 04 ноября 2011, 17:30
  • e_mc2

Комментарии (33)

RSS свернуть / развернуть
емнип, с интервалом в 1 мс, но «низкоскоростные» устройства могут опрашиваться не каждый раз, а, например раз за 10 циклов опроса
1мс, да. V-USB кстати умеет подстраивать RC-генератор по опорному килогерцовому сигналу SoF-маркеров.
Нинай насчет других устройств, а низкоскоростные HID (мышь там, клава) опрашиваются каждый 8-й цикл, т.е. 125Гц.
0
  • avatar
  • Vga
  • 04 ноября 2011, 18:07
автор молодец! давно хотел освоить УСБ но никак не доходили руки да и не понимал ничего. тут все описано просто, красиво и понятно. так держать!
0
С нетерпением ждём следующую статью :)
0
#define grammar_nazi_mode
итак пишется вместе
#undef grammar_nazi_mode
0
Аналогично «заодно».
0
«данная служба предаст»
0
А как же «длинна»?))
0
Спасибо, исправил.
0
«служебных пактов» или МБ все же «пакетов»?
0
Спасибо, исправил.
0
А также
(в устройстве «храниться» несколько разных дескрипторов
0
Спасибо, исправил.
0
Унес в коллективный блог. Все дальнейшие статьи по теме USB и других интерфейсов железо-комп просьба писать туда.
0
Эта серия статей, между прочим, тянет на конкурс.
0
Хорошее, конечно, дело, но вот тут уже давно читал примерно все то же самое, с примарами дескрипторов и т.д.
0
USB in a NutShell – один из лучших материалов на данную тематику. У меня так глубоко и качественно проработать материал не получится. Но, думаю, еще одна статья на тему реализации USB на МК имеет право на существование :)
0
А если одновременно будут подключены два устройства (не будет вдаваться в вероятности и подробности как такое может произойти, например какой-то глюк хаба или контроллера) — будет ли коллизия нулевых адресов?
0
Хороший вопрос. Он мне тоже не давал покоя :). Прямого ответа в спецификации я не нашел. Но, по логике, происходит следующее (хотя это дебри физического уровня).
Сразу после подключения, устройство не имеет право передавать какие либо данные (даже в ответ на запрос по нулевому адресу) пока хост не ресетнет устройство. Ресет – это не уровень протокола, физический уровень (если быть точнее, то удержание линий D+ D- в низком состоянии более 2.5 мкс.). После того, как устройство обнаружило ресет на линии, оно переходит в «Default state» и начинает отвечать на запросы от хоста по адресу 0. Я подозреваю, что хост (как и хаб) при одновременном подключении нескольких устройств будет ресетить их по очереди. Таким образом, в один момент времени в «Default state» будет находиться только одно устройство.
0
Спасибо за ответ.
Рассмотрим на примере: в хаб воткнуты 4 флешки. Хаб подключают к хосту — имеем 4 одновременных подключения. Как будут поступать хаб и хост в таком случае? По идее, хаб должен сбросить первое устройство, дернуть линию D хаба и дождаться инициализации текущего устройства. Потом повторить то же самое для второго и так для каждой флешки. Т.е. он должен работать не только на физическом, но и частично на канальном/сетевом уровнях. А я раньше думал что это тупо повторитель сигналов хитрый и все :)
0
Ну ведь флешки в хабе не в парралель включены просто, не забываем, что хаб это хитрое устройство, поддержмвающее адресацию и прочие плюшки протокола USB.
А ради интереса, надо взять пачку флешек и проделать с ними такой эксперимент :) но очевидно, что они просто последовательно будут опрашиватся, а винда то гарантированно драйвера по очереди ставить будет (искать имелось в виду).
0
Уважаемый e_mc2.
Забегая вперед, можно спрошу. Можно ли реализовать стандартными способами такую задачу или надо писать свои драйвера?
Хочу видеть определенную область ОЗУ микроконтроллера (несколько килобайт), получая данные из нее с максимально возможной скоростью на ПК. Т. е. объявляю в программе на ПК массив, связываю с USB устройством, и в этом массиве получаю постоянно обновляемую область памяти микроконтроллера.
0
Можно. Есть несколько вариантов — все зависит от необходимой скорости обмена.
Можно реализовать HID или CDC устройство, а внутри гонять свой прикладной протокол для записи/чтения фрагмента памяти (но там есть вопросы по скорости обмена). Теоретически, можно реализовать MSD и «замапить» фрагмент памяти на «сектора» такой «флешки». Можно, даже, эмулировать на такой «флешке» файловую систему, в которой есть файл, содержимое которого «замаплено» на фрагмент ОЗУ МК.
Какая скорость обмена Вам нужна?
0
Хотелось бы получить максимальную скорость, какую сможет дать связка: контроллер + ПК. Например, хотелось бы успевать считывать буфер с камеры на одну или несколько строк, а там поток 20-30 Мбайт/с. Позволит ли, теоретически, USB 2.0 HS столько пропихнуть? Думаю, какой-то упрощенный вариант протокола нужен, без эмуляции файловой системы, чтобы минимизировать «накладные» расходы?
0
20-30 Мбайт/с.

Хм, не слабо так… Теоретически, USB 2.0 HS позволяет предавать данные на скорости до 60Мбайт/с. Правда, нужно помнить, что процентов 6 от этой скорости уйдет на «накладные расходы» (служебные пакеты и. т. д.)

Можно ли пропустить через MSD такой поток данных я, честно говоря, даже не знаю. Я бы написал свой драйвер, дабы избежать любых накладных расходов (в случае с MSD, даже без эмуляции файловой системы они однозначно будут)

А почему Вы хотите передавать видео через буфер в памяти? Можно просто гнать сплошной поток как в USB Video Device Class.
0
Теоретически, USB 2.0 HS позволяет предавать данные на скорости до 60Мбайт/с.
Процентов до 10-20 от этого только на накладные расходы физического (или на один уровень выше ли) уровня шины уйдет, вроде bit stuffing'а.
Можно ли пропустить через MSD такой поток данных я, честно говоря, даже не знаю.
Можно, это как раз в районе практического лимита в условиях ПК с виндой. В условиях монопольного доступа к шине можно до 40-45 выжать вроде. Вот только видео обычно передается изохронной передачей, а не bulk'ом.
Но я, честно говоря, еще не видел МК общего назначения с USB 2.0 HS. Только FS.
0
Если верить даташиту на STM32F407:
Advanced connectivity
USB 2.0 high-speed/full-speed
device/host/OTG controller with dedicated
DMA, on-chip full-speed PHY and ULPI


Чем, собственно, и хотел воспользоваться.
0
Хороший контроллер…
0
По USB Video Device Class у меня нет никакой информации. MSD хоть примеры реализации есть, а значит есть шанс разобраться как оно работает. Дальше, поняв работу MSD, попробую разобраться с изосинхронной передачей.
0
Есть же спеки. Разобраться с ними наверняка куда проще, чем писать свой драйвер.
0
e_mc2.
Я бы написал свой драйвер, дабы избежать любых накладных расходов
Такого уровня знаний я бы и хотел достичь. С нетерпением жду Ваших статей и буду благодарен за любую информацию.
0
На самом деле, написание драйверов не такая сложная вещь, как это многим кажется. Касательно USB устройств – там все достаточно просто и в Windows DDK есть неплохие примеры. Аналогично с Linux (usb-skeleton.c)

С нетерпением жду Ваших статей и буду благодарен за любую информацию.

Спасибо :) Я не планировал писать статей по написанию драйверов, но если это многим интересно – можно попробовать. В любом случае, буду рад Вам помочь, если что – пишите в личку.
0
А как насчет связи между гольной теорией (все эти дескрипторы, конечные точки и «Стандартные заросы») с конкретными примерами кода со стороны устройства и/или со стороны хоста?
Давече, писал читалку данных с maxadclite+ весь исплевался пока сообразил как отправить стандартный и не очень запрос, получить ответ и т.д.
Тому кто будет проходить этот путь будет очень полезно ознакомиться.
0
пардон за вопрос, статью не до конца дочитал.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.