20д. Своё USB-устройство без драйвера: дескрипторы по байтам, MS OS 2.0 и WinUSB
В разделе 20 прибор стал COM-портом, в 20в — клавиатурой, в 198ш — тем и другим сразу. Это готовые классы: система берёт для них свой драйвер. Но бывает, что ни COM-порт, ни HID не подходят. Нужны свои команды, своя скорость, массовые точки без обёрток. Тогда класс у прибора свой — 0xFF, «у производителя». Своего драйвера для него в Windows нет. Его либо пишут и подписывают, либо прибор сам говорит системе: «возьми стандартный WinUSB». Для этого служат дескрипторы Microsoft OS 2.0.
Здесь собрано такое устройство: интерфейс класса 0xFF, две массовые точки, BOS с платформенной возможностью MS OS 2.0 и набор дескрипторов с совместимым ID «WINUSB» и GUID интерфейса. Устройство работает эхом: что пришло в точку OUT, то, «посоленное» ключом, уходит обратно в точку IN. Всё описано по байтам, и каждый байт проверен. Хост на компьютере опознаёт устройство через модель блока USB FS STM32F103 и разбирает каждый дескриптор своим разборщиком. Набор MS OS 2.0 и BOS побайтно сверены со структурами Zephyr. Программа на ПК прогнана поверх той же модели. Прошивки под F103 и F407 собраны arm-none-eabi-gcc. На плате и на Windows прибор не запускался.
Опоры: RM0008 Rev 17 (STM32F10x, глава 23 «USB full-speed device
interface»: 23.4.2, 23.5.2, 23.5.3, таблица 177; 30.2 — номер кристалла);
DocID13587 Rev 17 (STM32F103x8/B, таблица 43 — время запуска
приёмопередатчика); RM0090 Rev 19 (STM32F4, глава 34 «OTG_FS»: 34.2, 34.13.1);
заголовки stm32f10x.h (STM32F1xx_DFP 2.4.1) и stm32f407xx.h
(STM32F4xx_DFP 2.17.1); Zephyr 4.4.2 (github.com/zephyrproject-rtos/zephyr,
коммит dccb095: include/zephyr/usb/msos_desc.h, include/zephyr/usb/bos.h,
пример samples/subsys/usb/webusb, subsys/usb/device_next/usbd_ch9.c;
лицензия Apache-2.0); candleLight (github.com/candle-usb/candleLight_fw,
коммит 85e5b33, src/usbd_gs_can.c; лицензия MIT); MDK-Middleware 7.17.0 и
8.1.0 (usb_ms.h, USBD_Config_CustomClass.h, описание примера
WinUSB_Echo; лицензия Arm — только пересказ). Устройство, точка 0 и
драйвер F407 продолжают код раздела 198ш. Тексты спецификаций USB и
Microsoft OS 2.0 не использовались: форматы взяты из этих исходников.
Лицензия: свой код раздела — свободно.
Как это называется в документации
Оригинал Перевод vendor-specific class (0xFF) класс «у производителя»: смысл задаёт сам прибор vendor request запрос производителя: bmRequestType 0x40 или 0xC0 Binary device Object Store (BOS) хранилище возможностей устройства, дескриптор типа 0x0F Device Capability, Platform Capability возможность устройства; платформенная — с UUID платформы Microsoft OS 2.0 descriptor set набор дескрипторов Microsoft OS 2.0 bMS_VendorCode код запроса производителя, которым хост просит набор Compatible ID «WINUSB» совместимый ID: какой стандартный драйвер подходит DeviceInterfaceGUIDs, REG_MULTI_SZ GUID интерфейса устройства; тип реестра «список строк» Microsoft OS String Descriptor (0xEE) строка 0xEE — вход в старые дескрипторы MS OS 1.0 packet memory area (PMA), buffer descriptor table память пакетов F103 и таблица буферов в ней toggle bit, rc_w0 бит-переключатель (меняется записью 1); флаг, сбрасываемый записью 0
20д.1 Зачем своё устройство и что хост спросит
Три пути к прибору с компьютера:
| Класс | Драйвер на ПК | Что получает программа | Когда брать |
|---|---|---|---|
| CDC ACM (раздел 20, 198ш) | у системы свой: в Windows — usbser.sys (198ш.6) |
COM-порт: поток байт | консоль, замена UART |
| HID (раздел 20в) | у системы свой, во всех системах | отчёты фиксированной длины по прерывающей точке | клавиатура, кнопки, малые данные |
| свой, 0xFF + WinUSB | winusb.sys, если прибор отдал MS OS-дескрипторы |
точки как есть: управляющие запросы, массовые пакеты по 64 байта | свой протокол, поток данных, без обёрток COM-порта |
У COM-порта нет своих команд, кроме скорости и линий: команду придётся
прятать в поток. У HID отчёт фиксированной длины и прерывающая точка:
не больше одного пакета за период опроса. Своё устройство даёт всё
сразу: управляющие запросы для команд и массовые точки для данных.
Цена — драйвер на ПК. Его и снимают дескрипторы MS OS: в описании
примера WinUSB_Echo из MDK-Middleware сказано, что на Windows 8 и новее
драйвер ставится сам, а на Windows 7 нужен файл установки.
Что хост спрашивает у такого прибора — на рисунке. Новое по сравнению с 198ш — шаги 2 и 3: дескриптор BOS и запрос производителя.
Модель хоста повторяет этот порядок. Сначала она смотрит bcdUSB.
Стек Zephyr на стороне устройства отказывает в BOS, если bcdUSB меньше
0x0201 (sreq_get_desc_bos в usbd_ch9.c). Поэтому и модель просит BOS
только у устройства с версией 2.01 и выше. Платформенную возможность
она узнаёт по UUID и берёт из неё код запроса и длину набора. Набор
просит запросом производителя к устройству с wIndex = 7: это
MS_OS_20_DESCRIPTOR_INDEX в msos_desc.h Zephyr.
20д.2 Дескрипторы устройства и конфигурации по байтам
Общий заголовок устройства (vend.h) — номера точек, коды запросов и
длины всех дескрипторов:
#ifndef USB_BCD_USB
#define USB_BCD_USB 0x0201u /* 2.01: только у такого устройства хост спрашивает BOS */
#endif
#define USB_VID 0x1209u /* заглушки для макета: в изделии — свои номера */
#define USB_PID 0x0002u
#define EP0_SIZE 64u
#define EP_OUT 0x01u /* массовая OUT, 64 байта */
#define EP_IN 0x81u /* массовая IN, 64 байта */
#define BULK_SIZE 64u
#define EP_TYPE_BULK 2u
#define MS_VENDOR_CODE 0x20u /* bRequest, которым хост просит набор MS OS 2.0 */
#define MS_OS_20_INDEX 7u /* wIndex того же запроса */
#define VREQ_GET_INFO 0x01u /* свои запросы: IN, 12 байт состояния */
#define VREQ_SET_LED 0x02u /* без данных: wValue = 0 или 1 */
#define VREQ_SET_XOR 0x03u /* OUT, 1 байт: им «солится» эхо */
#define INFO_SIZE 12u
#define DEVICE_DESC_SIZE 18u
#define CONFIG_DESC_SIZE 32u
#define BOS_DESC_SIZE 33u
#define MSOS20_SET_SIZE 162u
#define LE16(x) ((x) & 0xFFu), (((x) >> 8) & 0xFFu)
Сравните с 198ш.2: там класс устройства EF/02/01 и IAD, потому что функций две. Здесь одна функция, и класс стоит в интерфейсе. Главное отличие — два байта версии: 2.01 вместо 2.00.
const uint8_t usb_device_desc[] = {
18, 0x01, /* bLength, DEVICE */
LE16(USB_BCD_USB), /* bcdUSB 2.01: у устройства есть BOS */
0x00, 0x00, 0x00, /* класс, подкласс, протокол — смотри интерфейс */
EP0_SIZE, /* bMaxPacketSize0 */
LE16(USB_VID),
LE16(USB_PID),
0x00, 0x01, /* bcdDevice 1.00 */
1, 2, 3, /* строки: изготовитель, изделие, серийный номер */
1 /* одна конфигурация */
};
Конфигурация — 32 байта: сама конфигурация, один интерфейс и две точки.
const uint8_t usb_config_desc[] = {
/* конфигурация */
9, 0x02, LE16(CONFIG_DESC_SIZE), /* wTotalLength = 32 */
1, /* bNumInterfaces */
1, /* bConfigurationValue */
0, /* iConfiguration */
0x80, /* bmAttributes: питание от шины */
50, /* bMaxPower: 50 x 2 мА = 100 мА */
/* интерфейс 0: свой класс */
9, 0x04, 0, 0, 2, /* номер 0, настройка 0, две точки */
0xFF, 0x00, 0x00, /* класс 0xFF «у производителя», подкласс и протокол — свои */
0, /* iInterface */
/* точки */
7, 0x05, EP_OUT, EP_TYPE_BULK, LE16(BULK_SIZE), 0,
7, 0x05, EP_IN, EP_TYPE_BULK, LE16(BULK_SIZE), 0
};
Хост модели прочитал оба дескриптора и разобрал их своим разборщиком:
SETUP 80 06 00 01 00 00 12 00 GET_DESCRIPTOR(DEVICE 0, 18): IN 18 = 18 байт
DEVICE: bcdUSB 2.01, класс 00/00/00, пакет точки 0 — 64, VID 1209, PID 0002, строки 1 2 3, конфигураций 1 — сходится
SETUP 80 06 00 02 00 00 09 00 GET_DESCRIPTOR(CONFIGURATION 0, 9): IN 9 = 9 байт
wTotalLength = 32
SETUP 80 06 00 02 00 00 FF 00 GET_DESCRIPTOR(CONFIGURATION 0, 255): IN 32 = 32 байт
0: 09 02 20 00 01 01 00 80 32
CONFIGURATION: wTotalLength 32, интерфейсов 1, номер 1, bmAttributes 0x80, 100 мА
9: 09 04 00 00 02 FF 00 00 00
INTERFACE 0: настройка 0, точек 2, класс FF/00/00
18: 07 05 01 02 40 00 00
ENDPOINT 0x01: массовая OUT, пакет 64, bInterval 0
25: 07 05 81 02 40 00 00
ENDPOINT 0x81: массовая IN, пакет 64, bInterval 0
итог разбора конфигурации: пройдено 32 байт из 32, интерфейсов 1 (заявлено 1), точек 2 — сходится
Что здесь важно:
bcdUSB= 0x0201 лежит младшим байтом вперёд:01 02. Хост, увидевший 2.00, BOS не спрашивает — тот же прибор с 2.00 остаётся без WinUSB (20д.3);- класс интерфейса 0xFF говорит хосту одно: «готового драйвера нет». Подкласс и протокол при 0xFF — на усмотрение производителя, у нас нули;
- у массовых точек
bIntervalне нужен, поэтому 0; пакет — 64 байта, больше полная скорость для массовых точек не даёт; - строки 1–3 — те же, что в 198ш.3: изготовитель (30 байт),
изделие «WinUSB echo 20d» (32 байта) и серийный номер из 96 бит номера
кристалла (50 байт). Номер лежит у F103 по адресу 0x1FFFF7E8 (RM0008,
30.2), у F407 — по 0x1FFF7A10 (
UID_BASE). В модели он нулевой.
Длины, записанные внутри дескрипторов, берутся из макросов, а размер массивов сверяет компилятор:
_Static_assert(sizeof usb_device_desc == DEVICE_DESC_SIZE, "дескриптор устройства: не 18 байт");
_Static_assert(sizeof usb_config_desc == CONFIG_DESC_SIZE, "конфигурация: wTotalLength не сходится");
_Static_assert(sizeof usb_bos_desc == BOS_DESC_SIZE, "BOS: wTotalLength не сходится");
_Static_assert(sizeof usb_msos20_set == MSOS20_SET_SIZE, "набор MS OS 2.0: wTotalLength не сходится");
Без этих строк массив можно объявить в заголовке с длиной: extern const
uint8_t usb_msos20_set[162]. Тогда недостающий байт компилятор молча
заполнит нулём, а лишний даст только предупреждение. С проверками оба
случая — ошибка сборки (error: static assertion failed, раздел 9
прогона).
20д.3 BOS и набор MS OS 2.0
BOS — корень на 5 байт и за ним возможности. У нас одна, платформенная: UUID платформы и четыре поля Microsoft.
const uint8_t usb_bos_desc[] = {
5, 0x0F, /* bLength, BOS */
LE16(BOS_DESC_SIZE), /* wTotalLength = 33: корень и все возможности */
1, /* bNumDeviceCaps */
/* платформенная возможность MS OS 2.0 */
28, 0x10, 0x05, /* bLength, DEVICE_CAPABILITY, PLATFORM */
0x00, /* bReserved */
0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, /* UUID D8DD60DF-4589-4CC7- */
0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, /* 9CD2-659D9E648A9F */
0x00, 0x00, 0x00, 0x0A, /* dwWindowsVersion 0x0A000000: Windows 10 */
LE16(MSOS20_SET_SIZE), /* wMSOSDescriptorSetTotalLength = 162 */
MS_VENDOR_CODE, /* bMS_VendorCode: этим bRequest набор и попросят */
0x00 /* bAltEnumCode: второго варианта нет */
};
UUID в дескрипторе записан как GUID Windows: первые три поля — младшим
байтом вперёд. Поэтому D8DD60DF начинается с байта DF. Код
производителя 0x20 выбран свободно: тот же код стоит у candleLight, у
примеров Zephyr — 0x02, у MDK по умолчанию — 0x01.
Набор MS OS 2.0 — заголовок и записи. У каждой записи первые 4 байта — длина и тип, по 2 байта.
const uint8_t usb_msos20_set[] = {
/* заголовок набора */
0x0A, 0x00, /* wLength = 10 */
0x00, 0x00, /* MS_OS_20_SET_HEADER_DESCRIPTOR */
0x00, 0x00, 0x00, 0x0A, /* dwWindowsVersion 0x0A000000 */
LE16(MSOS20_SET_SIZE), /* wTotalLength = 162: весь набор */
/* совместимый ID: какой драйвер ставить */
0x14, 0x00, /* wLength = 20 */
0x03, 0x00, /* MS_OS_20_FEATURE_COMPATIBLE_ID */
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, /* CompatibleID */
0, 0, 0, 0, 0, 0, 0, 0, /* SubCompatibleID: нет */
/* свойство реестра: GUID, по которому программа найдёт устройство */
0x84, 0x00, /* wLength = 132 */
0x04, 0x00, /* MS_OS_20_FEATURE_REG_PROPERTY */
0x07, 0x00, /* wPropertyDataType: REG_MULTI_SZ, список строк */
0x2A, 0x00, /* wPropertyNameLength = 42 */
U16('D'), U16('e'), U16('v'), U16('i'), U16('c'), U16('e'),
U16('I'), U16('n'), U16('t'), U16('e'), U16('r'), U16('f'), U16('a'), U16('c'), U16('e'),
U16('G'), U16('U'), U16('I'), U16('D'), U16('s'), 0x00, 0x00,
0x50, 0x00, /* wPropertyDataLength = 80 */
U16('{'), U16('9'), U16('D'), U16('E'), U16('E'), U16('3'), U16('3'), U16('1'), U16('4'),
U16('-'), U16('0'), U16('6'), U16('2'), U16('6'),
U16('-'), U16('4'), U16('A'), U16('2'), U16('1'),
U16('-'), U16('B'), U16('C'), U16('E'), U16('4'),
U16('-'), U16('9'), U16('4'), U16('5'), U16('6'), U16('A'), U16('E'), U16('2'), U16('2'),
U16('7'), U16('C'), U16('2'), U16('8'), U16('}'),
0x00, 0x00, 0x00, 0x00 /* конец строки и конец списка */
};
Хост модели прочитал BOS, нашёл в нём MS OS 2.0 и попросил набор:
SETUP 80 06 00 0F 00 00 05 00 GET_DESCRIPTOR(BOS 0, 5): IN 5 = 5 байт
BOS: wTotalLength = 33
SETUP 80 06 00 0F 00 00 21 00 GET_DESCRIPTOR(BOS 0, 33): IN 33 = 33 байт
0: 05 0F 21 00 01
BOS: wTotalLength 33, возможностей 1
5: 1C 10 05 00 DF 60 DD D8 89 45 C7 4C 9C D2 65 9D
21: 9E 64 8A 9F 00 00 00 0A A2 00 20 00
DEVICE_CAPABILITY 28 байт: PLATFORM, UUID D8DD60DF-4589-4CC7-9CD2-659D9E648A9F — MS OS 2.0
dwWindowsVersion 0x0A000000, wMSOSDescriptorSetTotalLength 162, bMS_VendorCode 0x20, bAltEnumCode 0
итог разбора BOS: пройдено 33 байт из 33, возможностей 1 (заявлено 1) — сходится
SETUP C0 20 00 00 07 00 A2 00 производитель IN: bRequest 0x20, wValue 0, wIndex 7, wLength 162: IN 64+64+34 = 162 байт
0: 0A 00 00 00 00 00 00 0A A2 00
заголовок: wLength 10, тип 0, dwWindowsVersion 0x0A000000, wTotalLength 162
10: 14 00 03 00 57 49 4E 55 53 42 00 00 00 00 00 00
26: 00 00 00 00
COMPATIBLE_ID 20 байт: CompatibleID «WINUSB», SubCompatibleID «»
30: 84 00 04 00 07 00 2A 00 44 00 65 00 76 00 69 00
46: 63 00 65 00 49 00 6E 00 74 00 65 00 72 00 66 00
62: 61 00 63 00 65 00 47 00 55 00 49 00 44 00 73 00
78: 00 00 50 00 7B 00 39 00 44 00 45 00 45 00 33 00
94: 33 00 31 00 34 00 2D 00 30 00 36 00 32 00 36 00
110: 2D 00 34 00 41 00 32 00 31 00 2D 00 42 00 43 00
126: 45 00 34 00 2D 00 39 00 34 00 35 00 36 00 41 00
142: 45 00 32 00 32 00 37 00 43 00 32 00 38 00 7D 00
158: 00 00 00 00
REG_PROPERTY 132 байт: тип данных 7, имя 42 байт «DeviceInterfaceGUIDs», данные 80 байт
список строк: {9DEE3314-0626-4A21-BCE4-9456AE227C28}; оканчивается двумя нулями: да
итог разбора набора: пройдено 162 байт из 162 — сходится
На что смотреть:
- три длины должны сойтись.
wMSOSDescriptorSetTotalLengthв BOS,wTotalLengthв заголовке набора и сумма записей: 10 + 20 + 132 = 162. Хост просит ровно столько, сколько обещал BOS, — 162 байта, три пакета 64 + 64 + 34; - совместимый ID — 8 байт ASCII с дополнением нулями:
WINUSB\0\0. Это и есть «возьми WinUSB»; - свойство
DeviceInterfaceGUIDsтипа 7 (REG_MULTI_SZ) — список строк UTF-16LE. Имя — 20 знаков и нуль, 42 байта. Данные — GUID в фигурных скобках (38 знаков), нуль строки и нуль конца списка: 40 знаков, 80 байт. По этому GUID программа на ПК ищет прибор. GUID у каждого изделия свой; наш создан генератором случайных GUID; - записей подмножеств (тип 1 — конфигурация, тип 2 — функция) нет: у прибора одна функция, и набор относится ко всему устройству. В составном приборе совместимый ID кладут в подмножество функции с номером первого интерфейса — так сделан пример Zephyr с CDC ACM.
Тот же прибор, собранный с bcdUSB 2.00, модель хоста опознаёт без BOS —
и без набора:
DEVICE: bcdUSB 2.00, класс 00/00/00, пакет точки 0 — 64, VID 1209, PID 0002, строки 1 2 3, конфигураций 1 — сходится
bcdUSB 2.00 < 2.01: BOS не запрашивается, MS OS 2.0 хост не увидит
SETUP 80 06 00 06 00 00 0A 00 GET_DESCRIPTOR(DEVICE_QUALIFIER 0, 10): STALL
SETUP 80 06 00 02 00 00 09 00 GET_DESCRIPTOR(CONFIGURATION 0, 9): IN 9 = 9 байт
wTotalLength = 32
SETUP 80 06 00 02 00 00 FF 00 GET_DESCRIPTOR(CONFIGURATION 0, 255): IN 32 = 32 байт
0: 09 02 20 00 01 01 00 80 32
CONFIGURATION: wTotalLength 32, интерфейсов 1, номер 1, bmAttributes 0x80, 100 мА
9: 09 04 00 00 02 FF 00 00 00
INTERFACE 0: настройка 0, точек 2, класс FF/00/00
18: 07 05 01 02 40 00 00
ENDPOINT 0x01: массовая OUT, пакет 64, bInterval 0
25: 07 05 81 02 40 00 00
ENDPOINT 0x81: массовая IN, пакет 64, bInterval 0
итог разбора конфигурации: пройдено 32 байт из 32, интерфейсов 1 (заявлено 1), точек 2 — сходится
SETUP 80 06 00 03 00 00 FF 00 GET_DESCRIPTOR(STRING 0, 255): IN 4 = 4 байт
длина 4: язык 0x0409
SETUP 80 06 01 03 09 04 FF 00 GET_DESCRIPTOR(STRING 1, 255): IN 30 = 30 байт
длина 30: «STM32 Handbook»
SETUP 80 06 02 03 09 04 FF 00 GET_DESCRIPTOR(STRING 2, 255): IN 32 = 32 байт
длина 32: «WinUSB echo 20d»
SETUP 80 06 03 03 09 04 FF 00 GET_DESCRIPTOR(STRING 3, 255): IN 50 = 50 байт
длина 50: «000000000000000000000000»
SETUP 80 06 EE 03 00 00 12 00 GET_DESCRIPTOR(STRING 238, 18): STALL
итог опознания: дескрипторы сходятся; MS OS 2.0: не найден
Сверка с Zephyr. Набор и BOS собраны тем же инициализатором структур,
что в примере webusb Zephyr (ветка без CDC), только с нашим GUID и
кодом, и сравнены с нашими массивами побайтно:
размеры структур Zephyr: заголовок набора 10, подмножество конфигурации 8, подмножество функции 8, совместимый ID 20, свойство GUID 132
BOS у Zephyr: корень 5, платформенная возможность 20 + MS OS 8 = 28
набор MS OS 2.0: Zephyr 162 байт, наш 162 байт, побайтно одинаковы
BOS: Zephyr 33 байт, наш 33 байт, побайтно одинаковы
MS OS 1.0 — старый путь. До BOS было так: строка с индексом 0xEE
и подписью «MSFT100» несёт код производителя. По нему хост делает два
запроса: wIndex 4 — совместимый ID, wIndex 5 — расширенные свойства.
Так сделан candleLight (прошивка адаптеров USB–CAN на STM32, раздел 19ж).
MDK-Middleware тоже умеет только этот путь (usb_ms.h: строка 0xEE,
индексы 4 и 5). Сравнение по прогону:
строка 0xEE: 18 байт, bLength 0x12, подпись «MSFT100», код 0x20
совместимый ID (wIndex 4): 64 байт, dwLength 0x40, секций 2
расширенные свойства (wIndex 5): 146 байт, dwLength 0x92, имя «DeviceInterfaceGUIDs» — есть
всего MS OS 1.0: 228 байт в трёх ответах
| MS OS 1.0 (candleLight) | MS OS 2.0 (этот раздел) | |
|---|---|---|
| как хост узнаёт | строка 0xEE с подписью «MSFT100» | BOS, платформенная возможность с UUID D8DD60DF-… |
| условие | — | bcdUSB не ниже 2.01 |
| запросов производителя | два: wIndex 4 и 5 |
один: wIndex 7 |
| байт в ответах | 18 + 64 + 146 = 228 | 33 + 162 = 195 |
| имя свойства GUID | DeviceInterfaceGUIDs |
DeviceInterfaceGUIDs |
Имя свойства пишут по-разному. У candleLight и Zephyr — DeviceInterfaceGUIDs
(список, тип 7). В настройках MDK по умолчанию — DeviceInterfaceGUID (одна
строка, тип 1), а тип 7 там прямо не поддерживается. Какое имя читает
Windows, в разделе не проверялось; у нас — как у Zephyr и candleLight.
20д.4 Точка 0: запросы производителя
Стадии передачи, строки, адрес и конфигурация — как в 198ш.3. Новое — ветка запросов производителя в разборе SETUP и сам обработчик:
void usb_setup(const uint8_t s[8])
{
const uint8_t *p = 0;
uint16_t len = 0, wlength = (uint16_t)(s[6] | s[7] << 8);
uint8_t type = (s[0] >> 5) & 3u, rcpt = s[0] & 0x1Fu;
int r = REQ_STALL;
c.rx_want = 0;
if (type == 0) r = standard_request(s, &p, &len);
else if (type == 2 && rcpt == 0) r = vendor_request(s, &p, &len); /* производитель, устройству */
if (r == REQ_IN && (s[0] & 0x80u) && wlength) {
ep0_send(p, len, wlength);
} else if (r == REQ_OUT && !(s[0] & 0x80u) && wlength && wlength <= sizeof c.buf) {
memcpy(c.setup, s, 8); /* данные придут следующими пакетами OUT */
c.rx_len = 0;
c.rx_want = wlength;
} else if (r == REQ_NODATA && !wlength) {
ep0_status();
} else {
drv_ep0_stall();
}
}
/* Запросы производителя к устройству. Набор MS OS 2.0 отдаётся только на свой
* bRequest и wIndex = 7 и в любом состоянии — хост просит его до SET_CONFIGURATION.
* Свои команды — только в настроенном устройстве. */
static int vendor_request(const uint8_t s[8], const uint8_t **p, uint16_t *len)
{
uint8_t in = s[0] & 0x80u;
uint16_t wvalue = (uint16_t)(s[2] | s[3] << 8), windex = (uint16_t)(s[4] | s[5] << 8);
if (s[1] == MS_VENDOR_CODE) {
if (!in || windex != MS_OS_20_INDEX) return REQ_STALL;
*p = usb_msos20_set; *len = MSOS20_SET_SIZE;
return REQ_IN;
}
if (!c.config) return REQ_STALL;
switch (s[1]) {
case VREQ_GET_INFO: /* версия, светодиод, ключ, счётчики байт */
if (!in) return REQ_STALL;
c.buf[0] = 0x00; c.buf[1] = 0x01; /* версия протокола 1.00 */
c.buf[2] = app.led;
c.buf[3] = app.xor_key;
put32(c.buf + 4, app.rx_bytes);
put32(c.buf + 8, app.tx_bytes);
*p = c.buf; *len = INFO_SIZE;
return REQ_IN;
case VREQ_SET_LED:
if (in || wvalue > 1) return REQ_STALL;
app.led = (uint8_t)wvalue;
board_led(app.led);
return REQ_NODATA;
case VREQ_SET_XOR: /* байт ключа придёт в стадии данных */
return in ? REQ_STALL : REQ_OUT;
default:
return REQ_STALL;
}
}
static int vendor_data(const uint8_t s[8], const uint8_t *d, uint16_t n)
{
if (s[1] != VREQ_SET_XOR || n != 1) return REQ_STALL;
app.xor_key = d[0];
return REQ_NODATA;
}
Три правила, которые здесь зашиты:
- набор MS OS 2.0 отдаётся до SET_CONFIGURATION. Хост просит его во время опознания, когда конфигурации ещё нет. Свои команды, наоборот, работают только в настроенном приборе;
- код MS и свои коды не пересекаются. Набор отдаётся только на
bRequest0x20 иwIndex7. Свои команды — 0x01…0x03. Если своя команда возьмёт тот же код и не посмотрит наwIndex, хост вместо набора получит её ответ; - получатель — устройство (младшие биты
bmRequestTypeравны 0). Запрос к интерфейсу (C1) — отказ.
GET_DESCRIPTOR тоже знает новый тип: 0x0F — BOS.
static int get_descriptor(uint8_t type, uint8_t index, const uint8_t **p, uint16_t *len)
{
switch (type) {
case 0x01: *p = usb_device_desc; *len = DEVICE_DESC_SIZE; return 1;
case 0x02: *p = usb_config_desc; *len = CONFIG_DESC_SIZE; return index == 0;
case 0x03: *p = c.buf; *len = usb_string_desc(index, c.buf); return *len != 0;
case 0x0F: *p = usb_bos_desc; *len = BOS_DESC_SIZE; return index == 0;
default: return 0; /* 0x06 DEVICE_QUALIFIER: только полная скорость — отказ */
}
}
Прогон отказов и краёв:
SETUP C0 20 00 00 08 00 FF 00 производитель IN: bRequest 0x20, wValue 0, wIndex 8, wLength 255: STALL
SETUP C0 21 00 00 07 00 FF 00 производитель IN: bRequest 0x21, wValue 0, wIndex 7, wLength 255: STALL
SETUP 40 20 00 00 07 00 00 00 производитель OUT: bRequest 0x20, wValue 0, wIndex 7, wLength 0: STALL
SETUP C0 20 00 00 07 00 0A 00 производитель IN: bRequest 0x20, wValue 0, wIndex 7, wLength 10: IN 10 = 10 байт
0A 00 00 00 00 00 00 0A A2 00
SETUP C0 20 00 00 07 00 FF 00 производитель IN: bRequest 0x20, wValue 0, wIndex 7, wLength 255: IN 64+64+34 = 162 байт
SETUP C1 20 00 00 07 00 FF 00 производитель IN: bRequest 0x20, wValue 0, wIndex 7, wLength 255: STALL
SETUP C0 01 00 00 00 00 0C 00 производитель IN: bRequest 0x01, wValue 0, wIndex 0, wLength 12: STALL
SETUP 00 09 01 00 00 00 00 00 SET_CONFIGURATION(1): состояние принято
EP1R 0x3021: точка 1 открыта
SETUP C0 7F 00 00 00 00 01 00 производитель IN: bRequest 0x7F, wValue 0, wIndex 0, wLength 1: STALL
SETUP 40 02 02 00 00 00 00 00 производитель OUT: bRequest 0x02, wValue 2, wIndex 0, wLength 0: STALL
Запрос с wIndex 8 (MS_OS_20_SET_ALT_ENUMERATION в Zephyr) — отказ:
bAltEnumCode = 0, второго варианта нет. Набор, запрошенный на 10 байт, —
ровно заголовок. Запрос на 255 байт даёт все 162: короткий третий пакет
(34 байта) сам говорит хосту «конец». GET_INFO до настройки — отказ, после
SET_CONFIGURATION — 12 байт (20д.6). Строку 0xEE прибор не отдаёт:
MS OS 1.0 ему не нужен.
20д.5 Драйвер F103: память пакетов и регистр EPnR
У F103 не OTG, как у F407 и U585, а свой блок USB FS (RM0008, глава 23). Буферы точек лежат в памяти пакетов на 512 байт (раздел 20.3). Процессор видит её 16-битными словами с шагом 4 байта: локальный адрес умножается на два (23.5.3). В начале памяти — таблица буферов, по 8 байт на точку: адрес и счётчик передачи, адрес и счётчик приёма. Дальше у нас — буферы по 64 байта: 0x40 и 0x80 у точки 0, 0xC0 и 0x100 у точки 1.
#define BT_ADDR_TX(n) PMA16(8u * (n))
#define BT_COUNT_TX(n) PMA16(8u * (n) + 2u)
#define BT_ADDR_RX(n) PMA16(8u * (n) + 4u)
#define BT_COUNT_RX(n) PMA16(8u * (n) + 6u)
#define BUF_TX(n) (0x40u + 0x80u * (n))
#define BUF_RX(n) (0x80u + 0x80u * (n))
static void pma_write(uint16_t a, const uint8_t *p, uint16_t n)
{
for (uint16_t i = 0; i < n; i += 2) {
uint16_t w = p[i];
if (i + 1u < n) w |= (uint16_t)(p[i + 1] << 8);
PMA16(a + i) = w;
}
}
Главная ловушка — регистр точки EPnR. Его поля трёх видов (23.5.2):
STAT_RX, STAT_TX, DTOG_RX, DTOG_TX меняются записью единицы
(запись нуля их не трогает); флаги CTR_RX и CTR_TX сбрасываются записью
нуля; тип, EP_KIND и номер — обычные. Чтение-изменение-запись здесь
ломает всё: прочитанная единица в STAT при записи перевернёт бит. Поэтому
нужное значение получают исключающим ИЛИ, а флаги CTR пишут единицами:
/* Запись в EPnR без чтения-изменения-записи: STAT и DTOG меняются записью 1,
* флаги CTR сбрасываются записью 0. «Ничего не менять» — 0 в переключатели
* и 1 во флаги CTR. Здесь в поля mask ставятся значения want. */
static void ep_set(uint32_t n, uint16_t mask, uint16_t want)
{
uint16_t r = (uint16_t)USB_RD(R_EPR(n));
USB_WR(R_EPR(n), (r & EP_KEEP) | EP_CTR_RX | EP_CTR_TX | ((r ^ want) & mask));
}
static void ep_clear_ctr(uint32_t n, uint16_t flag)
{
uint16_t r = (uint16_t)USB_RD(R_EPR(n));
USB_WR(R_EPR(n), ((r & EP_KEEP) | EP_CTR_RX | EP_CTR_TX) & ~flag);
}
Размер приёмного буфера пишется в счётчик приёма блоками: до 62 байт —
по 2 байта, дальше — по 32 (таблица 177; 64 байта — BL_SIZE = 1,
NUM_BLOCK = 1):
/* Размер приёмного буфера в COUNTn_RX: до 62 байт — блоки по 2, дальше — по 32 (табл. 177) */
static uint16_t rx_count_field(uint16_t mps)
{
return mps > 62u ? (uint16_t)(0x8000u | (mps / 32u - 1u) << 10) : (uint16_t)((mps / 2u) << 10);
}
Открытие точки: тип и номер — прямой записью, остальное — через ep_set.
Первый пакет массовой точки — DATA0, поэтому DTOG сбрасывается вместе
с состоянием:
void drv_ep_open(uint8_t addr, uint8_t type, uint16_t mps)
{
static const uint16_t kind[4] = { 0x0200u, 0x0400u, 0x0000u, 0x0600u }; /* упр., изохр., масс., прер. */
uint32_t n = addr & 0x0Fu;
USB_WR(R_EPR(n), EP_CTR_RX | EP_CTR_TX | kind[type & 3u] | n); /* тип и номер */
if (addr & 0x80u) {
BT_ADDR_TX(n) = BUF_TX(n);
BT_COUNT_TX(n) = 0;
ep_set(n, EP_STAT_TX | EP_DTOG_TX, TX_NAK); /* первый пакет — DATA0 */
} else {
BT_ADDR_RX(n) = BUF_RX(n);
BT_COUNT_RX(n) = rx_count_field(mps);
ep_set(n, EP_STAT_RX | EP_DTOG_RX, RX_NAK);
}
}
Адрес — после стадии состояния. У OTG адрес пишут в регистр сразу по
SETUP: так велит RM0090 (34.17.5, «Endpoint initialization on SetAddress
command»), так сделано в 198ш. У F103 наоборот: пока хост не забрал пустой
пакет состояния, устройство должно отвечать по адресу 0. Драйвер
запоминает адрес и пишет его в DADDR по CTR_TX точки 0:
void drv_set_address(uint8_t addr)
{
dev_addr = addr; /* в DADDR — только после стадии состояния */
addr_pending = 1;
}
void usb_f103_irq(void)
{
uint16_t istr;
while ((istr = (uint16_t)USB_RD(R_ISTR)) & (ISTR_CTR | ISTR_RESET)) {
if (istr & ISTR_RESET) {
USB_WR(R_ISTR, (uint16_t)~ISTR_RESET); /* флаги ISTR сбрасываются нулём */
bus_reset();
continue;
}
uint32_t n = istr & ISTR_EP_ID;
uint16_t r = (uint16_t)USB_RD(R_EPR(n));
if (r & EP_CTR_RX) {
uint16_t cnt = BT_COUNT_RX(n) & 0x3FFu;
if (cnt > sizeof rxbuf) cnt = sizeof rxbuf;
pma_read((uint16_t)BUF_RX(n), rxbuf, cnt);
ep_clear_ctr(n, EP_CTR_RX);
if (n == 0) {
if (r & EP_SETUP) {
ep0_stalled = 0;
usb_setup(rxbuf);
} else {
usb_ep0_out(rxbuf, cnt);
}
if (!ep0_stalled) ep_set(0, EP_STAT_RX, RX_VALID);
} else if (usb_ep_out((uint8_t)n, rxbuf, cnt)) {
ep_set(n, EP_STAT_RX, RX_VALID);
}
}
if (r & EP_CTR_TX) {
ep_clear_ctr(n, EP_CTR_TX);
if (n == 0) {
if (addr_pending) { /* стадия состояния SET_ADDRESS прошла */
addr_pending = 0;
USB_WR(R_DADDR, DADDR_EF | dev_addr);
}
usb_ep0_in_done();
} else {
usb_ep_in_done((uint8_t)(0x80u | n));
}
}
}
}
Включение — по 23.4.2: снять PDWN, подождать запуска приёмопередатчика
(tSTARTUP — до 1 мкс, таблица 43 DocID13587), снять FRES, сбросить флаги.
/* Включение: PDWN снять, подождать запуска приёмопередатчика (tSTARTUP до 1 мкс),
* снять FRES, сбросить ложные флаги. Такт USB и APB1 к этому моменту включены. */
void usb_f103_init(void)
{
USB_WR(R_CNTR, CNTR_FRES);
for (volatile uint32_t i = 0; i < 100u; i++) { }
USB_WR(R_CNTR, 0);
USB_WR(R_ISTR, 0);
USB_WR(R_CNTR, CNTR_CTRM | CNTR_RESETM);
}
Модель блока. На компьютере драйвер собран вместе с моделью блока USB
FS: регистры, память пакетов и транзакции SETUP/IN/OUT ведут себя так, как
их описывает RM0008. Переключатели меняются записью 1, CTR — сбрасывается
нулём. SETUP принимается при STAT_RX = NAK и STALL, но не при поднятом
CTR_RX. Пакет с чужим DATA0/1 подтверждается и выбрасывается. Устройство
с чужим адресом молчит. Хост (host_model.py) ходит к модели как по шине и
следит за DATA0/1 сам. Прогон начинается со сброса шины:
сброс шины: адрес 0, DADDR 0x80, EP0R 0x3220
SETUP 80 06 00 01 00 00 40 00 GET_DESCRIPTOR(DEVICE 0, 64): IN 18 = 18 байт
12 01 01 02 00 00 00 40 09 12 02 00 00 01 01 02 03 01
SETUP 00 05 05 00 00 00 00 00 SET_ADDRESS(5): состояние принято
адрес устройства: 5 (DADDR 0x85)
EP0R = 0x3220: приём разрешён (STAT_RX = 11), передача — NAK (STAT_TX =
10), тип CONTROL (01). Адрес 5 появился в DADDR только после стадии
состояния. Если записать его сразу, как у OTG, хост теряет устройство:
== 6. Ошибка: адрес записан в DADDR сразу, в стадии SETUP
SETUP 80 06 00 01 00 00 40 00 GET_DESCRIPTOR(DEVICE 0, 64): IN 18 = 18 байт
SET_ADDRESS(5): состояние IN: нет ответа; DADDR 0x85 — устройство уже слушает адрес 5
За весь прогон драйвер записал в EPnR 192 раза, и прогон прошёл без расхождений: перевёрнутый бит развёл бы DATA0/1 или состояния точек.
F407. Драйвер OTG_FS — из 198ш.6 с тремя отличиями: адрес блока
0x50000000; VBUS выключается битом NOVBUSSENS (бит 21 GCCFG), а у U585 тот
же бит — VBDEN с обратным смыслом (раздел 20.5); подмены сигнала сессии в
GOTGCTL у F407 нет. Память FIFO — 1,25 КБ, 320 слов (34.2); приёмному
FIFO отдано 64 слова, по расчёту 34.13.1 надо не меньше 47: 10 на SETUP,
1 на Global OUT NAK, два пакета по 64/4 + 1 и по слову на каждую точку OUT.
/* Ядро OTG_FS: такт AHB2 и выводы PA11/PA12 к этому моменту включены (main_f407.c) */
void otg_f407_init(void)
{
REG(R_GAHBCFG) &= ~GAHBCFG_GINT;
REG(R_GUSBCFG) |= GUSBCFG_PHYSEL; /* встроенный приёмопередатчик FS */
while (!(REG(R_GRSTCTL) & GRSTCTL_AHBIDL)) { }
REG(R_GRSTCTL) |= GRSTCTL_CSRST; /* сброс ядра */
while (REG(R_GRSTCTL) & GRSTCTL_CSRST) { }
REG(R_GCCFG) = GCCFG_PWRDWN | GCCFG_NOVBUSSENS; /* приёмопередатчик включён, VBUS не следим */
REG(R_GUSBCFG) = (REG(R_GUSBCFG) & ~GUSBCFG_TRDT_Msk) | GUSBCFG_FDMOD | 6u << GUSBCFG_TRDT_Pos;
REG(R_DCTL) |= DCTL_SDIS; /* пока не готовы — подтяжка D+ снята */
REG(R_PCGCCTL) = 0;
REG(R_DCFG) |= DCFG_DSPD_FS;
fifo_setup();
flush_fifos();
REG(R_DIEPMSK) = EPINT_XFRC;
REG(R_DOEPMSK) = EPINT_XFRC | EPINT_STUP;
REG(R_DAINTMSK) = 0;
REG(R_GINTSTS) = 0xBFFFFFFFu;
REG(R_GINTMSK) = GINT_USBRST | GINT_ENUMDNE | GINT_RXFLVL | GINT_IEPINT | GINT_OEPINT;
REG(R_GAHBCFG) |= GAHBCFG_GINT;
REG(R_DCTL) &= ~DCTL_SDIS; /* подтяжка D+: хост видит устройство */
}
Смещения и биты обоих драйверов сверены с заголовками CMSIS при сборке:
selfcheck_f103.c и selfcheck_f407.c состоят из _Static_assert.
20д.6 Программа на ПК, эхо и размеры
Эхо на стороне прибора — две функции. Пока ответ не забран, новый пакет OUT не принимается: хост получит NAK и подождёт.
/* Эхо: принятый пакет, «посоленный» ключом, уходит обратно в точку IN.
* Пока ответ не забран хостом, новых пакетов OUT не принимаем — хост получит NAK. */
int usb_ep_out(uint8_t addr, const uint8_t *p, uint16_t n)
{
if (addr != EP_OUT || app.in_busy) return 0;
for (uint16_t i = 0; i < n; i++)
app.pkt[i] = p[i] ^ app.xor_key;
app.rx_bytes += n;
app.in_len = n;
app.in_busy = 1;
(void)drv_ep_tx(EP_IN, app.pkt, n); /* точка IN свободна: за этим следит in_busy */
return 0; /* приём откроет usb_ep_in_done */
}
void usb_ep_in_done(uint8_t addr)
{
if (addr != EP_IN || !app.in_busy) return;
app.tx_bytes += app.in_len;
app.in_busy = 0;
drv_ep_rx_arm(EP_OUT);
}
Программа на ПК (vend_pc.py) обращается к прибору тремя вызовами в
духе pyusb: ctrl_transfer, write, read. На настоящем приборе объект
устройства даёт pyusb поверх libusb; в проверке — модель хоста с теми же
тремя методами.
def get_info(dev):
b = bytes(dev.ctrl_transfer(REQ_VENDOR_IN, VREQ_GET_INFO, 0, 0, 12))
return {"version": "%d.%02d" % (b[1], b[0]), "led": b[2], "xor": b[3],
"rx": int.from_bytes(b[4:8], "little"), "tx": int.from_bytes(b[8:12], "little")}
def set_xor(dev, key):
dev.ctrl_transfer(REQ_VENDOR_OUT, VREQ_SET_XOR, 0, 0, bytes([key]))
def echo(dev, data):
"""Отправить данные пакетами по 64 байта; на каждый пакет устройство отвечает своим."""
back = b""
for i in range(0, len(data), 64):
chunk = data[i:i + 64]
dev.write(EP_OUT, chunk)
back += bytes(dev.read(EP_IN, 64))
return back
Прогон программы поверх модели F103:
SETUP C0 01 00 00 00 00 0C 00 производитель IN: bRequest 0x01, wValue 0, wIndex 0, wLength 12: IN 12 = 12 байт
состояние: {'version': '1.00', 'led': 0, 'xor': 0, 'rx': 0, 'tx': 0}
SETUP 40 02 01 00 00 00 00 00 производитель OUT: bRequest 0x02, wValue 1, wIndex 0, wLength 0: состояние принято
SETUP 40 03 00 00 00 00 01 00 производитель OUT: bRequest 0x03, wValue 0, wIndex 0, wLength 1: OUT 1 байт, состояние принято
эхо: отправлено 200, принято 200, совпало с ожидаемым: да
SETUP C0 01 00 00 00 00 0C 00 производитель IN: bRequest 0x01, wValue 0, wIndex 0, wLength 12: IN 12 = 12 байт
состояние: {'version': '1.00', 'led': 1, 'xor': 90, 'rx': 200, 'tx': 200}
светодиод: 1, вызовов board_led: 1
программа на ПК: работает
200 байт ушли четырьмя пакетами (64 + 64 + 64 + 8) и вернулись теми же
байтами, сложенными по XOR с ключом 0x5A. Светодиод включён одним
вызовом board_led.
Поток и остановка точки:
два пакета OUT без чтения: первый ACK, второй NAK
IN: 64 байт, первый байт 0x5B
повтор второго: ACK, IN 64 байт, первый байт 0x58
IN без запроса: NAK
SETUP 02 03 00 00 01 00 00 00 SET_FEATURE(ENDPOINT_HALT, точка 0x01): состояние принято
OUT в остановленную точку: STALL
SETUP 82 00 00 00 01 00 02 00 GET_STATUS: IN 2 = 2 байт
GET_STATUS точки 0x01: 01 00
SETUP 02 01 00 00 01 00 00 00 CLEAR_FEATURE(ENDPOINT_HALT, точка 0x01): состояние принято
после CLEAR_FEATURE: OUT ACK, IN 1 байт 0x5E, EP1R 0x7061
SETUP C0 01 00 00 00 00 0C 00 производитель IN: bRequest 0x01, wValue 0, wIndex 0, wLength 12: IN 12 = 12 байт
состояние: {'version': '1.00', 'led': 1, 'xor': 90, 'rx': 329, 'tx': 329}
записей в EPnR драйвером за прогон: 192
- второй пакет без чтения получил NAK: прибор держит точку OUT закрытой, пока ответ лежит в буфере IN. Хост повторил его после чтения — ACK;
SET_FEATURE(ENDPOINT_HALT)ставит точку в STALL,GET_STATUSэто показывает (01 00). ПослеCLEAR_FEATUREобе стороны начинают с DATA0: драйвер сбрасываетDTOG_RX, хост — свой счётчик. Если хост забудет, первый пакет пропадёт молча — подтверждён, но выброшен;- счётчики сошлись: 200 + 64 + 64 + 1 = 329 байт туда и обратно.
В Windows по GUID прибор находят функциями SetupAPI: SetupDiGetClassDevsW
с флагами DIGCF_PRESENT | DIGCF_DEVICEINTERFACE, затем
SetupDiEnumDeviceInterfaces и SetupDiGetDeviceInterfaceDetailW — путь
устройства; файл по этому пути открывают и передают в WinUsb_Initialize.
Дальше — WinUsb_ControlTransfer, WinUsb_WritePipe, WinUsb_ReadPipe,
WinUsb_ResetPipe (SetupAPI.h, winusb.h Windows SDK 10.0.19041). Такая
программа в разделе не писалась и не запускалась.
Размеры — arm-none-eabi-gcc 12.2, -Os, без библиотеки HAL:
| Файл | Cortex-M3 (F103): код / данные / ОЗУ | Cortex-M4 (F407): код / данные / ОЗУ |
|---|---|---|
vend_desc.c — дескрипторы и строки |
441 / 25 / 0 | 441 / 25 / 0 |
vend_core.c — точка 0, запросы, эхо |
948 / 0 / 168 | 948 / 0 / 168 |
драйвер: usb_f103.c / otg_f407.c |
974 / 0 / 67 | 1090 / 0 / 148 |
main_f103.c / main_f407.c — такты, выводы, таблица векторов |
406 / 0 / 0 | 718 / 0 / 0 |
| образ целиком | 3160 / 28 / 236 | 3664 / 28 / 316 |
Все четыре дескриптора в образе F103 — 18 + 32 + 33 + 162 = 245 байт Flash. Прибор на F103 целиком меньше 3,5 КБ, на F407 — около 3,6 КБ: без библиотеки ST и без RTOS.
20д.7 Грабли
WinUSB не встаёт, хотя набор в прошивке есть. Проверьте bcdUSB.
С версией 2.00 хост BOS не спрашивает (20д.3): набор не
запрошен ни разу.
Хост просит набор и получает обрывок. Длина в BOS, wTotalLength в
заголовке набора и сумма записей разошлись. Держите все длины в макросах и
сверяйте размер массивов через _Static_assert: недостающий байт в
массиве с заданной длиной компилятор молча заполнит нулём.
Вместо набора — ответ своей команды. Свой запрос производителя взял
тот же bRequest, что bMS_VendorCode, и не смотрит на wIndex. Разведите
коды; набор отдавайте только на wIndex 7.
Набор не отдаётся при опознании. Обработчик требует SET_CONFIGURATION, а хост просит набор раньше. MS OS — в любом состоянии, свои команды — после.
GUID не находится программой. Строка без фигурных скобок, не в UTF-16LE или без второго нуля в конце списка REG_MULTI_SZ. У нас 38 знаков в скобках и 4 нулевых байта в конце.
F103 перестаёт отвечать после SET_ADDRESS. Адрес записан в DADDR в
стадии SETUP: пустой пакет состояния хост шлёт ещё на адрес 0, а прибор
слушает уже 5 (20д.5, «нет ответа»). У OTG — наоборот, адрес пишут сразу.
Точки F103 «сходят с ума» после записи в EPnR. Чтение-изменение-запись
переворачивает биты STAT и DTOG и сбрасывает чужие флаги CTR. Только
запись через исключающее ИЛИ с маской и с единицами в CTR.
После снятия остановки пропал первый пакет. Хост или прибор не
вернулся к DATA0. После CLEAR_FEATURE(ENDPOINT_HALT) — DATA0 у обоих.
Прибор на F407 не виден, хотя код с U585 работал. Бит 21 GCCFG: у
U585 это VBDEN (0 — не следить за VBUS), у F407 — NOVBUSSENS (1 — не
следить). Перенос строки «как есть» включает контроль VBUS.
Подписи и кэш Windows. Как Windows запоминает ответ на MS OS-дескрипторы
и нужно ли менять bcdDevice или PID после правки набора, в разделе не
проверялось. На макете надёжнее не трогать набор после первого
подключения к ПК без смены PID.
20д.8 Итоги
- Свой класс 0xFF с двумя массовыми точками по 64 байта: дескриптор
устройства 18 байт с
bcdUSB2.01, конфигурация 32 байта. - BOS 33 байта: корень 5 + платформенная возможность MS OS 2.0 на 28 байт (UUID D8DD60DF-4589-4CC7-9CD2-659D9E648A9F, код 0x20, набор 162 байта).
- Набор MS OS 2.0 — 162 байта: заголовок 10 + совместимый ID «WINUSB» 20 +
свойство
DeviceInterfaceGUIDs132 (имя 42 байта, GUID 80 байт). Хост получает его запросом C0 20 сwIndex7, пакетами 64 + 64 + 34. Набор и BOS побайтно совпали со структурами Zephyr 4.4.2. - MS OS 1.0 (candleLight): строка 0xEE и запросы
wIndex4 и 5, всего 228 байт против 195 у MS OS 2.0. - Драйвер F103: память пакетов — адрес умножается на 2, EPnR — запись через XOR и единицы в CTR, адрес — после стадии состояния. На модели блока прошли опознание, отказы, эхо 200 байт, NAK потока и остановка точки; 192 записи в EPnR, расхождений нет.
- Драйвер F407 — OTG из 198ш с
NOVBUSSENS; RX FIFO 64 слова при расчётных 47. - Образ целиком: F103 — 3160 байт кода, 28 данных, 236 ОЗУ; F407 — 3664, 28 и 316.
Полные исходники
vend.h — общий заголовок устройства: точки, запросы, длины дескрипторов.
/* vend.h — своё USB-устройство без драйвера: класс 0xFF, две массовые точки,
* BOS с возможностью MS OS 2.0 и совместимый ID «WINUSB». Раздел 20д.
* Общая часть без железа: дескрипторы, точка 0, эхо. Драйверы — usb_f103.c, otg_f407.c. */
#ifndef VEND_H
#define VEND_H
#include <stdint.h>
#ifndef USB_BCD_USB
#define USB_BCD_USB 0x0201u /* 2.01: только у такого устройства хост спрашивает BOS */
#endif
#define USB_VID 0x1209u /* заглушки для макета: в изделии — свои номера */
#define USB_PID 0x0002u
#define EP0_SIZE 64u
#define EP_OUT 0x01u /* массовая OUT, 64 байта */
#define EP_IN 0x81u /* массовая IN, 64 байта */
#define BULK_SIZE 64u
#define EP_TYPE_BULK 2u
#define MS_VENDOR_CODE 0x20u /* bRequest, которым хост просит набор MS OS 2.0 */
#define MS_OS_20_INDEX 7u /* wIndex того же запроса */
#define VREQ_GET_INFO 0x01u /* свои запросы: IN, 12 байт состояния */
#define VREQ_SET_LED 0x02u /* без данных: wValue = 0 или 1 */
#define VREQ_SET_XOR 0x03u /* OUT, 1 байт: им «солится» эхо */
#define INFO_SIZE 12u
#define DEVICE_DESC_SIZE 18u
#define CONFIG_DESC_SIZE 32u
#define BOS_DESC_SIZE 33u
#define MSOS20_SET_SIZE 162u
#define LE16(x) ((x) & 0xFFu), (((x) >> 8) & 0xFFu)
/* длины массивов — в макросах выше; совпадение проверяет _Static_assert в vend_desc.c */
extern const uint8_t usb_device_desc[];
extern const uint8_t usb_config_desc[];
extern const uint8_t usb_bos_desc[];
extern const uint8_t usb_msos20_set[];
uint16_t usb_string_desc(uint8_t index, uint8_t *buf); /* длина или 0, если строки нет */
void usb_serial_from_uid(const uint32_t uid[3]); /* 96 бит номера кристалла -> 24 знака */
/* --- что ядро просит у драйвера (usb_f103.c, otg_f407.c; в модели — usb_f103.c + usbfs_model.c) --- */
void drv_ep0_tx(const uint8_t *p, uint16_t n); /* один пакет IN точки 0, n <= 64 */
void drv_ep0_stall(void);
void drv_set_address(uint8_t addr); /* вызывается в стадии SETUP */
void drv_ep_open(uint8_t addr, uint8_t type, uint16_t mps);
void drv_ep_close(uint8_t addr);
int drv_ep_tx(uint8_t addr, const uint8_t *p, uint16_t n); /* 0 — пакет взят, -1 — точка занята */
void drv_ep_rx_arm(uint8_t addr); /* снова принимать OUT */
void drv_ep_stall(uint8_t addr, int on);
/* --- что драйвер сообщает ядру (vend_core.c) --- */
void usb_reset(void);
void usb_setup(const uint8_t s[8]);
void usb_ep0_out(const uint8_t *p, uint16_t n);
void usb_ep0_in_done(void);
int usb_ep_out(uint8_t addr, const uint8_t *p, uint16_t n); /* 1 — можно сразу принимать дальше */
void usb_ep_in_done(uint8_t addr);
uint8_t usb_configured(void);
void board_led(uint8_t on); /* светодиод платы */
#endif
vend_desc.c — дескрипторы по байтам: устройство, конфигурация, BOS, MS OS 2.0, строки.
/* vend_desc.c — дескрипторы устройства по байтам: устройство, конфигурация, BOS,
* набор MS OS 2.0 и строки. Раздел 20д. Длины, записанные внутри дескрипторов,
* берутся из макросов vend.h, а _Static_assert в конце сверяет их с числом байт. */
#include "vend.h"
#define U16(ch) (ch), 0x00 /* знак UTF-16LE */
const uint8_t usb_device_desc[] = {
18, 0x01, /* bLength, DEVICE */
LE16(USB_BCD_USB), /* bcdUSB 2.01: у устройства есть BOS */
0x00, 0x00, 0x00, /* класс, подкласс, протокол — смотри интерфейс */
EP0_SIZE, /* bMaxPacketSize0 */
LE16(USB_VID),
LE16(USB_PID),
0x00, 0x01, /* bcdDevice 1.00 */
1, 2, 3, /* строки: изготовитель, изделие, серийный номер */
1 /* одна конфигурация */
};
const uint8_t usb_config_desc[] = {
/* конфигурация */
9, 0x02, LE16(CONFIG_DESC_SIZE), /* wTotalLength = 32 */
1, /* bNumInterfaces */
1, /* bConfigurationValue */
0, /* iConfiguration */
0x80, /* bmAttributes: питание от шины */
50, /* bMaxPower: 50 x 2 мА = 100 мА */
/* интерфейс 0: свой класс */
9, 0x04, 0, 0, 2, /* номер 0, настройка 0, две точки */
0xFF, 0x00, 0x00, /* класс 0xFF «у производителя», подкласс и протокол — свои */
0, /* iInterface */
/* точки */
7, 0x05, EP_OUT, EP_TYPE_BULK, LE16(BULK_SIZE), 0,
7, 0x05, EP_IN, EP_TYPE_BULK, LE16(BULK_SIZE), 0
};
const uint8_t usb_bos_desc[] = {
5, 0x0F, /* bLength, BOS */
LE16(BOS_DESC_SIZE), /* wTotalLength = 33: корень и все возможности */
1, /* bNumDeviceCaps */
/* платформенная возможность MS OS 2.0 */
28, 0x10, 0x05, /* bLength, DEVICE_CAPABILITY, PLATFORM */
0x00, /* bReserved */
0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, /* UUID D8DD60DF-4589-4CC7- */
0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, /* 9CD2-659D9E648A9F */
0x00, 0x00, 0x00, 0x0A, /* dwWindowsVersion 0x0A000000: Windows 10 */
LE16(MSOS20_SET_SIZE), /* wMSOSDescriptorSetTotalLength = 162 */
MS_VENDOR_CODE, /* bMS_VendorCode: этим bRequest набор и попросят */
0x00 /* bAltEnumCode: второго варианта нет */
};
const uint8_t usb_msos20_set[] = {
/* заголовок набора */
0x0A, 0x00, /* wLength = 10 */
0x00, 0x00, /* MS_OS_20_SET_HEADER_DESCRIPTOR */
0x00, 0x00, 0x00, 0x0A, /* dwWindowsVersion 0x0A000000 */
LE16(MSOS20_SET_SIZE), /* wTotalLength = 162: весь набор */
/* совместимый ID: какой драйвер ставить */
0x14, 0x00, /* wLength = 20 */
0x03, 0x00, /* MS_OS_20_FEATURE_COMPATIBLE_ID */
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, /* CompatibleID */
0, 0, 0, 0, 0, 0, 0, 0, /* SubCompatibleID: нет */
/* свойство реестра: GUID, по которому программа найдёт устройство */
0x84, 0x00, /* wLength = 132 */
0x04, 0x00, /* MS_OS_20_FEATURE_REG_PROPERTY */
0x07, 0x00, /* wPropertyDataType: REG_MULTI_SZ, список строк */
0x2A, 0x00, /* wPropertyNameLength = 42 */
U16('D'), U16('e'), U16('v'), U16('i'), U16('c'), U16('e'),
U16('I'), U16('n'), U16('t'), U16('e'), U16('r'), U16('f'), U16('a'), U16('c'), U16('e'),
U16('G'), U16('U'), U16('I'), U16('D'), U16('s'), 0x00, 0x00,
0x50, 0x00, /* wPropertyDataLength = 80 */
U16('{'), U16('9'), U16('D'), U16('E'), U16('E'), U16('3'), U16('3'), U16('1'), U16('4'),
U16('-'), U16('0'), U16('6'), U16('2'), U16('6'),
U16('-'), U16('4'), U16('A'), U16('2'), U16('1'),
U16('-'), U16('B'), U16('C'), U16('E'), U16('4'),
U16('-'), U16('9'), U16('4'), U16('5'), U16('6'), U16('A'), U16('E'), U16('2'), U16('2'),
U16('7'), U16('C'), U16('2'), U16('8'), U16('}'),
0x00, 0x00, 0x00, 0x00 /* конец строки и конец списка */
};
_Static_assert(sizeof usb_device_desc == DEVICE_DESC_SIZE, "дескриптор устройства: не 18 байт");
_Static_assert(sizeof usb_config_desc == CONFIG_DESC_SIZE, "конфигурация: wTotalLength не сходится");
_Static_assert(sizeof usb_bos_desc == BOS_DESC_SIZE, "BOS: wTotalLength не сходится");
_Static_assert(sizeof usb_msos20_set == MSOS20_SET_SIZE, "набор MS OS 2.0: wTotalLength не сходится");
static char serial[25] = "000000000000000000000000";
void usb_serial_from_uid(const uint32_t uid[3])
{
static const char hex[] = "0123456789ABCDEF";
for (int w = 0; w < 3; w++)
for (int i = 0; i < 8; i++)
serial[w * 8 + i] = hex[(uid[w] >> (28 - 4 * i)) & 0xFu];
}
uint16_t usb_string_desc(uint8_t index, uint8_t *buf)
{
static const char *const text[] = { "STM32 Handbook", "WinUSB echo 20d", serial };
if (index == 0) { /* список языков: 0x0409, английский (США) */
buf[0] = 4; buf[1] = 0x03; buf[2] = 0x09; buf[3] = 0x04;
return 4;
}
if (index > 3) return 0; /* в том числе 0xEE: MS OS 1.0 не отдаём */
const char *s = text[index - 1];
uint16_t n = 2;
while (*s && n + 2u <= EP0_SIZE) {
buf[n++] = (uint8_t)*s++;
buf[n++] = 0;
}
buf[0] = (uint8_t)n;
buf[1] = 0x03;
return n;
}
vend_core.c — точка 0, запросы производителя и эхо.
/* vend_core.c — точка 0 (стандартные запросы, BOS, запросы производителя)
* и эхо по массовым точкам. Раздел 20д. Железа не касается: всё через drv_*. */
#include <string.h>
#include "vend.h"
enum { REQ_STALL = -1, REQ_NODATA = 0, REQ_IN = 1, REQ_OUT = 2 };
static struct {
const uint8_t *tx; /* стадия данных IN: что осталось отдать */
uint16_t tx_left;
uint8_t tx_zlp; /* после полных пакетов нужен пустой */
uint8_t setup[8]; /* запрос, чьи данные OUT ещё идут */
uint16_t rx_want, rx_len;
uint8_t buf[64];
uint8_t config;
uint8_t halt_in, halt_out;
} c;
static struct {
uint8_t led, xor_key, in_busy;
uint16_t in_len;
uint32_t rx_bytes, tx_bytes;
uint8_t pkt[BULK_SIZE];
} app;
static void ep0_next(void)
{
uint16_t n = c.tx_left < EP0_SIZE ? c.tx_left : EP0_SIZE;
drv_ep0_tx(c.tx, n);
c.tx += n;
c.tx_left -= n;
}
static void ep0_send(const uint8_t *p, uint16_t len, uint16_t wlength)
{
if (len > wlength) len = wlength;
c.tx = p;
c.tx_left = len;
c.tx_zlp = len < wlength && len != 0 && len % EP0_SIZE == 0;
ep0_next();
}
void usb_ep0_in_done(void)
{
if (c.tx_left) ep0_next();
else if (c.tx_zlp) { c.tx_zlp = 0; drv_ep0_tx(0, 0); }
}
static void ep0_status(void) { drv_ep0_tx(0, 0); }
static int get_descriptor(uint8_t type, uint8_t index, const uint8_t **p, uint16_t *len)
{
switch (type) {
case 0x01: *p = usb_device_desc; *len = DEVICE_DESC_SIZE; return 1;
case 0x02: *p = usb_config_desc; *len = CONFIG_DESC_SIZE; return index == 0;
case 0x03: *p = c.buf; *len = usb_string_desc(index, c.buf); return *len != 0;
case 0x0F: *p = usb_bos_desc; *len = BOS_DESC_SIZE; return index == 0;
default: return 0; /* 0x06 DEVICE_QUALIFIER: только полная скорость — отказ */
}
}
static uint8_t *halt_flag(uint8_t ep)
{
if (!c.config) return 0;
if (ep == EP_IN) return &c.halt_in;
if (ep == EP_OUT) return &c.halt_out;
return 0;
}
static void set_config(uint8_t v)
{
if (c.config) {
drv_ep_close(EP_OUT);
drv_ep_close(EP_IN);
}
c.config = v;
c.halt_in = c.halt_out = 0;
app.in_busy = 0;
if (v) {
drv_ep_open(EP_OUT, EP_TYPE_BULK, BULK_SIZE);
drv_ep_open(EP_IN, EP_TYPE_BULK, BULK_SIZE);
drv_ep_rx_arm(EP_OUT);
}
}
static int standard_request(const uint8_t s[8], const uint8_t **p, uint16_t *len)
{
uint8_t rcpt = s[0] & 0x1Fu, req = s[1];
uint16_t wvalue = (uint16_t)(s[2] | s[3] << 8), windex = (uint16_t)(s[4] | s[5] << 8);
uint8_t *h;
switch (req) {
case 0x00: /* GET_STATUS */
if (rcpt == 2 && (h = halt_flag((uint8_t)windex)) != 0) c.buf[0] = *h;
else if (rcpt == 2 && (windex & 0x7Fu) != 0) return REQ_STALL;
else c.buf[0] = 0; /* питание от шины, будить хост не умеем */
c.buf[1] = 0;
*p = c.buf; *len = 2;
return REQ_IN;
case 0x01: case 0x03: /* CLEAR_FEATURE, SET_FEATURE: только ENDPOINT_HALT */
if (rcpt != 2 || wvalue != 0 || (h = halt_flag((uint8_t)windex)) == 0) return REQ_STALL;
*h = req == 0x03;
drv_ep_stall((uint8_t)windex, *h);
if (!*h) { /* остановку сняли: эхо начинается с чистого листа */
app.in_busy = 0;
if (!c.halt_out) drv_ep_rx_arm(EP_OUT);
}
return REQ_NODATA;
case 0x05: /* SET_ADDRESS: когда писать в регистр, решает драйвер */
drv_set_address((uint8_t)(wvalue & 0x7Fu));
return REQ_NODATA;
case 0x06: /* GET_DESCRIPTOR */
return get_descriptor(s[3], s[2], p, len) ? REQ_IN : REQ_STALL;
case 0x08: /* GET_CONFIGURATION */
c.buf[0] = c.config;
*p = c.buf; *len = 1;
return REQ_IN;
case 0x09: /* SET_CONFIGURATION */
if (wvalue > 1) return REQ_STALL;
set_config((uint8_t)wvalue);
return REQ_NODATA;
case 0x0A: /* GET_INTERFACE: запасных настроек нет */
if (!c.config || windex != 0) return REQ_STALL;
c.buf[0] = 0;
*p = c.buf; *len = 1;
return REQ_IN;
case 0x0B: /* SET_INTERFACE */
return (c.config && windex == 0 && wvalue == 0) ? REQ_NODATA : REQ_STALL;
default:
return REQ_STALL;
}
}
static void put32(uint8_t *d, uint32_t v)
{
d[0] = (uint8_t)v; d[1] = (uint8_t)(v >> 8); d[2] = (uint8_t)(v >> 16); d[3] = (uint8_t)(v >> 24);
}
/* Запросы производителя к устройству. Набор MS OS 2.0 отдаётся только на свой
* bRequest и wIndex = 7 и в любом состоянии — хост просит его до SET_CONFIGURATION.
* Свои команды — только в настроенном устройстве. */
static int vendor_request(const uint8_t s[8], const uint8_t **p, uint16_t *len)
{
uint8_t in = s[0] & 0x80u;
uint16_t wvalue = (uint16_t)(s[2] | s[3] << 8), windex = (uint16_t)(s[4] | s[5] << 8);
if (s[1] == MS_VENDOR_CODE) {
if (!in || windex != MS_OS_20_INDEX) return REQ_STALL;
*p = usb_msos20_set; *len = MSOS20_SET_SIZE;
return REQ_IN;
}
if (!c.config) return REQ_STALL;
switch (s[1]) {
case VREQ_GET_INFO: /* версия, светодиод, ключ, счётчики байт */
if (!in) return REQ_STALL;
c.buf[0] = 0x00; c.buf[1] = 0x01; /* версия протокола 1.00 */
c.buf[2] = app.led;
c.buf[3] = app.xor_key;
put32(c.buf + 4, app.rx_bytes);
put32(c.buf + 8, app.tx_bytes);
*p = c.buf; *len = INFO_SIZE;
return REQ_IN;
case VREQ_SET_LED:
if (in || wvalue > 1) return REQ_STALL;
app.led = (uint8_t)wvalue;
board_led(app.led);
return REQ_NODATA;
case VREQ_SET_XOR: /* байт ключа придёт в стадии данных */
return in ? REQ_STALL : REQ_OUT;
default:
return REQ_STALL;
}
}
static int vendor_data(const uint8_t s[8], const uint8_t *d, uint16_t n)
{
if (s[1] != VREQ_SET_XOR || n != 1) return REQ_STALL;
app.xor_key = d[0];
return REQ_NODATA;
}
void usb_setup(const uint8_t s[8])
{
const uint8_t *p = 0;
uint16_t len = 0, wlength = (uint16_t)(s[6] | s[7] << 8);
uint8_t type = (s[0] >> 5) & 3u, rcpt = s[0] & 0x1Fu;
int r = REQ_STALL;
c.rx_want = 0;
if (type == 0) r = standard_request(s, &p, &len);
else if (type == 2 && rcpt == 0) r = vendor_request(s, &p, &len); /* производитель, устройству */
if (r == REQ_IN && (s[0] & 0x80u) && wlength) {
ep0_send(p, len, wlength);
} else if (r == REQ_OUT && !(s[0] & 0x80u) && wlength && wlength <= sizeof c.buf) {
memcpy(c.setup, s, 8); /* данные придут следующими пакетами OUT */
c.rx_len = 0;
c.rx_want = wlength;
} else if (r == REQ_NODATA && !wlength) {
ep0_status();
} else {
drv_ep0_stall();
}
}
void usb_ep0_out(const uint8_t *p, uint16_t n)
{
if (!c.rx_want) return; /* пустой пакет — стадия состояния после IN */
if (n > c.rx_want - c.rx_len) n = (uint16_t)(c.rx_want - c.rx_len);
memcpy(c.buf + c.rx_len, p, n);
c.rx_len = (uint16_t)(c.rx_len + n);
if (c.rx_len < c.rx_want) return;
c.rx_want = 0;
if (vendor_data(c.setup, c.buf, c.rx_len) == REQ_NODATA) ep0_status();
else drv_ep0_stall();
}
void usb_reset(void)
{
memset(&c, 0, sizeof c);
app.in_busy = 0;
}
uint8_t usb_configured(void) { return c.config; }
/* Эхо: принятый пакет, «посоленный» ключом, уходит обратно в точку IN.
* Пока ответ не забран хостом, новых пакетов OUT не принимаем — хост получит NAK. */
int usb_ep_out(uint8_t addr, const uint8_t *p, uint16_t n)
{
if (addr != EP_OUT || app.in_busy) return 0;
for (uint16_t i = 0; i < n; i++)
app.pkt[i] = p[i] ^ app.xor_key;
app.rx_bytes += n;
app.in_len = n;
app.in_busy = 1;
(void)drv_ep_tx(EP_IN, app.pkt, n); /* точка IN свободна: за этим следит in_busy */
return 0; /* приём откроет usb_ep_in_done */
}
void usb_ep_in_done(uint8_t addr)
{
if (addr != EP_IN || !app.in_busy) return;
app.tx_bytes += app.in_len;
app.in_busy = 0;
drv_ep_rx_arm(EP_OUT);
}
usb_f103.c — драйвер USB FS STM32F103 регистрами.
/* usb_f103.c — драйвер USB FS STM32F103 в режиме устройства, регистрами (RM0008, глава 23).
* Раздел 20д. Буферы точек лежат в памяти пакетов (512 байт): процессор видит её
* 16-битными словами через 32 бита, поэтому локальный адрес умножается на два.
* Точка n: передача с 0x40 + 0x80·n, приём с 0x80 + 0x80·n, по 64 байта; таблица — с 0. */
#include "vend.h"
#ifdef HOST_MODEL /* модель на компьютере: регистры — функции модели */
uint16_t usbm_read(uint32_t off);
void usbm_write(uint32_t off, uint16_t v);
extern uint8_t usbm_pma[1024];
#define USB_RD(off) usbm_read(off)
#define USB_WR(off, v) usbm_write((off), (uint16_t)(v))
#define PMA_BASE ((uintptr_t)usbm_pma)
#else
#define USB_RD(off) (*(volatile uint32_t *)(0x40005C00u + (off)))
#define USB_WR(off, v) (*(volatile uint32_t *)(0x40005C00u + (off)) = (uint16_t)(v))
#define PMA_BASE 0x40006000u
#endif
#define PMA16(a) (*(volatile uint16_t *)(PMA_BASE + 2u * (a)))
#define R_EPR(n) (4u * (n))
#define R_CNTR 0x40u
#define R_ISTR 0x44u
#define R_DADDR 0x4Cu
#define R_BTABLE 0x50u
#define CNTR_FRES 0x0001u
#define CNTR_RESETM 0x0400u
#define CNTR_CTRM 0x8000u
#define ISTR_EP_ID 0x000Fu
#define ISTR_RESET 0x0400u
#define ISTR_CTR 0x8000u
#define DADDR_EF 0x0080u
#define EP_CTR_RX 0x8000u /* сбрасывается записью 0 */
#define EP_DTOG_RX 0x4000u /* переключается записью 1 */
#define EP_STAT_RX 0x3000u /* переключается записью 1 */
#define EP_SETUP 0x0800u /* только чтение */
#define EP_TYPE 0x0600u
#define EP_KIND 0x0100u
#define EP_CTR_TX 0x0080u
#define EP_DTOG_TX 0x0040u
#define EP_STAT_TX 0x0030u
#define EP_EA 0x000Fu
#define EP_KEEP (EP_TYPE | EP_KIND | EP_EA)
#define RX_STALL 0x1000u
#define RX_NAK 0x2000u
#define RX_VALID 0x3000u
#define TX_STALL 0x0010u
#define TX_NAK 0x0020u
#define TX_VALID 0x0030u
#define BT_ADDR_TX(n) PMA16(8u * (n))
#define BT_COUNT_TX(n) PMA16(8u * (n) + 2u)
#define BT_ADDR_RX(n) PMA16(8u * (n) + 4u)
#define BT_COUNT_RX(n) PMA16(8u * (n) + 6u)
#define BUF_TX(n) (0x40u + 0x80u * (n))
#define BUF_RX(n) (0x80u + 0x80u * (n))
static uint8_t rxbuf[64];
static uint8_t addr_pending, dev_addr, ep0_stalled;
/* Запись в EPnR без чтения-изменения-записи: STAT и DTOG меняются записью 1,
* флаги CTR сбрасываются записью 0. «Ничего не менять» — 0 в переключатели
* и 1 во флаги CTR. Здесь в поля mask ставятся значения want. */
static void ep_set(uint32_t n, uint16_t mask, uint16_t want)
{
uint16_t r = (uint16_t)USB_RD(R_EPR(n));
USB_WR(R_EPR(n), (r & EP_KEEP) | EP_CTR_RX | EP_CTR_TX | ((r ^ want) & mask));
}
static void ep_clear_ctr(uint32_t n, uint16_t flag)
{
uint16_t r = (uint16_t)USB_RD(R_EPR(n));
USB_WR(R_EPR(n), ((r & EP_KEEP) | EP_CTR_RX | EP_CTR_TX) & ~flag);
}
/* Размер приёмного буфера в COUNTn_RX: до 62 байт — блоки по 2, дальше — по 32 (табл. 177) */
static uint16_t rx_count_field(uint16_t mps)
{
return mps > 62u ? (uint16_t)(0x8000u | (mps / 32u - 1u) << 10) : (uint16_t)((mps / 2u) << 10);
}
static void pma_write(uint16_t a, const uint8_t *p, uint16_t n)
{
for (uint16_t i = 0; i < n; i += 2) {
uint16_t w = p[i];
if (i + 1u < n) w |= (uint16_t)(p[i + 1] << 8);
PMA16(a + i) = w;
}
}
static void pma_read(uint16_t a, uint8_t *p, uint16_t n)
{
for (uint16_t i = 0; i < n; i += 2) {
uint16_t w = PMA16(a + i);
p[i] = (uint8_t)w;
if (i + 1u < n) p[i + 1] = (uint8_t)(w >> 8);
}
}
void drv_ep0_tx(const uint8_t *p, uint16_t n)
{
pma_write(BUF_TX(0), p, n);
BT_COUNT_TX(0) = n;
ep_set(0, EP_STAT_TX, TX_VALID);
}
void drv_ep0_stall(void)
{
ep_set(0, EP_STAT_TX | EP_STAT_RX, TX_STALL | RX_STALL); /* SETUP примется и так */
ep0_stalled = 1;
}
void drv_set_address(uint8_t addr)
{
dev_addr = addr; /* в DADDR — только после стадии состояния */
addr_pending = 1;
}
void drv_ep_open(uint8_t addr, uint8_t type, uint16_t mps)
{
static const uint16_t kind[4] = { 0x0200u, 0x0400u, 0x0000u, 0x0600u }; /* упр., изохр., масс., прер. */
uint32_t n = addr & 0x0Fu;
USB_WR(R_EPR(n), EP_CTR_RX | EP_CTR_TX | kind[type & 3u] | n); /* тип и номер */
if (addr & 0x80u) {
BT_ADDR_TX(n) = BUF_TX(n);
BT_COUNT_TX(n) = 0;
ep_set(n, EP_STAT_TX | EP_DTOG_TX, TX_NAK); /* первый пакет — DATA0 */
} else {
BT_ADDR_RX(n) = BUF_RX(n);
BT_COUNT_RX(n) = rx_count_field(mps);
ep_set(n, EP_STAT_RX | EP_DTOG_RX, RX_NAK);
}
}
void drv_ep_close(uint8_t addr)
{
uint32_t n = addr & 0x0Fu;
if (addr & 0x80u) ep_set(n, EP_STAT_TX, 0);
else ep_set(n, EP_STAT_RX, 0);
}
int drv_ep_tx(uint8_t addr, const uint8_t *p, uint16_t n)
{
uint32_t e = addr & 0x0Fu;
if ((USB_RD(R_EPR(e)) & EP_STAT_TX) == TX_VALID) return -1; /* прошлый пакет не забран */
pma_write((uint16_t)BUF_TX(e), p, n);
BT_COUNT_TX(e) = n;
ep_set(e, EP_STAT_TX, TX_VALID);
return 0;
}
void drv_ep_rx_arm(uint8_t addr)
{
ep_set(addr & 0x0Fu, EP_STAT_RX, RX_VALID);
}
void drv_ep_stall(uint8_t addr, int on)
{
uint32_t n = addr & 0x0Fu;
if (addr & 0x80u) ep_set(n, EP_STAT_TX | (on ? 0u : EP_DTOG_TX), on ? TX_STALL : TX_NAK);
else ep_set(n, EP_STAT_RX | (on ? 0u : EP_DTOG_RX), on ? RX_STALL : RX_NAK);
}
static void bus_reset(void)
{
USB_WR(R_BTABLE, 0);
BT_ADDR_TX(0) = BUF_TX(0);
BT_COUNT_TX(0) = 0;
BT_ADDR_RX(0) = BUF_RX(0);
BT_COUNT_RX(0) = rx_count_field(EP0_SIZE);
USB_WR(R_EPR(0), EP_CTR_RX | EP_CTR_TX | 0x0200u); /* точка 0, управляющая */
ep_set(0, EP_STAT_RX | EP_STAT_TX | EP_DTOG_RX | EP_DTOG_TX, RX_VALID | TX_NAK);
USB_WR(R_DADDR, DADDR_EF); /* адрес 0, функция включена */
addr_pending = ep0_stalled = 0;
usb_reset();
}
/* Включение: PDWN снять, подождать запуска приёмопередатчика (tSTARTUP до 1 мкс),
* снять FRES, сбросить ложные флаги. Такт USB и APB1 к этому моменту включены. */
void usb_f103_init(void)
{
USB_WR(R_CNTR, CNTR_FRES);
for (volatile uint32_t i = 0; i < 100u; i++) { }
USB_WR(R_CNTR, 0);
USB_WR(R_ISTR, 0);
USB_WR(R_CNTR, CNTR_CTRM | CNTR_RESETM);
}
void usb_f103_irq(void)
{
uint16_t istr;
while ((istr = (uint16_t)USB_RD(R_ISTR)) & (ISTR_CTR | ISTR_RESET)) {
if (istr & ISTR_RESET) {
USB_WR(R_ISTR, (uint16_t)~ISTR_RESET); /* флаги ISTR сбрасываются нулём */
bus_reset();
continue;
}
uint32_t n = istr & ISTR_EP_ID;
uint16_t r = (uint16_t)USB_RD(R_EPR(n));
if (r & EP_CTR_RX) {
uint16_t cnt = BT_COUNT_RX(n) & 0x3FFu;
if (cnt > sizeof rxbuf) cnt = sizeof rxbuf;
pma_read((uint16_t)BUF_RX(n), rxbuf, cnt);
ep_clear_ctr(n, EP_CTR_RX);
if (n == 0) {
if (r & EP_SETUP) {
ep0_stalled = 0;
usb_setup(rxbuf);
} else {
usb_ep0_out(rxbuf, cnt);
}
if (!ep0_stalled) ep_set(0, EP_STAT_RX, RX_VALID);
} else if (usb_ep_out((uint8_t)n, rxbuf, cnt)) {
ep_set(n, EP_STAT_RX, RX_VALID);
}
}
if (r & EP_CTR_TX) {
ep_clear_ctr(n, EP_CTR_TX);
if (n == 0) {
if (addr_pending) { /* стадия состояния SET_ADDRESS прошла */
addr_pending = 0;
USB_WR(R_DADDR, DADDR_EF | dev_addr);
}
usb_ep0_in_done();
} else {
usb_ep_in_done((uint8_t)(0x80u | n));
}
}
}
}
otg_f407.c — драйвер OTG_FS STM32F407 регистрами.
/* otg_f407.c — драйвер OTG_FS STM32F407 в режиме устройства, регистрами (RM0090, глава 34).
* Раздел 20д. Тот же драйвер, что для U585 в разделе 198ш, с тремя отличиями F407:
* адрес блока 0x50000000; VBUS выключается битом NOVBUSSENS (бит 21 GCCFG, а у U585
* этот бит — VBDEN с обратным смыслом); подмены сигнала сессии в GOTGCTL нет.
* Каждая передача IN — один пакет не больше 64 байт: он целиком входит в свой FIFO. */
#include "vend.h"
#define OTG_BASE 0x50000000u /* USB_OTG_FS_PERIPH_BASE */
#define REG(off) (*(volatile uint32_t *)(OTG_BASE + (off)))
#define R_GAHBCFG 0x008u
#define R_GUSBCFG 0x00Cu
#define R_GRSTCTL 0x010u
#define R_GINTSTS 0x014u
#define R_GINTMSK 0x018u
#define R_GRXSTSP 0x020u
#define R_GRXFSIZ 0x024u
#define R_DIEPTXF0 0x028u
#define R_GCCFG 0x038u
#define R_DIEPTXF(n) (0x104u + 4u * ((n) - 1u))
#define R_DCFG 0x800u
#define R_DCTL 0x804u
#define R_DIEPMSK 0x810u
#define R_DOEPMSK 0x814u
#define R_DAINT 0x818u
#define R_DAINTMSK 0x81Cu
#define R_DIEPCTL(n) (0x900u + 0x20u * (n))
#define R_DIEPINT(n) (0x908u + 0x20u * (n))
#define R_DIEPTSIZ(n) (0x910u + 0x20u * (n))
#define R_DOEPCTL(n) (0xB00u + 0x20u * (n))
#define R_DOEPINT(n) (0xB08u + 0x20u * (n))
#define R_DOEPTSIZ(n) (0xB10u + 0x20u * (n))
#define R_PCGCCTL 0xE00u
#define R_DFIFO(n) (0x1000u + 0x1000u * (n))
#define GAHBCFG_GINT (1u << 0)
#define GUSBCFG_PHYSEL (1u << 6)
#define GUSBCFG_TRDT_Pos 10
#define GUSBCFG_TRDT_Msk (0xFu << 10)
#define GUSBCFG_FDMOD (1u << 30)
#define GRSTCTL_CSRST (1u << 0)
#define GRSTCTL_RXFFLSH (1u << 4)
#define GRSTCTL_TXFFLSH (1u << 5)
#define GRSTCTL_TXFNUM_Pos 6
#define GRSTCTL_AHBIDL (1u << 31)
#define GINT_RXFLVL (1u << 4)
#define GINT_USBRST (1u << 12)
#define GINT_ENUMDNE (1u << 13)
#define GINT_IEPINT (1u << 18)
#define GINT_OEPINT (1u << 19)
#define GCCFG_PWRDWN (1u << 16)
#define GCCFG_VBUSASEN (1u << 18)
#define GCCFG_VBUSBSEN (1u << 19)
#define GCCFG_NOVBUSSENS (1u << 21)
#define DCFG_DSPD_FS 3u
#define DCFG_DAD_Pos 4
#define DCFG_DAD_Msk (0x7Fu << 4)
#define DCTL_SDIS (1u << 1)
#define DCTL_CGINAK (1u << 8)
#define EPCTL_USBAEP (1u << 15)
#define EPCTL_EPTYP_Pos 18
#define EPCTL_STALL (1u << 21)
#define EPCTL_TXFNUM_Pos 22
#define EPCTL_CNAK (1u << 26)
#define EPCTL_SNAK (1u << 27)
#define EPCTL_SD0PID (1u << 28)
#define EPCTL_EPDIS (1u << 30)
#define EPCTL_EPENA (1u << 31)
#define EPINT_XFRC (1u << 0)
#define EPINT_STUP (1u << 3)
#define TSIZ_PKTCNT_Pos 19
#define TSIZ_STUPCNT_Pos 29
#define RXSTS_EPNUM(s) ((s) & 0xFu)
#define RXSTS_BCNT(s) (((s) >> 4) & 0x7FFu)
#define RXSTS_PKTSTS(s) (((s) >> 17) & 0xFu)
#define PKTSTS_OUT_DATA 2u
#define PKTSTS_SETUP_DATA 6u
#define FIFO_RAM_WORDS 320u /* 1,25 КБ (RM0090, 34.2) */
#define FIFO_RX_WORDS 64u
#define FIFO_TX0_WORDS 16u /* точка 0: пакет 64 байта */
#define FIFO_TX1_WORDS 16u /* массовая IN: пакет 64 байта */
_Static_assert(FIFO_RX_WORDS + FIFO_TX0_WORDS + FIFO_TX1_WORDS <= FIFO_RAM_WORDS, "FIFO не помещаются");
_Static_assert(FIFO_RX_WORDS >= 10u + 1u + 2u * (64u / 4u + 1u) + 2u,
"RX FIFO меньше расчёта RM0090 34.13.1: SETUP, Global OUT NAK, два пакета, по слову на точку OUT");
static uint16_t out_mps[4];
static uint8_t setup_pkt[8];
static uint8_t rx_buf[2][64]; /* принятые пакеты: [0] — точка 0, [1] — массовая OUT */
static uint16_t rx_len[2];
static void fifo_setup(void)
{
REG(R_GRXFSIZ) = FIFO_RX_WORDS;
REG(R_DIEPTXF0) = FIFO_TX0_WORDS << 16 | FIFO_RX_WORDS; /* глубина | начало */
REG(R_DIEPTXF(1)) = FIFO_TX1_WORDS << 16 | (FIFO_RX_WORDS + FIFO_TX0_WORDS);
}
static void ep0_out_arm(void)
{
REG(R_DOEPTSIZ(0)) = 3u << TSIZ_STUPCNT_Pos | 1u << TSIZ_PKTCNT_Pos | EP0_SIZE;
REG(R_DOEPCTL(0)) |= EPCTL_EPENA | EPCTL_CNAK;
}
void drv_ep_open(uint8_t addr, uint8_t type, uint16_t mps)
{
uint8_t n = addr & 0x0Fu;
if (addr & 0x80u) { /* IN: свой FIFO с тем же номером, что у точки */
REG(R_DIEPCTL(n)) = mps | (uint32_t)type << EPCTL_EPTYP_Pos | (uint32_t)n << EPCTL_TXFNUM_Pos
| EPCTL_SD0PID | EPCTL_USBAEP | EPCTL_SNAK;
REG(R_DAINTMSK) |= 1u << n;
} else {
out_mps[n] = mps;
REG(R_DOEPCTL(n)) = mps | (uint32_t)type << EPCTL_EPTYP_Pos | EPCTL_SD0PID | EPCTL_USBAEP | EPCTL_SNAK;
REG(R_DAINTMSK) |= 1u << (16u + n);
}
}
void drv_ep_close(uint8_t addr)
{
uint8_t n = addr & 0x0Fu;
if (addr & 0x80u) {
REG(R_DAINTMSK) &= ~(1u << n);
REG(R_DIEPCTL(n)) = (REG(R_DIEPCTL(n)) & EPCTL_EPENA) ? EPCTL_EPDIS | EPCTL_SNAK : 0;
} else {
REG(R_DAINTMSK) &= ~(1u << (16u + n));
REG(R_DOEPCTL(n)) = (REG(R_DOEPCTL(n)) & EPCTL_EPENA) ? EPCTL_EPDIS | EPCTL_SNAK : 0;
}
}
void drv_ep_rx_arm(uint8_t addr)
{
uint8_t n = addr & 0x0Fu;
REG(R_DOEPTSIZ(n)) = 1u << TSIZ_PKTCNT_Pos | out_mps[n];
REG(R_DOEPCTL(n)) |= EPCTL_EPENA | EPCTL_CNAK;
}
int drv_ep_tx(uint8_t addr, const uint8_t *p, uint16_t len)
{
uint8_t n = addr & 0x0Fu;
if (REG(R_DIEPCTL(n)) & EPCTL_EPENA) return -1; /* прошлый пакет ещё не ушёл */
REG(R_DIEPTSIZ(n)) = 1u << TSIZ_PKTCNT_Pos | len;
REG(R_DIEPCTL(n)) |= EPCTL_EPENA | EPCTL_CNAK;
for (uint16_t i = 0; i < len; i += 4) {
uint32_t w = p[i];
if (i + 1u < len) w |= (uint32_t)p[i + 1] << 8;
if (i + 2u < len) w |= (uint32_t)p[i + 2] << 16;
if (i + 3u < len) w |= (uint32_t)p[i + 3] << 24;
REG(R_DFIFO(n)) = w;
}
return 0;
}
void drv_ep0_tx(const uint8_t *p, uint16_t len) { (void)drv_ep_tx(0x80, p, len); }
void drv_ep0_stall(void)
{
REG(R_DIEPCTL(0)) |= EPCTL_STALL; /* ядро само снимет остановку со следующим SETUP */
REG(R_DOEPCTL(0)) |= EPCTL_STALL;
ep0_out_arm();
}
void drv_ep_stall(uint8_t addr, int on)
{
uint32_t off = (addr & 0x80u) ? R_DIEPCTL(addr & 0x0Fu) : R_DOEPCTL(addr & 0x0Fu);
if (on) REG(off) |= EPCTL_STALL;
else REG(off) = (REG(off) & ~EPCTL_STALL) | EPCTL_SD0PID; /* после снятия — снова DATA0 */
}
void drv_set_address(uint8_t addr) /* у OTG адрес пишут сразу, до стадии состояния (RM0090) */
{
REG(R_DCFG) = (REG(R_DCFG) & ~DCFG_DAD_Msk) | (uint32_t)addr << DCFG_DAD_Pos;
}
static void flush_fifos(void)
{
REG(R_GRSTCTL) = GRSTCTL_TXFFLSH | 0x10u << GRSTCTL_TXFNUM_Pos; /* 0x10 — все передающие */
while (REG(R_GRSTCTL) & GRSTCTL_TXFFLSH) { }
REG(R_GRSTCTL) = GRSTCTL_RXFFLSH;
while (REG(R_GRSTCTL) & GRSTCTL_RXFFLSH) { }
}
/* Ядро OTG_FS: такт AHB2 и выводы PA11/PA12 к этому моменту включены (main_f407.c) */
void otg_f407_init(void)
{
REG(R_GAHBCFG) &= ~GAHBCFG_GINT;
REG(R_GUSBCFG) |= GUSBCFG_PHYSEL; /* встроенный приёмопередатчик FS */
while (!(REG(R_GRSTCTL) & GRSTCTL_AHBIDL)) { }
REG(R_GRSTCTL) |= GRSTCTL_CSRST; /* сброс ядра */
while (REG(R_GRSTCTL) & GRSTCTL_CSRST) { }
REG(R_GCCFG) = GCCFG_PWRDWN | GCCFG_NOVBUSSENS; /* приёмопередатчик включён, VBUS не следим */
REG(R_GUSBCFG) = (REG(R_GUSBCFG) & ~GUSBCFG_TRDT_Msk) | GUSBCFG_FDMOD | 6u << GUSBCFG_TRDT_Pos;
REG(R_DCTL) |= DCTL_SDIS; /* пока не готовы — подтяжка D+ снята */
REG(R_PCGCCTL) = 0;
REG(R_DCFG) |= DCFG_DSPD_FS;
fifo_setup();
flush_fifos();
REG(R_DIEPMSK) = EPINT_XFRC;
REG(R_DOEPMSK) = EPINT_XFRC | EPINT_STUP;
REG(R_DAINTMSK) = 0;
REG(R_GINTSTS) = 0xBFFFFFFFu;
REG(R_GINTMSK) = GINT_USBRST | GINT_ENUMDNE | GINT_RXFLVL | GINT_IEPINT | GINT_OEPINT;
REG(R_GAHBCFG) |= GAHBCFG_GINT;
REG(R_DCTL) &= ~DCTL_SDIS; /* подтяжка D+: хост видит устройство */
}
/* Пакет из приёмного FIFO: слово состояния, за ним данные словами */
static void rx_pop(void)
{
uint32_t s = REG(R_GRXSTSP);
uint32_t n = RXSTS_EPNUM(s), cnt = RXSTS_BCNT(s), sts = RXSTS_PKTSTS(s);
if (sts != PKTSTS_OUT_DATA && sts != PKTSTS_SETUP_DATA) return; /* «передача окончена» — без данных */
uint8_t *dst = sts == PKTSTS_SETUP_DATA ? setup_pkt : rx_buf[n ? 1 : 0];
for (uint32_t i = 0; i < cnt; i += 4) {
uint32_t w = REG(R_DFIFO(0));
for (uint32_t k = 0; k < 4 && i + k < cnt; k++)
if (i + k < 64u) dst[i + k] = (uint8_t)(w >> (8 * k));
}
if (sts == PKTSTS_OUT_DATA) rx_len[n ? 1 : 0] = (uint16_t)cnt;
}
void otg_f407_irq(void)
{
uint32_t st = REG(R_GINTSTS) & REG(R_GINTMSK);
if (st & GINT_USBRST) {
REG(R_GINTSTS) = GINT_USBRST;
REG(R_DCFG) &= ~DCFG_DAD_Msk;
REG(R_DAINTMSK) = 1u | 1u << 16; /* точка 0 в обе стороны */
usb_reset();
ep0_out_arm();
}
if (st & GINT_ENUMDNE) {
REG(R_GINTSTS) = GINT_ENUMDNE;
REG(R_DIEPCTL(0)) &= ~0x3u; /* MPSIZ точки 0: 00 — 64 байта */
REG(R_DCTL) |= DCTL_CGINAK;
}
while (REG(R_GINTSTS) & GINT_RXFLVL) rx_pop();
if (st & GINT_OEPINT) {
uint32_t eps = REG(R_DAINT) >> 16;
for (uint8_t n = 0; n < 4; n++) {
if (!(eps & 1u << n)) continue;
uint32_t f = REG(R_DOEPINT(n));
REG(R_DOEPINT(n)) = f;
if (f & EPINT_STUP) {
usb_setup(setup_pkt);
ep0_out_arm();
} else if (f & EPINT_XFRC) {
if (n == 0) {
usb_ep0_out(rx_buf[0], rx_len[0]);
rx_len[0] = 0;
ep0_out_arm();
} else if (usb_ep_out(n, rx_buf[1], rx_len[1])) {
drv_ep_rx_arm(n);
}
}
}
}
if (st & GINT_IEPINT) {
uint32_t eps = REG(R_DAINT) & 0xFFFFu;
for (uint8_t n = 0; n < 4; n++) {
if (!(eps & 1u << n)) continue;
uint32_t f = REG(R_DIEPINT(n));
REG(R_DIEPINT(n)) = f;
if (!(f & EPINT_XFRC)) continue;
if (n == 0) usb_ep0_in_done();
else usb_ep_in_done(0x80u | n);
}
}
}
vend_pc.py — программа на ПК в духе pyusb.
# -*- coding: utf-8 -*-
"""Программа на ПК для устройства раздела 20д. Обращается к устройству тремя
вызовами в духе pyusb: ctrl_transfer, write, read. На настоящем устройстве dev даёт
open_device() (нужны pyusb и libusb); в проверке — модель хоста (host_model.py)."""
VID, PID = 0x1209, 0x0002
EP_OUT, EP_IN = 0x01, 0x81
VREQ_GET_INFO, VREQ_SET_LED, VREQ_SET_XOR = 0x01, 0x02, 0x03
REQ_VENDOR_IN, REQ_VENDOR_OUT = 0xC0, 0x40 # производитель, устройству
def open_device():
import usb.core # pyusb
dev = usb.core.find(idVendor=VID, idProduct=PID)
if dev is None:
raise SystemExit("устройство %04X:%04X не найдено" % (VID, PID))
dev.set_configuration()
return dev
def get_info(dev):
b = bytes(dev.ctrl_transfer(REQ_VENDOR_IN, VREQ_GET_INFO, 0, 0, 12))
return {"version": "%d.%02d" % (b[1], b[0]), "led": b[2], "xor": b[3],
"rx": int.from_bytes(b[4:8], "little"), "tx": int.from_bytes(b[8:12], "little")}
def set_led(dev, on):
dev.ctrl_transfer(REQ_VENDOR_OUT, VREQ_SET_LED, 1 if on else 0, 0)
def set_xor(dev, key):
dev.ctrl_transfer(REQ_VENDOR_OUT, VREQ_SET_XOR, 0, 0, bytes([key]))
def echo(dev, data):
"""Отправить данные пакетами по 64 байта; на каждый пакет устройство отвечает своим."""
back = b""
for i in range(0, len(data), 64):
chunk = data[i:i + 64]
dev.write(EP_OUT, chunk)
back += bytes(dev.read(EP_IN, 64))
return back
def main(dev):
print("состояние:", get_info(dev))
set_led(dev, True)
set_xor(dev, 0x5A)
data = bytes(range(200))
back = echo(dev, data)
ok = back == bytes(b ^ 0x5A for b in data)
print("эхо: отправлено %d, принято %d, совпало с ожидаемым: %s" % (len(data), len(back), "да" if ok else "нет"))
print("состояние:", get_info(dev))
return ok
if __name__ == "__main__":
main(open_device())
Дальше: раздел 21 «SWO и ITM» — отладочный вывод, не занимающий ни UART, ни единого вывода сверх тех, что уже заняты отладчиком.