USB2.0高速傳輸:Slave FIFO模式實戰(zhàn)解析)
簡介面向需要實現(xiàn)USB2.0高速批量數(shù)據(jù)傳輸?shù)那度胧介_發(fā)者與FPGA工程師這份資源提供基于Cypress CY68013的完整Bulk傳輸實施方案采用Slave FIFO模式實現(xiàn)FPGA與USB主機間的高效數(shù)據(jù)交互適用于數(shù)據(jù)采集、文件備份等非實時大批量傳輸場景。包內(nèi)共45個文件其中Verilog源碼構(gòu)成FPGA側(cè)核心邏輯C語言固件與頭文件負(fù)責(zé)USB控制器配置另有a51啟動代碼、鏈接腳本、hex固件、IIC文件及docx控制器說明書整體約385KB工程結(jié)構(gòu)緊湊且模塊劃分清晰便于對照學(xué)習(xí)。目前已有721人學(xué)習(xí)下載。資源中FPGA代碼覆蓋握手信號管理、FIFO緩沖、時序控制、CRC錯誤檢測與協(xié)議轉(zhuǎn)換等關(guān)鍵模塊固件源碼則處理端點配置、中斷維護與Bulk傳輸隊列管理兩者協(xié)同可直接驗證高速通信。附帶詳盡的控制器說明書和測試?yán)棠軒椭_發(fā)者快速理解寄存器設(shè)置、數(shù)據(jù)傳輸流程及聯(lián)調(diào)方法極大簡化USB2.0設(shè)備端設(shè)計縮短項目開發(fā)周期。1. 給 FPGA 項目加一條 40MB/s 的高速通道CY68013 Slave FIFO 模式實戰(zhàn)做某圖像處理 Demo 時FPGA 端一幀數(shù)據(jù)就有 512×512×16bit串口根本傳不動等一幀出來夠喝一杯茶了。后來把這個 USB2.0 的 CY68013 芯片用起來跑高速 bulk 傳輸?shù)?Slave FIFO 模式FPGA 側(cè)只寫狀態(tài)機固件側(cè)只改初始化配置整條鏈路穩(wěn)定跑到了 30~40MB/s。這個項目等于把“FPGA 固件”兩段一起打通固件把 CY68013 配成 Slave FIFOFPGA 像操作普通 FIFO 一樣去讀寫端點PC 端再開一條 bulk 管道讀數(shù)據(jù)。適合不想從頭啃 USB 協(xié)議棧、但手里已經(jīng)有 FPGA 邏輯想做成高速采集通道的人。讀完這篇你能自己把模塊復(fù)刻到板子上。2. 整體架構(gòu)數(shù)據(jù)從 PC 到 FPGA 是怎么流過去的2.1 Slave FIFO 模式下 CPU 只干配置的活在 CY68013 的 Slave FIFO 模式下USB 端點 RAM 被直接映射到外部引腳8051 內(nèi)核在數(shù)據(jù)搬運路徑上被繞開了。你可以先把這個芯片理解成一個帶狀態(tài)標(biāo)志的 FIFOPC 通過 USB 寫下來的數(shù)據(jù)會出現(xiàn)在外部數(shù)據(jù)總線FD上FPGA 想往 PC 發(fā)數(shù)據(jù)只要往FD上寫硬件會把數(shù)據(jù)打包成 USB IN 包送出去。8051 只在TD_Init里完成配置之后就可以一直停在輪詢循環(huán)里數(shù)據(jù)不走 CPU。整條鏈路分三段PC 上跑測試程序通過 bulk 管道和設(shè)備通信設(shè)備這邊是 CY68013 的 SIE 和端點 FIFO再往外是 FPGA 的 Slave FIFO 控制邏輯。這個項目把 EP2 配置成 IN 方向FPGA 到 PC把 EP6 配置成 OUT 方向PC 到 FPGA。后續(xù)你會發(fā)現(xiàn)真正需要你寫邏輯的地方其實很少關(guān)鍵是初始化順序和寄存器值得對不對。2.2 關(guān)鍵信號表FPGA 側(cè)需要認(rèn)全的十幾根線FPGA 和 CY68013 之間不經(jīng)過任何協(xié)議轉(zhuǎn)換就是一根數(shù)據(jù)總線加幾根控制線。下面這張表是我在工程里一直對著看的方向都是以 FPGA 為基準(zhǔn)。信號方向FPGA 視角作用FD[15:0]輸入/輸出數(shù)據(jù)總線支持 8 位或 16 位FIFOADR[1:0]輸出選擇端點00EP201EP410EP611EP8SLCS輸出片選低有效調(diào)試時可以直接拉低SLWR輸出寫選通低電平有效SLRD輸出讀選通低電平有效SLOE輸出輸出使能控制 FD 總線方向低電平有效PKTEND輸出手動結(jié)束短包低電平有效FLAGB輸入當(dāng)前端點的狀態(tài)標(biāo)志FLAGC輸入當(dāng)前端點的狀態(tài)標(biāo)志常見做法是把 FLAGC 配成“當(dāng)前 FIFO 非滿”FPGA 上傳時先查它把 FLAGB 配成“當(dāng)前 FIFO 有數(shù)據(jù)”FPGA 下載時先查它。注意極性不是固定的可以在固件里通過端點 FIFO 極性寄存器改。后面所有代碼我都按低有效寫如果你的板子恰好是反的把if (!flagc)改成if (flagc)就行。2.3 為什么是 Slave FIFO而不是 GPIF 或 USB 轉(zhuǎn)串口GPIF 模式下CY68013 自己是主控固件要維護波形描述符時序由芯片產(chǎn)生。這樣做對復(fù)雜外設(shè)很靈活但調(diào)試成本高改一個等待周期都要重新編譯。Slave FIFO 正好反過來外部主控是 FPGA芯片只是個從設(shè)備。FPGA 里本來就有采集、緩存邏輯把 USB 側(cè)抽象成“往 FIFO 里寫、從 FIFO 里讀”狀態(tài)機能少寫一半。USB 轉(zhuǎn)串口芯片也不是不能傳數(shù)據(jù)但很多方案的穩(wěn)定吞吐只有幾 MB/s和高速 bulk 的 40MB/s 級別差太遠。這個項目選擇 Slave FIFO 的核心邏輯就是把 USB 協(xié)議復(fù)雜度全部留給硬件讓 FPGA 只碰最原始的讀寫信號。3. 固件側(cè)實現(xiàn)先把 CY68013 配成 Slave FIFO3.1 固件框架里必看的三個文件標(biāo)準(zhǔn)的 FX2 固件框架一般由fw.c、periph.c、dscr.c組成。拿到源碼先不要急著看所有文件我一般只看這三個fw/ fw.c # 主循環(huán)、設(shè)備枚舉、重枚舉 periph.c # TD_Init / TD_Poll項目主要改動在這 dscr.c # USB 描述符表TD_Init()在芯片上電后調(diào)用一次所有 Slave FIFO 模式相關(guān)的寄存器都在這里配。TD_Poll()是主循環(huán)里的周期函數(shù)Slave FIFO 模式下 CPU 不搬數(shù)據(jù)所以這個函數(shù)可以留空或者只放一條SUDPTRH命令。dscr.c里改 VID/PID 和端點描述符改完以后上位機驅(qū)動才認(rèn)你的設(shè)備是這個樣子。3.2 TD_Init 里的初始化順序時鐘、復(fù)位、端點TD_Init()里最關(guān)鍵的步驟如下代碼里我加了注釋可以直接對著寄存器手冊看void TD_Init(void) { // 1. 配置接口Slave FIFO 模式使用內(nèi)部 48MHz 時鐘 IFCONFIG 0xE3; SYNCDELAY; // 等待芯片內(nèi)部同步完成 // 2. 復(fù)位所有端點 FIFO這一步能解決很多“FLAG 拉不低”的玄學(xué)問題 FIFORESET 0x80; SYNCDELAY; FIFORESET 0x02; // 復(fù)位 EP2 SYNCDELAY; FIFORESET 0x04; // 復(fù)位 EP4 SYNCDELAY; FIFORESET 0x06; // 復(fù)位 EP6 SYNCDELAY; FIFORESET 0x08; // 復(fù)位 EP8 SYNCDELAY; FIFORESET 0x00; SYNCDELAY; // 3. 打開端點自動模式相關(guān)控制位 REVCTL 0x03; SYNCDELAY; // 4. 配置端點方向和類型 EP2CFG 0xA0; // EP2BULK IN512 字節(jié)端點 SYNCDELAY; EP6CFG 0xE0; // EP6BULK OUT512 字節(jié)端點 SYNCDELAY; // 5. 配置端點 FIFO 數(shù)據(jù)寬度和自動模式 EP2FIFOCFG 0x18; // EP216 位數(shù)據(jù) AUTOIN SYNCDELAY; EP6FIFOCFG 0x0C; // EP616 位數(shù)據(jù) AUTOOUT SYNCDELAY; // 6. 設(shè)置自動提交長度為 512 字節(jié) EP2AUTOINLENH 0x02; SYNCDELAY; EP2AUTOINLENL 0x00; SYNCDELAY; }第一行IFCONFIG 0xE3是很多例程里都會出現(xiàn)的值它把接口模式切到 Slave FIFO同時選擇內(nèi)部 48MHz 時鐘。如果你的 FPGA 想自己提供接口時鐘就要把這一行里的時鐘源位改掉我就遇到過有人在這里漏改導(dǎo)致后續(xù)所有數(shù)據(jù)都串位。FIFORESET順序很重要。我在第一次調(diào)這塊芯片時跳過這一步結(jié)果FLAGB、FLAGC一直保持同一個電平FPGA 狀態(tài)機卡死在等待里。后來把 0x80、0x02、0x04、0x06、0x08、0x00 按順序執(zhí)行一遍以后標(biāo)志位馬上正常了。EP2CFG 0xA0的二進制是1010_0000bit7 置 1 表示端點有效bit6 為 0 表示 IN 方向bit5、bit4 為10表示 BULK。EP6CFG 0xE0的 bit6 為 1表示 OUT 方向其余字段含義一致。如果你想讓 EP4 或 EP8 干活就要把EPxCFG和FIFOADR編址一起對上。3.3 AUTOIN 與短包提交FPGA 不用自己封 USB 包EP2FIFOCFG 0x18打開了 AUTOIN 位。AUTOIN 的意義是FPGA 往 EP2 的 FIFO 里寫滿 512 字節(jié)后芯片硬件自動把這個 packet 提交給 USBCPU 完全不參與。EP2AUTOINLENH/L設(shè)置的 0x0200 就是 512 字節(jié)剛好和端點描述符里的wMaxPacketSize對應(yīng)。既然有自動提交那短包怎么處理FPGA 傳的數(shù)據(jù)如果不足 512 字節(jié)AUTOIN 不會自己提交必須由 FPGA 拉低PKTEND信號告訴芯片“這個包到這里結(jié)束”。這個信號在項目里特別容易漏上位機收到的數(shù)據(jù)少了一段或者干脆整個事務(wù)卡死一查是 FPGA 端PKTEND沒有產(chǎn)生。后面 FPGA 狀態(tài)機里會給一個UP_PKT狀態(tài)專門處理它。EP6FIFOCFG 0x0C則是把 EP6 配成 AUTOOUT主機數(shù)據(jù)到達 512 字節(jié)后自動推給 FPGA。這個方向不需要 PKTEND主機結(jié)束包由主機側(cè)控制。3.4 描述符端點屬性和 VID/PID 改哪里描述符表不一定經(jīng)常動但改端點時必然要改。我把常用的改動點放在一個表里方便照著改描述符位置常見值說明VID/PID例如0x04B4/0x1004每款板子建議獨立設(shè)置驅(qū)動靠它認(rèn)設(shè)備接口描述符 bInterfaceClass0xFF廠商自定義避免綁定系統(tǒng)自帶的 HID 或 WinUSB 類驅(qū)動端點描述符 bmAttributes0x02BULK需要高速吞吐就用 BULK不是 Interrupt端點描述符 wMaxPacketSize0x0200512必須和EPxCFG里的 512 一致Slave FIFO 模式不改 USB 協(xié)議描述符里只要寫著“這是個 bulk 端點”上位機就能用 bulk 管道訪問。很多跑不通的例子最后都是 VID/PID 和驅(qū)動簽名不一致重新插拔后設(shè)備被識別成未知設(shè)備。建議每次改完固件用廠商推薦的重枚舉方式加載不要為了省事強制安裝不匹配的驅(qū)動。4. FPGA 側(cè)實現(xiàn)兩個狀態(tài)機打滿 FIFO 帶寬4.1 頂層模塊把 FX2 接口封裝成可復(fù)用 IPFPGA 側(cè)不需要知道 USB 協(xié)議它只需要知道“什么時候能寫、什么時候能讀”。我習(xí)慣把 Slave FIFO 接口封裝成一個獨立模塊這樣以后換板卡只要改頂層連接。module usb_fx2_slave_fifo #( parameter DW 16 )( input wire clk, input wire rst_n, inout wire [DW-1:0] fd, output reg slrd, output reg slwr, output reg sloe, output reg [1:0] fifoaddr, output reg slcs, output reg pktend, input wire flagb, input wire flagc, // 用戶側(cè)上行FPGA 要發(fā)給 PC 的數(shù)據(jù) input wire tx_valid, input wire [DW-1:0] tx_data, input wire tx_last, // 短包結(jié)束標(biāo)志 output reg tx_ready, // 用戶側(cè)下行PC 發(fā)給 FPGA 的數(shù)據(jù) output reg rx_valid, output reg [DW-1:0] rx_data );這里DW默認(rèn)是 16因為固件那邊已經(jīng)把EP2FIFOCFG和EP6FIFOCFG配成了 16 位數(shù)據(jù)寬度。如果你的板子是 8 位總線把DW改成 8同時固件里把那兩個寄存器的 WORDWIDE 位清掉。clk用 FPGA 全局時鐘就可以不一定非要和IFCLK同源但做時序收斂時輸出到 FX2 的這些控制信號要加適當(dāng)?shù)募s束。4.2 上行狀態(tài)機FPGA 如何把數(shù)據(jù)灌進 EP2上行方向的數(shù)據(jù)路徑是用戶邏輯 →tx_data→ 16 位 FD 總線 → EP2 FIFO → USB。核心狀態(tài)機如下注意所有條件都按低有效標(biāo)志寫的reg [3:0] up_state; reg [9:0] up_cnt; reg fd_out_en; reg [15:0] fd_out_reg; localparam UP_IDLE 4d0, UP_WR 4d1, UP_HOLD 4d2, UP_PKT 4d3; assign fd fd_out_en ? fd_out_reg : {16{1bz}}; always (posedge clk or negedge rst_n) begin if (!rst_n) begin up_state UP_IDLE; slwr 1b1; pktend 1b1; sloe 1b1; fifoaddr 2b00; // 默認(rèn)選 EP2 slcs 1b0; fd_out_en 1b0; up_cnt 10d0; end else begin case (up_state) UP_IDLE: begin fifoaddr 2b00; // 指定當(dāng)前 FIFO 是 EP2 pktend 1b1; if (tx_valid) begin tx_ready 1b1; up_state UP_WR; end end UP_WR: begin fd_out_en 1b1; fd_out_reg tx_data; slwr 1b0; // 拉低寫選通數(shù)據(jù)寫入 EP2 FIFO up_cnt up_cnt 1b1; if (up_cnt 10d511) begin if (tx_last) begin up_state UP_PKT; // 非整包手動提交短包 end else begin up_state UP_HOLD; // 整包寫完回到等待狀態(tài) end end end UP_HOLD: begin slwr 1b1; if (flagc 1b0) begin // flagc 低EP2 還有空間可寫 up_cnt 10d0; up_state UP_WR; end end UP_PKT: begin slwr 1b1; pktend 1b0; // 通知 CY68013當(dāng)前短包終止 up_state UP_IDLE; end endcase end end這個狀態(tài)機里UP_WR狀態(tài)下slwr被拉低一個時鐘周期FD 總線上同時放上tx_data。CY68013 在 Slave FIFO 異步寫模式下會在這個低電平期間采樣數(shù)據(jù)。連續(xù)寫時檢查flagc低電平代表 EP2 當(dāng)前 FIFO 沒滿可以繼續(xù)寫如果為高就停在UP_HOLD等一個周期再寫保證不會把 FIFO 寫爆。up_cnt從 0 數(shù)到 511一共寫 512 次正好是一個高速 bulk 包的長度。AUTOIN 會自動把這一包提交給主機所以代碼里看不到任何 USB 包處理邏輯。tx_last是短包信號當(dāng)用戶邏輯告訴狀態(tài)機“這批數(shù)據(jù)不滿 512 字節(jié)”就需要走UP_PKT拉低pktend。很多第一次調(diào)這塊芯片的人會在這里翻車因為把PKTEND漏掉了短包永遠發(fā)不出去。AUTOIN 模式下芯片會自動把 FIFO 里的 512 字節(jié)提交所以整包結(jié)束時并不需要PKTEND只需要回到UP_HOLD繼續(xù)下一個包。4.3 下行狀態(tài)機FPGA 從 EP6 把數(shù)據(jù)讀出來下行方向是 PC 發(fā)數(shù)據(jù)到 FPGA主機先寫入 EP6FPGA 再讀出來。方向相反狀態(tài)機也簡單一些reg [3:0] down_state; localparam DN_IDLE 4d0, DN_RD 4d1, DN_DONE 4d2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin down_state DN_IDLE; slrd 1b1; sloe 1b1; fifoaddr 2b10; // 默認(rèn)選 EP6 end else begin case (down_state) DN_IDLE: begin fifoaddr 2b10; // 指向 EP6 if (flagb 1b0) begin // flagb 低EP6 有數(shù)據(jù)可讀 down_state DN_RD; end end DN_RD: begin sloe 1b0; // 先打開輸出使能 slrd 1b0; // 再拉低讀選通 rx_data fd; // 采樣數(shù)據(jù)總線 down_state DN_DONE; end DN_DONE: begin sloe 1b1; slrd 1b1; rx_valid 1b1; // 給下游一個有效脈沖 if (flagb 1b0) begin down_state DN_RD; // 還能連續(xù)讀 end else begin down_state DN_IDLE; end end endcase end end讀方向有一個容易被忽略的點必須先拉低SLOE讓 FD 總線變成輸出模式再拉低SLRD。如果順序反了fd上可能還是高阻態(tài)讀進來的數(shù)據(jù)就是不定值。DN_DONE狀態(tài)下如果flagb仍然是低說明 EP6 里還有數(shù)據(jù)直接回到DN_RD繼續(xù)讀不用再回DN_IDLE。這樣連續(xù)讀的吞吐會好很多。但要注意rx_valid只拉高一個周期下游如果需要更寬的握手可以在外面用 FIFO 再緩沖一層。4.4 16 位模式下的字節(jié)序和包長16 位數(shù)據(jù)總線上FD[7:0]對應(yīng) USB 先傳輸?shù)淖止?jié)FD[15:8]對應(yīng)當(dāng)前 word 的高位字節(jié)。如果你的圖像數(shù)據(jù)是高位在前直接搬上去PC 端讀出來就會錯位。常見做法是在 FPGA 里對tx_data做一次{tx_data[7:0], tx_data[15:8]}的字節(jié)交換或者在上位機解析時處理。還有一點16 位模式下FPGA 每拍寫 2 字節(jié)512 字節(jié)的 packet只需要 256 拍。如果照抄 8 位模式的狀態(tài)機但計數(shù)上限沒改就會造成把數(shù)據(jù)切成兩個半包AUTOIN 提交的包長度和預(yù)期不一樣。我一般先明確自己用的是 16 位還是 8 位再去核對EPxFIFOCFG和狀態(tài)機里的計數(shù)上限。4.5 仿真驗證給狀態(tài)機喂一個假的 USB 側(cè)板級調(diào)試之前我習(xí)慣先跑一遍仿真。最簡單的做法是寫一個 testbench模擬flagc、flagb的電平變化再喂一組遞增數(shù)給tx_data。仿真里能看到slwr是否按預(yù)期翻轉(zhuǎn)fd上的數(shù)據(jù)是否跟著變化。initial begin clk 0; rst_n 0; flagc 1; flagb 1; #100 rst_n 1; flagc 0; // 模擬 EP2 FIFO 有空位 (posedge clk); tx_valid 1; tx_data 16hAAAA; (posedge clk); tx_valid 0; end仿真通過以后再上板做回環(huán)測試?;丨h(huán)測試是把 EP6 讀出來的數(shù)據(jù)直接接到 EP2 的tx_data上上位機發(fā)一串固定數(shù)據(jù)讀出來比對。這一步能快速判斷問題在 FPGA 狀態(tài)機還是固件配置避免兩邊同時排查。5. 常見問題排查Slave FIFO 調(diào)試中的五個翻車現(xiàn)場5.1 枚舉正常就是傳不了數(shù)據(jù)先查 IFCONFIG再看 SLOE/SLCS現(xiàn)象Windows 里設(shè)備管理器能看到設(shè)備上位機也能打開設(shè)備但 bulk 傳輸?shù)淖x接口一直返回 0 字節(jié)。原因設(shè)備枚舉成功只說明 USB 協(xié)議層是通的不代表 Slave FIFO 接口已經(jīng)啟用。最常見的是IFCONFIG沒有切到 Slave FIFO 模式芯片還停在默認(rèn)的端口模式FD 總線根本不受 FPGA 控制。另一個常見點是SLCS和SLOE沒拉低FPGA 端三態(tài)門沒打開數(shù)據(jù)總線一直浮空。解決先用邏輯分析儀或示波器抓 FPGA 到 FX2 的SLCS、SLOE、SLWR信號。SLCS在調(diào)試階段可以直接綁到 0SLOE在做讀操作時再拉低。然后重新燒錄固件確認(rèn)IFCONFIG的接口模式位是否正確。5.2 FLAGB 與 FLAGC 永遠拉不低FIFORESET 和端點方向順序現(xiàn)象FPGA 狀態(tài)機一直卡在等待標(biāo)志位不管怎么發(fā)數(shù)據(jù)FLAGB或FLAGC都紋絲不動。原因芯片上電后端點 FIFO 沒有經(jīng)過復(fù)位FIFO 指針處于未知狀態(tài)標(biāo)志位自然不變化。另外如果EP2CFG的 IN/OUT 方向和實際通信方向配反了FLAGC會永遠表示錯的狀態(tài)。解決TD_Init里按 0x80、0x02、0x04、0x06、0x08、0x00 的順序把所有端點 FIFO 復(fù)位一遍每條后都加SYNCDELAY。然后核對EP2CFG是不是 IN、EP6CFG是不是 OUT。我把這個順序貼在固件頭文件注釋里每次改完配置都先檢查這一步。5.3 速度只有 1MB/sAUTOIN 沒開數(shù)據(jù)全都走 CPU現(xiàn)象功能正常數(shù)據(jù)也正確但速度慘不忍睹用上位機測下來只有 1MB/s 左右。原因這是最典型的性能陷阱。如果EP2FIFOCFG沒開 AUTOIN 位FPGA 寫入的 FIFO 數(shù)據(jù)不會自動提交而是等待 8051 固件去讀取并轉(zhuǎn)發(fā)整個鏈路變成 CPU 搬運吞吐直接掉一個量級。同樣EP6FIFOCFG如果沒開 AUTOOUT主機數(shù)據(jù)也不會自動進入 FIFO。解決確認(rèn)EP2FIFOCFG的 AUTOIN 位和EP6FIFOCFG的 AUTOOUT 位都已置 1并設(shè)置EP2AUTOINLENH/L為 512。上位機一次讀請求最好不小于 1MB避免頻繁切換 USB 傳輸上下文。從那以后我每次測速前都會先讀一遍這兩個寄存器做確認(rèn)。5.4 16 位模式數(shù)據(jù)錯位WORDWIDE 沒配對或三態(tài)總線段錯了現(xiàn)象上位機讀到的數(shù)據(jù)內(nèi)容對但字節(jié)順序錯亂或者每次讀出來的同一段數(shù)據(jù)不穩(wěn)定。原因固件把端點 FIFO 配成了 16 位FPGA 里的inout三態(tài)門控制卻沒跟上?;蛘叻催^來FPGA 模塊用 16 位固件卻還是 8 位兩邊寬度不一致。另一個高頻原因是讀方向的SLOE和SLRD順序錯導(dǎo)致fd采樣時還在高阻態(tài)。解決先統(tǒng)一固件和 FPGA 的WORDWIDE位。16 位模式下FPGA 的fd要有一個明確的方向控制信號只在寫周期輸出讀周期保持高阻。然后用上位機回環(huán)發(fā)一組 0x00~0xFF 的遞增數(shù)如果錯位規(guī)律固定就很可能是字節(jié)序問題做一個總線交換即可。5.5 固件一下載設(shè)備就消失EEPROM 啟動和重枚舉的坑現(xiàn)象插上板子第一次能看到設(shè)備用下載工具把固件寫進去以后設(shè)備反而從設(shè)備管理器里消失了只能重新插拔。原因部分板子為了免驅(qū)動加載接了 EEPROM上電后芯片從 EEPROM 啟動固件。如果 EEPROM 里的 VID/PID 和固件描述符不一致重枚舉后系統(tǒng)找不到匹配驅(qū)動設(shè)備就消失了。還有一種情況是固件里TD_Init之后的代碼進入死循環(huán)8051 沒能正常處理 USB 中斷。解決調(diào)試階段先把 EEPROM 摘下或改為無 EEPROM 啟動模式每次都通過下載工具往 RAM 里加載固件。確認(rèn)鏈路穩(wěn)定后再把固件燒進 EEPROM。固件主循環(huán)里不要在TD_Poll放阻塞代碼最好保持空的輪詢讓 USB 中斷能持續(xù)響應(yīng)。6. 二次開發(fā)思路從高速傳輸改成自定義采集系統(tǒng)拿到這套源碼后我最推薦的改造方式不是馬上加功能而是先在 FPGA 內(nèi)部做一次回環(huán)。把rx_data直接接到tx_dataPC 發(fā)一串帶順序號的數(shù)據(jù)讀回來比對順序號和 CRC。鏈路通了再去接真正的采集模塊。這樣能明確區(qū)分“USB 通道沒通”和“采集邏輯有 bug”。第二步是改功能端點?,F(xiàn)在的項目固定用 EP2 做 IN、EP6 做 OUT。如果你的板子需要同時傳控制命令和圖像數(shù)據(jù)可以考慮把 EP4 配成第二個 IN 端點EP8 配成第二個 OUT 端點。FIFOADR的編碼是固定的00 對應(yīng) EP201 對應(yīng) EP410 對應(yīng) EP611 對應(yīng) EP8。FPGA 狀態(tài)機里的fifoaddr要和固件里的EPxCFG一一對應(yīng)不能只改一邊。第三步是加幀同步和校驗。高速 bulk 傳輸只管字節(jié)流不保證應(yīng)用層幀邊界。一種常見的做法是 FPGA 在每幀前面插入 4 字節(jié)幀頭幀起始標(biāo)志、幀號、長度、校驗和。無論上位機還是 FPGA都先找?guī)^再取數(shù)。代碼里加一個tx_frame_ctrl狀態(tài)在tx_valid有效前先發(fā)幀頭即可數(shù)據(jù)路徑不用動。我自己的教訓(xùn)是第一次調(diào)這個板子時為了省時間跳過了回環(huán)測試直接在固件和 FPGA 兩邊同時改最后查了三天發(fā)現(xiàn)只是 AUTOIN 沒開。從那以后我每次拿到新的 FX2 工程都會強制把“上位機回環(huán)”和“寄存器 dump”兩個步驟走一遍再談業(yè)務(wù)邏輯。這一套做法幫你避開我踩過的坑希望幫到你。本文還有配套的精品資源點擊獲取