動 CDC 設(shè)備:枚舉、端點綁定與穩(wěn)定收發(fā)實戰(zhàn))
簡介這份資料面向嵌入式開發(fā)者與USB協(xié)議學(xué)習(xí)者聚焦MCU通過USB Host直接識別并驅(qū)動CDC類設(shè)備這一實際難題幫助打破傳統(tǒng)RS232接口的技術(shù)壁壘。內(nèi)容圍繞USB協(xié)議分析展開涵蓋插入檢測、NRZI編碼、SYNC同步域、SOP/EOP包結(jié)構(gòu)、PID類型、幀號與微幀機制并結(jié)合CH32V307主板實驗平臺演示以0地址獲取設(shè)備描述符、設(shè)置地址、獲取配置與接口端點描述符直至建立雙向通信的完整流程。資源包為1個PDF文件大小4.12MB內(nèi)容系統(tǒng)完整便于按章節(jié)查閱。目前已有2654人學(xué)習(xí)下載適合希望掌握USB Host通信流程、理解協(xié)議細節(jié)并應(yīng)用于串口轉(zhuǎn)USB設(shè)備對接的開發(fā)者參考。1. USB Host 驅(qū)動 CDC 設(shè)備從枚舉失敗到穩(wěn)定收發(fā)的那條路把一塊帶 USB Host 接口的開發(fā)板插上 4G 模組、串口轉(zhuǎn) USB 芯片或者某類 CDC 類設(shè)備最常遇到的不是「完全沒反應(yīng)」而是枚舉能過、設(shè)備也認到了但數(shù)據(jù)就是收不全、發(fā)不出或者跑幾分鐘就掉線。這類問題幾乎都卡在 USB Host 驅(qū)動 CDC 設(shè)備這條鏈路上主機控制器要正確枚舉CDC 的通信類接口和數(shù)據(jù)類接口要分別綁定端點要配對緩沖區(qū)要夠大還要處理 CDC 特有的控制請求。標題里說的「超詳細資料」真正有價值的部分不是協(xié)議手冊的復(fù)述而是把枚舉、描述符解析、端點配置、數(shù)據(jù)收發(fā)這幾步拆到能照著改代碼的程度。這篇面向正在用 USB Host 接 CDC 設(shè)備、被枚舉或收發(fā)卡住的嵌入式開發(fā)者從最小可跑通的路徑講到參數(shù)邊界和踩坑記錄。2. 先搞懂 CDC 設(shè)備在 USB Host 眼里長什么樣2.1 CDC 不是「一個串口」而是兩類接口的組合很多人第一次寫 USB Host 驅(qū)動 CDC 設(shè)備腦子里默認它就是一個串口打開、讀、寫。但 USB 協(xié)議層面CDCCommunication Device Class設(shè)備在描述符里通常暴露兩個接口一個通信類接口Communication Class Interface負責(zé)下發(fā)控制命令、上報線路狀態(tài)一個數(shù)據(jù)類接口Data Class Interface負責(zé)真正的數(shù)據(jù)搬運。通信類接口一般用中斷端點做事件通知數(shù)據(jù)類接口用批量端點做收發(fā)。這意味著主機側(cè)驅(qū)動要維護的不是一個句柄而是兩套接口狀態(tài)。枚舉階段如果只綁定了數(shù)據(jù)接口控制請求就沒人處理設(shè)備可能一直停在未配置狀態(tài)如果只綁定了通信接口數(shù)據(jù)端點沒打開讀寫自然全失敗。常見做法是在解析配置描述符時按接口逐個判斷 bInterfaceClass0x02 是通信類0x0A 是數(shù)據(jù)類分別記錄接口號、端點地址和端點屬性最后把兩者關(guān)聯(lián)到同一個設(shè)備實例上。判斷一個 CDC 設(shè)備是否被正確識別可以看主機側(cè)打印的接口信息。下面是一段典型的描述符解析輸出結(jié)構(gòu)用偽代碼表示字段含義/* 遍歷配置描述符中的每個接口 */ for (each interface in config_desc) { if (iface.bInterfaceClass 0x02) { /* 通信類接口通常含一個中斷 IN 端點 */ comm_iface_num iface.bInterfaceNumber; for (each ep in iface) { if (ep.bmAttributes 0x03) { /* 中斷端點 */ comm_ep_addr ep.bEndpointAddress; comm_ep_maxpacket ep.wMaxPacketSize; } } } else if (iface.bInterfaceClass 0x0A) { /* 數(shù)據(jù)類接口通常含批量 IN 和批量 OUT 各一個 */ data_iface_num iface.bInterfaceNumber; for (each ep in iface) { if (ep.bmAttributes 0x02) { /* 批量端點 */ if (ep.bEndpointAddress 0x80) data_in_ep ep.bEndpointAddress; else data_out_ep ep.bEndpointAddress; } } } }這段邏輯的關(guān)鍵在于不要假設(shè)端點順序固定。不同廠商的 CDC 實現(xiàn)里批量 IN 和 OUT 的地址順序可能相反中斷端點的最大包長也可能是 8、16 或 64 字節(jié)。把端點地址和最大包長都存下來后面配置主機控制器通道時直接用不要寫死。2.2 枚舉階段最容易忽略的三個描述符字段USB Host 驅(qū)動 CDC 設(shè)備時枚舉能不能過往往取決于三個字段有沒有被正確處理。第一個是 bNumInterfaces。復(fù)合 CDC 設(shè)備可能不止兩個接口有的還帶音頻控制接口或廠商自定義接口。如果主機側(cè)只按固定數(shù)量解析多出來的接口會被跳過設(shè)備配置不完整。第二個是 bmAttributes 里的自供電和遠程喚醒位。CDC 設(shè)備如果是自供電主機側(cè)配置時不能按總線供電去限制電流遠程喚醒位如果置位主機要允許設(shè)備發(fā)起喚醒否則設(shè)備休眠后主機收不到事件。第三個是 wMaxPacketSize。批量端點的最大包長常見是 64 字節(jié)但全速設(shè)備和高速設(shè)備不同高速批量端點最大可以到 512 字節(jié)。主機控制器通道配置時如果按 64 去設(shè)實際設(shè)備發(fā) 512 字節(jié)的包就會被截斷或報錯。提示枚舉失敗時先抓 USB 總線上的描述符原始數(shù)據(jù)逐字節(jié)對照不要只看主機側(cè)打印的「枚舉成功」或「枚舉失敗」。2.3 主機控制器通道分配端點不是越多越好USB Host 控制器一般有固定的通道數(shù)比如 8 個或 16 個。CDC 設(shè)備至少占用三個通道一個中斷 IN、一個批量 IN、一個批量 OUT。如果主機上還接了其他 USB 設(shè)備通道可能不夠用。常見做法是枚舉完成后先釋放掉控制端點占用的通道再按優(yōu)先級分配中斷端點和批量端點。中斷端點用于接收線路狀態(tài)變化優(yōu)先級可以高一些批量端點用于數(shù)據(jù)搬運可以共用通道或者按方向分開。如果通道實在不夠可以考慮把中斷端點輪詢關(guān)掉只靠批量端點收發(fā)但這樣會丟失串口斷線、振鈴等事件。通道分配失敗時主機側(cè)通常會報「管道打開失敗」或「端點配置錯誤」。這時候不要急著改端點地址先確認主機控制器還剩幾個空閑通道以及每個通道支持的最大包長和傳輸類型。3. 用最小工程跑通 CDC 枚舉與端點綁定3.1 從復(fù)位到設(shè)置配置主機側(cè)必須發(fā)的幾條命令USB Host 驅(qū)動 CDC 設(shè)備的枚舉流程和普通 USB 設(shè)備一致但每一步的返回值都要檢查。下面是一段主機側(cè)枚舉的偽代碼按順序執(zhí)行/* 1. 復(fù)位設(shè)備等待至少 10ms */ usb_host_port_reset(port); delay_ms(10); /* 2. 發(fā)送 GET_DESCRIPTOR 讀設(shè)備描述符先讀 8 字節(jié)拿 bMaxPacketSize0 */ usb_control_transfer(dev, 0x80, 0x06, 0x0100, 0, buf8, 8); ep0_max_packet buf8[7]; /* 3. 重新讀完整設(shè)備描述符長度 18 字節(jié) */ usb_control_transfer(dev, 0x80, 0x06, 0x0100, 0, dev_desc, 18); /* 4. 設(shè)置設(shè)備地址地址從 1 開始分配 */ usb_control_transfer(dev, 0x00, 0x05, new_addr, 0, NULL, 0); delay_ms(2); /* 5. 讀配置描述符先讀 9 字節(jié)拿 wTotalLength */ usb_control_transfer(dev, 0x80, 0x06, 0x0200, 0, cfg9, 9); total_len cfg9[2] | (cfg9[3] 8); /* 6. 讀完整配置描述符 */ usb_control_transfer(dev, 0x80, 0x06, 0x0200, 0, cfg_buf, total_len); /* 7. 設(shè)置配置通常選配置 1 */ usb_control_transfer(dev, 0x00, 0x09, 1, 0, NULL, 0);每一步的返回值都要判斷。第 2 步如果讀不到 8 字節(jié)說明設(shè)備根本沒響應(yīng)檢查供電和 D 上拉。第 4 步設(shè)置地址后后續(xù)所有通信都要用新地址。第 7 步設(shè)置配置如果返回 STALL說明配置值不對或者設(shè)備不支持該配置。3.2 解析配置描述符并綁定 CDC 端點拿到完整配置描述符后按接口和端點逐個解析。下面這段代碼把通信類接口和數(shù)據(jù)類接口的端點信息存到結(jié)構(gòu)體里typedef struct { uint8_t comm_iface; uint8_t comm_ep; uint16_t comm_ep_size; uint8_t data_iface; uint8_t data_in_ep; uint16_t data_in_ep_size; uint8_t data_out_ep; uint16_t data_out_ep_size; } cdc_endpoints_t; void parse_cdc_config(uint8_t *cfg, uint16_t len, cdc_endpoints_t *out) { uint16_t pos 0; uint8_t cur_iface_class 0; uint8_t cur_iface_num 0; while (pos len) { uint8_t bLength cfg[pos]; uint8_t bType cfg[pos 1]; if (bLength 0) break; if (bType 0x04) { /* INTERFACE */ cur_iface_num cfg[pos 2]; cur_iface_class cfg[pos 5]; } else if (bType 0x05) { /* ENDPOINT */ uint8_t ep_addr cfg[pos 2]; uint8_t ep_attr cfg[pos 3]; uint16_t ep_size cfg[pos 4] | (cfg[pos 5] 8); if (cur_iface_class 0x02 (ep_attr 0x03) 0x03) { out-comm_iface cur_iface_num; out-comm_ep ep_addr; out-comm_ep_size ep_size; } else if (cur_iface_class 0x0A (ep_attr 0x03) 0x02) { out-data_iface cur_iface_num; if (ep_addr 0x80) { out-data_in_ep ep_addr; out-data_in_ep_size ep_size; } else { out-data_out_ep ep_addr; out-data_out_ep_size ep_size; } } } pos bLength; } }參數(shù)說明cfg 是完整配置描述符緩沖區(qū)len 是 wTotalLength。bLength 為 0 表示描述符異常直接退出。接口描述符的 bInterfaceClass 在偏移 5端點描述符的 bmAttributes 在偏移 3wMaxPacketSize 在偏移 4 和 5。解析完后comm_ep 和 data_in_ep、data_out_ep 都應(yīng)該有值否則說明設(shè)備描述符不符合 CDC 規(guī)范。3.3 打開管道并做第一次批量收發(fā)端點綁定完成后主機控制器要為每個端點打開管道。下面以批量 OUT 發(fā)送為例/* 打開批量 OUT 管道 */ pipe_out usb_host_pipe_open(dev, data_out_ep, PIPE_TYPE_BULK, data_out_ep_size); if (pipe_out 0) { printf(open bulk out pipe failed\n); return -1; } /* 發(fā)送數(shù)據(jù)長度不超過端點最大包長的整數(shù)倍 */ uint8_t send_buf[64] {0x01, 0x02, 0x03}; int ret usb_host_pipe_write(pipe_out, send_buf, sizeof(send_buf), 1000); if (ret ! sizeof(send_buf)) { printf(bulk out write failed, ret%d\n, ret); }批量 IN 接收類似但要注意CDC 數(shù)據(jù)類接口的批量 IN 端點可能一直有數(shù)據(jù)也可能只在設(shè)備有數(shù)據(jù)時才返回。主機側(cè)接收時要設(shè)置合理的超時不要死等。如果設(shè)備發(fā)來的數(shù)據(jù)長度不是最大包長的整數(shù)倍說明一個短包結(jié)束可以認為本次傳輸完成。第一次收發(fā)建議先發(fā)一條已知命令比如 CDC 的 SET_CONTROL_LINE_STATE看設(shè)備是否響應(yīng)。如果批量 OUT 寫成功但設(shè)備沒反應(yīng)檢查通信類接口的控制請求有沒有發(fā)對。4. CDC 控制請求與數(shù)據(jù)通道的配合細節(jié)4.1 SET_LINE_CODING 和 SET_CONTROL_LINE_STATE 怎么發(fā)CDC 設(shè)備的數(shù)據(jù)通道能不能正常工作取決于通信類接口上的幾個控制請求。最常見的是 SET_LINE_CODING設(shè)置波特率、數(shù)據(jù)位、停止位、校驗位和 SET_CONTROL_LINE_STATE設(shè)置 DTR、RTS。這兩個請求都通過控制端點發(fā)送目標接口是通信類接口。下面是一段發(fā)送 SET_LINE_CODING 的代碼/* SET_LINE_CODING: bmRequestType0x21, bRequest0x20 */ uint8_t line_coding[7]; line_coding[0] 0x80; /* dwDTERate 低字節(jié)115200 */ line_coding[1] 0xC2; line_coding[2] 0x01; line_coding[3] 0x00; line_coding[4] 0x00; /* 1 停止位 */ line_coding[5] 0x00; /* 無校驗 */ line_coding[6] 0x08; /* 8 數(shù)據(jù)位 */ usb_control_transfer(dev, 0x21, 0x20, 0, comm_iface, line_coding, 7);參數(shù)說明bmRequestType 的 0x21 表示主機到設(shè)備、類請求、接口接收者。wValue 為 0wIndex 是通信類接口號。line_coding 的 7 個字節(jié)按小端序排列波特率 115200 對應(yīng) 0x0001C200。SET_CONTROL_LINE_STATE 更簡單wValue 的 bit0 是 DTRbit1 是 RTS/* 拉高 DTR 和 RTS */ usb_control_transfer(dev, 0x21, 0x22, 0x03, comm_iface, NULL, 0);如果設(shè)備要求先拉 DTR 才能收發(fā)數(shù)據(jù)這一步不能省。很多 CDC 模組在 DTR 為低時會忽略批量數(shù)據(jù)。4.2 中斷端點上報的線路狀態(tài)怎么處理通信類接口的中斷 IN 端點會周期性上報線路狀態(tài)比如串口斷線、振鈴、載波檢測。主機側(cè)要開一個接收任務(wù)或者輪詢這個端點否則設(shè)備可能認為主機不關(guān)心狀態(tài)而停止上報。中斷端點的數(shù)據(jù)通常是 2 字節(jié)或更多第一個字節(jié)是狀態(tài)位圖。下面是一段輪詢處理uint8_t status_buf[16]; int n usb_host_pipe_read(comm_pipe, status_buf, sizeof(status_buf), 100); if (n 2) { if (status_buf[0] 0x01) { printf(cdc: carrier detected\n); } if (status_buf[0] 0x02) { printf(cdc: dcd changed\n); } }輪詢超時不要設(shè)太長100ms 左右比較合適。如果一直讀不到數(shù)據(jù)檢查中斷端點的輪詢間隔和主機控制器是否支持中斷傳輸。4.3 批量收發(fā)緩沖區(qū)大小與流控CDC 數(shù)據(jù)類接口的批量端點沒有硬件流控主機側(cè)必須自己控制發(fā)送節(jié)奏。如果主機發(fā)得太快設(shè)備緩沖區(qū)滿了批量 OUT 會返回 NAK主機控制器會重試但重試次數(shù)有限超時后就報錯。常見做法是發(fā)送緩沖區(qū)不要超過端點最大包長的整數(shù)倍每次發(fā)送后等設(shè)備返回 ACK 或者等一個短延時。接收緩沖區(qū)要足夠大至少能放下一次最大傳輸?shù)臄?shù)據(jù)。如果設(shè)備發(fā)來的數(shù)據(jù)超過緩沖區(qū)多出來的部分會丟失。下面是一個帶流控的發(fā)送函數(shù)int cdc_send(uint8_t *data, int len) { int sent 0; while (sent len) { int chunk len - sent; if (chunk data_out_ep_size) chunk data_out_ep_size; int ret usb_host_pipe_write(pipe_out, data sent, chunk, 500); if (ret ! chunk) { printf(cdc send failed at %d, ret%d\n, sent, ret); return -1; } sent chunk; delay_ms(1); /* 給設(shè)備一點處理時間 */ } return sent; }參數(shù)說明data_out_ep_size 是批量 OUT 端點的最大包長chunk 不能超過它。超時 500ms 是經(jīng)驗值太短容易誤報太長會卡住上層。delay_ms(1) 是給設(shè)備緩沖區(qū)留出處理時間如果設(shè)備處理快可以去掉。5. 避坑CDC 驅(qū)動調(diào)試中最容易翻車的五個點5.1 枚舉成功但批量端點打不開現(xiàn)象主機側(cè)打印「設(shè)備枚舉成功」但打開批量管道時返回失敗。原因配置描述符解析時把端點地址搞錯了或者主機控制器通道不夠。還有一種可能是設(shè)備要求先發(fā) SET_CONFIGURATION 再發(fā) SET_INTERFACE但主機側(cè)只發(fā)了前者。解決打印解析出的端點地址和主機控制器剩余通道數(shù)逐字節(jié)對照配置描述符。如果通道不夠先關(guān)掉不用的端點。5.2 批量 OUT 寫成功但設(shè)備沒反應(yīng)現(xiàn)象usb_host_pipe_write 返回寫入長度但設(shè)備端收不到數(shù)據(jù)。原因CDC 設(shè)備的 DTR 沒拉高或者 SET_LINE_CODING 沒發(fā)。有些設(shè)備在收到 SET_CONTROL_LINE_STATE 之前會忽略批量數(shù)據(jù)。解決在打開批量管道后先發(fā) SET_LINE_CODING 和 SET_CONTROL_LINE_STATE再發(fā)數(shù)據(jù)。如果設(shè)備有狀態(tài)上報觀察中斷端點有沒有返回。5.3 接收數(shù)據(jù)斷斷續(xù)續(xù)或丟包現(xiàn)象批量 IN 能收到數(shù)據(jù)但偶爾丟幾個字節(jié)或者整包丟失。原因接收緩沖區(qū)太小或者主機側(cè)讀得太慢設(shè)備緩沖區(qū)溢出。也可能是中斷端點沒處理設(shè)備狀態(tài)異常。解決增大接收緩沖區(qū)至少是端點最大包長的 4 倍。提高接收任務(wù)優(yōu)先級或者用 DMA 搬運。檢查中斷端點是否有斷線事件。5.4 跑幾分鐘后設(shè)備掉線現(xiàn)象初始收發(fā)正常運行一段時間后設(shè)備從主機側(cè)消失。原因USB 線纜質(zhì)量差、供電不足或者主機側(cè)沒有處理掛起和恢復(fù)。CDC 設(shè)備如果進入掛起主機沒有發(fā)恢復(fù)信號設(shè)備會掉線。解決檢查供電和線纜確保 VBUS 穩(wěn)定。在主機側(cè)實現(xiàn)掛起檢測和恢復(fù)流程或者關(guān)閉設(shè)備的遠程喚醒讓主機定期發(fā) SOF 保持連接。5.5 控制請求返回 STALL現(xiàn)象發(fā) SET_LINE_CODING 或 SET_CONTROL_LINE_STATE 時返回 STALL。原因wIndex 填錯了接口號或者設(shè)備不支持該請求。有些 CDC 設(shè)備只支持部分控制請求。解決確認 wIndex 是通信類接口號不是數(shù)據(jù)類接口號。如果設(shè)備確實不支持跳過該請求直接試數(shù)據(jù)收發(fā)。6. 用狀態(tài)機把 CDC 收發(fā)做成可復(fù)用的模塊6.1 把枚舉、配置、收發(fā)拆成四個狀態(tài)CDC 驅(qū)動如果寫成一條直線調(diào)試時很難定位問題。我一般會把它拆成四個狀態(tài)IDLE、ENUMERATING、CONFIGURED、ERROR。每個狀態(tài)只做一件事狀態(tài)遷移條件明確。typedef enum { CDC_STATE_IDLE, CDC_STATE_ENUMERATING, CDC_STATE_CONFIGURED, CDC_STATE_ERROR } cdc_state_t; void cdc_task(void) { switch (state) { case CDC_STATE_IDLE: if (usb_device_connected()) { state CDC_STATE_ENUMERATING; } break; case CDC_STATE_ENUMERATING: if (cdc_enumerate() 0) { state CDC_STATE_CONFIGURED; } else { state CDC_STATE_ERROR; } break; case CDC_STATE_CONFIGURED: cdc_poll(); if (usb_device_disconnected()) { state CDC_STATE_IDLE; } break; case CDC_STATE_ERROR: cdc_cleanup(); state CDC_STATE_IDLE; break; } }參數(shù)說明cdc_enumerate 內(nèi)部完成復(fù)位、讀描述符、設(shè)置配置、打開管道。cdc_poll 負責(zé)中斷端點輪詢和批量數(shù)據(jù)搬運。狀態(tài)機的好處是掉線后能自動回到 IDLE 重新枚舉不用重啟整個系統(tǒng)。6.2 用環(huán)形緩沖區(qū)解耦接收和業(yè)務(wù)處理批量 IN 收到的數(shù)據(jù)不要直接在中斷里處理放到環(huán)形緩沖區(qū)業(yè)務(wù)任務(wù)從緩沖區(qū)讀。這樣接收不會因為業(yè)務(wù)處理慢而丟包。#define CDC_RX_BUF_SIZE 1024 static uint8_t rx_buf[CDC_RX_BUF_SIZE]; static volatile int rx_head 0, rx_tail 0; void cdc_rx_push(uint8_t *data, int len) { for (int i 0; i len; i) { int next (rx_head 1) % CDC_RX_BUF_SIZE; if (next rx_tail) { /* 緩沖區(qū)滿丟棄最舊數(shù)據(jù) */ rx_tail (rx_tail 1) % CDC_RX_BUF_SIZE; } rx_buf[rx_head] data[i]; rx_head next; } } int cdc_rx_pop(uint8_t *out, int max_len) { int n 0; while (n max_len rx_tail ! rx_head) { out[n] rx_buf[rx_tail]; rx_tail (rx_tail 1) % CDC_RX_BUF_SIZE; } return n; }參數(shù)說明CDC_RX_BUF_SIZE 根據(jù)業(yè)務(wù)數(shù)據(jù)量調(diào)整一般 1024 或 2048 夠用。rx_head 和 rx_tail 用 volatile 修飾因為可能在中斷和任務(wù)兩個上下文里訪問。緩沖區(qū)滿時丟棄最舊數(shù)據(jù)保證新數(shù)據(jù)能進來。6.3 驗證方法用回環(huán)測試確認鏈路完整CDC 驅(qū)動寫完后最直接的驗證是回環(huán)測試主機發(fā)一串數(shù)據(jù)設(shè)備收到后原樣返回。如果返回的數(shù)據(jù)和發(fā)送的一致說明枚舉、配置、收發(fā)、控制請求都對了。void cdc_loopback_test(void) { uint8_t tx[32], rx[32]; for (int i 0; i sizeof(tx); i) tx[i] i; cdc_send(tx, sizeof(tx)); delay_ms(100); int n cdc_rx_pop(rx, sizeof(rx)); if (n sizeof(tx) memcmp(tx, rx, n) 0) { printf(cdc loopback ok\n); } else { printf(cdc loopback failed, n%d\n, n); } }回環(huán)測試通過后再逐步加大數(shù)據(jù)量和縮短間隔觀察多久會丟包。如果跑 10 分鐘不丟基本可以認為鏈路穩(wěn)定。6.4 一個參數(shù)表CDC 驅(qū)動常用配置項配置項常見值說明批量端點最大包長64 / 512全速 64高速 512中斷端點輪詢間隔16 / 32 ms根據(jù)設(shè)備描述符設(shè)置批量 OUT 超時500 ms太短誤報太長卡住接收緩沖區(qū)大小1024 / 2048按業(yè)務(wù)數(shù)據(jù)量調(diào)整控制請求超時1000 msSET_LINE_CODING 等狀態(tài)機輪詢周期10 ms檢測掉線和狀態(tài)變化這張表里的值不是固定的要根據(jù)具體設(shè)備和主機控制器調(diào)整。比如高速設(shè)備批量端點最大包長是 512如果按 64 配吞吐量會掉到八分之一。6.5 我踩過的一個坑忽略端點最大包長導(dǎo)致吞吐量上不去早期做 CDC 驅(qū)動時批量端點最大包長我直接寫死 64設(shè)備是高速的實際支持 512。結(jié)果收發(fā)都正常但吞吐量只有理論值的十分之一。查了很久才發(fā)現(xiàn)是主機控制器通道配置時按 64 設(shè)的每次傳輸都被拆成多個小包。后來改成從描述符里讀 wMaxPacketSize吞吐量直接上來了。這個坑的教訓(xùn)是USB 描述符里的每個字段都有用不要憑經(jīng)驗寫死。尤其是端點最大包長、輪詢間隔、接口數(shù)量這些不同設(shè)備差異很大。調(diào)試時先把描述符完整打印出來逐字段確認比事后猜要快得多。希望幫到你。本文還有配套的精品資源點擊獲取