скрытое меню

краткая шпаргалка по USB

Коротко о USB - откуда все начинается. D+ и D- это дифференциальная пара, данные передаются в противофазе с одной лишь целью уменьшить помехи. То есть линия передачи по сути одна ! Есть ведущее устройство (Хост) и ведомое (Device).

Ведущее и ведомое могут одновременно что-то посылать в канал. Поэтому протокол USB очень требовательно распределяет , что ведущий и когда посылает и что (и когда) ведомый должен ответить. Иначе никак нельзя.

Вот на картинке ниже все отчетливо видно (один пакет от ведомого):

фотка 1

Сначала все просто:

Пакет всегда начинается с SYN (10000000).

Завершается пакет всегда EOP (End Of Packet ) . На картинке выше видна единственная ассиметрия в конце пакета, когда : 2 линии DP и DM различаются.

Примерная последовательность пакетов.

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

Периодические пакеты "НЕ СПАТЬ" SOF (Start Of Frame) - это примерно 1раз/1мс посылка от хоста ведомому ("не спи"). Их лучше сразу как-то отфильтровывать для понимания.

Далее остается три типа пакетов типа . Ниже их PID (Packet Identificator) , он же токен :
SETUP это служебные пакеты стандартного протокола настройки устройства
OUT это хост передает данные
IN это хост запрашивает данные от девайса

Эти пакеты вкладываются между SYNK и EOP .

фотка 2

Получается примерно такая структура [SYNC] [PID] [Address(7 бит)] [EndPoint] (4 бит) [EOP ]. На картинке выше видно как девайс отвечает NAK практически сразу и это нормально. Это означает , что девайсу надо подумать и сразу он не может ответить на команду.

PID это токен или (Program Identificator) SETUP, IN , OUT.

Address - это адрес нашего устройства на шине USB . Сначала он всегда 0 после подключения USB. Потом хост перенумеровывает все устройства на шине и присваивает каждому устройству уникальный адрес (размер всего 1 байт).

EndPoint - хост всегда общается не просто с устройством по адресу , а еще и с конкретной конечной точкой (end-point) устройства , которых может быть несколько. Как же хост узнает какие значения у конечных точек (EP) ? Правильно для этого зарезервировано значение 0 (конечная точка EP0), служебный end-point , через который хост получает первичную информацию от других конечных точках. Как всегда все просто.

Допустим наш хост уже получил всю информацию о конечных точках , интерфейсах, конфигурациях через EP0.

Как происходит дальше работа на примере обычной клавиатуры

Хост долбит периодически PID IN по адресу устройства плюс Endpoint устройства (у нас EndP 0x01), который отвечает за прием данных от клавиатуры (IN для хоста).

фотка 3

Если никакая клавиша не нажата ведомый обязан ответить и отвечает NAK. Такие пакеты хост передает примерно 1 раз в 10ms и устройство если не нажата клавиша передает NAK.

А вот когда на клавиатуре нажимается какая-нибудь клавиша, ведомый ответит сначала DATA0 пакетом и следом пакет ACK.

фотка 4

Количество передаваемых байт в DATA0 зависит от типа клавиатуры, то есть каждый решает сколько использовать байт для передачи скан кода нажатой клавиши. Клавиатура сообщает по стандартному протоколу через EP0 о своих настройках.

Тут есть нюанс , что хост всегда посылает запрос устройству на конкретный EP. Если запрос идет на EP для передачи данных (у нас EP1 ) это одно , если запрос идет на служебный EP0 - это хост хочет подключить , настроить устройство. То есть хост всегда определяет логику обмена , а девайс обязан подстраиваться под запрос.

Вообще кто есть хост? Это драйвер например клавиатура или сетевого адаптера и у каждого драйвера соответственно свой протокол , своя логика.

Таким образом если вы разрабатываете USB устройство и ПК шлет вам все пакеты на EP0 , а до других EP не доходит дело, то значит что-то еще не закончено с настройками устройства, что-то хосту не нравится.

Хост вообще говоря может ждать ответ одновременно от 2 и более конечных точек . Это абсолютно нормально. Выглядит это в логах анализатора LA1010 примерно так:

0.210871930,IN,0x2A,0x02,,,0x0F // запрос данных от EP2
0.210875190,NAK,,,,,
0.210897110,IN,0x2A,0x00,,,0x0A // запрос данных от EP0
0.210900440,NAK,,,,,
0.210902980,IN,0x2A,0x02,,,0x0F // запрос данных от EP2
0.210906280,NAK,,,,,
0.210928120,IN,0x2A,0x00,,,0x0A // запрос данных от EP0
0.210931450,NAK,,,,,
0.210934770,IN,0x2A,0x02,,,0x0F // запрос данных от EP2
0.210938030,NAK,,,,,

Видно как хост тупо чередует EP0 и EP2.

Если не возникает какого-то прерывания у девайса

То есть если на шине пакеты бегут, а прерывание необходимое не возникает. Например тупо не возникает прерывание IN bulk у RNDIS адаптера (DataIn у EP2). То есть на шине вижу , что девайс отсылает NAK на IN EP2, но самого прерывания в девайсе не возникает.

0.205299000,OUT,0x0F,0x00,,,0x03
0.205302350,DATA1,,,,,0x0000
0.205305600,NAK,,,,,
0.205328000,OUT,0x0F,0x00,,,0x03
0.205331350,DATA1,,,,,0x0000
0.205334600,ACK,,,,,
0.205373310,IN,0x0F,0x02,,,0x06
0.205376610,NAK,,,,,
0.205379080,IN,0x0F,0x02,,,0x06
0.205382370,NAK,,,,,

Тут надо в регистры лезть и отсрочки уже не будет. Какие мысли возникают в первую очередь. Прерывания маскируются вроде (надо проверить).

Так как у нас есть один рабочий проект но без FreeRTOS , то сначала тупо начинаем сверять регистры USB ( OTG_FS_GLOBAL и OTG_FS_DEVICE ): после инициализации , после открытия конечных точек, после приема нужного пакета и т.д. Эти регистры кстати удобно просматривать на закладке SFRSAtollic true Studio), тут видна их внутренняя структура. И еще с момента последней точки остановки подсвечиваются изменения.

фотка 1

В процессе сверки регистров мы находим отличия в OTG_FS_GLOBAL, исправляем, заодно изучаем назначение регистров и в какой-то момент даже ловим __HAL_PCD_IS_INVALID_INTERRUPT (на картинке выше видно). Ура хоть что-то.

На самом деле не знач - не ведая мы подошли к главному моменту. Мы наконец-то обратили внимание на USBD_LL_Init, а точнее на загадочные функции HAL_PCDEx_SetRxFiFo(..) и HAL_PCDEx_SetTxFiFo(..) .

Момент истины

Огромное спасибо товарищу в интернете https://mcu.goodboard.ru/viewtopic.php?id=40 . Просто , кратко и доходчиво объяснил какие регистры отвечают за USB и самое главное что с ними делать (их нюансы).

И выяснилось , что мы не понимаем и половины логики работы USB . Не зная регистры вообще нет возможности понять что делать. В данном случае HAL это вред.

Итак HAL_PCDEx_SetRxFiFo / HAL_PCDEx_SetTxFiFo создает таблицу во внутренней памяти контроллера USB. Да именно контроллера USB , а не контроллера STM32. Так как у STM32F имеется как-бы свой встроенный контроллер , отвечающий за USB. И у него есть своя память 512К, в которой надо создать таблицу с буферами приема / передачи для каждой конечной точки.

Где эта таблица, где ее адреса...

#define USB_OTG_FS_PERIPH_BASE 0x50000000U [stm32f205xx.h]
..............
#define USB_OTG_FS ((USB_OTG_GlobalTypeDef *) USB_OTG_FS_PERIPH_BASE) [stm32f205xx.h]
..........

USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
.......
  hpcd_USB_OTG_FS.Instance = USB_OTG_FS;
void HAL_PCD_IRQHandler(PCD_HandleTypeDef *hpcd)
{
  USB_OTG_GlobalTypeDef *USBx = hpcd->Instance;

А вот сама структура USB_OTG_GlobalTypeDef .

typedef struct
{
  __IO uint32_t GOTGCTL;              /*!<  USB_OTG Control and Status Register    Address offset : 0x00      */
  __IO uint32_t GOTGINT;              /*!<  USB_OTG Interrupt Register             Address offset : 0x04      */
  __IO uint32_t GAHBCFG;              /*!<  Core AHB Configuration Register        Address offset : 0x08      */
  __IO uint32_t GUSBCFG;              /*!<  Core USB Configuration Register        Address offset : 0x0C      */
  __IO uint32_t GRSTCTL;              /*!<  Core Reset Register                    Address offset : 0x10      */
  __IO uint32_t GINTSTS;              /*!<  Core Interrupt Register                Address offset : 0x14      */
  __IO uint32_t GINTMSK;              /*!<  Core Interrupt Mask Register           Address offset : 0x18      */
  __IO uint32_t GRXSTSR;              /*!<  Receive Sts Q Read Register            Address offset : 0x1C      */
  __IO uint32_t GRXSTSP;              /*!<  Receive Sts Q Read & POP Register      Address offset : 0x20      */
  __IO uint32_t GRXFSIZ;              /* Receive FIFO Size Register                Address offset : 0x24      */
  __IO uint32_t DIEPTXF0_HNPTXFSIZ;   /*!<  EP0 / Non Periodic Tx FIFO Size Register Address offset : 0x28    */
  __IO uint32_t HNPTXSTS;             /*!<  Non Periodic Tx FIFO/Queue Sts reg     Address offset : 0x2C      */
  uint32_t Reserved30[2];             /* Reserved                                  Address offset : 0x30      */
  __IO uint32_t GCCFG;                /*!<  General Purpose IO Register            Address offset : 0x38      */
  __IO uint32_t CID;                  /*!< User ID Register                          Address offset : 0x3C      */
  uint32_t  Reserved40[48];           /*!< Reserved                                  Address offset : 0x40-0xFF */
  __IO uint32_t HPTXFSIZ;             /*!< Host Periodic Tx FIFO Size Reg            Address offset : 0x100 */
  __IO uint32_t DIEPTXF[0x0F];        /*!< dev Periodic Transmit FIFO */
}
USB_OTG_GlobalTypeDef;

HAL - кий код становится намного прозрачнее теперь.

Опять момент истины

Но проблемка и неотправкой по IN EP2 вдруг разрешается до неприличия банальным образом. Чтобы что-то отправить , надо что-то послать ! Ха-Ха! А кто сказал что , мы что-то посылаем? Дело в том , что функции класса устройства из структуры USBD_ClassTypeDef типа DataIn , DataOut , SOF , EP0_RxReady - ЭТО ВСЕ КОЛЛБЕК ФУНКЦИИ, то есть функции , вызываемы по факту , уже по результату команды.
А реально командой приема и отправки с девайса является USB_EP0StartXfer (USB_EPStartXfer) и отправки соответственно тоже USB_EP0StartXfer (USB_EPStartXfer) ! Смотрите еще раз внимательно стек вызовов функций:

USBD_LL_PrepareReceive
  HAL_PCD_EP_Receive
    USB_EP0StartXfer | USB_EPStartXfer.

USBD_LL_Transmit
  HAL_PCD_EP_Transmit
    USB_EP0StartXfer | USB_EPStartXfer.

Дальше , если интересно немного о передаваемых скан кода клавиатуры . Проводная клавиатура Low Speed

Скан коды USB HID клавиатур это не ASCII коды и не не коды PS/2 клавы.

Простые нажатия возвращают DATA0 и DATA1
1 - 00 00 1E 00 00 00 00 00 00 (DATA0) 00 00 00 00 00 00 00 00 00 (DATA1)
2 - 00 00 1F 00 00 00 00 00 00 (DATA0) 00 00 00 00 00 00 00 00 00 (DATA1)
3 - 00 00 20 00 00 00 00 00 00 (DATA0) 00 00 00 00 00 00 00 00 00 (DATA1)
<- 00 00 50 00 00 00 00 00 00 (DATA0) 00 00 00 00 00 00 00 00 00 (DATA1)

И значение у нашей клавы будет в 3 байте из 8 переданных DATA0.

SHIFT F 02 00 00 00 00 00 00 00 (DATA0) 02 00 09 00 00 00 00 00 00 (DATA1).

В общем опытным путем разобраться можно.

Беспроводная клавиатура Full Speed

Возвращает в DATA0 12 байт

01 00 00 00 00 00 00 00 00 00 00 00 (DATA0) 01 00 1E 00 00 00 00 00 00 00 00 00 (DATA1)

Значение PID читаем так : xxxxyyyyy - это первые четыре бита (x) , y это тоже самое , только наоборот.

PID типы

Группа Значение PID Идентификатор пакета
Token 0001 OUT Token
Token 1001 IN Token
Token 0101 SOF Token
Token 1101 SETUP Token
Data 0011 DATA0
Data 1011 DATA1
Data 0111 DATA2
Data 1111 MDATA
Handshake 0010 ACK Handshake
Handshake 1010 NAK Handshake
Handshake 1110 STALL Handshake
Handshake 0110 NYET (No Response Yet)
Special 1100 PREamble
Special 1100 ERR
Special 1000 Split
Special 0100 Ping