USB4: все тот же USB?
В конце 2020 года ожидается выход устройств с поддержкой интерфейсов нового поколения USB4/Thunderbolt 4. Данные интерфейсы похожи, однако имеют ряд принципиальных отличий. Среди таких отличий можно выделить наиболее значимое: спецификации на USB4 общедоступные и любой желающий может изучить основные принципы работы данного интерфейса, в отличие от Thunderbolt 4.
В данной статье мы рассмотрим изменения, которые претерпел интерфейс USB4 по сравнению с предыдущей версией (USB 3.2), разберем архитектуру USB4 и перечислим его основные характеристики.
Более подробную информацию можно найти в спецификации на USB4.
Что мы знаем о USB4?
Основные характеристики USB4:
Разъем: аналогично современным интерфейсам от Intel (Thunderbolt 3/4) USB4 поддерживает только разъем USB-C.
Скорость передачи данных: тут уже дело обстоит немного сложнее и все не так однозначно, попытаемся разобраться: минимальная поддерживаемая скорость для устройства, имеющего USB4-сертификацию, составляет 20 Гбит/c. Но также может поддерживаться скорость 40 Гбит/c, если хост, устройство и кабель на это способны. И данная пропускная способность уже ничем не уступает своему конкуренту от Intel – Thunderbolt 4.
Туннелирование интерфейсов: одной из основных задач, которые необходимо было решить на этапе разработки USB4-интерфейса, являлось объединение нескольких различных протоколов, работающих через разъем USB-C, в единый физический интерфейс. Основные интерфейсы, работающие в режиме туннелирования:
- Enhanced SuperSpeed USB (USB3) (предыдущее поколение);
- DisplayPort (DP);
- PCI Express (PCIe) (не является обязательной опцией).
Поддержка конфигурации шины: поддержка возможности специального соединения между двумя хостами (host-to-host).
Питание USB4: для работы USB4 питание устанавливается и регулируется в соответствии со спецификациями USB Type-C и USB PowerDelivery (PD). Реализована возможность передачи питания до 100 Вт.
Поддержка Thunderbolt 3: хост или устройство, работающее по интерфейсу USB4, может также взаимодействовать с устройствами, поддерживающими подключение по интерфейсу Thunderbolt 3. Но данный функционал не является обязательным, поэтому поддержка данной возможности зависит только от разработчиков устройств.
Сравнение с USB 3.2
После того как мы рассмотрели основные характеристики USB4, можем выделить ряд значительных изменений по сравнению с предыдущей версией стандарта:
Разъем: предыдущие версии разъемов не поддерживаются. В связи с необходимостью использовать дополнительную сигнальную линию данных (Sideband Channel), нет возможности использовать такие разъемы, как USB Type A/B.
- Отказ от разъемов USB Type A/B, miniUSB, microUSB
Скорость передачи данных: для последней версии USB 3.2 Gen2 x 2 максимальная скорость передачи данных составляет 20 Гбит/c, но данная скорость достигалась только с использованием одновременно двух линий передачи данных, то есть только для разъема USB-C. На предыдущих версиях разъемов скорость была вдвое меньше – 10 Гбит/c.
- Увеличение скорости передачи данных вдвое – с 20 до 40 Гбит/c
Питание: распределение питания в стандарте USB 3.2 регламентируется аналогично стандарту USB 2.0, с увеличением потребляемого тока для устройств, работающих на SuperSpeed-шине. Стандарты USB BC (Battery Charging) и USB PD для них являются дополнением, расширяющим возможности питания. Для USB4, в отличие от USB 2.0 и USB 3.2, не определена собственная модель питания устройства и регламентируется только с помощью спецификаций USB PD и USB Type-C.
- В отличие от USB 3.2, у USB4 не определена собственная модель питания
Поддержка дополнительных возможностей подключения: для интерфейса USB 3.2 полностью отсутствуют какие-либо дополнительные возможности. Отсутствуют режимы туннелирования для интерфейсов DP и PCIe, нет возможности организовать специальное соединение между двумя хостами. Только при использовании разъема USB-C появляется несколько опциональных альтернативных режимов (USB-C Alt Mode), например DisplayPort Alternate Mode, но данный функционал относится именно к использованию разъема USB-C и регламентируется спецификациями конкретного вендора, а не стандартом USB 3.2.
- Для USB 3.2 полностью отсутствует возможность дополнительных сторонних подключений
Отличие архитектуры USB4 от USB 3.2
На рисунке ниже представлена архитектура подключения системы USB 3.2. Как мы видим из рисунка, в системе присутствуют два параллельно работающих интерфейса – Enhanced SuperSpeed и USB 2.0, – за счет чего обеспечивается обратная совместимость интерфейса USB 3.2 с более ранней версией USB 2.0. Так как обе шины работают параллельно, то они могу быть активны одновременно.
Архитектура системы USB4 имеет ряд отличий. Для совместной работы с интерфейсом USB 2.0 все так же присутствует шина, которая функционирует независимо от других интерфейсов. Так как обмен данными по интерфейсу USB 3.2 выполняется по тем же линиям данных, по которым обеспечивается обмен данными и для других поддерживаемых интерфейсов, необходимо использовать туннелированный протокол. Подробная схема системной архитектуры USB4 представлена на рисунке.
Для туннелирования таких интерфейсов, как USB 3.2 и PCIe, необходимо использовать специальные адаптеры протокола (Protocol Adapters). Так, для туннелирования интерфейса USB 3.2 используется специальный хаб (Enhanced SuperSpeed Hub). В свою очередь для PCIe используется специальный коммутатор (PCIe Switch), необходимый для обработки связанных с протоколом маршрутизации пакетов и обеспечивающий буферизацию данных. Для туннелирования DP не требуется никакой промежуточной логики. Соединение устанавливается напрямую, как сквозное.
В каждом маршрутизаторе (Router) системы установлен блок, отвечающий за синхронизацию и распределение времени. На схеме он обозначен как TMU (Time Management Unit).
Маршрутизатор (Router) – основной блок, необходимый для построения архитектуры USB4. Он отвечает за сопоставление трафика туннельных протоколов с пакетами USB4, формирует и направляет пакеты через структуру USB4. За счет внутреннего TMU-блока маршрутизатор синхронизирует время по всей структуре передачи USB4. За настройку и обнаружение маршрутизатора на линии отвечает диспетчер подключения (Connection Manager), расположенный на стороне хост-устройства. Выделяются всего два типа маршрутизаторов: маршрутизатор устройств (Device Router) и хост-маршрутизатор (Host Router).
Для полной поддержки интерфейса USB4 необходимо, чтобы на обеих сторонах располагался USB4-порт. Он состоит из линий приема и передачи данных (RX/TX) и двухпроводного дополнительного канала (Sideband (SB)) (SBTX/SBRX). USB4-порт может работать в двух режимах: одноканальном или двухканальном. В одноканальном режиме линия 1 (Lane 1) будет отключена. В двухканальном режиме линии 0 и 1 связаны и обеспечивают общий канал данных. На рисунке ниже представлены оба режима работы.
Дополнительный SB-канал необходим для инициализации устройства на линии с хостом и для управления между портами.
Важно понимать, что интерфейс USB 2.0 не входит в маршрутизатор USB4 и работает параллельно с ним.
Таким образом, для порта USB Type-C с поддержкой USB4 полный режим подключения включает в себя:
- порт USB4;
- шину данных USB 2.0 (D+/D-);
- канал конфигурации по CC-линии, необходимый для передачи протокола USB PD;
- шины питания (VBUS/VCONN/GND).
Новые уровни в функциональной модели USB4
На рисунке ниже представлена функциональная модель USB 3.2. Присутствуют три основных уровня: физический уровень (Physical Layer), канальный уровень (Link Layer) и наивысший уровень – уровень протокола (Protocol Layer).
В USB4 мы уже видим другую модель, так как были внесены значительные изменения. Самым низким из всех остается физический уровень (Physical Layer), который в свою очередь состоит из двух подуровней: логического (Logical Layer) и электрического (Electrical Layer). Уровнем выше расположен транспортный уровень (Transport Layer). Наивысшими равнорасположенными уровнями являются: уровень конфигурации (Configuration Layer) и уровень протокола адаптера (Protocol Adapter Layer). Уровень протокола адаптера был добавлен в связи с реализацией туннелирования. Он используется для обработки данных туннелированных интерфейсов.
На рисунке ниже представлена схема функциональной модели USB4.
Рассмотрим каждый из них подробнее:
Электрический уровень (Electrical Layer) определяет электрические характеристики для USB4-соединения: уровни напряжения сигнала, фазовое дрожание (jitter), скремблирование и кодирование сигнала.
Логический уровень (Logical Layer) расположен над электрическим уровнем и ниже транспортного. Данный уровень устанавливает соединения между двумя маршрутизаторами и необходим для передачи потоков байт между ними.
Транспортный уровень (Transport Layer) определяет формат передаваемого пакета, маршрутизацию, синхронизирует передачу по времени и управляет передаваемыми потоками. На данном уровне выполняется передача туннелированных пакетов и пакетов управления (Control Packets) через шину.
Уровень конфигурации (Configuration Layer) необходим для обработки входящих пакетов управления и обеспечивает настройку конфигурации маршрутизатора. Данный уровень также определяет схему адресации для пакетов управления в домене и гарантирует наличие надежного транспортного механизма для пакетов управления, которые предоставляют доступ к пространству конфигурации маршрутизатора.
Уровень протокола адаптера (Protocol Adapter Layer) необходим для преобразования пакетов между транспортным уровнем и туннельным протоколом. На данном уровне определяется тип туннельного протокола.
Новый канал связи (Sideband Channel)
Основное отличие интерфейса USB4 от предыдущей версии и в то же время одной из причин невозможности использования нового интерфейса с предыдущими версиями разъема является появление нового дополнительного канала связи, расположенного на дополнительных линиях разъема USB Type-C (SBU1/SBU2).
Данный канал выполняет ряд функций:
- инициализацию линии передачи данных;
- подключение и отключение устройств от USB4-порта;
- включение и отключение линии передачи данных;
- ввод и вывод из состояния сна (Sleep state).
Транзакции дополнительного канала отправляются по линии SBTX и принимаются по линии SBRX. На рисунке ниже показано несколько примеров подключения SBTX и SBRX между двумя маршрутизаторами с использованием активных, пассивных кабелей и встроенных на устройствах ретаймеров.
Существуют три основных типа транзакций для данного канала. Каждая из них отвечает за свой функционал:
- Link Type (LT) – LT-транзакция, предназначенная для инициализации дорожки. Данная транзакция также используется для того, чтобы сигнализировать об изменениях состояния подключенного адаптера, например об отключении линии передачи данных или о переходе в состояние низкого энергопотребления;
- Administrative Type (AT) – AT-транзакция, предназначенная для чтения и записи информации в специальную конфигурационную область;
- Re-timer Type (RT) – RT-транзакция, необходимая для общения маршрутизаторов с ретаймерами.
Каждый порт USB4 реализует набор регистров конфигурации канала. Каждый ретаймер, установленный на линии, также имеет собственную конфигурацию. Маршрутизатор использует AT-транзакции для получения доступа к регистровому пространству другого маршрутизатора или RT-транзакции для доступа к регистровому пространству ретаймера.
Вывод
Обещание, данное Intel в 2017 году, было выполнено, благодаря чему стандарт USB4 вобрал в себя многое от Thunderbolt 3. В итоге можно сказать, что USB4 остается все тем же стандартным интерфейсом, выполняющим роль обмена данными между хост-устройством и широким спектром одновременно доступных периферийных устройств. В то же время в нем появилось множество изменений, которые выглядят крайне положительными и многообещающими на текущий момент: избавление от различных версий интерфейса и объединение в одно общее название (USB4), отказ от различных разъемов в сторону одного единого (USB Type-C), попытка сделать общедоступным объединение различных интерфейсов, таких как DisplayPort, PCI Express, USB3, добавление новых дополнительных возможностей, например соединение host-to-host – все эти факторы, а также открытость стандарта (в отличие от Thunderbolt 4) свидетельствуют о том, что USB4 имеет все шансы стать более «массовым» интерфейсом, чем Thunderbolt 4.
- type-c
- компьютерное железо
- электроника
- производство электроники
- унификация интерфейсов
- usb
- Блог компании НТЦ Вулкан
- Интерфейсы
- Производство и разработка электроники
- Компьютерное железо
100 ватт по USB или как работает Power Delivery
Почитав вот этот пост и сопутствующую ему дискуссию, я решил попробовать внести ясность в то, что такое USB Power Delivery и как это работает на самом деле. К сожалению у меня сложилось впечатление, что большинство участников дискуссии воспринимают 100 ватт по USB слишком буквально, и не до конца понимают что за этим стоит на уровне схематики и протоколов.
Итак, кратко – основные пункты:
- USB PD определяет 5 стандартных профилей по электропитанию – до 5V@2А, до 12V@1.5А, до 12V@3А, до 12-20V@3А и до 12-20V@4.75-5А
- Кабели и порты для Power Delivery сертифицируются и имеют дополнительные пины в разьеме
- Тип кабеля и его соответствие профилю определяются автоматически через дополнительные пины и определение типа USB коннектора (микро, стандарт, A, B и т.д.)
- Обычные USB кабели (не Power Delivery) сертифицируются только по первому профилю до 5V@2A
- При подключении распределяются роли, между тем кто дает ток (Source / Источник ) и кто потребляет (Sink / Приемник)
- Источник и Приемник обмениваются сообщениями по специальному протоколу, который работает параллельно традиционному USB
- В качестве физического носителя протокол использует пару – VBus / GND. Именно поэтому Power Delivery не зависит от основного USB протокола и обратно совместим с USB 2.0 и 3.0
- Используя сообщения, источник и приемник могут в любой момент времени меняться ролями, изменять силу тока и/или напряжение, уходить в спячку или просыпаться, и т.д.
- По желанию устройства могут поддерживать управление PD через традиционные USB запросы, дескрипторы и т.д.
О кобелях Про кабели
USB Power Delivery работает с шестью типами коннекторов:

- USB 3.0 PD Standard-A USB 3.0 PD Standard-B plug
- USB 3.0 PD Standard-A USB 3.0 PD Micro-B plug
- USB 3.0 PD Micro-A USB 3.0 PD Micro-B plug
- USB 3.0 PD Micro-A USB 3.0 PD Standard-B plug
- USB 2.0 PD Standard-A USB 2.0 PD Standard-B plug
- USB 2.0 PD Standard-A USB 2.0 PD Micro-B plug
- USB 2.0 PD Micro-A USB 2.0 PD Micro-B plug
- USB 2.0 PD Micro-A USB 2.0 PD Standard-B plug
Про порты
После сертификации USB PD порты маркируются следующим образом:

Данное лого информирует о версии USB (2.0 или 3.0 SuperSpeed), а также о профилях электропитания которые поддерживает данный порт. Значение ”I” означает потребляемый профиль, необходимый для полноценного функционирования устройства, а значение «О» то какой профиль порт может предоставить. Примеры маркировки портов:
- Первый порт поддерживает USB2. Он может давать питание по Профилю 1 ( 2A@5V) и использует Профиль 3 ( 5V@2A или 12V@3A) для полноценного функционирования. Например порт для планшета или нетбука.
- Второй порт поддерживает USB2. Он может давать питание по Профилю 2 (2A@5V или 12V@1.5A) и использует Профиль 4 ( 5V@2A или 12V@3A или 20V@3A) для полноценного функционирования. Например порт для ноутбука или лаптопа.
- Третий порт поддерживает USB3. Он только дает питание по Профилю 1 (5V@2A). Сам он по VBus не запитывается. Например порт десктопа, монитора, телевизора, и т.д.
- Четвертый порт поддерживает USB3. Как и в первом примере он может давать питание по Профилю 1 (5V@2A) и сам требует питание по Профилю 3 для полноценного функционирования (5V@2A или 12V@3A). Пример придумайте сами 🙂
Физический канал
USB PD определяет принципиальную схему физической организации соединения посредством кабеля следующим образом:

Как видно из схемы, USB PD также требует чтобы и в источнике и в приемнике были реализованы схемы определения падения/скачка напряжения, а так же методы определения разряженной батареи для случаев когда одна из сторон не может запитаться от своего внутреннего источника.
В качестве алгоритмов для определения разряженной батареи предлагаются следующее. Если одна из сторон выставляет сопротивление в 1кОм между экраном и землей, это свидетельствует о том что ее батарея разряжена. В такой ситуации другая сторона берет на себя роль источника и начинает отдавать минимальные 5В, чтобы дать через VBus питание противной стороне и начать обмен сообщениями по протоколу USB PD.
Как уже упоминалось ранее, для обмена сообщениями USB PD протокол использует линию VBus. Ниже приведена блок-схема, определяющая ключевые функциональные элементы передатчика:

И соответственно такая же блок-схема для приемника:

Сериализированная кодировка 4b5b и декодировка 5b4b подразумевает что все данные по шине, кроме преамбулы пакета, передаются пятибитными последовательностями в соответствии c таблицей кодировки, определяемой стандартом. Каждая такая последовательность кодирует либо одну из 16 цифр (0x00..0x0F), либо сигналы начала / синхронизации / сброса и конца пакета. Таким образом передача одного байта занимает 10 бит, 16-битного слова – 20 бит и 32-битного двойного слова – 40 бит и т.д.
Логический канал
USB PD протокол основывается на последовательных парах типа запрос-ответ. Запросы и ответы пересылаются с использованием пакетов. Пакеты состоят из преамбулы (фаза подготовки к передаче), начала пакета SOP (три сигнала Sync-1 и завершающий Sync-2 в кодировке 4b5b), заголовок, 0..N байт полезной нагрузки, контрольной суммы (CRC-32) и сигнала конца пакета (одиночный сигнал EOP):

Как было упомянуто выше, преамбула не кодируется в 4b5b. SOP, CRC и EOP кодируются 4b5b на физическом уровне, заголовок и полезная нагрузка кодируются на уровне логического протокола.
Сброс шины производится путем посылки трех сигналов RST1 и завершающего сигнала RST2, в соответствии с кодировкой 4b5b.
Протокол
Все USB PD сообщения состоят из заголовка и порции данных произвольной длины. Сообщения либо генерируются на уровне логического протокола и затем пересылаются на физический уровень, либо принимаются на физическом уровне и затем пересылаются на уровень логического протокола.


Заголовок сообщения имеет фиксированную длину 16 бит и состоит из следующих полей:
Сообщения бывают двух видов – управляющие (control) и информационные (data).
Управляющие сообщения

Контрольные сообщения состоят только из заголовка и CRC. Количество объектов данных для таких сообщений всегда устанавливается в 0. Типы управляющих сообщений USB PD представлены в таблице ниже:
Отдельно следует упомянуть что поля вида tSourceActivity, tSinkRequest и т.д. — это константы, значения которых глобально заданы самой спецификацией в отдельной главе. Сделано это потому что они определялись опытным путем в результате прототипирования, и найденные оптимальные значения просто подставили в отдельную главу, чтобы не рыскать по всей спецификации.
Информационные сообщения
Данный вид сообщений предназначен для получения детальной информации об источнике или приемнике, а также для передачи запрашиваемых характеристик электропитания – сила тока, напряжение и т.д. Информационные сообщения всегда содержат ненулевое значение в поле ”Number of Data Objects”.
Спецификация определяет четыре вида информационных сообщений:

- Power Data Object (PDO) – используется для описания характеристик порта источника или требований приемника
- Request Data Object (RDO) – используется портом приемника для установки соглашения по характеристикам электропитания
- BIST (Built In Self Test) Data Object (BDO) – используется для тестирования подключения на соответствие требованиям спецификации для физического соединения
- Vendor Data Object (VDO) – используется для передачи нестандартной, дополнительной или иной проприетарной информации определяемой производителем оборудования и выходящей за рамки спецификации USB PD.
Виды информационных сообщений кодируются в поле ”Message Type” заголовка сообщения следующим образом:
Сообщение о характеристиках

Порт источника всегда обязан сообщать свои характеристики приемнику путем передачи серии 32-битных объектов PDO. Информация переданная посредством этих объектов используется для определения возможностей источника, в том числе включая возможность работать в режиме приемника.
Сообщения о характеристиках представляются в виде одного или нескольких объектов следующих за заголовком:
- От источника к приемнику через определенный временной интервал, при непосредственном подключении кабеля. Источник должен продолжать посылать сообщения на протяжении одной минуты после подключения до тех пор пока не будет установлено успешное соглашение по электропитанию, либо приемник не вернет RDO с флагом Capability Mismatch – несоответствие характеристик.
- От источника к приемнику с целью принудительного переустановления соглашения по электропитанию или смены характеристик.
- В ответ на управляющие сообщения Get_Source_Cap или Get_Sink_Cap
PDO соответствующий элементу с постоянным типом электропитания 5V всегда должен идти первым в цепочке объектов.

Структура объекта PDO:
Для каждого типа электропитания предлагаются различные характеристики.

Постоянный тип электропитания, напряжение постоянное. Источник должен иметь хотя бы один такой элемент:

Программируемый тип электропитания, напряжение может регулироваться путем запросов в пределах между минимальным и максимальным:

Вариативный тип электропитания, напряжение может изменяться в заданных пределах абсолютного минимума и абсолютного максимума, но не может регулироваться:

Батарея, данный тип используется для обозначения батарей которые могут быть напрямую подключены к линии VBus:
Сообщение о запросе
Сообщения о запросах передаются приемником к источнику для передачи своих требований в фазе установления соглашения по электропитанию. Данное сообщение посылается в ответ на сообщение о характеристиках и должно содержать один и только один объект запроса данных – RDO, который описывает информацию о требуемых характеристиках электропитания для приемника.
Данный запрос имеет два типа, в зависимости от адресуемого типа элемента электропитания, переданного в сообщении о характеристиках источника. Для запросов к элементу электропитания постоянного или вариативного типа, либо батареи поля ”Operating Current / Power” и ”Total Current / Prog Voltage” интерпретируются одним путем, а для запросов к элементу программируемого типа – другим путем, так как в этом случае запрашивается и напряжение, и сила тока.

Структура объекта RDO:
На мой взгляд данной информации достаточно, чтобы получить хорошее представление о принципах работы USB Power Delivery. Я сознательно не стал углубляться в дебри, связанные с таймерами, счетчиками и обработкой ошибок.
Взаимодействие с традиционным USB
Как уже было упомянуто выше, Power Delivery – это самостоятельная подсистема, которая функционирует параллельно и независимо от канонического USB. Тем не менее, в случаях когда устройства реализуют оба протокола – и USB и Power Delivery, спецификация рекомендует реализацию т.н. System Policy Manager или SPM, компонента который может контролировать оборудование USB PD посредством традиционных запросов USB.

Для систем с поддержкой SPM, спецификация рекомендует предоставить PD информацию посредством специальных типов USB дескрипторов. Не считаю нужным в них детально углубляться, просто перечислю их названия:

- Power Delivery Capability Descriptor, является составной частью BOS дескриптора и сообщает о том поддерживает ли устройство зарядку батареи через USB, поддерживает ли оно стандарт USB PD, может ли оно выступать источником питания, и может ли оно быть приемником. Кроме того данный дескриптор содержит информацию о количестве портов-источников, портов-приемников и версии поддерживаемых спецификаций USB Battery Charging и Power Delivery.
- Battery Info Capability Descriptor, требуется для всех устройств заявивших батарею в качестве одного из элементов электропитания. Содержит информацию о названии, серийном номере и производителе батареи, ее емкости, а также о пороговых значениях тока в заряженном и разряженом состоянии.
- PD Consumer Port Capability Descriptor, требуется для всех устройств которые заявили поддержку хотя бы одно порта-приемника. Содержит информацию о поддержке стандартов Power Delivery и Battery Charging, минимальное и максимальное напряжение, операционную мощность, максимальную пиковую мощность и максимальное время, которое оно может эту пиковую мощность потреблять
- PD Provider Port Capability Descriptor, требуется для всех устройств которые заявили поддержку хотя бы одного порта-источника питания. Содержит информацию о поддержке стандартов Power Delivery и Battery Charging, а так же список всех PDO объектов, характеризующих элементы электропитания доступных устройству.
- PD Power Requirement Descriptor, требуется для всех устройств-приемников поддерживающих USB PD. Каждое устройство должно возвращать хотя бы один такой дескриптор в составе дескриптора конфигурации. Этот дескриптор должен идти сразу после первого дескриптора интерфейса. В случае когда их несколько, он должен идти после каждого первого дескриптора интерфейса функции, если используется IAD, или в случае композитного устройства без IAD, непосредственно после каждого дескриптора интерфейса, и до endpoint дескрипторов.
Для управления USB Power Delivery через запросы USB, в случае если устройство поддерживает Power Delivery класс, спецификация предлагает команды, которые могут использоваться для передачи PD запросов и объектов посредством USB, то есть через шину данных. Сводная таблица дана ниже:
Заключение
Надеюсь что данным постом я подогрел интерес публики к USB Power Delivery. Скромно замечу, что автор имеет непосредственное отношение к данной спецификации, поэтому готов ответить на любые вопросы по Power Delivery в частности и USB в общем.
Разбираем и собираем обратно стек USB
Иллюстрированная проекция модели сетевого взаимодействия OSI на универсальную последовательную шину.
Три «замечательных» уровня стека USB
Меня не устроил вид стека USB, который можно встретить чаще всего на просторах сети:
Не сильно полезный стек USB
Уровень шины, логический, функциональный… Это, конечно, замечательные абстракции, но они скорее для тех, кто собирается делать драйвер или прикладной софт для хоста. На стороне же микроконтроллера я ожидаю шаблонный конечный автомат, в узлы которого мы обычно встраиваем свой полезный код, и он сперва будет по всем законам жанра глючить. Или же глючить будет софт на хосте. Или драйвер. В любом случае кто-то будет глючить. В библиотеках МК тоже с наскока не разобраться. И вот я смотрю на трафик по шине USB анализатором, где происходящие события на незнакомом языке с тремя замечательными уровнями вообще не вяжутся. Интересно, это у меня от гриппозной лихорадки в голове такой диссонанс?
Если у читателя бывали сходные ощущения, предлагаю альтернативное, явившееся мне неожиданно ясно в перегретом мозгу видение стека USB, по мотивам любимой 7-уровневой модели OSI. Я ограничился пятью уровнями:

Я не хочу сказать, что весь софт и библиотеки уже сделаны или должны проектироваться, исходя из этой модели. Из инженерных соображений код c уровнями будет сильно перемешан. Но я хочу помочь тем, кто начинает своё знакомство с шиной USB, кто хочет понять протоколы обмена устройств и терминологию предметной области, подобраться поближе к готовым примерам, библиотекам и лучше ориентироваться в них. Эта модель не для загрузки в МК, но в ваши блестящие умы, дорогие друзья. А ваши золотые руки потом всё сами сделают, я не сомневаюсь:)
Итак, поехали, поправляйте, если увидите косяки. Это draft-версия, и если уже такое где-то было нарисовано, прошу простить, я не нашёл и потому скрутил сам. Думаю, картинка никуда не убежит, а я пока объясню почтенной публике, зачем вообще взялся за эту публикацию.
Очередной флэшбэк из девяностых
Свой первый баг из чужого кода я вытряхнул в конце девяностых, будучи студентом на подработках. Это был pppd под FreeBSD, который мы тогда прикрутили на модемный пул. Мотороловские модемы залипали в отбое, дозвониться никто не мог, линия пропадала зазря, и единственный оставшийся способ через PPP keep-alive почему-то глючил. Вот тогда я и выяснил, что pppd зачем-то ждёт шесть ответных байтов LCP вместо положенных четырёх. Почувствовал я себя тогда эдаким лихим жукотрясом из девяностых:-) При чём тут PPP? Просто он на USB похож: пакетный и двухточечный. Правда, в отличие от USB 2.0, полнодуплексный.
Хотим мы этого или нет, но эволюция микроконтроллеров на месте стоять явно не собирается. Нет, нет, да и мелькнёт в публикациях (http://habrahabr.ru/post/208026/, http://habrahabr.ru/post/233391/) «тяжёлая периферия» — вмонтированные в МК реализации шины USB, с разборами примеров, использованием HID и т.п. Надо воздать должное автору RaJa: из восьми примеров, приведённых в стандартной библиотеке STSW-STM32121 (UM0424) и кое-как документированных, он выбрал самый полезный (Custom HID), портировал его в бесплатную среду Em::Blocks, изложил понятным языком, немного приукрасил, браво! Это сэкономило мне уйму времени.
Как пройти в библиотеку?
Получив на GitHub любезно выложенный автором проект RHIDDemo для Em::Blocks, я начал портировать его в Keil (мой отладчик CoLink на базе FTDI; кто-нибудь, подскажите плагин от Coocox для Em::Blocks). Но никак не мог понять: где, чёрт возьми, автор раздобыл SPL 3.6.1 выпуска 2012г, если на сайте выложен 3.5.0 от 2011г? Я прошёл довольно скучный квест, который к моему удивлению привёл… прямо на готовый проект Custom HID для Keil в составе библиотеки USB FS 4.0.0. Лежит у всех на виду, как мышь под веником. Ну и ладно. Зато я раскурил, наконец, релизы STMicroelectronics, нашёл описание библиотеки USB FS STSW-STM32121 (UM0424) и пресёк попытки разработчика свести меня с ума. Вот скажите, это нормально подкладывать винтажный CMSIS 1.30 образца 2009г в набор SPL 3.5.0 выпуска 2011г, новый SPL 3.6.1 релиза 2012г прятать в USB-FS 4.0.0 релиза 2013г (подложив туда же и CMSIS 3.0.1 от 2012г), при том, что у них же выложена актуальная версия CMSIS 3.30 релиза 2014г? Кстати, в SPL 3.6.x для STM32F10X исправили пару багов с USART, касающихся сигналов о переполнении буфера. Спасибо, хоть release notes оставили…
HID vs SNMP
Итак, взявшись за STM32F103C8T6, я тоже решил слегка задвинуться по теме USB HID, уж больно хорошо абстракция USB HID укладывается в концепцию всяческих датчиков, сенсоров и прочих ШИМ-управляемых драйверов питания. Чем-то напомнило мне SNMP, только в сильно упрощённом виде: дескрипторы HID играют роль SNMP MIB. Когда устройство инициализируется хостом: «Привет, хост! Я кофеварка. У меня есть кнопка [старт], регуляторы [сливки], [сахар], датчики [остаток кофе], [остаток воды], [остаток сахара], [остаток сливок]. Подтягивай драйвера, дави на кнопку, кофейку попьём». Ничего не напоминает? Пример диалога по SNMP: «Ну, привет, управляющая станция с софтом за $100000. А я шасси коммутатора за $200000, и на мне сидят ещё 4 модуля по $100000 за штуку; в каждом ещё по 16 портов с неприличной скоростью, и всех функций тут просто не перечислить… спрашивай отдельно по каждому пункту; ах, да загрузка процессора такая-то, памяти столько-то…». И ещё на дюжину страниц в таком же духе.
Понравилась мне идея HID. Но стоило выйти из Windows за рамки учебных задач мигания светодиодами (вперёд к реальным окружениям UNIX!), как начало сквозить из всех незаделанных щелей, и я почувствовал себя каким-то беспомощным ламером. Отлаживая проект, я инстинктивно схватился за некое подобие tcpdump (так и называется: usbdump(8), или usbmon), но увидел лишь сообщения на незнакомом языке.
Стало очевидно: не хватает фундаментальных знаний о шине USB. Если модель OSI и стек TCP/IP любой тёртый айтишник осознаёт где-то на уровне спинного мозга просто в силу необходимости, то с USB ситуация другая. Оно и понятно: там можно (нужно) подсмотреть трафик через тот же tcpdump да настроить железо с софтом, а тут полный plug and play, и исправить что-то можно, обновив драйвер или прошивку (или переустановив ОС). Но ведь мы тут с вами собрались как раз за тем, чтобы делать хорошие прошивки, не так ли? Почитав некоторые описания USB в сети, я был удивлён, насколько запутанной может быть документация. У меня даже возникло ощущение, что нас специально хотят сбить с пути истинного, напустив туману и избавившись от конкуренции в зародыше. Я не согласен с таким положением вещей!
Ещё одна замечательная схема

На просторах сети встретил ещё такую иллюстрацию (лежало в формате BMP, без шуток):
Сперва выглядит оптимистично. Наконец-то, стек в разобранном виде. Кадры, правда, обозначены неудачно: я бы нарисовал их вертикальными пунктирными линиями, а EOF — это просто пауза, реально данные не передаются. Но начинаем читать контекст и теряем понимаем истинный замысел автора (запутать нас):
Хост-контроллер интерфейса шины USB формирует кадры;
Кадры передаются последовательной передачей бит по методу NRZI.
каждый кадр состоит из наиболее приоритетных посылок, состав которых формирует драйвер хоста;
каждая передача состоит из одной или нескольких транзакций;
каждая транзакция состоит из пакетов;
каждый пакет состоит из идентификатора пакета, данных (если они есть) и контрольной суммы.
Вроде бы и нарисовано всё правильно, но по мере прочтения вопросов становится всё больше. Минимальная передаваемая структура данных по шине — это кадр или пакет? Вообще, это сверху вниз надо смотреть или наоборот? И что кодируется по методу NRZI — кадры, пакеты или просто весь битовый поток по шине? Из транзакций состоит посылка, передача, или, может быть, ценная бандероль какая?
Почему нельзя просто: хост группирует пакеты в транзакции и распределяет их по временным квантам, именуемым кадрами, чтобы давать приоритет критичным по времени данным (видео, аудио) исходя из текущей пропускной способности шины? Да, в USB есть нюансы с планированием передачи пакетов, я их пока не затрагиваю.
Моё видение стека USB
Хорошей документацией считаю упоминавшийся тут на хабре USB in a NutShell (ура, перевод), а также USB Made Simple. По ним я и собрал свою версию стека USB, нарисую её ещё раз.

Физический уровень
На физическом уровне используется набор электрических режимов дифференциальной пары проводников (вместе с землёй) для обозначения состояний, с помощью которых кодируется битовый поток по методу NRZI со вставкой битов (bit stuffing): здесь после шести идущих подряд «1» (ну захотелось передать, скажем, 0xffff) вставляется «0», чтобы приёмник подолгу не залипал в одном состоянии; приёмник узнает вставленный «0» и как данные не засчитает, это довольно распространённый приём в кодировании для лучшей автоподстройки частот. Пара проводов вместе с землёй даёт возможность сформировать, как минимум, четыре статических состояния (они обозначаются J, K, SE0, SE1). В USB 2.0 SE1 не используется, а три оставшихся дополнительно разыгрываются в динамике (с часами и переходами) для передачи ещё нескольких управляющих символов (границы пакетов, сброс, подключение/отключение, энергосбережение/выход). Хорошие иллюстрации есть в USB Made Simple, Part 3 — Data Flow.
Т.е. в итоге передаются данные в виде ноликов и единичек, плюс всякие управляющие символы, чтобы можно было из всей этой электродинамической кухни готовить нормальные пакеты данных.
(дополнено по просьбе читателей)
Пакетный уровень
На пакетном уровне между хостом и устройством передаются безадресные пакеты (пара устройств на полудуплексной линии может обойтись и без адресации). Пакет состоит из маркера SYNC для синхронизации тактов приёмника, последовательности байт и символа EOP. Длина пакета переменная, но оговаривается через верхние уровни стека. Первый байт называется Packet Identifier (PID), имеет простой избыточный формат для помехоустойчивости и пригоден для скармливания автомату следующего уровня (для сборки транзакций из пакетов). Пакеты с начинкой (длиннее одного байта PID) снабжаются контрольной суммой (короткой CRC5 или длинной CRC16, в зависимости от типа пакета). Анализатор протоколов должен, как минимум, показывать нам пакеты.
Уровень транзакций
На следующем уровне из пакетов собираются транзакции. Транзакция — это малый набор пакетов (в Full Speed USB 1, 2 или 3), следующих строго друг за другом, которыми (в полудуплексном режиме) хост обменивается с оконечной точкой (endpoint), и только с одной. Очень важно, что транзакцию открывает только хост, это специфика USB (нам в прошивке МК меньше мороки). На уровне транзакций можно говорить о канале (pipe) между хостом и одной из оконечных точек устройства, но я намеренно избегаю термина «канальный уровень» (Data Link) из модели OSI. Анализатор протоколов должен хотя бы декодировать транзакции.
Уровень передач
Поверх транзакций расположим уровень передач (transfers). Их в USB используется четыре типа: контрольные с оконечной точкой №0 (control transfers), передачи с прерываниями (interrupt transfers), изохронные (isochronous transfers) и крупноблочные передачи (bulk transfers). Последние три являются вариантами потоковых каналов (stream pipe), про которые я ещё скажу несколько слов. Этот уровень тоже должен отобразить хороший анализатор протоколов.
Прикладной уровень
Венчает стек, как обычно, прикладной уровень. Здесь происходят: установка адреса устройству хостом, рассказ устройства о себе на языке дескрипторов, команды хоста на выбор конфигурации (контрольные передачи), обмен данными с HID-устройствами (в примерах пока нашёл передачу с прерываниями, хочу попробовать контрольную), печать на принтере и сканирование, доступ к накопителю USB (крупноблочные), общение через гарнитуры и веб-камеры (изохронные) и многие другие замечательные вещи.
Последний штрих
Сбежав на секунду вниз по уровням, можно добавить, что хост периодически вбрасывает по шине те самые пакеты Start of Frame (SOF), разбивая время на равные интервалы, но так, чтобы не разбить при этом сами транзакции. Поэтому пакеты SOF можно считать самостоятельными транзакциями. Не следует путать кадр (фрейм) USB с омонимом канального уровня модели OSI. Лучше уж вспомнить кадры (фреймы) аудио CD, это просто квант времени: хост «тикает» в шину пакетами SOF, чтобы подключённые устройства заранее планировали участие в т.н. изохронных передачах, гоняющих потоки данных в реальном времени. Ну или вот так: группы транзакций планируются хостом по временным интервалам, именуемым кадрами. Кадр составляет 1мс на Full Speed и 125мкс на High Speed USB, но High Speed — более сложный стандарт, его лучше изучать отдельно.
UPD:
Хороший вопрос задали читатели: а как насчёт фрагментации? Я не нашёл в USB 2.0 признаков фрагментации на уровне транзакций и ниже, т.е. транзакции для того и есть, чтобы передаваться целиком. Передачи же в ряде случаев могут и должны разбиваться на несколько транзакций, особенно с учётом изохронных режимов. И я повторю, что всем планированием у нас пока заведует хост (на стороне МК меньше думать приходится).
Смотрим на трафик по USB
Хорошая подборка иллюстраций есть в упомянутой книжке USB Made Simple, глава 5: www.usbmadesimple.co.uk/ums_5.htm
Вот одна из них
Итак, транзакция всегда инициируется хостом в отношении одной выбранной оконечной точки на устройстве (помимо специальной точки с номером 0, их может быть ещё до 15 штук на одном устройстве, например, комбинированная клавиатура с мышью, термометром, флэшкой, кофеваркой и кнопкой вызова сантехника заказа пиццы).
В случае приёма хостом данных с устройства последнее не может само открыть транзакцию, но может только дождаться нужного момента и поучаствовать в ней. Хост открывает транзакцию устройству пакетом с PID = IN (группа Token) и гарантирует на нужное время свободу шины, устройство вбрасывает пакет из группы Data, в зависимости от типа транзакции хост может подтвердить успех третьим пакетом из группы Handshake (ACK, NAK, STALL, NYET), транзакция закрыта.
При отправке данных на устройство (PID = OUT, группа Token) хост открывает транзакцию, отправляет пакет с данными (Data), также в зависимости от режима может принять пакет Handshake с подтверждением успешности транзакции.
По окончании транзакции всё вернётся на круги своя, устройство снова будет ждать управляющих пакетов от хоста.
Режимы передачи USB в примерах STM32 USB FS
Чтобы по одной паре проводов можно было гнать копирование с диска одновременно с аудио-видео потоком, жестами мышью и сигналом скоростного осциллографа, существуют разные типы сообщений и передач.
Чуть выше я только что описал простой потоковый канал (Stream Pipe) между хостом и оконечной точкой, где пакеты с начинкой (группы Data) не несут никакой специальной или управляющей информации самой подсистеме USB. Полная свобода переписки, библиотека контроллера должны предоставлять примитивы для закачки буфера произвольного размера из памяти МК хосту или обратно. Нарезкой на пакеты, пересылкой и «дефрагментацией» пусть занимаются библиотека МК на пару с драйвером хоста. В STM32 это USB_SIL_Write() и USB_SIL_Read(), описаны в UM0424. Они и есть тот самый логический уровень абстракции. На стороне хоста см. описание соответствующего драйвера (например, во FreeBSD это ugen(4)).
Однако использовать тяжёлую периферию вроде USB для организации простого потокового канала я считаю кощунством (спрашивается: чем USART не угодил?). Но ситуации, конечно, бывают всякие.
В любом случае, чтобы подсистема USB вообще ожила и устройство определилось, требуется обмен контрольными транзакциями.
DISCLAIMER
Дальше будут упоминаться примеры из той самой библиотеки UM0424 для работы с Full Speed USB от STMicroelectronics, но они рассчитаны под их родные демоплаты. Берите пример с автора Raja, проявляйте инженерную смекалку в адаптации проектов под свою демоплату.
По софту всё понятно: это примеры не для промышленного использования, там могут быть баги, некоторые части (типа таблицы ссылок в примере Mass storage) защищены патентом, и вы не имеете прав их использовать в коммерческом проекте. Но это ещё ничего, китайцы ухитряются потом продавать на рынке USB-изделия, у которых даже библиотечные VID и PID не удосужились поменять.
По железу, как я понял, надо начинать с кварца. У меня челябинский PinBoard II с кварцем 12Мгц (все библиотеки заточены под 8МГц), я менял умножитель ФАПЧ с 9 на 6 (ссылка с разъяснениями), иначе МК разгонится до 108МГц вместо 72МГц, а USB на 72МГц вместо положенных 48МГц вообще не поедет. Можно ещё сбавить обороты МК до 48МГц, поменяв делитель шины USB с полутора до единицы. Использовать внутренний генератор МК HSI спецы не любят: частота может слегка уплыть от нагрева, последствия для USB предсказать затрудняюсь. Ну и не забываем о периферии, конечно. Без флэш-памяти SPI/SDIO из примера Mass storage можно сделать разве что аналог /dev/null, но его ведь хрен отформатируешь:-)
Контрольные передачи и каналы сообщений
Думая про USB, вспоминаю добрый старый протокол PPP с его LCP , IPCP , CCP и ещё хзCP . Обмен хоста с оконечной точкой №0 сообщениями особого вида и есть местный эквивалент xзCP.
Через контрольные передачи устройство инициализируется, получает адрес, рассказывает о себе хосту на языке дескрипторов (чтобы тот подыскал и активировал нужный драйвер). Без контрольных операций не «поедут» и простые потоковые передачи, если устройство не ответит по форме, хост поскорее заглушит порт: протокол надо соблюдать.
В принципе, протокол не запрещает повесить на контрольную точку №0 и обмен данными, аналогично режиму с прерываниями. Заодно задумайтесь: как будете обновлять прошивку МК, так сказать, в полевых условиях? Программатор наготове держать? Есть и другое решение.
Пример: Device firmware upgrade
Передачи с прерываниями
Эта разновидность (interrupt transfer) предназначена для обмена небольшими транзакциями, сходными с контрольными. Нет, устройство не может прерывать хоста, оно ждёт опроса, их частота и размеры пакетов оговариваются заранее в дескрипторе устройства. Хорошо подходят для всевозможных пультов, датчиков, сенсоров, мышек, светодиодов и прочих HID-кофеварок. Канал с прерываниями каждой точки однонаправленный.
Примеры: Custom HID, Joystick mouse, Virtual COM port
Передачи изохронные
Χρόνος по-гречески значит «время». Изохронная передача (isochronous transfer) — местный хайтек, позволяющий управлять потоками данных в реальном времени. Отличается гарантированной (но необязательно широкой) полосой пропускания и отсутствием подтверждающих транзакций, почти как UDP с QoS. Битый пакет? Это бог Хронос толкнул МК по ноге. Не надо пытаться отправить пакет заново, иначе бог огорчится. Контрольные суммы, тем не менее, проверяем втихую от Хроноса. Изохронные передачи хороши для аудио-видео и измерительных систем реального времени, а также прочих игрушек двойного назначения. Хотя на некоторые из них м.б. интереснее повесить какой-нибудь AVR, связав его с нашим ARM по USART или SPI. Изохронные операции участвуют во фреймовой сигнализации (вспомним про тиканье пакетом SOF).
Пример: USB voice speaker
Передачи крупноблочные
Нет, мешки с цементом таскать не будем. Я думаю, все узнали режим работы всевозможных накопителей USB. Передачи bulk transfer имеют цель отправить данных как можно больше и быстрее, обязательно с пересылкой битых пакетов, но без гарантий по полосе пропускания, уступая её изохронным передачам при необходимости (как в TCP без QoS). О внутреннем устройстве USB флэшек я как-то уже рассказывал, теперь можно скачать и запустить действующий прототип. Я сам его не пробовал, но таблица команд SCSI в описании примера (как-бы, между прочим) весьма символизирует. Признаков алгоритма управления износом для NAND-памяти я не нашёл:-)
ВНИМАНИЕ: местами действует патентная защита STM.
Пример: Mass storage
Что осталось нераскрытым
Я не имею цель сделать ещё один учебник по USB, их и без меня хватает, и там хорошо описаны: электрическая часть, подробности протоколов, работа с концентраторами, дескрипторный язык и уровень абстракции HID, проблемы с уникальностью VID/PID, USB 3.0 и многие другие замечательные возможности шины USB, как полезные нам, так и не очень. Айтишникам особо рекомендую экскурсию на тёмную сторону с обзором вражеских девайсов (флэшка с замаскированной HID-клавиатурой, которая будет делать страшные вещи).
Ссылки
Адаптация примера Custom HID к бесплатной среде Em::Blocks и бюджетной демо-плате STM32F103C8T6 производства LC-Tech: habrahabr.ru/post/208026
Битва за ИБП: habrahabr.ru/post/233391 ещё одна битва за ИБП: habrahabr.ru/post/233391/#comment_7944489
Экскурсия на тёмную сторону (шпионский девайс из AVR): habrahabr.ru/post/153571
Инструкции по анализу USB в Wireshark для Windows и Linux: wiki.wireshark.org/CaptureSetup/USB
Книжка USB in a NutShell: www.beyondlogic.org/usbnutshell/usb1.shtml
Перевод USB in a NutShell: microsin.ru/content/view/1107/44
Книжка USB Made Simple (действительно упростили): www.usbmadesimple.co.uk
STSW-STM32121, библиотека STMicroelectronics USB full speed device library и все упомянутые примеры (UM0424) www.st.com/web/en/catalog/tools/PF258157
P.S.
Читая публикации на хабре, посвящённые в той или иной степени микроэлектронике, я разглядел две инженерных касты, назовём их условно: Промэлектронщики и Айтишники. Это своего рода инженерный Инь и Ян, в каждом из нас есть доля того и другого.
Промэлектронщики имеют блестящие знания и навыки по железу, паяют радиодетали толщиной с волос левой рукой с закрытыми глазами (причём потом это работает). Взглянув на электронную схему, почти физически начинают ощущать все её токи с потенциалами, работают также и с силовыми схемами, и с (большими, быстрыми, опасными) промышленными изделиями. Подход к программированию МК соответствующий: он просто должен выдать нужные логические уровни на нужные ножки в нужное время, не столь важно каким способом. Консервативны в технологиях (не влезай — работает), тяжёлую периферию МК не особо жалуют. При обсуждении объектно-ориентированного программирования, информационной безопасности, гигантских проектов в миллион строк кода и всяких навороченных графических интерфейсов скучнеют. Вместо пакетно-ориентированной шины USB предпочитают потоковый режим USART, усиленный либо привычным RS-232, либо более брутальным RS-485 (последовательная шина для промышленных применений, до 10Мбит/с на 15м, до 100кБит/с на 1200м, до 32 устройств).
Айтишники воспитаны на понимании операционных систем, сетевой инфраструктуры и сложных взаимодействий, элита хорошо подкована в информационной безопасности и разбирается во всяких незримых способах проникновения в чужую систему. Некоторые при этом очень любят котиков (ну как их можно не любить? я, правда, не держу, не развожу и не готовлю:-). Многие любят свободу информации, ругать корпорации/правительства и побеждать силы природы усилием мысли. Паталогически ленивы, но обожают новые технологии и закрученные инженерные ребусы с дорогими игрушками (желательно решаемые на уровне софта или, в крайнем случае, перемычек). Отношения с паяльником настороженные: не спрашивайте у айтишника, любит ли он паяльник, может неправильно понять; лучше спросите, любит ли он паять электронные схемы.
К чему я? Мы просто видим этот мир по-разному… Ведь ядро Linux кроили такие же ребята, из модулей на С и ассемблерных вставок для конкретных платформ, и без холиваров вроде обошлись. По-настоящему серьёзный проект я вижу как многоядерную систему, сочетающую современнейшие МК с тяжёлой периферией, но не исключаю связки с классическими моделями типа AVR: ими можно обвесить какие-нибудь критичные быстровращающиеся острия технического прогресса. Если код проверенный годами, то почему нет?
Интерфейс USB. Реализация, часть 2.

Для того чтобы продолжить реализацию устройства, нам придется немного вернуться на физический уровень и уровень передачи данных. Задача периферии МК — абстрагировать нас от этих уровней интерфейса, но кое-какие моменты нам придется обрабатывать самостоятельно.
Возвращение на физический уровень
Итак, рассмотрим более детально физический уровень передачи данных. Если в двух словах, то для передачи данных используется кодирование NRZI. Нас интересуют некоторые особые состояния шины, а именно, «Сброс» (D+ и D- low >= 2.5 мкс) и «Состояние неактивности» (Idle State, кодируются по разному, в зависимости от типа устройства).
Детально, о состояниях шины можно почитать здесь.
При обнаружении на линии состояния «Сброс» устройство должно перейти в исходное состояние (Default state). На практике, нам нужно присвоить устройству «нулевой» адрес и подготовить «нулевую контрольную точку» к приему и обработке стандартных USB запросов от хоста. Хост всегда выставляет «Сброс» на шине сразу после того, как определит подключение устройства. До «сброса» устройство не должно передавать какие-либо данные в т. ч. в ответ на запрос по адресу 0 (благодаря этому, как я понимаю, решается проблема коллизии, возникающей в случае одновременного подключения к хосту нескольких новых устройств).
Теперь разберемся с «состоянием неактивности». На физическом уровне хост раз в 1 мс посылает устройству определенный сигнал («начало пакета» либо «конец пакета» в зависимости от типа устройства) для сигнализации о активности шины. Если хост хочет «усыпить» определенное устройство, он перестает слать указанный сигнал и переводит шину в особое состояние «состояние неактивности». Если на шине нет активности в течение 3 мс, устройство должно «уснуть», снизив свое энергопотребление. «Просыпается» устройство при обнаружении активности на шине (любое изменение состояния шины отличное от «состояния неактивности»). Также, стандартом предусмотрен режим «удаленного пробуждения» (remote wakeup) – устройство само может уведомить хост о необходимости пробуждения.
Наш МК автоматически распознает перечисленные состояния шины, при обнаружении одного из них, выставляется соответствующий бит в регистре статуса и инициируется аппаратное прерывание.
Возвращение на уровень передачи данных
Теперь рассмотрим, как происходит обмен пакетами на низком уровне. Для наглядности, я позаимствую иллюстрации с этого сайта. Рассмотрен будет несколько обобщенный пример обмена данными, который не учитывает некоторых «тонкостей».
Как мы помним, инициаторам обмена всегда является хост. Обмен осуществляется транзакциями (сеансами). Для того, чтобы начать обмен данными с определенным устройством (начать транзакцию), хост посылает специальный TOKEN пакет.
Данный пакет содержит следующие поля:
— Тип пакета (PID), 8 бит
— Адрес получателя (ADDR), 7 бит
— Номер конечной точки получателя (ENDP), 4 бита
— Контрольная сумма TOKEN пакета (CRC5), 5 бит
Тип пакета определяет в каком направлении будет происходить обмен данными – от хоста к устройству или от устройства к хосту.
Передача данных от хоста к устройству.
Хост отравляет TOKEN пакет типа OUT, следовательно, в дальнейшем ожидается передача данных от хоста к устройству.
После этого хост посылает пакеты данных. Размер каждого пакета зависит от максимального размера пакета для конкретной конечной точки (эта информация есть в дескрипторе устройства, об этом позже). Для контрольных точек типа Control, Bulk и Interrupt каждый пакет данных содержит контрольную суму. Также, для перечисленных типов контрольных точек, устройство должно подтверждать получение каждого пакета.
Для подтверждения получения пакета (управления обменом) используются следующие «служебные» (Handshake) пакеты:
— ACK – пакет получен успешно.
— NAK – устройство не может принять пакет (например, буфер-приемник переполнен). В этом случае хост должен отправить пакет повторно.
— STALL – пакет получен, но не может быть обработан. Свидетельствует о логической ошибке, например конечная точка типа Bulk получает пакет, предназначенный для управления устройством (для конечной точки типа Control).
Для контрольных точек типа Isochronous пакеты данных идут «сплошным потоком» без подтверждения приема.
Передача данных от устройства к хосту.
Хост отравляет TOKEN пакет типа IN, следовательно, в дальнейшем ожидается передача данных от устройства к хосту.
Далее устройство предает пакеты данных, а хост подтверждает их получение (по аналогии с предыдущем случае, только хост и устройство меняются «ролями»). Если в устройстве нет данных для передачи хосту – устройство в ответ на TOKEN IN пакет посылает NAK пакет, и хост переходит к обмену данными со следующим устройством.
Передача «запросов управления».
Как мы уже знаем, «нулевая контрольная точка» должна обрабатывать специальные запросы, предназначенные для управления (инициализации) устройством. Но, данная контрольная точка также может использоваться для передачи «пользовательских» данных. Чтобы отличать «запросы управления» от пользовательских данных, для передачи «запросов управления» используется специальный TOKEN пакет — TOKEN SETUP.
Сами запросы мы будем рассматривать в следующей статье. Пока нам нужно знать, что после SETUP транзакции хост может начать OUT транзакцию, для того, чтобы передать дополнительные параметры запроса, и/или IN транзакцию, для того чтобы прочитать ответ устройства на «запрос управления».
Еще раз подчеркну, что данное описание упрощено и не учитывает некоторых «тонкостей», с подробностями можно ознакомиться в разделе спецификации USB 2.0
На практике, всю работу с транзакциями возьмет на себя периферия МК.
Как уже указывалось в предыдущей статье, для передачи данных нам достаточно заполнить буфер передатчика и выставить флаг готовности данных к отправке. Когда МК получит очередной TOKEN IN пакет, он самостоятельно сформирует пакет данных, посчитает, при необходимости, контрольную сумму и передаст пакет хосту. Получив ACK пакет от хоста, периферия МК выставит бит в статусном регистре «передача завершена» и инициирует аппаратное прерывание. Нам, по большому счету, нужно только «подливать» данные в буфер отправки.
Аналогично с приемной частью – периферия МК, получив TOKEN OUT пакет, самостоятельно скопирует последующий пакет данных в буфер приемника (в один из банков) и, при необходимости, проверит контрольную суму. После этого периферия инициализирует аппаратное прерывание для того, чтобы наша программа могла забрать данные из буфера. Вычитав данные из буфера, мы устанавливаем флаг «прием завершен» в регистре управления и периферия шлет хосту ACK –пакет.
В данном случае, обмен данными на уровне программы чем-то напоминает обмен данными через USART 🙂
Возвращение к реализации.
Дальше много кода. Если Вас не интересует реализация стека под данный МК – можете смело «перематывать» в конец статьи :).
Мы готовы писать обработчик прерывания (на этом мы остановились в предыдущей статье).
«Усыплять» МК мы не будем — просто сделаем «заглушку» для событий связанных с управлением энергопотреблением.
//--- Обработка аппаратного прерывания static void USB_IrqHandler(void) < DWORD isr = AT91C_BASE_UDP->UDP_ISR; //обнаружен "Сброс" на шине if (isr & AT91C_UDP_ENDBUSRES) < //Сбрасываем все КТ EndpointReset(AT91C_EP_CFG, EP_TYPE_CFG, EP_CFG_BUFFER_SIZE); EndpointReset(AT91C_EP_OUT, EP_TYPE_BULK, EP_BUFFER_SIZE); EndpointReset(AT91C_EP_IN, EP_TYPE_BULK, EP_BUFFER_SIZE); //Устанавливаем "нулевой" адрес AT91F_UDP_SetAddress(AT91C_BASE_UDP, 0); //Подготавливаем "нулевую конечную точку" для обработки запросов *EndpointCfg->CSR = (AT91C_UDP_EPEDS | AT91C_UDP_EPTYPE_CTRL); //Сбрасываем номер текущей конфигурации CurrentConfiguration = 0; //Разрешаем прерывания для "нулевой конечной точки" EndpointEnableInterrupt(EndpointCfg); //Сбрасываем флаг прерывания AT91C_BASE_UDP->UDP_ICR = AT91C_UDP_ENDBUSRES; > //Прерывание на конечной точке 0 - вызываем обработчик и сбрасываем флаг if (isr & AT91C_UDP_EPINT0) < EndpointHandler(&Endpoint[0]); AT91C_BASE_UDP->UDP_ICR = AT91C_UDP_EPINT0; > //Прерывание на конечной точке 1 - вызываем обработчик и сбрасываем флаг if (isr & AT91C_UDP_EPINT1) < EndpointHandler(&Endpoint[1]); AT91C_BASE_UDP->UDP_ICR = AT91C_UDP_EPINT1; > //Прерывание на конечной точке 2 - вызываем обработчик и сбрасываем флаг if (isr & AT91C_UDP_EPINT2) < EndpointHandler(&Endpoint[2]); AT91C_BASE_UDP->UDP_ICR = AT91C_UDP_EPINT2; > //Прерывание на конечной точке 3 - вызываем обработчик и сбрасываем флаг if (isr & AT91C_UDP_EPINT3) < EndpointHandler(&Endpoint[3]); AT91C_BASE_UDP->UDP_ICR = AT91C_UDP_EPINT3; > //Заглушка для событий Sleep, Wakeup и т. д. - просто сбрасываем флаг прерывания if(isr & (AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM | AT91C_UDP_EXTRSM | AT91C_UDP_SOFINT | AT91C_UDP_WAKEUP)) < AT91C_BASE_UDP->UDP_ICR = AT91C_UDP_RXSUSP | AT91C_UDP_RXRSM | AT91C_UDP_EXTRSM | AT91C_UDP_SOFINT | AT91C_UDP_WAKEUP; > >
Теперь реализация EndpointHandler() — обработчика событий для конечной точки:
static void EndpointHandler(UsbEndpoint * endpoint) < //Передача предыдущего блока данных завершена if(*endpoint->CSR & AT91C_UDP_TXCOMP) < //Если мы назодимся в состоянии отправки данных if(endpoint->Status == EP_STATUS_WTITE) < //Если отправлены все данные if(endpoint->Size == endpoint->ByteReady) < if(endpoint->Size && endpoint->Size % endpoint->MaxSize == 0) EndpointSetFlag(endpoint, AT91C_UDP_TXPKTRDY); //Вызываем callback для асинхронного режима EndpointEndOfTransfer(endpoint, TRANSFER_STATUS_SUCCESS); > else < //Отправляем следующий пакет данных DWORD cpt = MIN(endpoint->Size - endpoint->ByteReady, endpoint->MaxSize); while (cpt--) *endpoint->FDR = endpoint->Buffer[endpoint->ByteReady++]; EndpointSetFlag(endpoint, AT91C_UDP_TXPKTRDY); > > else < //Передача данных завершена, можно запретить прерывания (для "нулевой" КТ прерывания запрещать нельзя) if(endpoint->Type != EP_TYPE_CFG) EndpointDisableInterrupt(endpoint); > //Сбрасываем флаг "передача завершена" EndpointClearFlag(endpoint, AT91C_UDP_TXCOMP); > //Получены данные от хоста if(*endpoint->CSR & (AT91C_UDP_RX_DATA_BK0 | AT91C_UDP_RX_DATA_BK1)) < //Если мы находимся в состоянии чтения данных if(endpoint->Status == EP_STATUS_READ) < //Копируем данные из аппаратного буфера DWORD cpt = MIN((*endpoint->CSR) >> 16, endpoint->Size); for(DWORD i = 0; i < cpt; i++) endpoint->Buffer[endpoint->ByteReady++] = *endpoint->FDR; //Сбрасываем флаг "данные получены" EndpointClearRxFlag(endpoint); //Если это последний блок данных - запрещаем прерывания и вызываем callback для асинхронного режима if(cpt < endpoint->MaxSize || endpoint->Size == endpoint->ByteReady) < //для "нулевой" КТ прерывания запрещать нельзя if(endpoint->Type != EP_TYPE_CFG) EndpointDisableInterrupt(endpoint); EndpointEndOfTransfer(endpoint, TRANSFER_STATUS_SUCCESS); > > else < //Если мы не готовы получать данные - запрещаем прерывания EndpointClearRxFlag(endpoint); //для "нулевой" КТ прерывания запрещать нельзя if(endpoint->Type != EP_TYPE_CFG) EndpointDisableInterrupt(endpoint); > > //Получили запрос управления - вычитываем запрос и вызываем обработчик if(*endpoint->CSR & AT91C_UDP_RXSETUP) < UsbRequest Request; for(BYTE i = 0; i < 8; i++) ((BYTE *) &Request)[i] = *endpoint->FDR; if(Request.bmRequestType & 0x80) EndpointSetFlag(endpoint, AT91C_UDP_DIR); EndpointClearFlag(endpoint, AT91C_UDP_RXSETUP); USB_Enumerate(&Request); > //Получили STALL пакет - просто сбрасываем соответствующие флаги if(*endpoint->CSR & AT91C_UDP_STALLSENT) < EndpointClearFlag(endpoint, AT91C_UDP_STALLSENT); EndpointClearFlag(endpoint, AT91C_UDP_FORCESTALL); >>
Мы еще немного вернемся к этому коду в следующей статье.
Ну вот, нам осталось только реализовать обработку стандартных запросов USB и создать дескрипторы устройства. Этим мы займемся в следующей статье.
У меня вопрос – нужно ли «побитно» разбирать дескрипторы устройства?
Просто есть много источников, в которых структура дескрипторов и значение каждого байта/бита подробно описана. Плюс, дескриптор нашего устройства будет мало чем отличатся от дескриптора любого другого устройства CDC класса.