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")
Типы пакетов:
Размер данных, которые можно передать в BULK ENDPOINT составляет:
Microsoft: How to send USB bulk transfer requests Microsoft: Примеры BULF транзакций
При обмене OUT, от хоста:
С приемом данных от устройства все аналогично, за исключением того, что если данных для чтения нет, то устройство сразу ответит NACK.
Микросхема FTDI выдает данные при чтении в HOST, когда:
Получается, что HOST может прождать недостающие 10 байт целых 16 миллисекунд. При перезапуске фреймов USB каждую 1 миллисекунду, это спустя 16 фреймов! К счастью, этот параметр можно сократить до 2 миллисекунд функцией:
FT_STATUS FT_SetLatencyTimer (FT_HANDLE ftHandle, UCHAR ucTimer);
Важно отметить, что при непрерывном потоке данных в РС, этот таймер в общем-то бездействует, т.к. данных для отправки всегда будет накапливаться более 62 байт. Но вот остатки буфера, при остановке передачи, будут отправлены только по истечении этого таймера.
Драйвер FTDI выделяет в памяти два буфера для чтения:
Размер трансферного буфера должен быть кратен 64 и иметь размер от 64 до 64К байт. Значение задается функцией:
FT_STATUS FT_SetUSBParameters (FT_HANDLE ftHandle, DWORD dwInTransferSize, DWORD dwOutTransferSize)
Системный драйвер запрашивает транзакции 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:
Если dwInTransferSize будет задан в 4096 байт, то буфер такого размера будет запрошен у системного драйвера USBD. Далее, если микроконтроллер будет отсылать данные по 62 байта в 16 миллисекунд, то фреймы данных в HOST будут приходить все по 64 байта (62 байта данных + 2 байта статуса). Т.е. возникает ситуация:
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-байтном пакете занято битами статуса микросхемы.
Микросхема FTDI имеет внутренний буфер на 384 байта. Когда микроконтроллер, например по UART, пишет в FTDI, то эти данные сохраняются в данном буфере, а за тем, при возникновении USB транзакции IN, передаются в HOST компьютера. Транзакции IN формирует планировщик HOST-а, для которого пакеты типа BULK имеют наименьший приоритет и который должен опрашивать все подключенные устройства. Поэтому, в микросхеме FTDI возможно переполнение буфера из этих 384 байт, если UART пишет в микросхему данные достаточно быстро, а планировщик HOST-а запрашивает транзакции IN не достаточно часто.
Чтобы дать понять микроконтроллеру, что буфер FTDI полон и не готов принимать данные, настоятельно рекомендуется использовать дополнительные сигналы FlowControl. Согласно спецификации FTDI это:
Картинка поясняет взаимодействие на примере пар подключений:
Расшифровка дескриптора репорта из примера
/** 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 };