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