|
Информационный портал технической поддержки Центра проектирования интегральных микросхем |

На рисунке 1 показаны слои стека в нашем проекте: всё, что выше интерфейса DCD, взято из TinyUSB; в файлы стека внесены лишь две небольшие правки для сборки в Keil (таблица 4 ниже).
Приложение со стеком связывают три вещи:
• Файл конфигурации tusb_config.h - в нём включаются нужные классы и задаются размеры буферов.
• Дескрипторы usb_descriptors.c - сообщают хосту, что за устройство к нему подключено.
• Функции обратного вызова классов (tud_cdc_rx_cb(), tud_msc_read10_cb() и другие) - через них стек передаёт приложению данные и запросы хоста.
В таблице 1 перечислены файлы, которые добавляются в проект. Из всего каталога стека с названием src вместе с портом нужно всего девять исходных файлов, а драйверы других микроконтроллеров из каталога src/portable не нужны.
Таблица 1 - Файлы проекта
| Файл | Слой | Назначение |
| main.c, usb_descriptors.c, tusb_config.h | Приложение | Логика устройства, дескрипторы, конфигурация стека |
| src/class/cdc/cdc_device.c | Класс | Виртуальный COM-порт |
| src/class/hid/hid_device.c | Класс | Мышь, клавиатура и другие HID-устройства |
| src/class/msc/msc_device.c | Класс | Накопитель (флэшка) |
| src/class/vendor/vendor_device.c | Класс | Собственный протокол, WebUSB |
| src/device/usbd.c | Ядро | Энумерация, стандартные запросы, очередь событий |
| src/tusb.c, src/common/tusb_fifo.c | Ядро | Инициализация стека, кольцевые буферы |
| src/typec/usbc.c | Ядро | Поддержка USB Type-C, в примерах не используется |
| src/tusb_MDR32Fx.c | Порт (DCD) | Драйвер для работы контроллера USB со стеком |
Помимо исходников стека, проекту нужна библиотека SPL: компоненты RST_CLK, PORT и USB (первичный драйвер MDR32FxQI_usb.c (не файлы библиотеки для реализации класса CDC), где приведены базовые функции по работе с USB: сброс, настройка конечных точек и т.д) - а для примеров с WebUSB ещё и ADC. В IDE Keil они подключаются галочками в окне "Manage Run-Time Environment".
Таблица 2 - Функции драйвера контроллера
| Функция | Назначение |
| dcd_init() | Тактирование (ядро работает на частоте HSE*10, USB на частоте HSE*6 = 48 МГц при HSE = 8 МГц), режим устройства Full Speed, подключение необходимых подтяжек для организации шины USB |
| dcd_int_enable(), dcd_int_disable() | Разрешение и запрет прерывания USB_IRQn |
| dcd_set_address() | Запоминает адрес, он применяется после подтверждения статуса SET_ADDRESS |
| dcd_edpt_open(), dcd_edpt_close(), dcd_edpt_close_all() | Включение/выключение работы конечных точек |
| dcd_edpt_xfer() | Передача или приём пакета по запросу стека |
| dcd_edpt_stall(), dcd_edpt_clear_stall() | Установка и снятие состояния STALL |
| dcd_int_handler(), USB_IRQHandler() | Сброс шины, завершение транзакций, обработка ключевых событий для стека |
Таблица 3 - Особенности контроллера и их решение в порте
| Особенность контроллера | Решение в порте |
| Всего четыре конечные точки (EP0-EP3) с FIFO по 64 байта | При передаче больших объемов данных (например, дескрипторы на IN) стек делит данные на порции |
| Один бит готовности EPRDY на оба направления (IN и OUT) | EPRDY (точка готова) взводится только тогда, когда стек поставил передачу в очередь, до этого хост получает NAK |
| Регистры состояния TS/STS не очищаются и хранят результат последней транзакции | Транзакция считается завершенной, только если порт сам взводил EPRDY, а теперь он сброшен |
| Одно прерывание SCTDONE (окончена передача) на все точки | В обработчике проверяется каждая точка |
| OUT-пакет может прийти раньше, чем стек выдал буфер | Пакет остаётся в RX FIFO и отдаётся стеку при следующем вызове dcd_edpt_xfer() |
Главное правило работы порта представлено во фрагменте кода 1: порт сам запоминает, что взвёл точку, и по этому признаку решает, что транзакция завершилась.
Фрагмент кода 1 - Взведение точки и проверка завершения транзакции
// Взвести EPRDY - точка готова к обмену
static void ep_set_ready(uint8_t ep)
{
USB_SetSEPxCTRL((USB_EP_TypeDef)ep, USB_SEPx_CTRL_EPRDY_Ready);
ep_armed[ep] = true;
}
// Прошла ли транзакция с момента взведения EPRDY
static bool ep_transaction_done(uint8_t ep)
{
if (ep_armed[ep] && !(USB_GetSEPxCTRL((USB_EP_TypeDef)ep) & USB_SEPx_CTRL_EPRDY_Ready)) {
ep_armed[ep] = false;
return true;
}
return false;
}
Ещё одна тонкость касается нулевой точки. Обычно TinyUSB обрабатывает запрос SETUP позже, в tud_task(). Но если EP0 взведена раньше, чем ответ попал в FIFO, контроллер отправит хосту пустой пакет, и длинный дескриптор оборвётся. Поэтому для EP0 порт вызывает tud_task() прямо в обработчике прерывания и взводит точку уже с готовым ответом.
Таблица 4 - Правки в файлах стека TinyUSB
| Файл | Изменение | Причина |
| src/common/tusb_compiler.h | Для armcc (__ARMCC_VERSION < 6000000) макрос TU_ATTR_FALLTHROUGH определяется без вызова __has_attribute() | В armcc нет __has_attribute(), препроцессор выдаёт ошибку #109 |
| src/common/tusb_compiler.h | TU_BSWAP16 записан сдвигами вместо __builtin_bswap16() | В armcc нет функции __builtin_bswap16() |
В ARM Compiler 6 (armclang) эти правки не нужны: он построен на Clang и поддерживает оба вызова.
Минимальная конфигурация стека на примере виртуального COM-порта приведена во фрагменте кода 2. Для другого класса меняются только строки CFG_TUD_* и настройка буферов.
Фрагмент кода 2 - Файл tusb_config.h примера CDC
#define CFG_TUSB_DEBUG 0
#define CFG_TUSB_OS OPT_OS_NONE
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
#define CFG_TUD_ENABLED 1
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE
#define CFG_TUD_ENDPOINT0_SIZE 64
#define CFG_TUSB_SPEED OPT_MODE_FULL_SPEED
// Только CDC (COM-порт)
#define CFG_TUD_CDC 1
#define CFG_TUD_HID 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
// Буферы для работы CDC
#define CFG_TUD_CDC_RX_BUFSIZE 64
#define CFG_TUD_CDC_TX_BUFSIZE 64
Инициализация и главный цикл (с небольшим уточнением, что функция обработки для каждого класса своя - для CDC, например, это cdc_task()) одинаковы для всех примеров. Тактирование ядра и USB настраивает сам порт в dcd_init().
Фрагмент кода 3 - Инициализация стека и главный цикл
int main(void)
{
gpio_init();
tusb_rhport_init_t dev_init = {.role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_FULL};
tusb_init(0, &dev_init); // здесь же настраивается PLL
SystemCoreClockUpdate(); // частота ядра изменилась в tusb_init
SysTick_Config(SystemCoreClock / 1000);
while (1)
{
tud_task(); // обработка событий USB-стека
cdc_task(); // логика примера
}
}
Таблица 5 - Описание проектов
| Название | Каталог | Макрос | Устройство | VID:PID |
| CDC_ONLY | examples/cdc-only | EXAMPLE_CDC | Виртуальный COM-порт | CAFE:4001 |
| HID_MOUSE | examples/hid-only | EXAMPLE_HID | Мышь | CAFE:4002 |
| HID_KEYBOARD | examples/hid-only | EXAMPLE_HID, HID_MODE=2 | Клавиатура | CAFE:4003 |
| MSC_ONLY | examples/msc-only | EXAMPLE_MSC | Флэшка | CAFE:4004 |
| WEBUSB_ONLY | examples/webusb-only | EXAMPLE_WEBUSB_ONLY | Устройство WebUSB | CAFE:4005 |
| MSC_HID | examples/msc-hid | EXAMPLE_MSC_HID | Флэшка + клавиатура | CAFE:4006 |
| MSC_WEBUSB | examples/msc-webusb | EXAMPLE_MSC_WEBUSB | Флэшка + WebUSB | CAFE:4007 |
Фрагмент кода 4 - Файл-переходник example_main.c
#if defined(EXAMPLE_CDC)
#include "cdc-only/main.c"
#elif defined(EXAMPLE_HID)
#include "hid-only/main.c"
#elif defined(EXAMPLE_MSC)
#include "msc-only/main.c"
#elif defined(EXAMPLE_MSC_HID)
#include "msc-hid/main.c"
#elif defined(EXAMPLE_WEBUSB_ONLY)
#include "webusb-only/main.c"
#elif defined(EXAMPLE_MSC_WEBUSB)
#include "msc-webusb/main.c"
#endif
Исходники примеров правятся в каталогах examples/<имя>При подключении хост запрашивает у устройства дескрипторы (этот процесс называется энумерацией) и по классу интерфейса выбирает драйвер. Для стандартных классов драйвер уже есть в Windows, поэтому устанавливать ничего не нужно. Краткое описание классов из примеров приведено в таблице 6.
Таблица 6 - Классы USB, используемые в примерах
| Класс | Что представляет | Как передаются данные | Драйвер на ПК |
| CDC (Communication Device Class) | Виртуальный COM-порт | Поток байтов в обе стороны в рамках BULK-транзакций (две точки) плюс отдельная точка для уведомлений | Встроенный (usbser) |
| HID (Human Interface Device) | Мышь, клавиатура, джойстик | Короткие отчёты по interrupt-точке, формат задаёт дескриптор отчёта | Встроенный |
| MSC (Mass Storage Class) | Накопитель (флэшка) | Команды SCSI и блоки по 512 байт на точках BULK | Встроенный |
| Vendor (с поддержкой WebUSB) | Устройство со своим протоколом | Кадры произвольного формата; в WebUSB на ПК с ним работает веб-страница в браузере | WinUSB, Windows подключает сама по дескрипторам Microsoft OS |
Одно физическое устройство может содержать несколько интерфейсов разных классов - такое устройство называется составным (composite). В TinyUSB для этого достаточно включить несколько классов в tusb_config.h и описать их интерфейсы в дескрипторе конфигурации - дополнительный код не нужен.
Таблица 7 - Команды примера CDC_ONLY
| Команда | Действие |
| help | Список команд |
| led N on | off | Включить или выключить светодиод VD3 или VD4 (N = 3 или 4) |
| btn | Какие кнопки нажаты сейчас |
| uptime | Время с момента сброса |
| speed | Передача 64 Кбайт на ПК и измерение скорости |
| echo | Реализация эха (то, что отправляется с ПК, возвращается обратно) - используется в том числе при запуске стресс-теста |
Работа с портом в прошивке сводится к функциям tud_cdc_read_char() и tud_cdc_write_char(): стек сам буферизует данные и делит их на пакеты.
В прошивке за вывод и приём отвечает несколько функций стека. Функция tud_cdc_connected() возвращает true, когда терминал открыл порт, поэтому ею удобно отсекать работу без слушателя. Функция tud_cdc_write_char() кладёт байт в буфер передачи размером 64 байта и возвращает false, если буфер полон. В этом случае tud_cdc_write_flush() просит стек отправить накопленное, а tud_task() даёт ему время это сделать. Приём устроен проще: tud_cdc_available() сообщает число принятых байтов, а tud_cdc_read_char() забирает очередной.
Фрагмент кода 5 - Вывод строки и приём команд в CDC_ONLY
// Если буфер CDC (64 байта) заполнен, ждём, обслуживая USB, пока хост его заберёт
static void put(const char* s)
{
while (*s && tud_cdc_connected()) {
if (tud_cdc_write_char(*s)) {
s++;
} else {
tud_cdc_write_flush();
tud_task();
}
}
tud_cdc_write_flush();
}
// В главном цикле: терминал открыл порт - разбираем всё, что он прислал
if (tud_cdc_connected()) {
while (tud_cdc_available()) {
console_char((char)tud_cdc_read_char());
}
}
Оба примера собираются из одних исходников examples/hid-only и отличаются макросом HID_MODE: в цели HID_KEYBOARD в Define добавлено HID_MODE=2. Режим (мышь или клавиатура) дополнительно задан в PID, поэтому Windows считает мышь и клавиатуру разными устройствами. Назначение кнопок приведено в таблице 8.
Таблица 8 - Назначение кнопок в примерах HID
| Кнопка | HID_MOUSE | HID_KEYBOARD |
| SELECT | Левая кнопка мыши | Enter |
| LEFT, RIGHT, UP, DOWN | Курсор движется, пока кнопка удержана (сделано так, что скорость растет с временем удержания) | Стрелки |
| UP + DOWN | Курсор рисует окружность (в Paint с зажатой левой кнопкой) | Микроконтроллер сам набирает текст "Hello from Milandr!" |
Фрагмент кода 6 - Отправка отчёта мыши
if ((changed || timer) && tud_hid_ready()) {
tud_hid_mouse_report(0, buttons, dx, dy, 0, 0); // кнопки, смещение X и Y
}
Микроконтроллер на плате определяется как съёмный диск с меткой "Milandr MSC". Диск находится в ОЗУ (16 секторов по 512 байт, файловая система FAT12) и содержит два файла: README.TXT с описанием и LED.TXT. Если записать в LED.TXT цифру "1" и сохранить файл - загорится светодиод VD3; запись "0" его выключит. Также легко передавать устройству настройки или считывать из него журнал измерений без какой-либо программы на ПК.
От приложения стек ждёт несколько функций обратного вызова: размер диска, чтение и запись блоков. Все команды SCSI стек обрабатывает сам.
Фрагмент кода 7 - Чтение блока диска
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
{
(void)lun;
if (lba * DISK_BLOCK_SIZE + offset + bufsize > sizeof(msc_disk)) return -1;
memcpy(buffer, msc_disk[lba] + offset, bufsize);
return (int32_t)bufsize;
}
Для реального устройства в этих функциях достаточно заменить memcpy() на чтение и запись внешней Flash-памяти или SD-карты.Технология WebUSB позволяет веб-странице обмениваться данными с USB-устройством напрямую, без драйверов (на практике используется драйвер WinUSB, но он подключается автоматически посредством энумерации, информация о чем раскрыта далее по тексту) и без прав администратора. Поддерживается браузерами на базе Chromium (Chrome, Edge и т.д - в Firefox и Safari WebUSB не поддерживается). Страница примера предоставляет следующий функционал:
• Светодиоды VD3 и VD4 включаются кнопками на странице;
• Нажатая на плате кнопка подсвечивается на экране;
• Выводится код АЦП встроенного датчика температуры и график показаний за последние 10 секунд - достаточно коснуться микроконтроллера пальцем, чтобы увидеть изменение;
• Встроена игра "Змейка", которой управляют кнопки платы.
Устройство содержит Vendor-интерфейс с двумя точками типа BULK: EP1 OUT для команд и EP2 IN для данных. Протокол обмена приведён в таблице 9, все числа передаются младшим байтом вперёд.
Таблица 9 - Протокол обмена примера WebUSB
| Направление | Кадр | Когда |
| Плата > Страница | 0x01, состояние кнопок, светодиодов, АЦП (2 байта), количество мс с запуска (4 байта) | Каждые 20 мс и сразу при нажатии кнопки |
| Страница > Плата | 0x01, управление светодиодами | По клику на странице |
В прошивке обмен со страницей занимает две короткие функции. Кадр состояния отправляет tud_vendor_write() с последующим tud_vendor_write_flush(); перед ней tud_vendor_write_available() проверяет, есть ли в буфере место. Если страница не читает данные, кадр просто пропускается. Команды принимает пара tud_vendor_available() и tud_vendor_read(): они работают так же, как функции чтения CDC в первом примере.
Фрагмент кода 8 - Обмен кадрами со страницей в WEBUSB_ONLY
static void send_state(void)
{
uint16_t adc = adc_read();
uint32_t ms = ms_ticks;
uint8_t frame[9] = {
MSG_STATE, buttons_state, leds_state,
(uint8_t)adc, (uint8_t)(adc >> 8),
(uint8_t)ms, (uint8_t)(ms >> 8), (uint8_t)(ms >> 16), (uint8_t)(ms >> 24) // Время с момента запуска, 4 байта
};
if (tud_vendor_write_available() >= sizeof(frame)) {
tud_vendor_write(frame, sizeof(frame));
tud_vendor_write_flush();
}
}
static void receive_commands(void)
{
uint8_t cmd[2];
while (tud_vendor_available() >= sizeof(cmd)) {
tud_vendor_read(cmd, sizeof(cmd));
if (cmd[0] == MSG_LEDS) leds_set(cmd[1]);
}
}
Драйвер WinUSB ОС Windows подключает сама. Для этого микроконтроллер отвечает на запросы Microsoft OS 1.0: идентификатор совместимости WinUSB и GUID интерфейса (специальная последовательность символов, которую можно видеть в файле usb_descriptors.c примера). Страница запускается прямо с диска, как обычный файл. На уровне файла app.js (файл JavaScript, работающий в связке со страницей .html - именно в этом файле реализован низкий уровень всех функций со страницы (уровень транзакций USB)) контрольно проверяется VID устройства для подключения, то есть если такое устройство есть в системе, то оно появится в доступных, когда будет нажата кнопка "Подключить плату".
Общий вид страницы представлен на рисунке 2.

Рисунок 2 - Общий вид страницы браузера примера с реализацией WebUSB
Интерфейсы распределены по точкам так: флэшка - EP1 OUT и EP2 IN, клавиатура - EP3 IN. Код приложения при этом просто объединяет код двух предыдущих примеров.
Основа примера - функция печати текста. Для каждого символа она отправляет два отчёта: "клавиша нажата" и "клавиша отпущена". Функция tud_hid_ready() сообщает, что предыдущий отчёт уже ушёл и точка свободна, а tud_hid_keyboard_report() ставит в очередь новый. Пока точка занята, вызывается tud_task(), поэтому диск продолжает работать во время печати. Таблицу соответствия символов и кодов клавиш содержит сам стек: это макрос HID_ASCII_TO_KEYCODE.
Фрагмент кода 9 - Печать текста клавиатурой в MSC_HID
static void type_text(const char* text, int len)
{
static const uint8_t ascii_to_key[128][2] = { HID_ASCII_TO_KEYCODE }; // {нужен Shift, код клавиши}
for (int i = 0; i < len; i++) {
uint8_t c = (uint8_t)text[i];
if (c >= 128 || c == '\r' || ascii_to_key[c][1] == 0) continue; // символа нет на клавиатуре
uint8_t keycodes[6] = { ascii_to_key[c][1] };
uint8_t shift = ascii_to_key[c][0] ? KEYBOARD_MODIFIER_LEFTSHIFT : 0;
for (int press = 1; press >= 0; press--) { // сначала "нажата", потом "отпущена"
while (!tud_hid_ready()) tud_task();
tud_hid_keyboard_report(0, press ? shift : 0, press ? keycodes : NULL);
wait_ms(KEY_PERIOD_MS);
}
}
}
На рисунке 3 показана структура работы примера: через интерфейс MSC браузер получает страницу, а через Vendor-интерфейс эта страница обменивается данными с микроконтроллером.

Составное устройство собирается в usb_descriptors.c из готовых макросов стека. TUD_CONFIG_DESCRIPTOR() открывает конфигурацию, TUD_MSC_DESCRIPTOR() добавляет флэшку, а для WebUSB описан собственный Vendor-интерфейс с единственной точкой IN. Номера интерфейсов и точек заданы константами, поэтому видно, как делятся ресурсы контроллера: EP1 и EP2 отданы флэшке, EP3 - WebUSB.
Фрагмент кода 10 - Дескриптор конфигурации MSC_WEBUSB
static uint8_t const desc_configuration[] = {
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
// Флэшка: интерфейс 0, EP1 OUT и EP2 IN
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64),
// WebUSB: интерфейс 1, EP3 IN (команды идут через EP0)
VENDOR_IN_ONLY_DESCRIPTOR(ITF_NUM_VENDOR, EPNUM_VENDOR_IN, 64),
};
Особенности реализации:
Запросы к нулевой точке обрабатывает функция обратного вызова tud_vendor_control_xfer_cb(). Стек вызывает её для каждого Vendor-запроса хоста. Через неё проходят и команда светодиодам, и ответы на запросы Windows о драйвере WinUSB. Ответ отправляет tud_control_xfer() (с данными) или tud_control_status() (подтверждение без данных); возврат false заставляет стек ответить STALL.
Фрагмент кода 11 - Vendor-запросы хоста в MSC_WEBUSB
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) {
if (stage != CONTROL_STAGE_SETUP) return true; // нужен только этап SETUP
if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR) return false;
if (request->bRequest == REQ_SET_LEDS) { // страница управляет светодиодами
leds_set((uint8_t) request->wValue);
return tud_control_status(rhport, request);
}
if (request->bRequest != MS_VENDOR_CODE) return false; // неизвестный запрос: STALL
if (request->wIndex == MS_REQ_COMPAT_ID) { // подключение WinUSB
return tud_control_xfer(rhport, request, (void *) desc_ms_compat_id, sizeof(desc_ms_compat_id));
}
if (request->wIndex == MS_REQ_PROPERTIES) { // GUID интерфейса
return tud_control_xfer(rhport, request, (void *) desc_ms_properties, sizeof(desc_ms_properties));
}
return false;
}
Чтобы изменить страницу, нужно отредактировать файлы в examples/msc-webusb/web, выполнить python tools/make_disk.py и пересобрать пример. Имена файлов на диске ограничены форматом 8.3 (формат коротких имен - используется для оптимизации места), поэтому index.html лежит на диске как index.htm.
Чтобы убедиться в устойчивости порта (в случае любых изменений), к примерам прилагаются скрипты на Python в каталоге tools. Они проверяют два типичных источника ошибок USB-устройств: повторное подключение и длительный обмен данными.
• Тест энумерации enum_test.py (требуются права администратора) программно отключает и снова включает USB-порт командой IOCTL_USB_HUB_CYCLE_PORT и проверяет, что устройство восстановило работу: все его узлы запущены без ошибок, а у дисков читается том. Для каждого цикла печатается время подключения или причина отказа.
• Нагрузочные тесты: cdc_stress.py гоняет поток данных и эхо через COM-порт и переоткрывает порт (нужен пакет pyserial), msc_stress.py читает диск случайными блоками (режим raw) или создаёт, перезаписывает и удаляет файлы с проверкой содержимого (режим files).
Таблица 10 - Проверка примеров
| Пример | Тест энумерации | Нагрузочный тест |
| CDC_ONLY | python enum_test.py cdc-only 250 0.3 | python cdc_stress.py COMx all --minutes 15 |
| HID_MOUSE | python enum_test.py hid-mouse 250 0.3 | Вручную движением курсора и отрисовкой окружности при помощи сочетания "UP + DOWN" |
| HID_KEYBOARD | python enum_test.py hid-keyboard 250 0.3 | Вручную движением курсора в текстовом файле и набором текста при помощи сочетания "UP + DOWN" |
| MSC_ONLY | python enum_test.py msc-only 250 0.3 | python msc_stress.py raw \\.\E: --minutes 10, затем files E:\ |
| WEBUSB_ONLY | python enum_test.py webusb-only 250 0.3 | Вручную открыть страницу в браузере и протестировать функционал со светодиодами, кнопками и АЦП |
| MSC_HID | python enum_test.py msc-hid 250 0.3 |
python msc_stress.py raw \\.\E: --minutes 10, затем files E:\ Здесь же вручную нажать "SELECT" и проконтролировать набор текста из файла TEXT.TXT |
| MSC_WEBUSB | python enum_test.py msc-webusb 250 0.3 |
python msc_stress.py raw \\.\E: --minutes 10 Здесь же вручную открыть страницу в браузере и протестировать функционал со светодиодами, кнопками и АЦП |
Аргументы enum_test.py: имя примера, число циклов (по умолчанию 100, в примерах используем 250) и пауза между циклами в секундах. Букву диска и номер COM-порта необходимо подставить свои.
Собственное USB-устройство удобнее всего делать на основе ближайшего примера с реализацией нужного класса (классов). Порт и стек при этом не меняются, изменяются только файлы приложения:
1. В tusb_config.h включить нужные классы и подобрать размеры буферов (стандартно достаточно 64 байт).
2. В usb_descriptors.c задать VID, PID, строки производителя, названия и серийного номера, описать интерфейсы и номера конечных точек.
3. Реализовать функции обратного вызова класса: например, для флэшки заменить диск в ОЗУ на внешнюю память.
4. В главном цикле регулярно вызывать tud_task() и не допускать длительных блокирующих задержек.
5. Проверить устройство тестом энумерации и нагрузочными тестами.
При этом нужно учитывать ограничения из таблицы 11.
Таблица 11 - Ограничения при разработке устройства
| Ограничение | Что оно значит |
| Идентификатор производителя VID 0xCAFE является тестовым | Для серийного изделия нужен собственный VID или PID, выделенный владельцем VID |
| Три конечные точки помимо EP0 | Реализация составного устройства чаще всего будет ограничена лишь двумя классами, для большего числа требуется больше аппаратных конечных точек |
| Бесплатная версия IDE Keil | Стек с одним классом занимает лишь часть лимита, но образ диска или большая логика потребуют полной лицензии или другого компилятора |
| Windows кэширует драйвер по VID/PID | При изменении набора интерфейсов следует менять PID или bcdDevice |
Порт TinyUSB для микроконтроллеров К1986ВЕ9х и К1986ВЕ1х занимает один файл и даёт микроконтроллеру доступ к стандартным классам USB из популярного открытого стека. Семь примеров показывают, что COM-порт, мышь, клавиатура, флэшка и управление из браузера укладываются в несколько сотен строк кода приложения, а составное устройство собирается из них как из кубиков. Работа примеров проверена циклами переподключения и длительной нагрузкой.
Проект целиком (порт, стек TinyUSB 0.21.0, примеры и скрипты проверки) доступен для загрузки в разделе "Файлы для скачивания" после статьи.
| Сайт: | https://support.milandr.ru |
| E-mail: | support@milandr.ru |
| Телефон: | +7 495 221-13-55 |