Update README.md
This commit is contained in:
parent
0d062a6c21
commit
dc5cfac386
1 changed files with 324 additions and 294 deletions
|
|
@ -1,342 +1,372 @@
|
||||||
# USB — теория для embedded-разработчика
|
# bsp_usb_cdc — USB CDC ACM (Virtual COM Port)
|
||||||
|
|
||||||
> Памятка: фокус на CDC ACM (Virtual COM Port) для NXP IMXRT1052
|
USB CDC ACM device на USB1 (EHCI0). Хост видит устройство как виртуальный COM-порт
|
||||||
|
(`/dev/ttyACM*` на Linux/macOS, `COMx` на Windows).
|
||||||
|
|
||||||
|
Используется для передачи данных между платой и ПК: отладочные лог-каналы,
|
||||||
|
CLI команды, обновление конфигурации. Работает параллельно с `bsp_uart_host`
|
||||||
|
(LPUART1) — два независимых канала.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Основы архитектуры USB
|
## Аппаратура
|
||||||
|
|
||||||
USB — это **master-slave** шина. Хост всегда инициирует обмен, устройство только отвечает. Никакой "самодеятельности" от устройства быть не может — только реакция на запросы хоста.
|
| Сигнал | Пин MCU | Назначение |
|
||||||
|
|--------------|----------------|----------------------------------|
|
||||||
|
| USB_OTG1_DN | USB_OTG1_DN | USB1 Data− |
|
||||||
|
| USB_OTG1_DP | USB_OTG1_DP | USB1 Data+ |
|
||||||
|
| USB_OTG1_VBUS| USB_OTG1_VBUS | VBUS detect (self-powered) |
|
||||||
|
|
||||||
|
Встроенный HS PHY (480 MHz PLL). Контроллер: EHCI0 (`kUSB_ControllerEhci0`).
|
||||||
|
Скорость: High-Speed (480 Mbit/s) при поддержке хоста, fallback Full-Speed (12 Mbit/s).
|
||||||
|
|
||||||
|
USB PHY калибровка: `D_CAL=0x0C`, `TXCAL45DP=0x06`, `TXCAL45DM=0x06` — стандартные
|
||||||
|
значения для EVKB, подходят для кабелей до 3 м.
|
||||||
|
|
||||||
|
**VID/PID**: `0x1234` / `0x0001` (placeholder, заменить на производственные).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HOST (PC) DEVICE (MCU)
|
bsp_usb_cdc_write()
|
||||||
────────── ────────────
|
↓
|
||||||
OS USB stack USB device stack
|
memcpy → s_sendBuf (NonCacheable OCRAM)
|
||||||
↕ ↕
|
↓
|
||||||
Host controller (xHCI/EHCI) ←→ Device controller (EHCI на IMXRT)
|
USB_DeviceSendRequest()
|
||||||
D+ D− VBUS GND
|
↓
|
||||||
|
[EHCI0 DMA] → USB1_DP/DN → Host
|
||||||
|
|
||||||
|
Host → USB1_DP/DN → [EHCI0 DMA]
|
||||||
|
↓
|
||||||
|
USB_OTG1_IRQHandler → BulkOut callback
|
||||||
|
↓
|
||||||
|
s_recvBuf (NonCacheable OCRAM)
|
||||||
|
↓
|
||||||
|
s_recvSize = len (volatile)
|
||||||
|
↓
|
||||||
|
bsp_usb_cdc_read() ← main loop polling
|
||||||
```
|
```
|
||||||
|
|
||||||
### Физический уровень
|
Все DMA-буферы (`s_sendBuf`, `s_recvBuf`, дескрипторы) размещены в секции
|
||||||
|
`NonCacheable` (OCRAM `0x20200000`). MPU region 9 настраивает эту область
|
||||||
| Параметр | USB Full Speed | USB High Speed |
|
как Normal non-cacheable — записи CPU видны DMA без `SCB_CleanDCache()`.
|
||||||
|----------|---------------|----------------|
|
|
||||||
| Скорость | 12 Мбит/с | 480 Мбит/с |
|
|
||||||
| IMXRT1052 | ✅ | ✅ |
|
|
||||||
| Практическая пропускная способность BULK | ~1 МБ/с | ~40 МБ/с |
|
|
||||||
| Применение | CDC ACM, HID | MSD, Video |
|
|
||||||
|
|
||||||
IMXRT1052 имеет два USB контроллера: `USB1` (OTG, EHCI) и `USB2` (Host only). Для CDC ACM используем `USB1`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Ключевые понятия
|
## USB стек — lite архитектура
|
||||||
|
|
||||||
### Дескрипторы
|
Модуль использует **lite** вариант NXP USB стека (не full class framework).
|
||||||
|
Это сознательное решение:
|
||||||
|
|
||||||
Дескрипторы — это набор структур, которые устройство возвращает хосту при подключении (в ответ на `GET_DESCRIPTOR`). Хост читает их и решает, какой драйвер загрузить.
|
| Аспект | Full stack | Lite stack (наш выбор) |
|
||||||
|
|--------|-----------|----------------------|
|
||||||
|
| Class framework | `usb_device_class.h`, `class_handle_t` | Отсутствует |
|
||||||
|
| `usb_device_ch9.c` | SDK middleware, тянет class driver | Приватная копия в `src/` |
|
||||||
|
| CDC ACM хедер | Полный: struct + API функции | Только define-ы request codes |
|
||||||
|
| Callbacks | Через class driver dispatch | Напрямую в `usb_cdc.c` |
|
||||||
|
| Размер кода | ~12 KB | ~6 KB |
|
||||||
|
| Гибкость | Multi-class composite | Один CDC ACM |
|
||||||
|
|
||||||
|
Lite stack достаточен для одного CDC ACM интерфейса. Переход на full stack
|
||||||
|
понадобится только при добавлении composite device (CDC + MSC).
|
||||||
|
|
||||||
|
### Стек зависимостей
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Device Descriptor
|
bsp_usb_cdc
|
||||||
└── Configuration Descriptor
|
├── src/usb_cdc.c ← BSP API + USB device callbacks
|
||||||
├── Interface Descriptor #0 (CDC Control)
|
├── src/usb_cdc_descriptors.c ← дескрипторы + descriptor callbacks
|
||||||
│ ├── CDC Header Functional Descriptor
|
├── src/usb_cdc_hw.c ← clock, PHY, IRQ handler
|
||||||
│ ├── CDC Call Management Descriptor
|
├── src/usb_device_ch9.c ← lite Chapter 9 (приватная копия)
|
||||||
│ ├── CDC ACM Functional Descriptor
|
│
|
||||||
│ ├── CDC Union Functional Descriptor
|
├── SDK (PRIVATE):
|
||||||
│ └── Endpoint Descriptor (INT IN)
|
│ ├── sdk_usb_device_ehci ← EHCI контроллер + DCI абстракция
|
||||||
└── Interface Descriptor #1 (CDC Data)
|
│ │ ├── usb_device_ehci.c
|
||||||
├── Endpoint Descriptor (BULK IN)
|
│ │ └── usb_device_dci.c
|
||||||
└── Endpoint Descriptor (BULK OUT)
|
│ ├── sdk_usb_phy ← USB PHY инициализация
|
||||||
|
│ │ └── usb_phy.c
|
||||||
|
│ └── sdk_osa_bm ← OS Abstraction (bare-metal)
|
||||||
|
│ ├── fsl_os_abstraction_bm.c
|
||||||
|
│ └── fsl_component_generic_list.c
|
||||||
|
│
|
||||||
|
└── Приватные конфиги в src/:
|
||||||
|
├── usb_device_config.h ← EHCI=1, CDC_ACM=1, endpoints=4
|
||||||
|
├── fsl_os_abstraction_config.h ← bare-metal OSA конфиг
|
||||||
|
├── usb_device_descriptor.h ← VID/PID, endpoint numbers
|
||||||
|
├── usb_device_ch9.h ← lite ch9 API (1 arg)
|
||||||
|
└── usb_device_cdc_acm.h ← lite: только CDC request codes
|
||||||
```
|
```
|
||||||
|
|
||||||
Важные поля `Device Descriptor`:
|
### Проброс конфиг-хедеров (sdk_usb_config)
|
||||||
|
|
||||||
| Поле | Значение | Смысл |
|
NXP USB middleware при компиляции ищет `usb_device_config.h` и
|
||||||
|------|----------|-------|
|
`fsl_os_abstraction_config.h` через include path. Эти файлы —
|
||||||
| `bDeviceClass` | 0xEF | Composite (классы на уровне интерфейсов) |
|
application-specific, живут в `bsp/usb_cdc/src/`.
|
||||||
| `idVendor` | 0x1FC9 | VID NXP (или свой) |
|
|
||||||
| `idProduct` | произвольный | PID — идентификатор продукта |
|
|
||||||
| `bcdUSB` | 0x0200 | USB 2.0 |
|
|
||||||
|
|
||||||
### Endpoints (эндпоинты)
|
Проблема: SDK таргеты (`sdk_usb_device_ehci`, `sdk_usb_phy`, `sdk_osa_bm`)
|
||||||
|
компилируются независимо от `bsp_usb_cdc` и не видят его include paths.
|
||||||
|
|
||||||
Эндпоинт — это буфер в устройстве с определённым направлением и типом передачи. EP0 — всегда управляющий (Control), остальные — настраиваются.
|
Решение: INTERFACE библиотека `sdk_usb_config` в `sdk/CMakeLists.txt`:
|
||||||
|
|
||||||
| Тип | Гарантия доставки | Применение |
|
```cmake
|
||||||
|-----|------------------|------------|
|
add_library(sdk_usb_config INTERFACE)
|
||||||
| Control | да | конфигурация устройства, EP0 |
|
target_include_directories(sdk_usb_config SYSTEM
|
||||||
| Bulk | да (retry) | большие данные, CDC ACM данные |
|
INTERFACE ${CMAKE_SOURCE_DIR}/bsp/usb_cdc/src)
|
||||||
| Interrupt | да (периодически) | HID, CDC ACM нотификации |
|
```
|
||||||
| Isochronous | нет | аудио, видео |
|
|
||||||
|
|
||||||
**Для CDC ACM нужны три эндпоинта:**
|
Все SDK USB таргеты линкуют `sdk_usb_config` и находят конфиг-хедеры при
|
||||||
|
компиляции. Циклических зависимостей нет — `sdk_usb_config` не содержит кода.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
|
||||||
|
/* После board_hw_init() + bsp_tick_init(): */
|
||||||
|
bsp_usb_cdc_init();
|
||||||
|
|
||||||
|
/* Ждём подключения хоста */
|
||||||
|
while (!bsp_usb_cdc_is_ready()) {
|
||||||
|
/* USB enumeration в процессе */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TX — неблокирующая отправка */
|
||||||
|
const char *msg = "Hello from TFT Board\r\n";
|
||||||
|
bsp_usb_cdc_write((const uint8_t *)msg, strlen(msg));
|
||||||
|
|
||||||
|
/* RX — polling в main loop */
|
||||||
|
uint8_t buf[64];
|
||||||
|
size_t n = bsp_usb_cdc_read(buf, sizeof(buf));
|
||||||
|
if (n > 0) {
|
||||||
|
/* обработать buf[0..n-1] */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `bsp_usb_cdc_init()`
|
||||||
|
|
||||||
|
Полная инициализация: USB PHY clock 480 MHz → EHCI0 init → endpoint registration →
|
||||||
|
NVIC enable → USB_DeviceRun. Включает задержку 5 мс для стабилизации DP pull-down.
|
||||||
|
|
||||||
|
**Предусловие**: `board_hw_init()` вызван (MPU настроен, NonCacheable регион активен).
|
||||||
|
|
||||||
|
Возвращает `BSP_OK` или `BSP_ERR_HW`.
|
||||||
|
|
||||||
|
### `bsp_usb_cdc_is_ready()`
|
||||||
|
|
||||||
|
`true` когда USB enumeration завершён **и** хост открыл COM-порт (DTR установлен
|
||||||
|
через `SET_CONTROL_LINE_STATE`). До этого момента `write()` вернёт `BSP_ERR_NOT_READY`.
|
||||||
|
|
||||||
|
### `bsp_usb_cdc_write(data, len)`
|
||||||
|
|
||||||
|
Неблокирующая отправка. Копирует данные в NonCacheable TX буфер и ставит в очередь
|
||||||
|
USB IN transfer. Максимум `BSP_USB_CDC_MAX_PACKET_SIZE` (512) байт за вызов.
|
||||||
|
|
||||||
|
| Возврат | Условие |
|
||||||
|
|---------|---------|
|
||||||
|
| `BSP_OK` | Transfer поставлен в очередь |
|
||||||
|
| `BSP_ERR_BUSY` | Предыдущий transfer не завершён |
|
||||||
|
| `BSP_ERR_NOT_READY` | Хост не подключён |
|
||||||
|
| `BSP_ERR_INVALID` | `data == NULL`, `len == 0` или `len > 512` |
|
||||||
|
|
||||||
|
Проверить готовность TX канала перед отправкой: `bsp_usb_cdc_write_ready()`.
|
||||||
|
|
||||||
|
### `bsp_usb_cdc_write_ready()`
|
||||||
|
|
||||||
|
`true` если предыдущий TX transfer завершён и хост подключён.
|
||||||
|
Удобно для non-blocking write loop:
|
||||||
|
|
||||||
|
```c
|
||||||
|
if (bsp_usb_cdc_write_ready()) {
|
||||||
|
bsp_usb_cdc_write(data, len);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bsp_usb_cdc_read(buf, max_len)`
|
||||||
|
|
||||||
|
Неблокирующее чтение. Забирает данные из RX буфера, заполненного USB OUT ISR callback.
|
||||||
|
Автоматически перепланирует следующий OUT transfer. Возвращает количество прочитанных
|
||||||
|
байт (0 если данных нет).
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* Polling в main loop: */
|
||||||
|
uint8_t buf[64];
|
||||||
|
size_t n = bsp_usb_cdc_read(buf, sizeof(buf));
|
||||||
|
```
|
||||||
|
|
||||||
|
### `bsp_usb_cdc_poll()`
|
||||||
|
|
||||||
|
Зарезервировано. Для bare-metal на EHCI NXP стек обрабатывает всё в ISR.
|
||||||
|
Для будущего использования с `USB_DEVICE_CONFIG_USE_TASK`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## NonCacheable память
|
||||||
|
|
||||||
|
USB EHCI DMA требует некэшируемые буферы. Модуль размещает буферы через макросы
|
||||||
|
`USB_DMA_INIT_DATA_ALIGN()` и `USB_DMA_NONINIT_DATA_ALIGN()`, которые помещают
|
||||||
|
данные в секции `NonCacheable.init` и `NonCacheable`.
|
||||||
|
|
||||||
|
Линкер-скрипт размещает эти секции в OCRAM (`m_data2`, `0x20200000`).
|
||||||
|
`board_mpu_init()` настраивает MPU region 9 для этой области.
|
||||||
|
|
||||||
|
Проверка: `firmware_test/main.c` содержит `ncache_test_run()` — верификация
|
||||||
|
что NonCacheable буфер физически попадает в ожидаемый регион.
|
||||||
|
|
||||||
|
**Объём**: ~2.5 KB (два bulk буфера по 512 байт + дескрипторы + ACM info +
|
||||||
|
setup buffer). При NonCacheable регионе 8 KB запас достаточный.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ISR и синхронизация
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
EP0 Control IN/OUT — управление (всегда есть, не конфигурируется)
|
USB_OTG1_IRQHandler (usb_cdc_hw.c)
|
||||||
EP1 INT IN — нотификации CDC (DTR, RTS — наследие модемов)
|
└── USB_DeviceEhciIsrFunction() (SDK)
|
||||||
EP2 BULK IN — данные device → host (твои JSON-строки → PC)
|
├── BulkOut callback → s_recvSize = len (volatile)
|
||||||
EP3 BULK OUT — данные host → device (команды PC → плата)
|
├── BulkIn callback → s_txIdle = 1 (volatile)
|
||||||
|
└── DeviceCallback → s_cdcState.attach (volatile)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Enumeration — что происходит при подключении кабеля
|
Синхронизация между ISR и main loop:
|
||||||
|
|
||||||
|
- **RX**: `bsp_usb_cdc_read()` входит в critical section (`DisableGlobalIRQ`),
|
||||||
|
копирует `s_recvSize`, сбрасывает в 0, выходит. Копирование из `s_recvBuf`
|
||||||
|
происходит после выхода из critical section.
|
||||||
|
- **TX**: `s_txIdle` — volatile flag, устанавливается в BulkIn callback (ISR),
|
||||||
|
проверяется в `bsp_usb_cdc_write()` (main loop). Гонка исключена: write
|
||||||
|
сбрасывает flag перед `USB_DeviceSendRequest`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FreeRTOS
|
||||||
|
|
||||||
|
Модуль работает без изменений в контексте FreeRTOS-задачи:
|
||||||
|
|
||||||
|
| Контекст | TX | RX |
|
||||||
|
|----------|----|----|
|
||||||
|
| bare-metal | `bsp_usb_cdc_write()` — non-blocking | `bsp_usb_cdc_read()` — polling |
|
||||||
|
| FreeRTOS | Из задачи, `write_ready()` + `vTaskDelay()` | Из задачи с yield |
|
||||||
|
|
||||||
|
Для минимальной латентности в FreeRTOS — будущий `USB_DEVICE_CONFIG_USE_TASK=1`
|
||||||
|
с `bsp_usb_cdc_poll()` из выделенной задачи.
|
||||||
|
|
||||||
|
`USB_DEVICE_INTERRUPT_PRIORITY` (3) должен быть ниже
|
||||||
|
`configMAX_SYSCALL_INTERRUPT_PRIORITY` при использовании FreeRTOS API из ISR.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Подключение
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# bsp/CMakeLists.txt — уже добавлено
|
||||||
|
add_subdirectory(usb_cdc)
|
||||||
|
|
||||||
|
# firmware/test/CMakeLists.txt
|
||||||
|
target_link_libraries(firmware_test PRIVATE
|
||||||
|
bsp_board
|
||||||
|
bsp_tick
|
||||||
|
bsp_usb_cdc
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
### HIL-тест
|
||||||
|
|
||||||
|
USB CDC появляется как второй COM-порт на хосте (помимо MCU-Link VCOM).
|
||||||
|
C-прошивка `tests/target/hil_usb_cdc/` — CLI через USB CDC.
|
||||||
|
pytest: `tools/hil/test_usb_cdc.py` — отправка/приём через pyserial.
|
||||||
|
|
||||||
|
Переменная окружения `HIL_USB_CDC_PORT` — порт USB CDC устройства таргета.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
1. Хост видит устройство (pull-up на D+)
|
just host::hil-usb-cdc
|
||||||
2. USB Reset (SE0, 10 мс)
|
|
||||||
3. GET_DESCRIPTOR(Device) → хост узнаёт VID/PID, версию USB
|
|
||||||
4. SET_ADDRESS → устройство получает адрес на шине (1–127)
|
|
||||||
5. GET_DESCRIPTOR(Configuration) → хост видит интерфейсы
|
|
||||||
6. GET_DESCRIPTOR(String) × N → имена для Device Manager
|
|
||||||
7. SET_CONFIGURATION(1) → USB stack поднимает эндпоинты
|
|
||||||
→ на стороне устройства срабатывает callback kUSB_DeviceEventSetConfiguration
|
|
||||||
8. Хост загружает драйвер по (bDeviceClass, idVendor, idProduct)
|
|
||||||
→ CDC ACM: cdc_acm.ko (Linux) / usbser.sys (Windows)
|
|
||||||
9. Появляется /dev/ttyACM0 или COM3
|
|
||||||
10. Пользователь открывает порт → хост посылает SET_CONTROL_LINE_STATE с DTR=1
|
|
||||||
→ устройство видит "хост подключён"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Шаг 10 критически важен: **пока терминал не открыт — DTR = 0**. Слать данные до появления DTR бессмысленно — хост их не читает.
|
Команды CLI прошивки:
|
||||||
|
|
||||||
|
| Команда | Ответ | Описание |
|
||||||
|
|---------|-------|----------|
|
||||||
|
| `PING` | `PONG` | Проверка канала |
|
||||||
|
| `ECHO <data>` | `<data>` | Echo-back данных |
|
||||||
|
|
||||||
|
### Host unit-тесты
|
||||||
|
|
||||||
|
Не применяются — модуль полностью завязан на USB hardware и NXP middleware.
|
||||||
|
Тестирование только через HIL.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. CDC ACM — детали класса
|
## Конфигурация
|
||||||
|
|
||||||
CDC (Communications Device Class) — класс для коммуникационных устройств. ACM (Abstract Control Model) — подкласс, изначально для модемов, сейчас стандарт де-факто для Virtual COM Port.
|
Все настройки находятся в приватных хедерах `src/`:
|
||||||
|
|
||||||
### Почему CDC ACM а не другие классы
|
| Файл | Настройка | Значение | Описание |
|
||||||
|
|------|-----------|----------|----------|
|
||||||
| Класс | Что видит OS | Проблема |
|
| `usb_device_config.h` | `USB_DEVICE_CONFIG_EHCI` | `1` | Контроллер EHCI0 |
|
||||||
|-------|-------------|----------|
|
| `usb_device_config.h` | `USB_DEVICE_CONFIG_ENDPOINTS` | `4` | EP0 + interrupt IN + bulk IN/OUT |
|
||||||
| **CDC ACM** | `/dev/ttyACM0`, `COM3` | — нет, это и нужно |
|
| `usb_device_config.h` | `USB_DEVICE_CONFIG_SELF_POWER` | `1` | Self-powered device |
|
||||||
| Vendor | ничего | нужен свой драйвер под каждую ОС |
|
| `usb_device_descriptor.h` | `USB_DEVICE_VID` | `0x1234` | Vendor ID (placeholder) |
|
||||||
| HID | `/dev/hidraw0` | пакет максимум 64 байта, неудобно |
|
| `usb_device_descriptor.h` | `USB_DEVICE_PID` | `0x0001` | Product ID (placeholder) |
|
||||||
| MSC | блочное устройство | совсем не то |
|
| `usb_cdc_hw.c` | `USB_DEVICE_INTERRUPT_PRIORITY` | `3` | NVIC приоритет |
|
||||||
|
| `usb_cdc_hw.c` | `BOARD_USB_PHY_D_CAL` | `0x0C` | PHY калибровка |
|
||||||
Главное преимущество CDC ACM: **стандартный драйвер есть везде** — Linux, Windows 10+, macOS — без установки чего-либо.
|
|
||||||
|
|
||||||
### Ограничения которые надо знать
|
|
||||||
|
|
||||||
**USB CDC не гарантирует границы сообщений.** Данные идут потоком через BULK-эндпоинты. Если ты послал `{"type":"result"}\n{"type":"summary"}\n` — хост может получить это как один кусок, два куска, или три куска произвольного размера.
|
|
||||||
|
|
||||||
Поэтому **всегда нужен frame delimiter**. В нашем проекте — символ `\n` (JSON-lines). Приёмная сторона буферизирует до `\n` и только тогда парсит JSON.
|
|
||||||
|
|
||||||
**Скорость** не ограничена физическими 115200 бод как у UART. USB Full Speed BULK даёт практически ~1 МБ/с. Baudrate в настройках терминала для CDC ACM — декоративный, реально на скорость не влияет.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. NXP USB Stack на IMXRT1052
|
## Файловая структура
|
||||||
|
|
||||||
### Архитектура стека
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
твой код (bsp_usb_cdc)
|
bsp/usb_cdc/
|
||||||
↕ callbacks + API
|
├── CMakeLists.txt
|
||||||
usb_device_cdc_acm.c ← CDC ACM класс (middleware/usb/device/class/)
|
├── README.md
|
||||||
↕
|
├── include/
|
||||||
usb_device_dci.c ← Device Controller Interface (middleware/usb/device/)
|
│ └── bsp/
|
||||||
↕
|
│ └── usb_cdc.h # публичный API — без NXP хедеров
|
||||||
usb_device_ehci.c ← EHCI контроллер (middleware/usb/device/)
|
└── src/
|
||||||
↕
|
├── usb_cdc.c # BSP API + USB device callbacks
|
||||||
USB PHY (usb_phy.c) ← физический уровень (middleware/usb/phy/)
|
├── usb_cdc_descriptors.c # дескрипторы + descriptor callbacks
|
||||||
↕
|
├── usb_cdc_hw.c # clock, PHY init, IRQ handler
|
||||||
EHCI hardware registers
|
├── usb_device_ch9.c # lite Chapter 9 (копия из NXP примера)
|
||||||
```
|
├── usb_device_ch9.h # lite ch9 API
|
||||||
|
├── usb_device_cdc_acm.h # lite: только CDC request codes
|
||||||
### Callback-архитектура
|
├── usb_device_config.h # конфигурация USB стека
|
||||||
|
├── usb_device_descriptor.h # VID/PID, endpoints, packet sizes
|
||||||
NXP USB stack работает через callbacks — ты не вызываешь функции стека для приёма данных, стек сам вызывает твои функции когда что-то происходит.
|
└── fsl_os_abstraction_config.h # OSA bare-metal конфиг
|
||||||
|
|
||||||
Два уровня callbacks:
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* 1. Callback уровня устройства — системные события */
|
|
||||||
usb_status_t USB_DeviceCallback(usb_device_handle handle,
|
|
||||||
uint32_t event,
|
|
||||||
void *param)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case kUSB_DeviceEventBusReset:
|
|
||||||
/* сброс шины — переинициализировать эндпоинты */
|
|
||||||
break;
|
|
||||||
case kUSB_DeviceEventSetConfiguration:
|
|
||||||
/* хост завершил enumeration — можно начинать работать */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Callback уровня CDC ACM класса — данные и управление */
|
|
||||||
usb_status_t USB_DeviceCdcAcmCallback(class_handle_t handle,
|
|
||||||
uint32_t event,
|
|
||||||
void *param)
|
|
||||||
{
|
|
||||||
switch (event) {
|
|
||||||
case kUSB_DeviceCdcEventSendResponse:
|
|
||||||
/* BULK IN передача завершена — буфер можно переиспользовать */
|
|
||||||
break;
|
|
||||||
case kUSB_DeviceCdcEventRecvResponse:
|
|
||||||
/* BULK OUT данные получены — param указывает на буфер */
|
|
||||||
break;
|
|
||||||
case kUSB_DeviceCdcEventSetControlLineState:
|
|
||||||
/* DTR/RTS изменились — проверяем подключение хоста */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### usb_device_config.h — конфигурационный файл
|
|
||||||
|
|
||||||
NXP USB stack требует конфигурационный хедер. Он **не входит в SDK** — его пишешь ты и кладёшь в `bsp/usb_cdc/src/`. Ключевые параметры:
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* bsp/usb_cdc/src/usb_device_config.h */
|
|
||||||
|
|
||||||
/* Тип контроллера: EHCI для IMXRT1052 */
|
|
||||||
#define USB_DEVICE_CONFIG_EHCI 1
|
|
||||||
|
|
||||||
/* Включаем CDC ACM класс */
|
|
||||||
#define USB_DEVICE_CONFIG_CDC_ACM 1
|
|
||||||
|
|
||||||
/* Количество одновременных CDC инстансов */
|
|
||||||
#define USB_DEVICE_CONFIG_CDC_ACM_INSTANCE_COUNT 1
|
|
||||||
|
|
||||||
/* Количество эндпоинтов (EP0 + INT + BULK IN + BULK OUT = 4) */
|
|
||||||
#define USB_DEVICE_CONFIG_ENDPOINTS 4
|
|
||||||
|
|
||||||
/* Размер BULK буферов (степень двойки, FS max = 64 байта на транзакцию,
|
|
||||||
но можно использовать большие буферы для нескольких транзакций) */
|
|
||||||
#define USB_DEVICE_CONFIG_CDC_ACM_MAX_DATAPIPE_SIZE 512
|
|
||||||
|
|
||||||
/* Bare-metal (без RTOS) */
|
|
||||||
#define USB_DEVICE_CONFIG_USE_TASK 0
|
|
||||||
```
|
|
||||||
|
|
||||||
### IRQ и polling
|
|
||||||
|
|
||||||
На IMXRT1052 USB работает через прерывания. Стек нужно "тикать" из ISR:
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* в startup или IRQ handler регистрации */
|
|
||||||
void USB_OTG1_IRQHandler(void) {
|
|
||||||
USB_DeviceEhciIsrFunction(g_usb_device_handle);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
В bare-metal также нужен периодический вызов `USB_DeviceTaskFunction()` из main loop — он обрабатывает отложенные события которые нельзя делать прямо в ISR.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Практические моменты для firmware_test
|
|
||||||
|
|
||||||
### Инициализация — правильный порядок
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* 1. Clock init — USB PLL должен быть поднят ДО USB init */
|
|
||||||
CLOCK_InitUsb1Pll(...); /* 480 MHz USB PLL */
|
|
||||||
CLOCK_InitUsb1Pfd(...);
|
|
||||||
|
|
||||||
/* 2. PHY init */
|
|
||||||
USB_EhciPhyInit(CONTROLLER_ID, CLK_USRPH_24MHZ, NULL);
|
|
||||||
|
|
||||||
/* 3. Device stack init */
|
|
||||||
USB_DeviceInit(CONTROLLER_ID, USB_DeviceCallback, &handle);
|
|
||||||
|
|
||||||
/* 4. Регистрация CDC ACM класса */
|
|
||||||
USB_DeviceCdcAcmInit(...);
|
|
||||||
|
|
||||||
/* 5. Старт */
|
|
||||||
USB_DeviceRun(handle);
|
|
||||||
```
|
|
||||||
|
|
||||||
Если clock не инициализирован до USB — enumeration не пройдёт, хост увидит "USB device not recognized".
|
|
||||||
|
|
||||||
### Определение факта подключения хоста
|
|
||||||
|
|
||||||
Не надо проверять "есть ли питание на VBUS". Правильный способ — смотреть на **DTR флаг** из `SET_CONTROL_LINE_STATE`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
static volatile bool s_host_connected = false;
|
|
||||||
|
|
||||||
/* внутри USB_DeviceCdcAcmCallback */
|
|
||||||
case kUSB_DeviceCdcEventSetControlLineState: {
|
|
||||||
usb_device_cdc_acm_request_param_struct_t *p = param;
|
|
||||||
/* бит 0 = DTR, бит 1 = RTS */
|
|
||||||
s_host_connected = (p->setupValue & 0x01) != 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool usb_cdc_is_connected(void) {
|
|
||||||
return s_host_connected;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Буферизация
|
|
||||||
|
|
||||||
NXP USB stack не буферизует — это твоя ответственность. Минимальная схема:
|
|
||||||
|
|
||||||
```
|
|
||||||
TX: кольцевой буфер → usb_cdc_write() кладёт туда данные
|
|
||||||
→ USB task вычитывает и передаёт через USB_DeviceCdcAcmSend()
|
|
||||||
→ по kUSB_DeviceCdcEventSendResponse — можно слать следующий чанк
|
|
||||||
|
|
||||||
RX: USB_DeviceCdcAcmRecv() регистрирует буфер для приёма
|
|
||||||
→ по kUSB_DeviceCdcEventRecvResponse — данные в буфере
|
|
||||||
→ приложение вычитывает до '\n' и парсит JSON
|
|
||||||
```
|
|
||||||
|
|
||||||
### Важно: двойная буферизация TX
|
|
||||||
|
|
||||||
`USB_DeviceCdcAcmSend()` принимает указатель на буфер и **не копирует данные**. Буфер должен жить до получения `kUSB_DeviceCdcEventSendResponse`. Типичная ошибка — передать указатель на локальную переменную.
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* НЕПРАВИЛЬНО */
|
|
||||||
void send_something(void) {
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof(buf), "{\"type\":\"result\"}\n");
|
|
||||||
USB_DeviceCdcAcmSend(handle, EP_BULK_IN, (uint8_t*)buf, strlen(buf));
|
|
||||||
/* buf уходит из стека — UB! */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ПРАВИЛЬНО — статический или глобальный буфер */
|
|
||||||
static uint8_t s_tx_buf[512];
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Схема эндпоинтов для дескрипторов
|
## Зависимости
|
||||||
|
|
||||||
```
|
| Зависимость | Тип | Описание |
|
||||||
EP номер Направление Тип Размер пакета Назначение
|
|-------------|-----|----------|
|
||||||
──────── ─────────── ───────── ───────────── ──────────
|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
|
||||||
EP0 IN + OUT Control 64 байта enumeration (автоматически)
|
| `bsp_board` | PRIVATE | Транзитивно: `clock_config.h`, `pin_mux.h`, SDK headers |
|
||||||
EP1 IN Interrupt 16 байт CDC нотификации (DTR/RTS events)
|
| `sdk_usb_device_ehci` | PRIVATE | EHCI контроллер + DCI абстракция |
|
||||||
EP2 IN Bulk 64 байта (FS) данные device → host
|
| `sdk_usb_phy` | PRIVATE | USB PHY инициализация (480 MHz PLL) |
|
||||||
EP3 OUT Bulk 64 байта (FS) данные host → device
|
| `sdk_osa_bm` | PRIVATE | OS Abstraction Layer (bare-metal, generic list) |
|
||||||
|
| `sdk_usb_common` | PRIVATE (транзитивно) | USB common headers (`usb.h`, `usb_misc.h`) |
|
||||||
|
| `sdk_usb_config` | PRIVATE (транзитивно) | INTERFACE: проброс конфиг-хедеров в SDK |
|
||||||
|
|
||||||
|
### Зависимости на уровне SDK CMake
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sdk_usb_device_ehci ─┬─ sdk_usb_common ── sdk_osa_bm ── sdk_usb_config
|
||||||
|
└─ sdk_usb_config │ │
|
||||||
|
components/osa bsp/usb_cdc/src/
|
||||||
|
sdk_usb_phy ── sdk_usb_common components/lists (конфиг-хедеры)
|
||||||
```
|
```
|
||||||
|
|
||||||
Номера EP назначаются в дескрипторах. NXP примеры используют именно эту схему для Full Speed CDC ACM.
|
`sdk_usb_config` — INTERFACE библиотека без кода. Единственная роль —
|
||||||
|
прокинуть include path к `bsp/usb_cdc/src/` для SDK таргетов,
|
||||||
---
|
которым нужны `usb_device_config.h` и `fsl_os_abstraction_config.h`.
|
||||||
|
|
||||||
## 7. Отладочные признаки проблем
|
|
||||||
|
|
||||||
| Симптом | Вероятная причина |
|
|
||||||
|---------|------------------|
|
|
||||||
| "USB device not recognized" на хосте | не инициализирован USB PLL / PHY |
|
|
||||||
| Устройство определяется, порт не появляется | ошибка в дескрипторах (класс, подкласс, протокол) |
|
|
||||||
| Порт появился, данные не идут | DTR не поднят (терминал не открыт) или ошибка TX буферизации |
|
|
||||||
| Данные обрываются / мусор | буфер TX освобождается до SendResponse |
|
|
||||||
| Работает раз через раз | нет re-submit RX буфера после RecvResponse |
|
|
||||||
| Зависает при переподключении | нет обработки kUSB_DeviceEventBusReset → не сбрасываются эндпоинты |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Ссылки
|
|
||||||
|
|
||||||
- `sdk/middleware/usb/` — исходники NXP USB stack
|
|
||||||
- `sdk/boards/evkbimxrt1050/usb_examples/usb_device_cdc_vcom/` — референсный пример
|
|
||||||
- `sdk/middleware/usb/device/class/usb_device_cdc_acm.c` — реализация класса
|
|
||||||
- `sdk/middleware/usb/include/usb_device_cdc_acm.h` — API класса
|
|
||||||
- USB 2.0 Specification — [usb.org](https://www.usb.org/document-library/usb-20-specification)
|
|
||||||
- USB CDC Specification (PSTN) — [usb.org](https://www.usb.org/document-library/class-definitions-communication-devices-12)
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue