Ниже информация из разных источников и она зачастую противоречит друг другу (размеры фреймов, времена и т.д.). Для полного понимания лучше наверное читать официальную спецификацию.
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
};
При посылке данных от РС в HID устройство в функцию hid_write() первым байтом надо посылать ReportID (обычно равен 0). Поэтому если HID устройство должно принять 10 байт, то в hid_write() надо передавать массив из 11 байт. При этом, в дескрипторе HID должна быть указана длина принимаемого фрейма равного 10.
#define NT_USB_RX_LEN 10
/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
....
0x95, NT_USB_RX_LEN, // REPORT_COUNT (10)
....
}
Максимальная пропускная способность для FullSpeed
в спецификации USB 2.0 в главах 5.7.4 и 5.8.4:
CDC протокол использует Bulk Transfers. Накладные расходы Bulk составляют 13 байт:
HID протокол использует Interrupt Transfers. Накладные расходы Interrupt составляют 19 байт:
При использовании полезной нагрузки объёмом 64 байта максимальная пропускная способность для Bulk передачи данных составляет 1 216 000 байт/с. Это означает, что один кадр длительностью 1 мс может передать 1216 байт = 19 блоков по 64 байта (max packet len for FullSpeed).
Конечные точки Interrupt опрашиваются с максимальным интервалом один раз в миллисекунду, поэтому максимальная полезная нагрузка составит 64 Кбайт/с или меньше.