Обновить

Разработка цифровой аппаратуры нетрадиционным методом: Контроллер USB 1.0 на SpinalHDL

Уровень сложностиСредний
Время на прочтение127 мин
Охват и читатели5.1K
Всего голосов 23: ↑23 и ↓0+30
Комментарии50

Комментарии 50

Автор молодец, Хабр - торт. Большое спасибо! Очень круто!

Такой фундаментальный труд полезно держать под рукой. Отдельно спасибо за pdf

Сохранил в закладки , надо бы выделить месяц на прочтение статьи))) Чувствую что-то шедевральное вы там натворили

Вроде-бы HID'ы имеют обратную совместимость с PS/2.

Честно говоря я не представляю как это возможно. PS/2 это последовательный синхронный интерфейс с побайтовой передачей работающий на частотах 7-12 кГц, у него два сигнала: DATA и CLK, оба имеют TTL уровни +5V. USB 1.0 - это асинхронная шина с пакетной коммутацией, сигналы D+ и D- имеют уровни +3.6V max, частоты: 1.5 и 12 МГц. Как это совместить ?

Тоже программно. Контроллер клавиатуры/мыши определяет, куда его подключили и переопределяет свои выводы в PS/2.
Переходник - 4 провода.

Это не HIDы, а конкретные реализации типа мышек Logitech древних времен.

На Wikipedia в статье про PS/2 написано, что некоторые клавиатуры и мыши, предназначенные для работы в процессе загрузки ОС, могут определить куда они подключены и задействовать две имеющиеся сигнальные линии либо для встроенного USB Device устройства, либо для PS/2. Для этого промышленностью выпускались специальные переходники позволяющие подключить одно и то же устрйство с USB type A разъемом либо в USB type A (HID), либо в круглый PS/2. Но это не совместимость на уровне стандартов, это такой костыль от производителей устройств ввода нацеленный на облегчение перехода пользователей на USB HID.

Есть мышки с клавами, которые умеют и в usb, и в ps/2 аппаратно включаиься. Не пипец как это сложно.

А программно: Если в HID-дескрипторе описать классическое поведение устройств рс/2, то да, можно о какой-то совместимости говорить. Типа ту же последовательность байт при событии слать.

Но - совместимы в каком месте? Для пользователя и программиста под любой ОС они по определению совместимы, хоть они рс/2, хоть usb, хоть rs232, хоть TCP/IP с удалëнной машины .

У USB и PS/2 абслютно разные электрические интерфейсы, это не просто посылка байтов. Никакой совместимости между этими двумя стандартами нет и быть не может. Может быть только общий уровень абстракций, как например в Input Device в Linux который формирует общий поток событий для ПО прикладного уровня.

USB можно даже на AVR микроконтроллерах реализовать программно.
Проект называется V-USB
https://ru.wikipedia.org/wiki/V-USB

Спасибо. Про V-USB мне подсказали на конференции FPGA-Systems уже после моего доклада, не знал. Хочу посмотреть как там вычисляется CRC16 и при этом библиотека успевает вложиться в жесткие тайминги. У меня это сделать не получилось на RV32 ядре 60+ МГц, поэтому пришлось отдать расчет CRC на железную часть.

Я добавил в конце статьи ссылку на презентацию (PDF, 56 слайдов) к моему докладу на эту же тему с которым я выступил на конференции FPGA-Systems 2025 прошедшей 29 ноября 2025 в Москве. Это для тех, кто хочет быстро ознакомиться с содержимым статьи не читая её. :-)

Спасибо за хаброторт! До конца дочитаю, наверное, только в следующем году) Но всякие справочные штуки типа форматов сообщений - и я буду искать здесь, и поисковики, надеюсь, тоже (когда раздуплят свои ИИ. ЫЫ) Давно хотел видеть открытое и понятное описание открытого протокола. Осцилл вот у меня умеет в анализ uart/spi/i2c, но вот usb - как-то не срослось, собственными глазами приходится отлаживать.

Пользуясь случаем вынесу тему на всеобщиее срач обсуждение. Как вы считаете, почему в 2000х годах USB победил конкурирующую технологию FireWire? Хотя в FireWire тогда уже было всё, что сейчас USB 3 только внедряет - две скоростные дифф.пары точка-точка, PowerDelivery больше 3 Вт. С какого?

На мой взгляд FireWire гораздо более сложен в реализации. Если перенестись в конец 90-х, то можно понять что не всякий производитель мог себе позволить проектировать такие сложные интерфейсы. USB 1.0 (и даже 2.0) относительно простой протокол. Ну и я думаю Apple приложила не мало усилий, чтобы усложнить сторонним разработчикам адаптацию этого стандарта.

С программной точки зрения - оба хороши. Они оба предполагают более-менее умный девайс на том конце линии, который по крайней мере способен назвать своё имя текстом. А аппаратно - ну тут вопросики:

Схема USB-изолятора ADUM4160. Оранжевым отмечена половина схемы, достаточная для LVDS
Схема USB-изолятора ADUM4160. Оранжевым отмечена половина схемы, достаточная для LVDS

В то время, как FireWire основан на обычном LVDS, стандартизированным IEEE, ISO и даже ГОСТ успел скопировать это всё. Что на 100 Мбит/c, что на 3 гигабит. И Две дифф.пары USB3.0 на этом фоне выглядят, как костыль.

Возможно, не взлетело из-за отсутствия низких скоростей. Сразу, понимаш, давай мышку на 100 Мбит/c, понимашь. Наверное. Но стандарт мог бы и в сторону низких скоростей развиться, было бы желание.

Тот же маркер окончания пакета USB тоже выглядит каким-то костылём, я б сказал. Одновременно он пипец как важен, но декодировать его приходится не в дифферениальном виде. И вся стойкость дифф.сигнала к наводкам в самом важном месте теряется.

Ну, USB 1.0 вообще не является дифф линией, а в USB 2.0 есть её жалкое подобие. Зато аппаратная часть очень просто реализуется и не требуется дорогостоящих трансиверов. При желании можно программно модулировать сигнал на шине. Иными словами, USB взял рынок дешевизной.

На мой взгляд маркер EOP это очень годное решение - избавляет от необходимости вводить поле с размером пакета или полезной нагрузки, занимает всего три битовых интервала.

Ну вообще да. Дифф. пара и согласование волнового сопротивления кабеля начинается только с 2.0. Но, wtf, тогда для 1.0 и 1.1 вполне должно и одного сигнального провода хватать. I1C называлось бы))), т.к. 1wire уже к этому времени был запатентован.

Сейчас как раз упёрся в совместимость USB через гальвано-изолятор. Не знал бы про протоколы в железе - подключение или нет так и осталось бы для меня магией таро.

С двумя проводами надежней - можно закодировать тактовый сигнал, и еще ряд управляющих "символов". :)

Мощно. Объем впечатляющий. Апплодирую стоя!

Надеюсь когда-нибудь дойду до глубокого погружения в прочтение :)

Распечатать и положить на полку. Авось, сгодиться когда нибудь. :)

Норм талмуд. Почти нет фактических ошибок, так по мелочи. В своё время тоже занимался такой ерундой)

Могу разве что добавить, что автор хоть и упомнял смену протокола для HID устройств, но почему-то в коде этого не сделал. Желательно принудительно отправить SetProtocol, чтоб устройство точно работало в Boot Protocol режиме.

  req = (USB_Request_Header) {
    .bmRequestType = (REQ_DIR_HOSTTODEVICE | REQ_TYPE_CLASS | REQ_REC_INTERFACE),
    .bRequest      = REQ_SetProtocol,
    .wValue        = 0,
    .wIndex        = 0,
    .wLength       = 0,
  };    

Ну и на практике у меня некоторые клавиатуры не заводились (не отправляли данных "Interrupt IN") пока я не дергал GetDescriptor(HID_Report)

  uint8_t data[0x100];
  req = (USB_Request_Header) {
      .bmRequestType = (REQ_DIR_DEVICETOHOST | REQ_TYPE_STANDARD | REQ_REC_INTERFACE),
      .bRequest      = REQ_GetDescriptor,
      .wValue        = (HID_Report << 8),
      .wIndex        = 0,
      .wLength       = sizeof(data),
  };

  // requesting descriptors for HID reports to let device know that we are ready to interact with it   



Спасбо за Ваше ревью. Похоже, что Вы единственный кто смог прочесть статью целиком. :-)

На счет установки Boot Protocol принудительно. Мне где-то попадалась инфа (помоему на OSDev), что с некоторых пор все клавы и мыши, по просьбам разработчиков BIOS, по умолчанию работают в Boot протоколе. Во всяком лучае, мне пока что другие не попадались. :-) Но я подправлю код конечно же.

Есть идея расширить до USB 1.1 и сделать поддержку Mass Storage Class, чтобы загружать на ПЛИСе какую нибудь ОС.

Тогда в статье есть неоднозначность:
"Согласно спецификации, все USB HID устройства по-умолчанию активируют «Report Protocol». "

Я не помню была ли у меня проблема с какими-то устройствами без принудительной установки протокола, но думаю что код у меня не возник из ниоткуда, и всё же что-то там глючило.

 И «Keepalive» и «SOF» используются в том числе для синхронизации внутренних часов на устройстве.

Можно про это по подробнее?
Тут имеется в виду RTC?
Какой формат пакета со временем? Год месяц день тоже передается?

Это кривой перевод. "Часы" это "clock" в оригинале, что означает обычный тактовый генератор, потому что "часы" в нашем смысле у них это RTC или ЧРВ если по-русски.

К RTC это не имеет никакого отношения. "Внутренние часы" это тактовый генератор с обратной связью.

Спасибо, понятно.

Какой порядок байт в параметрах пакетах USB? Litttle endian или big endian?

Следва на право: от младшего бита к старшему, от младшего байта к старшему.

 от младшего байта к старшему.

Значит Litttle endian 

Не совсем. В Little Endian биты в байтах идут от старшего к младшему, а тут - наоборот.

USB является использования «бит-стаффинга» — методики позволяющей избежать долгое нахождение шины в каком либо из состояний («K» или «J») при передачи пакета данных. ..................стаффинг в USB работает следующим образом: при передаче шести и более последовательных «единиц» (а «единицы» кодируются отсутствие смены состояния шины), в передачу вставляется одна дополнительная смена состояний. Если принимающая сторона по какой-то причине получает семь последовательных «единиц», то произошел сбой передачи!


Забавно вот что.
В CAN бит-стаффинг делают после пяти бит.
В USB Low Speed (USB 1.0 ) бит-стаффинг делают после шести бит.
5 != 6
При этом, и там и там скорость около 1 MBit/s.

Почему в CAN и USB разные настройки бит-стаффинга?

CAN бит-стаффинг делают после пяти бит.
CAN бит-стаффинг делают после пяти бит.

Почему в CAN и USB разные настройки бит-стаффинга?

Быть может потому, что USB это строгие допуски и использование кварцевых стабилизаторов, в то время как CAN - нет. USB это постоянное слежение ФАПЧ и подстройка приёмника под сигнал, а у CAN нет, там время бита задаётся жёстко конфигурацией приёмника. Он как UART, а стаффинг там больше для того, чтобы другой приёмник IDLE не усмотрел посреди передачи.

На STM32 (как и на любом ARM Cortex-M MCU) CAN-трансивер тоже тактируется от PLL, а PLL тактируется тоже кварцевым резонатором.

При этом битовую скорость CAN, при желании, можно менять на лету. Посмотрите на PCAN-Pro-X

У любого CAN нет следящего ФАПЧ в приёмнике. Только отлавливание первого перепада стартового бита как точка отсчёта. Неточно настроил - короткие пакеты проходят а длинные уже нет. В USB ФАПЧ приёмника работает всё время пока есть сигнал. И для начальной стабилизации присутствует преамбула.

PS Если вы хотели обратить моё внимание на расширение FD, то там законы ровно те же.

Следящий PLL нужен в GNSS приемниках. Так как там из-за эффекта Доплера несущая частота уползает.

А USB почему частота тактирования не стабильная?

Потому что устройства имеют свои, независимые от хоста, тактовые генераторы. И учитывая длинные посылки приёмник должен следить за частотой и фазой. Изохронные пакеты должны пролезать без ошибок, а не только короткие стандартные.

Я тоже задавался таким вопросом, но ответа не нашел. В Ethernet еще в 80-х уже был bit-stuffing в 5 единичных бит. Почему в Microsoft решили сделать это иначе - не известно. Наверное по принципу "чтоб не как у других".

Пообщался с нейросетью. Вот что в сухом остатке.
Более редкий бит-стаффинг, как в USB увеличивает пропускную способность.
Более частый бит-стаффинг, как в CAN увеличивает надежность.

Широко известно, что логически анализаторы типа DS Logic умеют производить синтаксический разбор USB сигнала и показывать его в виде пакета внутри клиентского ПО.

Существуют ли покупные кабели с разъемом для прослушивания USB трафика логическим анализатором?
А то вот так глядя на устройство даже не очевидно куда можно подцепиться чтобы сигнал померять.

Сначала думал иголки в провод втыкать, но передумал, опасаюсь КЗ задев провод питания и GND . На PCB тоже дорожки USB под маской.

Очевидно одно. Нужно какая-то примочка, чтобы соединить USB шину и логический анализатор.

Что мне купить чтобы анализировать трафик USB на DS Logic?

Изготовить пробник самому? Ну такой, с одной стороны USB A, с другой USB B/mini USB/micro USB/TypeC а между ними PLS для пробника. Включаешь через него кабелями устройство и слушаешь DS Logic'ком.

Это опасно так как будут нарушены характеристики длинной линии. Сигнал может пере отражаться назад. У меня устройство USB High Speed.

High Speed подразумевает частоты 480МГц. DSL не умеет в такие частоты, он только для Low/Full Speed, т.е. до 12МГц. Я вам советую нормальный логгер, если так необходимо смотреть сырые данные. Есть как полностью китайская поделка приличного уровня USB Packet Viewer, так и Open Source Portable USB Sniffer. Оба можно купить на известном сайте.

На коротком проводе всё будет хорошо. В крайнем случае можно сделать повторитель на операционниках. Но для захвата сигнала с частотой 480 МГц (USB 2.0 High Speed) требуется анализатор способный прокачать минимум 1 GS/sec (а лучше 2GB/s). Есть ли в природе дешевый анализатор способный на такое и через какой интрефейс снимать с него данные для анализа ? На такое способны всякие Tektronix TBS2000 и прочие, но цена вопроса...

Но для захвата сигнала с частотой 480 МГц (USB 2.0 High Speed) требуется анализатор способный прокачать минимум 1 GS/sec (а лучше 2GB/s).

DSLogic U3Pro32 может в 1ГГц на 3 входа. Но я не уверен, что он корректно захватит 480МГц сигнал с плавающей фазой.

1ГГц или 1GS/s ? Китайцы плохо понимают разницу. Если DSLogic U3Pro действительно способен захватывать сигнал частотой 1ГГц, то это минимум 2GS/s или ~4 сэмпла на битовый интервал. Можно попробовать.

Это частота сэмплирования. Так что по сути 1GS/s. Для 3х входов внутренняя FPGA пакует данные на лету и потом они улетают в настоящий USB3.1

Размер непосредственно поля для полезных данных в спецификации USB 1.0 и 1.1 определен следующим образом: не более 8 байт для «Low Speed» и не более 64 байт для «Full Speed».

Это не совсем так. Размер пакета специфицируется для каждого типа конечной точки. Для LS разрешены только Control (строго 8 байт) и Interrupt (0-8 байт). Вот для FS интереснее. Там разрешены все типы точек: Control и Bulk: 8, 16, 32 или 64 байта, Interrupt: 0-64, Isochronous: 0-1023 (не 1024!). Ну и до кучи HS, поскольку на эти грабли я уже наступил: Control (включая EP0): 64 байта (и только 64), Isochronous, Interrupt: 0-1024 (уже до 1024, на целый байт больше, чем в FS), Bulk: 512 (и только 512).

В частности, EP0 для LS строго 8 байт, для FS из набора 8, 16, 32, 64 байта, для HS строго 64 байта.

Передающая сторона обязана дождаться подтверждения ACK на каждый отправленный пакет

Не считая изохронных точек.

Инициатором любой транзакции в USB 1.0 и 1.1 всегда является хост.

В USB 1.0 и 1.1 выделяют четыре вида транзакций:

А в USB3 не так?

Если у данной конечной точки возникла внутрення ошибка препятствующая её нормальному функционированию, то на входной токен IN устройство отвечает пакетом STALL

Как это можно проверить? Я wireshark-ом пытался увидеть разницу между NAK и STALL, и не преуспел.

Поля bDeviceClass, bDeviceSubClass and bDeviceProtocol используются для поиска и назначения драйвера для обслуживания данного устройства, но не всегда. Часто устройства предпочитают идентифицировать себя через интерфейс, то есть поля bDeviceClass, bDeviceSubClass в структуре «Device Descriptor» содержат 0x00

Если устройство составное - то как иначе-то? Правда, для составных устройств Class:Subclass:Protocol должны быть 0xEF:0x02:0x01

И по bcdUSB добавлю: если выставить в 0x0200, хост будет запрашивать QualiferDescriptor. Поэтому, если нет желания его писать, выставляем в 0x0110 и спим спокойно. Вот для HS это может быть важно, сейчас точно не помню.

Напомню, что на рубеже веков у пользователей ПК существовала серьезная проблема с поиском и установкой «правильного» драйвера для приобретенного устройства. Драйверы от производителей «железа» не отличались высоким качеством кода и стабильностью работы. Создание универсальных открытых драйверов в конечном счете решило эту проблему, но уже ближе к 2010-у году.

И все равно производители норовят впихнуть свой VendorSpecific куда можно и куда нельзя. Даже в такой банальной вещи, как переходник USB-COM вместо того, чтобы воспользоваться стандартным CDC-ACM лепят свои костыли.

Фрагмент вывода команды usbconfig -vv

Это в какой ОС? Ни линукс, ни винда (по крайней мере до win7) ее не знают.

bMaxPacketSize0 — оно показывает предельный размер блока данных отправляемого в нулевую конечную точку на устройстве. Напомню, что для «Low Speed» устройств максимальный размер блока полезных данных в пакете DATAx не может превышать 8 байт, а для устройств «Full Speed» и «High Speed» размер блока данных уже существенно больше — 1023 и 1024 байт соответственно.

см. выше: даже для HS размер для ep0 не превышает 64 байта.

После присвоения номера, производится полное считывания дескриптора устройства, конфигурационных структур, активация требуемой конфигурации, настройка интерфейсов и конечных точек.

В реальности хост может сначала, еще на нулевом адресе, запросить дескрипторы, и только потом провести энумерацию. А может и наоборот.

Повторно запросить структуру «Device Descriptor» отправив «Device Descriptor Request» на адрес 0:0, полностью принять и сохранить всю структуру.

Вот это забавное поведение. Размер DeviceDescriptor мы ведь знаем, он всегда 18 байт. Наверное, первоначально 8 байт запрашивается чтобы узнать размер ep0? Кстати, иногда хост запрашивает DeviceDescriptor с размером 64 байта и удивляется, когда ему приходит 8. Впрочем, для него такая ошибка не критична.

После подключения источников сигнала необходимо установить частоту сэмплирования в значение на один порядок больше чем частота анализируемого сигнала.

Еще бы анализаторы такую высокую частоту поддерживали. Для LS-то ладно, но вот FS уже обычными анализаторами не захватить, там получается хорошо если 1-2 отсчета на такт.

Класс «HID» является одним из самых простых видов устройств подключаемых к шине USB

Одним из самых простых?! Да там разбирать HidDescriptor посинеть можно.

Вот MSD простой, ACM простой.

Дальше хост-контроллеров пока не осилил, уж извините. Впрочем, и так комментарий размером с небольшую статью получился.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации