Инструменты пользователя

Инструменты сайта


usb:notes

USB Notes

USB Host управляет всеми транзакциями на шине. Цикл транзакций на шине (Frame) запускается раз в миллисекунду. Планировщик перед запуском фрейма решает какие устройства будут опрошены. Поэтому, если осуществить запись (write) в QSerialPort или FTDI, то эти данные уйдут только в следующем фрейме. Т.е. ориентировочное время передачи данных в подключенное устройство составляет порядка 1мс. Но если подключенных устройств на шине USB много и у них более высокий приоритет, то есть вероятность, что данные не уйдут с ближайшим фреймом. Транзакции bulk data transfers имеют наименьший приоритет и проигрывают при планировании остальными типами передач: isochronous transfers / interrupts / control transfers. (Так написано у FTDI AN232B-04_DataLatencyFlow.pdf раздел "2.2 Data Transfer Comparison")

  • Frame USB запускается один раз в миллисекунду
  • Frame состоит из нескольких транзакций к подключенным устройствам которые выберет планировщик. В frame может быть несколько транзакций к какому-то устройству, а может не быть и одного.
  • Каждая транзакция состоит из обмена пакетами с одной из четырех EndPoint подключенного устройства. Нулевой EndPoint служит для настройки устройства, остальные три EndPoint служат для обмена данными. EndPoint - аналог порта в TCP/IP, т.е. логический адресат транзакции.

Типы пакетов:

  • Пакеты - маркеры:
    • SOF (0101) - пакет обозначает начало нового фрейма, содержит индекс фрейма 11bit.
    • SETUP (1101) - управляющий, содержит адрес устройства 7bit и конечную точку 4bit, с которой HOST планирует общаться.
    • OUT (0001) - маркер пакета с данными для записи в Endpoint. Содержит адрес устройства и конечную точку.
    • IN (1001) - маркер пакета с данными для чтения из Endpoint. Устройство выводит свои данные на шину в заданный промежуток времени внутри пакета. Содержит адрес устройства и конечную точку.
  • Пакеты данных:
    • Data0 (0011) - идентификатор четного пакета с данными
    • Data1 (1011) - идентификатор нечетного пакета с данными.
    • Data2 (0111) - дополнительные типы для широкополосных изохронных передач в HS USB2.0
    • MData (1111) - используется совместно с Data2
  • Пакеты подтверждения:
    • ACK (0010) - OK
    • NAK (1010) - точка занята, не готова к обмену данными
    • STALL (1110) - проблема, требуется вмешательство
    • NYET (0110) - пакет принят, но нет места для следующего пакета (USB2.0)
  • Спец-пакеты:
    • PING (0100) - пробный маркер управления потоков (USB2.0)

Размер данных, которые можно передать в BULK ENDPOINT составляет:

  • 64 байта для FullSpeed
  • 512 байт для Hi-Speed
  • 1024 для SuperSpeed

Microsoft: How to send USB bulk transfer requests Microsoft: Примеры BULF транзакций

При обмене OUT, от хоста:

  • подается IN или OUT пакет, задающий направление передачи данных.
  • вслед идут пакеты данных DATA0 и DATA1 поочередно.
  • Как только приходит пакет неполностью заполненный данными, например меньше чем 64 байта для FullSpeed, то значит это последний пакет. Принимающая сторона проверяет целостность данных и отправляет пакет-подтверждение:
    • ACK - всё успешно принято
    • NACK - не смог принять: например, переполнен входной буфер
    • STALL - данные адресованы отключённому endpoint

С приемом данных от устройства все аналогично, за исключением того, что если данных для чтения нет, то устройство сразу ответит NACK.

Wiki: USB

Драйвер FTDI

Чтение данных

Микросхема FTDI выдает данные при чтении в HOST, когда:

  • в буфере микросхемы есть более чем 62 байта пользовательских данных для отправки в РС. Два байта статуса добавляются в каждый пакет, что в сумме составит как раз 64 байта для полного заполнения пакета IN.
  • Если в буфере данных меньше чем 62 байта, пусть для определенности там осталось 10 байт, то ожидается, что этот буфер будет дозаполнен в ближайшее время со стороны микроконтроллера. Но если микроконтроллер ничего писать не планирует в ближайшее время, то эти байты могут надолго зависнуть в буфере FTDI. Чтобы этого не происходило, внутри FTDI реализован таймер, который сбрасывается при каждом отправлении пакета данных. По истечение этого таймера, микросхема FTDI посылает свой статус из 2-х байт и оставшиеся данные, если таковые залежались. По умолчанию значение таймера составляет 16 миллисекунд.

Получается, что HOST может прождать недостающие 10 байт целых 16 миллисекунд. При перезапуске фреймов USB каждую 1 миллисекунду, это спустя 16 фреймов! К счастью, этот параметр можно сократить до 2 миллисекунд функцией:

FT_STATUS FT_SetLatencyTimer (FT_HANDLE ftHandle, UCHAR ucTimer);

Важно отметить, что при непрерывном потоке данных в РС, этот таймер в общем-то бездействует, т.к. данных для отправки всегда будет накапливаться более 62 байт. Но вот остатки буфера, при остановке передачи, будут отправлены только по истечении этого таймера.

Драйвер FTDI выделяет в памяти два буфера для чтения:

  • трансферный буфер - для накопления данных из пакетов IN транзакций USB. Драйвер FTDI запрашивает системный драйвер USB заполнить этот буфер и ждет пока этот буфер будет полностью заполнен.
  • буфер для чтения - сюда копируются данные из трансферного буфера, когда трансферный буфер заполняется полностью. Именно этот буфер опрашивается функциями чтения из ftd2xx.dll.

Размер трансферного буфера должен быть кратен 64 и иметь размер от 64 до 64К байт. Значение задается функцией:

FT_STATUS FT_SetUSBParameters (FT_HANDLE ftHandle, DWORD dwInTransferSize, DWORD dwOutTransferSize)

  • ftHandle Handle of the device.
  • dwInTransferSize Transfer size for USB IN request. Note that, at present, only dwInTransferSize is supported!
  • dwOutTransferSize Transfer size for USB OUT request.

Системный драйвер запрашивает транзакции IN пока не накопит буфер размером dwInTransferSize. После этого буфер возвращается в драйвер FTDI который позволяет считать данный буфер в приложении пользователя.

Data is received from USB to the PC by a polling method. The driver will request a certain amount of data from the USB scheduler. This is done in multiples of 64 bytes. The 'bulk packet size' on USB is a maximum of 64 bytes. The host controller will read data from the device until either:

  • a packet shorter than 64 bytes is received or
  • the requested data length is reached

Если dwInTransferSize будет задан в 4096 байт, то буфер такого размера будет запрошен у системного драйвера USBD. Далее, если микроконтроллер будет отсылать данные по 62 байта в 16 миллисекунд, то фреймы данных в HOST будут приходить все по 64 байта (62 байта данных + 2 байта статуса). Т.е. возникает ситуация:

  • Системный драйвер формирует IN транзакции пока не накопит 4096 байт, либо пока не придет неполный пакет.
  • Но пакеты всегда приходят в HOST полные, по 64 байта. Потому что эти пакеты приходят ровно через период latency таймера, т.е. 16 миллисекунд. Переполнения таймера latency не происходят, и не возникает ситуация, когда FTDI мог бы посылать почти пустой пакет с 2-мя байтами статуса, что прервало бы ожидание в системном драйвере.
  • Драйверу остается ждать 4096 / 64 = 64 пакета, которые приходят через каждые 16 миллисекунд, что займет 1,024 секунды. Все это время пользовательское приложение не будет пришедших данных. Хотя, если бы размер запрашиваемого буфера был меньше, то приложение уже успело бы обработать часть пришедших данных!

The device driver will request packet sizes between 64 Bytes and 4 Kbytes. The size of the packet will affect the performance and is dependent on the data rate. For very high speed, the largest packet size is needed. For 'real-time' applications that are transferring audio data at 115200 Baud for example, the smallest packet possible is desirable, otherwise the device will be holding up 4k of data at a time. This can give the effect of 'jerky' data transfer if the USB request size is too large and the data rate too low (relatively).

Поэтому следует как-то обосновано выбирать размер буфера параметром dwInTransferSize. Согласно документу "AN232B-03 D2XX Optimizing D2XX Data Throughput", максимальная пропускная способность достигается если размер транспортного буфера выставить в максимальное значение, т.к. 64Кбайт. При этом в приложение будут поступать полезные данные размером 63448 байт, т.к. по 2 байта в каждом 64-байтном пакете занято битами статуса микросхемы.

  • 64К = 64 * 1024 = 65536 байт со статусом, максимальный размер трансферного буфера, из них:
    • 2 * 1024 = 2048 байт статуса
    • 62 * 1024 = 63448 байт полезных данных

Flow Control

Микросхема FTDI имеет внутренний буфер на 384 байта. Когда микроконтроллер, например по UART, пишет в FTDI, то эти данные сохраняются в данном буфере, а за тем, при возникновении USB транзакции IN, передаются в HOST компьютера. Транзакции IN формирует планировщик HOST-а, для которого пакеты типа BULK имеют наименьший приоритет и который должен опрашивать все подключенные устройства. Поэтому, в микросхеме FTDI возможно переполнение буфера из этих 384 байт, если UART пишет в микросхему данные достаточно быстро, а планировщик HOST-а запрашивает транзакции IN не достаточно часто.

Чтобы дать понять микроконтроллеру, что буфер FTDI полон и не готов принимать данные, настоятельно рекомендуется использовать дополнительные сигналы FlowControl. Согласно спецификации FTDI это:

  • RTS (Ready To Send, OUT) / CTS (Clear To Send, IN) - 2 wire handshake. The device will transmit if CTS is active and will drop RTS if it cannot receive any more.
  • DTR (Data Terminal Ready, OUT) / DSR (Data Set Ready, IN) - 2 wire handshake. The device will transmit if DSR is active and will drop DTR if it cannot receive any more.
  • XON/XOFF - flow control is done by sending or receiving special characters. One is XOn (transmit on) the other is XOff (transmit off). They are individually programmable to any value.

Картинка поясняет взаимодействие на примере пар подключений:

  • "Компьютер - Модем", для которого изначально был предназначен RS232. Модем передает данные в сеть Ethernet.
  • "Микроконтроллер - FTDI", FTDI передает данные в USB.

HID Report Descriptor

Расшифровка дескриптора репорта из примера

/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  //  b......xx : 0,1,2,3 - Extra Bytes count: 0,1,2,4
  //  b....xx.. : 0-Main, 1-Global, 2-Local, 3-Reserved
  //  bxxxx.... : Tag
  //  From Example:                            nn Extra Bytes count
  //    0x06 : Len_2, Global,          b0000_01nn UsagePage
  //    0x09 : Len_1, Local ,          b0000_10nn Usage
  //    0xA1 : Len_1, Main  , Tag = A  b1010_00nn Collection  0x01 - Application
  //    0x75 : Len_1, Global, Tag = 7  b0111_01nn ReportSize
  //    0x95 : Len_1, Global, Tag = 9  b1001_01nn ReportCount
  //    0x91 : Len_1, Main  , Tag = 9  b1001_00nn Output      0x02 - DataArray 
  //    0x81 : Len_1, Main  , Tag = 8  b1000_00nn Input       0x02 - DataArray
  //    0xC0 : Len_0, Main  , Tag = C  b1100_00nn EndCollection, nn = 0
    // COLLECTION:
    0x06, 0x00, 0xff,              // 	USAGE_PAGE (Vendor Defined Page 1, 0xFF00 - 0xFFFF is User Defined)
    0x09, 0x01,                    // 	USAGE (Vendor Usage 1)
    0xa1, 0x01,                    // 	COLLECTION (x01 - Application)
    // FROM PC
    0x06, 0x00, 0xff,              // 	  USAGE_PAGE (Vendor Defined Page 1)
    0x09, 0x01,                    //     USAGE (Vendor Usage 1)
    0x75, 0x08,                    //     REPORT_SIZE (8-bits)
    0x95, NT_USB_RX_LEN,           //     REPORT_COUNT (3)
    0x91, 0x02,                    //     OUTPUT (Data,Var,Abs)
    // TO PC
    0x06, 0x00, 0xff,              // 	  USAGE_PAGE (Vendor Defined Page 1)
    0x09, 0x01,                    //     USAGE (Vendor Usage 1)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, NT_USB_ACK_LEN,          //     REPORT_COUNT (12)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)  0x02 - DataArray, b0: Data/Const b1: Array/Variable

    0xC0                           //  END_COLLECTION
};
usb/notes.txt · Последнее изменение: 2023/05/05 11:37 — vasco