加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 六、FPGA 固件開發(fā)
    • 七、USB 驅(qū)動和軟件開發(fā)
    • 總結(jié)
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

基于FPGA的USB接口控制器設(shè)計(附代碼)

2023/12/04
6059
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。

今天給大俠帶來基于 FPGA 的 USB 接口控制器設(shè)計(VHDL),由于篇幅較長,分三篇。今天帶來第三篇,下篇,F(xiàn)PGA 固件開發(fā)、USB驅(qū)動和軟件開發(fā)。話不多說,上貨。

2019年9月4日,USB-IF終于正式公布USB 4規(guī)范。它引入了Intel此前捐獻給USB推廣組織的Thunderbolt雷電協(xié)議規(guī)范,雙鏈路運行(Two-lane),傳輸帶寬因此提升,與雷電3持平,都是40Gbps。需要注意的是,你想要體驗最高傳輸速度,就必須使用經(jīng)過認(rèn)證的全新數(shù)據(jù)線。USB4保留了良好的兼容性,可向下兼容USB 3.2/3.1/3.0、雷電3。除此之外,USB4將只有USB Type-C一種接口,并支持多種數(shù)據(jù)、顯示協(xié)議,包括DisplayPort,可以一起充分利用高速帶寬,也支持USB PD供電。

比較遺憾的是,USB4的發(fā)布時間至今暫未公布。值得注意的是,此次發(fā)布的USB4是規(guī)范,而并非USB4.0。在此之前,USB Implementers Forum(USB-IF)計劃取消USB 3.0/3.1命名,統(tǒng)一劃歸為USB 3.2。其中USB 3.0更名USB 3.2 Gen 1(5Gbps),USB 3.1更名USB 3.2 Gen 2(10Gbps),USB 3.2更名為USB 3.2 Gen 2x2(20Gbps)。以上就是關(guān)于USB標(biāo)準(zhǔn)以及命名的訊息。

現(xiàn)在大部分USB設(shè)備(比如USB接口的鼠標(biāo)、鍵盤、閃存、U盤等等)都是采用了USB通用驅(qū)動,而你的系統(tǒng)有USB通用驅(qū)動的話(比如XP就內(nèi)建了USB通用驅(qū)動)就能用。而有些USB設(shè)備是需要特殊驅(qū)動的,比如某些手機,連接到電腦的USB口,是需要安裝驅(qū)動才能使用的。下面我們一起動手做一做USB接口控制器設(shè)計,了解一下如何設(shè)計。

第三篇內(nèi)容摘要:本篇會介紹FPGA 固件開發(fā),包括固件模塊劃分、自定義包編寫、分頻器模塊的實現(xiàn)、沿控制模塊的實現(xiàn)、輸入/輸出切換模塊的實現(xiàn)、請求處理模塊的實現(xiàn)、設(shè)備收發(fā)器模塊的實現(xiàn)、測試平臺的編寫;USB 驅(qū)動和軟件開發(fā),包括USB 驅(qū)動編寫、USB 軟件編寫以及總結(jié)等相關(guān)內(nèi)容。

六、FPGA 固件開發(fā)

6.1 固件模塊劃分

在本例中,固件開發(fā)指的就是 FPGA 開發(fā),也就是使用硬件描述語言(VHDL 或者 VerilogHDL)編寫 FPGA 內(nèi)部程序。FPGA 的作用就是和 PDIUSBD12 進行通信,從 PDIUSBD12 中獲取數(shù)據(jù)并且根據(jù)主機的要求發(fā)送數(shù)據(jù)。PDIUSBD12 和 FPGA 之間的通信就是 8 位數(shù)據(jù)總線加上若干控制信號(A0、WR_N、RD_N 等),只要控制 FPGA 產(chǎn)生符合 PDIUSBD12 輸入/輸出時序的脈沖,即可實現(xiàn)兩者之間的通信。

FPGA 固件的模塊圖如圖 34 所示,各個模塊的功能如下。

圖 34 硬件加密系統(tǒng)設(shè)計方案

(1)分頻器模塊

由于 PDIUSBD12 在讀寫時序上有時間限制,例如每次讀寫操作之間的間隔不能小于 500ns,而 FPGA 的系統(tǒng)時鐘一般頻率都比較高,所以不能直接使用系統(tǒng)時鐘控制 PDIUSBD12,必須進行分頻。分頻器模塊的功能就是按照要求由系統(tǒng)時鐘生成所需頻率的時鐘信號。

(2)沿控制器模塊

PDIUSBD12 的讀寫操作都各自有一個讀寫控制信號 WR_N 和 RD_N,每次讀寫操作都在對應(yīng)的控制信號的下降沿觸發(fā),沿控制模塊的功能就是可控地產(chǎn)生一個下降沿信號,用于控制讀寫操作。

(3)輸入/輸出切換模塊

輸入/輸出切換模塊在整個系統(tǒng)中非常重要,因為 FPGA 芯片和 PDIUSBD12 芯片之間的數(shù)據(jù)總線是雙向的總線,所以當(dāng)讀寫操作之一在進行的時候另一個操作的信號源必須關(guān)閉,否則就會造成雙驅(qū)動,這不但不能得到正確的數(shù)據(jù)還會損害芯片。輸入/輸出切換模塊的功能就是根據(jù)當(dāng)前的讀寫狀況控制信號源,保證在一個時刻只有一個信號源在驅(qū)動總線。

(4)設(shè)備收發(fā)器模塊

這個模塊是整個固件的核心模塊,它完成的工作包括配置 PDIUSBD12 芯片、處理 PDIUSBD12產(chǎn)生的中斷、完成從緩存讀取數(shù)據(jù),并且根據(jù)需要將數(shù)據(jù)通過 PDIUSBD12 發(fā)送。設(shè)備收發(fā)器模塊完成對每個主機請求的解析工作,此外,還要將解析完成的請求數(shù)據(jù)傳遞給請求處理模塊。

(5)請求處理模塊

請求處理模塊的作用是接收設(shè)備收發(fā)器模塊解析完成的主機請求,并且決定如何處理此請求。

模塊劃分完畢之后就可以使用 ISE 創(chuàng)建工程了,然后就各個模塊分別編寫實現(xiàn)代碼和測試平臺,最后將所有模塊整合起來作為一個實體并且對其進行仿真、測試,這樣就是一次完整的FPGA 開發(fā)過程。

ISE 的一些基本使用方法在前面的文章已有詳細介紹,這里放超鏈接,在此不詳細說明。下面詳細介紹一下各個模塊的實現(xiàn)方法。

ISE 14.7 安裝教程及詳細說明

6.2 自定義包編寫

在實際實現(xiàn)各個模塊功能之前,首先需要編寫兩個自定義包,分別是 USB 包和 PDIUSBD12包。

USB 包定義了 USB 協(xié)議以及 USB 設(shè)備相關(guān)的數(shù)據(jù)類型、常量等內(nèi)容,比如自定義數(shù)據(jù)類型、設(shè)備類型代碼值、請求代碼值、設(shè)備描述符、設(shè)備的工作狀態(tài)機等。設(shè)備的工作狀態(tài)機定義如下:

- 定義設(shè)備的工作狀態(tài)機type TRANSEIVER_STATEis ( TS_DISCONNECTED, -- 未連接TS_CONNECTING, -- 正在連接TS_IDLE, -- 閑置TS_END_REQUESTHANDLER, -- 請求處理完成TS_READ_IR, -- 讀取中斷寄存器TS_READ_LTS, -- 讀取最后處理狀態(tài)TS_BUSRESET, -- 總線復(fù)位TS_SUSPENDCHANGE, -- 掛起改變TS_EP0_RECEIVE, -- 端點 0 接收完成TS_EP0_TRANSMIT, -- 端點 0 發(fā)送完成TS_EP2_RECEIVE, -- 端點 2 接收完成TS_EP2_TRANSMIT, -- 端點 2 發(fā)送完成TS_END_RECEIVE, -- 從 PDIUSBD12 讀取數(shù)據(jù)完成TS_END_TRANSMIT, -- 向 PDIUSBD12 寫數(shù)據(jù)完成TS_SEND_DESCRIPTOR_1ST, -- 首次發(fā)送設(shè)備描述符TS_SEND_DESCRIPTOR, -- 發(fā)送設(shè)備描述符TS_SET_ADDRESS, -- 設(shè)置地址TS_SET_CONFIGURATION, -- 設(shè)置配置TS_GET_CONFIGURATION, -- 獲取配置TS_GET_INTERFACE, -- 獲取接口TS_SEND_STATUS, -- 發(fā)送狀態(tài)TS_CLEAR_FEATURE, -- 清除特性TS_SET_FEATURE, -- 啟用特性TS_SET_INTERFACE, -- 設(shè)置接口TS_READ_ENDPOINT, -- 從端點讀取數(shù)據(jù)TS_WRITE_ENDPOINT, -- 向端點寫入數(shù)據(jù)TS_SEND_PASSWORD, -- 發(fā)送密碼TS_SET_PASSWORD_HIGH, -- 設(shè)置密碼低位TS_SET_PASSWORD_LOW, -- 設(shè)置密碼高位TS_SEND_EMPTY_PACKET, -- 發(fā)送空包TS_STALL, -- 禁止TS_ERROR); -- 錯誤

請求類型以及請求的代碼定義如下:

-- 描述符類型constant TYPE_DEVICE_DESCRIPTOR: STD_LOGIC_VECTOR(7 downto 0) := X"01";constant TYPE_CONFIGURATION_DESCRIPTOR: STD_LOGIC_VECTOR(7 downto 0) := X"02";constant TYPE_STRING_DESCRIPTOR: STD_LOGIC_VECTOR(7 downto 0) := X"03";constant TYPE_INTERFACE_DESCRIPTOR: STD_LOGIC_VECTOR(7 downto 0) := X"04";constant TYPE_ENDPOINT_DESCRIPTOR: STD_LOGIC_VECTOR(7 downto 0) := X"05";constant TYPE_POWER_DESCRIPTOR: STD_LOGIC_VECTOR(7 downto 0) := X"06";
-- 設(shè)備描述符相關(guān)的代碼、索引值等constant CODE_DEVICE_CLASS: STD_LOGIC_VECTOR(7 downto 0) := X"DC";constant CODE_BCD_USB_HIGH: STD_LOGIC_VECTOR(7 downto 0) := X"00";constant CODE_BCD_USB_LOW: STD_LOGIC_VECTOR(7 downto 0) := X"01";constant CODE_ID_VENDOR_HIGH: STD_LOGIC_VECTOR(7 downto 0) := X"71";constant CODE_ID_VENDOR_LOW: STD_LOGIC_VECTOR(7 downto 0) := X"04";constant CODE_ID_PRODUCT_HIGH: STD_LOGIC_VECTOR(7 downto 0) := X"66";constant CODE_ID_PRODUCT_LOW: STD_LOGIC_VECTOR(7 downto 0) := X"06";constant CODE_BCD_DEVICE_HIGH: STD_LOGIC_VECTOR(7 downto 0) := X"00";constant CODE_BCD_DEVICE_LOW: STD_LOGIC_VECTOR(7 downto 0) := X"01";constant CODE_NUMBER_CONFIGURATIONS: STD_LOGIC_VECTOR(7 downto 0) := X"19";

另一個包是 PDIUSBD12 包,它定義的則是和 PDIUSBD12 相關(guān)的內(nèi)容,比如 PDIUSBD12 的命令代碼值、中斷代碼值等內(nèi)容。對 PDIUSBD12 控制命令的定義如下:

-- PDIUSBD12 控制命令constant D12_COMMAND_ENABLE_ADDRESS: STD_LOGIC_VECTOR(7 downto 0) := X"D0";constant D12_COMMAND_ENABLE_ENDPOINT: STD_LOGIC_VECTOR(7 downto 0) := X"D8";constant D12_COMMAND_SET_MODE: STD_LOGIC_VECTOR(7 downto 0) := X"F3";constant D12_COMMAND_SET_DMA: STD_LOGIC_VECTOR(7 downto 0) := X"FB";constant D12_COMMAND_READ_IR: STD_LOGIC_VECTOR(7 downto 0) := X"F4";constant D12_COMMAND_SEL_EP0_OUT: STD_LOGIC_VECTOR(7 downto 0) := X"00";constant D12_COMMAND_SEL_EP0_IN: STD_LOGIC_VECTOR(7 downto 0) := X"01";constant D12_COMMAND_SEL_EP1_OUT: STD_LOGIC_VECTOR(7 downto 0) := X"02";constant D12_COMMAND_SEL_EP1_IN: STD_LOGIC_VECTOR(7 downto 0) := X"03";constant D12_COMMAND_SEL_EP2_OUT: STD_LOGIC_VECTOR(7 downto 0) := X"04";constant D12_COMMAND_SEL_EP2_IN: STD_LOGIC_VECTOR(7 downto 0) := X"05";constant D12_COMMAND_READ_LTS_EP0_OUT: STD_LOGIC_VECTOR(7 downto 0) := X"40";constant D12_COMMAND_READ_LTS_EP0_IN: STD_LOGIC_VECTOR(7 downto 0) := X"41";constant D12_COMMAND_READ_LTS_EP1_OUT: STD_LOGIC_VECTOR(7 downto 0) := X"42";constant D12_COMMAND_READ_LTS_EP1_IN: STD_LOGIC_VECTOR(7 downto 0) := X"43";constant D12_COMMAND_READ_LTS_EP2_OUT: STD_LOGIC_VECTOR(7 downto 0) := X"44";constant D12_COMMAND_READ_LTS_EP2_IN: STD_LOGIC_VECTOR(7 downto 0) := X"45";constant D12_COMMAND_RW_BUFFER: STD_LOGIC_VECTOR(7 downto 0) := X"F0";constant D12_COMMAND_ACK_SETUP: STD_LOGIC_VECTOR(7 downto 0) := X"F1";constant D12_COMMAND_CLEAR_EP_BUFFER: STD_LOGIC_VECTOR(7 downto 0) := X"F2";constant D12_COMMAND_ENABLE_BUFFER: STD_LOGIC_VECTOR(7 downto 0) := X"FA";

鑒于篇幅以及其他原因,以上僅僅介紹?USB 包和 PDIUSBD12 包的部分內(nèi)容作為參考。

6.3?分頻器模塊的實現(xiàn)

分頻器模塊實現(xiàn)的基本原理就是設(shè)計一個工作在系統(tǒng)時鐘下的計數(shù)器,循環(huán)地遞減或者遞加計數(shù),在某個計數(shù)的固定值將輸出翻轉(zhuǎn),即可實現(xiàn)時鐘分頻的功能。

例如,實驗板上的系統(tǒng)時鐘是 50MHz,而所需的讀寫周期間隔要求大于 500ns,即讀寫的時鐘頻率不能高于 2MHz,需要將原系統(tǒng)時鐘進行至少 25 倍分頻。所以,我們設(shè)定一個計數(shù)器,工作在系統(tǒng)時鐘下,每個系統(tǒng)時鐘周期計數(shù)減一,減到零后恢復(fù)到 13,這樣,每經(jīng)過 13×2=26個系統(tǒng)時鐘周期,計數(shù)器的輸出會是一個完整的周期。

分頻器模塊的示意圖如圖 35 所示。

圖 35 分頻器模塊的示意圖

實現(xiàn)分頻器模塊的代碼如下:

-- 申明所使用的包library IEEE;use IEEE.STD_LOGIC_1164.all;use WORK.USB_PACKAGE.all;-- 申明實體entity FrequencyDivider is      generic(              div_factor : INTEGER8 := 0 -- 分頻系數(shù)屬性        );      port(              reset_n : in STD_LOGIC; -- 復(fù)位端口              clk_origin : in STD_LOGIC; -- 輸入時鐘端口              clk : out STD_LOGIC -- 輸出時鐘端口        );end FrequencyDivider;architecture FrequencyDivider of FrequencyDivider is-- 內(nèi)部信號,在內(nèi)部隨時改變同時又輸出給輸出時鐘端口signal clk_tmp: STD_LOGIC;begin    -- 信號連接    clk <= clk_tmp;    -- 主過程    main_process: process( reset_n, clk_origin )    variable count: INTEGER8;    begin        if reset_n = '0' then            count := 0;            clk_tmp <= '0';        elsif rising_edge(clk_origin) then        -- 計數(shù)到達分頻系數(shù)時翻轉(zhuǎn)輸出,并且重置計數(shù)            if count = div_factor then                clk_tmp <= not clk_tmp;                count := 0;            else                count := count+1;            end if;        end if;    end process;end FrequencyDivider;

6.4 沿控制模塊的實現(xiàn)

沿控制模塊的功能是提供可控的下降沿輸出,實現(xiàn)的方案如下:用一個使能信號 CE_N 控制輸出。輸入為分頻后的時鐘,當(dāng) CE_N 輸入為高的時候,輸出保持高電平,而當(dāng) CE_N 輸入變?yōu)榈偷臅r候,將時鐘接到輸出上,這樣就能得到連續(xù)的下降沿信號(和時鐘的下降沿同步)。只要對 CE_N 進行適當(dāng)?shù)目刂?,就能得到需要的下降沿?/p>

沿控制模塊的示意圖和時序圖如圖 36 所示。輸入時鐘連接到分頻器模塊的輸出時鐘上,使能信號控制沿輸出信號,只要在某一個時鐘周期內(nèi)將使能信號保持低電平,就可以得到一個下降沿輸出。

圖 36 沿控制模塊的示意圖和時序圖

沿控制模塊的實現(xiàn)代碼如下:

--申明所使用的包library IEEE;use IEEE.STD_LOGIC_1164.all;-- 申明實體entity EdgeController is    port(          clk : in STD_LOGIC; -- 輸入時鐘端口          ce_n : in STD_LOGIC; -- 使能端口          edge : out STD_LOGIC -- 沿信號輸出端口      );end EdgeController;architecture EdgeController of EdgeController isbegin    -- 輸出信號賦值        edge <= clk when ce_n = '0' else                '1';end EdgeController;

6.5 輸入/輸出切換模塊的實現(xiàn)

由于 PDIUSBD12 的 8 位數(shù)據(jù)線是雙向總線,所以當(dāng)進行讀寫操作的時候,應(yīng)該注意避免雙驅(qū)動。雙驅(qū)動的意思就是在總線兩邊同時往總線上加輸出信號,這樣總線數(shù)據(jù)就處于一種不定態(tài)(用 X 表示),并且還容易損壞器件。例如,沒有處理好雙驅(qū)動的仿真波形就會如圖 37 所示,這種情況下無法得到正確的數(shù)據(jù)的。

圖 37 仿真不定態(tài)時序圖

信號的 4 種基本狀態(tài)是高電平(1)、低電平(0)、不定態(tài)(X)和高阻態(tài)(Z),當(dāng)一個總線上同時加有兩個信號時,組合起來的結(jié)果如表 35 所示。

表 35 信號狀態(tài)表

可見,當(dāng)一個總線上同時有兩個驅(qū)動的時候,很有可能產(chǎn)生不定態(tài) X,但是如果其中一個信號為高阻態(tài) Z 的話,則是一個確定的狀態(tài)(即另一個信號的狀態(tài))。所以,避免雙驅(qū)動的基本思想就是根據(jù)目前的讀寫狀態(tài)關(guān)閉某一個驅(qū)動源,也就是說將其另一個驅(qū)動源輸出設(shè)置為高阻態(tài)。由于讀寫操作是由各自的控制信號(WR_N、RD_N)控制的,所以可以將這兩個信號作為互斥關(guān)系的信號來控制總線數(shù)據(jù)的信號源。例如,當(dāng) RD_N 為低時,要從 PDIUSBD12 讀取數(shù)據(jù),就應(yīng)該關(guān)閉 FPGA 對總線的輸出,即將 FPGA 的總線輸出信號變?yōu)楦咦钁B(tài) Z。反過來也一樣,當(dāng) WR_N 為低時,要向 PDIUSBD12 發(fā)送數(shù)據(jù),此時 PDIUSBD12 也會自動關(guān)閉它在總線上的輸出。以上思想可用公式表示為:

輸入/輸出切換模塊的示意圖如圖 6-38 所示。其中左邊的總線表示連接到 PDIUSBD12 的總線,右邊的輸入、輸出總線是在 FPGA 內(nèi)部的總線信號,表示在 FPGA 內(nèi)部將總線的輸入和輸出區(qū)分開來;RD_N 和 WR_N 信號分別用于讀、寫控制。

圖 38 輸入/輸出切換模塊的示意圖

輸入/輸出切換模塊的實現(xiàn)代碼如下:

--申明所使用的包library IEEE;use IEEE.STD_LOGIC_1164.all;-- 申明實體entity IOSwitch is    port(          data : inout STD_LOGIC_VECTOR(7 downto 0); -- 8 位雙向數(shù)據(jù)總線,和 PDIUSBD12 相連          din : in STD_LOGIC_VECTOR(7 downto 0); -- 8 位輸入數(shù)據(jù)總線,僅用于輸入          dout : out STD_LOGIC_VECTOR(7 downto 0); -- 8 位輸出數(shù)據(jù)總線,僅用于輸出          sel_in_n : in STD_LOGIC; -- 總線輸入控制信號          sel_out_n : in STD_LOGIC -- 總線輸出控制信號      );end IOSwitch;
architecture IOSwitch of IOSwitch is-- 創(chuàng)建一個內(nèi)部信號,用作數(shù)據(jù)傳遞signal data_tmp : STD_LOGIC_VECTOR(7 downto 0);
begin    -- 信號連接    data <= data_tmp;    dout <= data;    -- 主進程    process(sel_in_n, sel_out_n, data, din)    begin        -- 當(dāng)輸出控制信號有效時,將 data_tmp 賦值高阻        if sel_out_n = '0' then            data_tmp <= "ZZZZZZZZ";        -- 當(dāng)輸入控制信號有效時,將輸入的信號賦值給 data_tmp        elsif sel_in_n = '0' then            data_tmp <= din;        else            data_tmp <= "ZZZZZZZZ";        end if;    end process;end IOSwitch;

6.6 請求處理模塊的實現(xiàn)

請求處理模塊的功能是根據(jù)主機的請求控制設(shè)備收發(fā)器模塊的處理狀態(tài)。在本例中,請求處理模塊實際的功能就是根據(jù)目前接收到的主機請求控制設(shè)備收發(fā)器模塊發(fā)送數(shù)據(jù),所以請求處理模塊的實現(xiàn)就是一個簡單的狀態(tài)機。

請求處理模塊的示意圖如圖 39 所示。時鐘信號是由分頻器的輸出時鐘提供;請求類型輸入是一個 8 位端口,它和接收事件輸入?yún)f(xié)同工作,當(dāng)設(shè)備收發(fā)器接收到一個請求時,就會將請求代碼發(fā)送到請求類型輸入端口,在接收事件輸入端口輸出一個時鐘周期的低電平,表示一次新的請求處理;命令輸出端口和命令中斷端口則用于控制設(shè)備收發(fā)器模塊的操作狀態(tài)。

圖 39 請求處理模塊的示意圖

請求處理模塊的實現(xiàn)代碼如下:

-- 申明要使用的庫library IEEE;use IEEE.STD_LOGIC_1164.all;use WORK.USB_PACKAGE.all;use WORK.PDIUSBD12_PACKAGE.all;-- 申明實體entity RequestHandler is    port(            reset_n : in STD_LOGIC; -- 復(fù)位端口            clk : in STD_LOGIC; -- 輸入時鐘            recv_n : in STD_LOGIC; -- 接收事件輸入端口            req_type : in STD_LOGIC_VECTOR(7 downto 0); -- 請求類型輸入端口            cmd : out STD_LOGIC_VECTOR(7 downto 0); -- 命令輸出端口            exec_n : out STD_LOGIC -- 命令中斷端口        );end RequestHandler;
architecture RequestHandler of RequestHandler is-- 狀態(tài)機,已在 USB 包中有定義signal rh_state: REQUEST_HANDLER_STATE := RH_IDLE;-- 寄存器,用于標(biāo)示是否已分配地址signal address_set: STD_LOGIC := '0';begin    -- 主進程    main_process: process( reset_n, clk )    begin        if reset_n = '0' then            -- reset output signals            cmd <= X"00";            exec_n <= '1';            address_set <= '0';            -- reset state machine            rh_state <= RH_IDLE;        elsif falling_edge(clk) then            case rh_state is            when RH_IDLE =>                -- recv_n 為低時候表示需要進行請求處理                if recv_n = '0' then                    -- req_type 就是請求的代碼                    case req_type is                    -- 獲取描述符請求                    when REQUEST_GET_DESCRIPTOR =>                        if address_set = '0' then                            cmd <= RH_SEND_DESCRIPTOR_1ST;                        else                            cmd <= RH_SEND_DESCRIPTOR;                        end if;                        exec_n <= '0';                    -- 獲取狀態(tài)請求                    when REQUEST_GET_STATUS =>                        cmd <= RH_SEND_STATUS;                        exec_n <= '0';                    -- 設(shè)置地址狀態(tài)                    when REQUEST_SET_ADDRESS =>                        address_set <= '1';                        cmd <= RH_SET_ADDRESS;                        exec_n <= '0';                    -- 啟用特性請求                    when REQUEST_SET_FEATURE =>                        cmd <= RH_SET_FEATURE;                        exec_n <= '0';                    -- 清除特性請求                    when REQUEST_CLEAR_FEATURE =>                        cmd <= RH_CLEAR_FEATURE;                        exec_n <= '0';                    -- 設(shè)置配置請求和設(shè)置描述符請求                    when                        REQUEST_SET_CONFIGURATION | REQUEST_SET_DESCRIPTOR =>                        cmd <= RH_SET_CONFIGURATION;                        exec_n <= '0';                    -- 獲取配置請求                    when REQUEST_GET_CONFIGURATION =>                        cmd <= RH_SEND_CONFIGURATION;                        exec_n <= '0';                    -- 設(shè)置接口請求                    when REQUEST_SET_INTERFACE =>                        cmd <= RH_SET_INTERFACE;                        exec_n <= '0';                    -- 獲取密碼請求                    when REQUEST_GET_PASSWORD =>                        cmd <= RH_SEND_PASSWORD;                        exec_n <= '0';                    -- 獲取密碼高位請求                    when REQUEST_SET_PASSWORD_HIGH =>                        cmd <= RH_SET_PASSWORD_HIGH;                        exec_n <= '0';                    -- 獲取密碼低位請求                    when REQUEST_SET_PASSWORD_LOW =>                        cmd <= RH_SET_PASSWORD_LOW;                        exec_n <= '0';                    when others =>                        NULL;                    end case;                else                    exec_n <= '1';                    cmd <= RH_INVALID_COMMAND;                end if;            when others =>                NULL;            end case;        end if;    end process;end?RequestHandler;

6.7 設(shè)備收發(fā)器模塊的實現(xiàn)

設(shè)備收發(fā)器模塊是整個固件系統(tǒng)的核心,實現(xiàn)的基本思想是創(chuàng)建一個狀態(tài)機,將各個處理操作都作為一個狀態(tài)處理,在每個狀態(tài)中按照 PDIUSBD12 的時序要求對其進行數(shù)據(jù)訪問和控制。

設(shè)備收發(fā)器模塊的示意圖如圖 40 所示。

圖 40 設(shè)備收發(fā)器模塊的示意圖

由于 USB 協(xié)議很復(fù)雜并且 PDIUSBD12 的控制也比較復(fù)雜,所以設(shè)備收發(fā)器狀態(tài)機的狀態(tài)量會較多。根據(jù)設(shè)備收發(fā)器的功能,可以將狀態(tài)機各個狀態(tài)的功能分為 3 類。

? 初始化器件:初始化器件就是對 PDIUSBD12 器件進行配置的狀態(tài),需要配置的內(nèi)容包括設(shè)置地址/使能、設(shè)置 DMA 以及設(shè)置模式等。

? 數(shù)據(jù)訪問:數(shù)據(jù)訪問即實現(xiàn) PDIUSBD12 和 FPGA 之間的數(shù)據(jù)讀寫,包括讀取中斷寄存器、讀取前次傳輸狀態(tài)、由端點讀取數(shù)據(jù)、由端點發(fā)送數(shù)據(jù)等。

? 請求回復(fù):請求回復(fù)是指根據(jù)各種類型請求的數(shù)據(jù)格式提取所需要的數(shù)據(jù),并且在解析完成后通知請求處理模塊。下面詳細介紹一下以上 3 種狀態(tài)的實現(xiàn)。

1)初始化器件

初始化器件相關(guān)的狀態(tài)主要是 TS_DISCONNECTED 和 TS_CONNECTING(狀態(tài)的定義見USB_Package.vhd 文件),其中 TS_DISCONNECTED 是系統(tǒng)復(fù)位后的狀態(tài),TS_CONNECTING 是配置PDIUSBD12 寄存器的狀態(tài)。需要注意的是 PDIUSBD12 器件在復(fù)位后應(yīng)該等待至少 3 ms 后再訪問其寄存器,這樣可讓晶振穩(wěn)定下來。

由于對寄存器配置的命令以及時序都是確定的,所以可以在自定義包中將配置數(shù)據(jù)定義為常數(shù),例如:

constant?D12_CONNECT_DATA:?REG8x8:=(                                      D12_COMMAND_SET_DMA,                                      D12_DMA,                                      D12_COMMAND_SET_MODE,                                      D12_MODE_CONFIG,                                      D12_MODE_CLOCK_DIV,                                      others => X"00"????????????????????????????????????);????????????????????????????????????constant?D12_CONNECT_DATA_TYPE:?REG8x1:=(                                          D12_COMMAND,                                          D12_DATA,                                          D12_COMMAND,                                          D12_DATA,                                          D12_DATA,                                          others => '0'?????????????????????????????????????????);constant D12_CONNECT_DATA_LENGTH: INTEGER8 := 5;

上面定義的就是 PDIUSBD12 的配置參數(shù),第一個常數(shù)數(shù)組是配置命令和數(shù)據(jù),第二個數(shù)組表示命令、數(shù)據(jù)的順序,最后一個參數(shù)是配置參數(shù)的總長度。定義的過程是首先向 PDIUSBD12發(fā)送命令 D12_COMMAND_SET_DMA(設(shè)置 DMA 命令),然后發(fā)送此命令的數(shù)據(jù) D12_DMA(D12_DMA定義為 0xC0,其意義請參考圖 23);之后發(fā)送設(shè)置模式命令和此命令的兩個數(shù)據(jù)。D12_COMMAND_SET_DMA、D12_DMA、D12_COMMAND、D12_DATA 等都是已定義的常數(shù),例如:

constant D12_COMMAND: STD_LOGIC := '1';constant D12_DATA: STD_LOGIC := '0';--constant D12_COMMAND_SET_DMA: STD_LOGIC_VECTOR(7 downto 0) := X"FB";constant D12_DMA:STD_LOGIC_VECTOR(7 downto 0) := X"C0";

詳細的常數(shù)定義請參考 PDIUSBD12 包的定義文件。這樣定義雖然顯得復(fù)雜,但是便于將數(shù)據(jù)與格式分離,也便于代碼閱讀。此外,在調(diào)用配置數(shù)據(jù)時也較為方便,只需要使用一個循環(huán)索引變量,依次讀取 D12_CONNECT_DATA 數(shù)組和D12_CONNECT_DATA 數(shù)組的數(shù)值,發(fā)送給 PDIUSBD12 即可,代碼如下:

-- TS_CONNECT 狀態(tài),對 PDIUSBD12 進行配置when TS_CONNECTING =>    -- handle_step 作為循環(huán)變量    if handle_step = D12_CONNECT_DATA_LENGTH then        ts_state <= TS_IDLE;    else        data_out <= D12ConnectData(handle_step);        a0 <= D12ConnectDataType(handle_step);        wr_n_var := '0'; -- wr_n_var 置為低表示向 PDIUSBD12 輸出    end if;    handle_step := handle_step+1;

以上代碼運行的結(jié)果就是經(jīng)過 5 個時鐘周期,F(xiàn)PGA 完成向 PDIUSBD12 輸出的一系列命令以及數(shù)據(jù),通過編寫測試平臺仿真可以看到運行的結(jié)果(測試平臺的編寫將會在下面專門介紹),如圖 41 所示。

圖 41 器件配置仿真時序圖

通過上面的時序圖可以看出,8 位總線上傳輸?shù)氖?D12_CONNECT_DATA 定義的配置命令和數(shù)據(jù),而 a0 位表明了總線上的是命令還是數(shù)據(jù),通過一個下降沿的寫信號可以將命令或者數(shù)據(jù)發(fā)送給 PDIUSBD12。

2)數(shù)據(jù)訪問狀態(tài)

數(shù)據(jù)訪問狀態(tài)的功能簡單地說就是中斷監(jiān)測和數(shù)據(jù)收發(fā)。每次系統(tǒng)復(fù)位后 FPGA 會自動配置 PDIUSBD12 器件,配置完成之后設(shè)備收發(fā)器模塊會處于空閑狀態(tài)(TS_IDLE)。PDIUSBD12 器件在接收到數(shù)據(jù)包時會通過中斷來通知設(shè)備收發(fā)器,此外,請求處理模塊也會通過命令中斷信號控制設(shè)備收發(fā)器模塊。所以,中斷監(jiān)測就是在每個時鐘周期讀取一次 PDIUSBD12 的中斷信號和請求處理模塊的命令中斷信號,如果發(fā)現(xiàn)其中的一個中斷信號為低,則轉(zhuǎn)為其他狀態(tài)。

中斷監(jiān)測的代碼如下:

-- 空閑狀態(tài),監(jiān)測中斷信號when TS_IDLE =>    data_out <= X"00";    recv_n <= '1';    ih_state <= IH_START;    -- 判斷 PDIUSBD12 的中斷信號    if int_n = '0' then        handle_step := 0;        ts_state <= TS_READ_IR;    -- 判斷請求處理模塊的命令中斷信號    elsif exec_n = '0' then        ts_state <= GetCommandHandler(cmd);        handle_step := 0;    end if;

當(dāng)監(jiān)測到 PDIUSBD12 的中斷時,設(shè)備收發(fā)器首先讀取中斷寄存器,然后就會進入數(shù)據(jù)收發(fā)狀態(tài),如果監(jiān)測到的是請求處理模塊的命令中斷,則進入的是請求回復(fù)狀態(tài)。請求回復(fù)狀態(tài)包括了發(fā)送描述符、發(fā)送配置信息等,這些內(nèi)容將在下面一個小節(jié)介紹。數(shù)據(jù)收發(fā)狀態(tài)包括讀取中斷寄存器、控制端點數(shù)據(jù)收發(fā)等。讀取中斷寄存器的流程圖如圖42 所示。

圖 42 中斷處理流程圖

讀取中斷寄存器的代碼如下:

-- 讀取中斷寄存器狀態(tài)when TS_READ_IR =>    -- 第一步,發(fā)送讀取中斷寄存器命令    if handle_step = 0 then        a0 <= D12_COMMAND;        data_out <= D12_COMMAND_READ_IR;        wr_n_var := '0';    -- 第二步,設(shè)置讀信號為低,讀取第一個返回參數(shù),即中斷寄存器第一個字節(jié)    elsif handle_step = 1 then        a0 <= D12_DATA;        rd_n_var := '0';    -- 第三步,保存中斷寄存器第一個字節(jié)并讀取第二個返回參數(shù)(中斷寄存器第二個字節(jié))    elsif handle_step = 2 then        -- 保存中斷寄存器第一個字節(jié)        ir_0 := data_in;        -- 讀取第二個參數(shù)        a0 <= D12_DATA;        rd_n_var := '0';        -- 最后,保存第二個參數(shù),進入下一處理狀態(tài)    else        -- 保存中斷寄存器第二個字節(jié)        ir_1 := data_in(0);        -- 根據(jù)中斷寄存器選擇進入下一處理狀態(tài)        ts_state <= GetInterruptHandler(ir_0, ir_1);        ih_state <= IH_START;    end if;    handle_step := handle_step+1;

下面介紹一下控制輸出的處理流程??刂戚敵龅妮敵鍪窍鄬χ鳈C來說的,所以相對于設(shè)備來說,就是接收主機的數(shù)據(jù)。當(dāng)一次控制輸出發(fā)生時,設(shè)備首先會判斷接收到的是不是建立包(Setup Packet),如果是則開始接收下面的數(shù)據(jù),否則,接收前次傳輸所剩余的數(shù)據(jù)??刂苽鬏?shù)奶幚砹鞒虉D如圖 43 所示。

圖 43 控制輸出流程圖

從上面的流程圖可以看出,設(shè)備收發(fā)器首先要選擇控制輸出端點,提取建立包的內(nèi)容,再進行端點是為滿還是空的判斷。如果控制端點不為空,設(shè)備收發(fā)器將從緩沖區(qū)讀出內(nèi)容并將其保存。之后,它將判斷設(shè)備請求的有效性,如果是一個有效的請求,設(shè)備收發(fā)器必須向控制輸出端點發(fā)送應(yīng)答建立命令以重新使能下一個建立階段。

接下來,設(shè)備收發(fā)器需要證實控制傳輸是控制讀還是寫。這可以通過讀建立包中bmRequestType 的第 8 位來判斷。如果控制傳輸是一個控制讀類型,那就是說器件需要在下一個數(shù)據(jù)階段向主機發(fā)回數(shù)據(jù)包。設(shè)備收發(fā)器會設(shè)置一個標(biāo)志以指示設(shè)備現(xiàn)在正處于傳輸模式,即準(zhǔn)備在主機發(fā)送請求時進入傳輸狀態(tài)(TS_EP0_TRANSMIT)向主機發(fā)送數(shù)據(jù)。

處理流程的各個步驟在設(shè)備收發(fā)器模塊中被劃分在兩個狀態(tài)中實現(xiàn),其中選擇端點和讀取、保存數(shù)據(jù)的操作在 TS_READ_ENDPOINT 狀態(tài)中實現(xiàn),其他的內(nèi)容在 TS_EP0_RECEIVE 狀態(tài)中實現(xiàn)。下面是從端點(PDIUSBD12 的緩沖)數(shù)據(jù)讀取的實現(xiàn)代碼,即 TS_READ_ENDPOINT 狀態(tài)的代碼,由于篇幅原因,這里只提供部分參考代碼。

-- 讀取端點數(shù)據(jù)狀態(tài)when TS_READ_ENDPOINT =>    -- handle_step 表示操作步驟    case handle_step is    -- 首先,發(fā)送選擇端點命令,選擇端點    when 0 =>        a0 <= D12_COMMAND;        data_out <= active_ep;        wr_n_var := '0';        handle_step := handle_step+1;    -- 發(fā)送讀取端點數(shù)據(jù)的命令,準(zhǔn)備接收數(shù)據(jù)    when 1 =>        a0 <= D12_COMMAND;        data_out <= D12_COMMAND_RW_BUFFER;        wr_n_var := '0';        handle_step := handle_step+1;    -- 讀取緩沖數(shù)據(jù)的前兩個字節(jié),第一個字節(jié)為保留數(shù)據(jù),第二個字節(jié)表示數(shù)據(jù)長度    when 2 | 3 =>        a0 <= D12_DATA;        rd_n_var := '0';        handle_step := handle_step+1;    -- 保存第二個字節(jié)(數(shù)據(jù)長度),準(zhǔn)備接收有效數(shù)據(jù)    when 4 =>        -- 保留第二個字節(jié)        read_in := conv_integer(data_in);        -- 判斷數(shù)據(jù)長度是否為零        if read_in = 0 then            handle_step := 7;        else            -- 獲取剩余的數(shù)據(jù)            handle_step := handle_step+1;            a0 <= D12_DATA;            rd_n_var := '0';        end if;    -- 依次讀取數(shù)據(jù)并且保存數(shù)據(jù)    when 5 =>        -- 保存前一個周期要求獲取的數(shù)據(jù)        ts_data(ram_address) <= data_in;        ram_address := ram_address+1;        read_count := read_count+1;        -- 判斷全部數(shù)據(jù)是否已經(jīng)獲取        if read_count = read_in then            handle_step := 6;        else            -- 繼續(xù)要求獲取下一個數(shù)據(jù)            a0 <= D12_DATA;            rd_n_var := '0';        end if;    -- 最后,發(fā)送清除端點緩沖的命令    when 6 =>        a0 <= D12_COMMAND;        data_out <= D12_COMMAND_CLEAR_EP_BUFFER;        wr_n_var := '0';        handle_step := 7;    -- 恢復(fù)到原始處理狀態(tài)    when others =>        handle_step := 0;        ts_state <= last_ts_state;    end case;

下面介紹一下控制輸入的處理過程??刂戚斎刖褪窃O(shè)備向主機發(fā)送數(shù)據(jù),最為典型的就是設(shè)備向主機發(fā)送描述符,圖 44 所示是控制輸入的流程圖。

圖 44 控制輸入流程圖

從控制輸入的流程圖可以看出,設(shè)備收發(fā)器首先需要通過讀 PDIUSBD12 的最后處理狀態(tài)寄存器清零中斷標(biāo)志位。接著設(shè)備收發(fā)器在確認(rèn) PDIUSBD12 處于傳輸模式后進行數(shù)據(jù)包的發(fā)送。PDIUSBD12 的控制端點只有 16 字節(jié) FIFO,如果傳輸?shù)拈L度大于 16 字節(jié),設(shè)備收發(fā)器在傳輸階段就必須控制數(shù)據(jù)的數(shù)量。設(shè)備收發(fā)器必須檢查要發(fā)送到主機的當(dāng)前和剩余的數(shù)據(jù)大小,如果剩下的字節(jié)數(shù)大于 16,設(shè)備收發(fā)器將先發(fā)送 16 字節(jié)并繼續(xù)等待下一次發(fā)送。

當(dāng)下一個數(shù)據(jù)發(fā)送中斷來到時,設(shè)備收發(fā)器將確定剩余的字節(jié)是否為零。如果已經(jīng)沒有數(shù)據(jù)要發(fā)送,設(shè)備收發(fā)器需要發(fā)送一個空的包以指示主機數(shù)據(jù)已經(jīng)發(fā)送完畢。

控制輸入是在 TS_EP0_TRANSMIT 和 TS_WRITE_ENDPOINT 兩個狀態(tài)中實現(xiàn)的。其中,TS_EP0_TRANSMIT 實 現(xiàn) 的 是 控 制 輸 入 流 程 控 制 , 而 TS_WRITE_ENDPOINT 的 實 現(xiàn) 和TS_READ_ENDPOINT 很類似,只不過是將讀取數(shù)據(jù)換為發(fā)送數(shù)據(jù)。TS_WRITE_ENDPOINT 狀態(tài)的實現(xiàn)代碼如下,由于篇幅原因,這里只提供部分參考代碼。

-- 寫端點緩存數(shù)據(jù)的狀態(tài)when TS_WRITE_ENDPOINT =>    case handle_step is    -- 首先,發(fā)送選擇端點的命令,選擇端點 0    when 0 =>        a0 <= D12_COMMAND;        data_out <= active_ep;        wr_n_var := '0';        handle_step := handle_step+1;    -- 讀取選擇端點命令的一個返回參數(shù)(可選)    when 1 =>        a0 <= D12_DATA;        rd_n_var := '0';        handle_step := handle_step+1;    -- 發(fā)送讀寫端點的命令    when 2 =>        a0 <= D12_COMMAND;        data_out <= D12_COMMAND_RW_BUFFER;        wr_n_var := '0';        handle_step := handle_step+1;    -- 寫入端點緩存第一個字節(jié),為保留字節(jié),值為 0    when 3 =>        a0 <= D12_DATA;        data_out <= X"00";        wr_n_var := '0';        handle_step := handle_step+1;    -- 寫入端點緩存第二個字節(jié),為有效數(shù)據(jù)的長度    when 4 =>        a0 <= D12_DATA;        data_out <= conv_std_logic_vector(to_write, 8);        wr_n_var := '0';        write_count := 0;        handle_step := handle_step+1;    -- 順序?qū)懭胗行?shù)據(jù)    when 5 =>        if to_write = 0 then            -- send comnand: enable buffer            a0 <= D12_COMMAND;            data_out <= D12_COMMAND_ENABLE_BUFFER;            wr_n_var := '0';            handle_step := 7;        else            handle_step := handle_step+1;        end if;    -- 發(fā)送緩沖區(qū)有效命令,允許 PDIUSBD12 發(fā)送數(shù)據(jù)    when 6 =>        -- 判斷是否所有數(shù)據(jù)已經(jīng)被寫入        if write_count = to_write then            --發(fā)送緩沖區(qū)有效命令            a0 <= D12_COMMAND;            data_out <= D12_COMMAND_ENABLE_BUFFER;            wr_n_var := '0';            handle_step := 7;          else            -- 寫入數(shù)據(jù)            a0 <= D12_DATA;            data_out <= ts_data(ram_address);            ram_address := ram_address+1;            wr_n_var := '0';            write_count := write_count+1;        end if;    -- 恢復(fù)到原始處理狀態(tài)    when 7 =>        handle_step := 0;        ts_state <= last_ts_state;        when others =>        NULL;    end case;

以上便是數(shù)據(jù)訪問狀態(tài)的實現(xiàn)方法,在測試平臺中可以對以上代碼進行測試,測試時的輸入數(shù)據(jù)應(yīng)該由測試平臺產(chǎn)生(測試平臺的編寫將在下面的章節(jié)進行專門介紹)。如第一次發(fā)送設(shè)備描述符的仿真波形。此仿真過程可以分為兩個部分,第一部分(如圖 45 所示)是接收建立包(Setup Packet)以及讀取 PDIUSBD12 請求數(shù)據(jù)的過程;第二部分(如圖 46 所示)是將設(shè)備描述符數(shù)據(jù)寫入 PDIUSBD12 端點緩存并且使緩沖區(qū)有效。

圖 45 發(fā)送設(shè)備描述符仿真波形 1

圖 46 發(fā)送設(shè)備描述符仿真波形 2

3)請求回復(fù)狀態(tài)

請求回復(fù)狀態(tài)的功能就是對各個請求作出響應(yīng)。USB 的標(biāo)準(zhǔn)請求已經(jīng)在前面做了介紹,下面就以獲取描述符請求為例介紹一下請求響應(yīng)的實現(xiàn)方法,其他的標(biāo)準(zhǔn)請求以及廠商請求(獲取、設(shè)置密碼)相對來說比較簡單,實現(xiàn)的方法請讀者參考源代碼。

獲取描述符請求是最為重要的請求,因為這在設(shè)備枚舉過程中是必需的,它是主機了解設(shè)備的第一個步。獲取描述符請求的處理流程如圖 47 所示。

圖 47 獲取描述符處理流程

獲取設(shè)備描述符請求響應(yīng)的實現(xiàn)代碼如下:

-- 獲取描述符請求響應(yīng)狀態(tài)when TS_SEND_DESCRIPTOR =>    handle_step := 0;    active_ep := X"01";    -- 判斷是否是設(shè)備請求        if ts_data(ADDRESS_DESCRIPTOR_TYPE) = TYPE_DEVICE_DESCRIPTOR then            -- LED 輸出,提示作用            led(0) <= '0';            -- 檢查數(shù)據(jù)長度是否符合要求            if data_length > LENGTH_DEVICE_DESCRIPTOR then                data_length := LENGTH_DEVICE_DESCRIPTOR;            end if;            -- 判斷描述符長度是否超過端點 0 的緩存大小            if data_length > LENGTH_ENDPOINT0_BUFFER then                to_write := LENGTH_ENDPOINT0_BUFFER;                is_transmit := '1';            else                to_write := data_length;            end if;            -- 設(shè)置傳輸狀態(tài)標(biāo)志位,設(shè)置傳輸數(shù)據(jù)源(描述符)以及數(shù)據(jù)長度            data_count := to_write;            ram_address := ADDRESS_DEVICE_DESCRIPTOR;            -- 準(zhǔn)備轉(zhuǎn)入進入控制輸入狀態(tài)(TS_WRITE_ENDPOINT),發(fā)送數(shù)據(jù)            ts_state <= TS_WRITE_ENDPOINT;        elsif ts_data(ADDRESS_DESCRIPTOR_TYPE) = TYPE_CONFIGURATION_DESCRIPTOR then            -- 檢查數(shù)據(jù)長度,LED 輸出,提示作用            if data_length > LENGTH_CONFIGURATION_DESCRIPTOR then                data_length := LENGTH_CONFIGURATION_DESCRIPTOR;                led(2) <= '0';            else                led(1) <= '0';            end if;            -- 判斷描述符長度是否超過端點 0 的緩存大小            if data_length > LENGTH_ENDPOINT0_BUFFER then                to_write := LENGTH_ENDPOINT0_BUFFER;                is_transmit := '1';            else                to_write := data_length;            end if;            -- 設(shè)置傳輸狀態(tài)標(biāo)志位,設(shè)置傳輸數(shù)據(jù)源(描述符)以及數(shù)據(jù)長度            data_count := to_write;            ram_address := ADDRESS_CONFIGURATION_DESCRIPTOR;            -- 設(shè)置傳輸狀態(tài)標(biāo)志位,設(shè)置傳輸數(shù)據(jù)源(描述符)以及數(shù)據(jù)長度            ts_state <= TS_WRITE_ENDPOINT;        else            ts_state <= TS_IDLE;        end if;        last_ts_state := TS_END_REQUESTHANDLER;

6.8 測試平臺的編寫

上面介紹的是整個 FPGA 固件系統(tǒng)的實現(xiàn)方法,為了驗證設(shè)計的正確性,還需要編寫一個測試平臺對整個系統(tǒng)進行仿真。由于實際情況下 FPGA 是和 PDIUSBD12 進行通信,所以在測試平臺中需要虛擬一個 PDIUSBD12,來實現(xiàn)仿真的目的。

首先,在測試平臺中需要產(chǎn)生一個虛擬的時鐘信號,產(chǎn)生的方法就是使用 wait for 語句等待固定時間后將信號值翻轉(zhuǎn)。時鐘信號的實現(xiàn)代碼如下:

-- 時鐘信號生成代碼clk_gen: processbegin    -- 翻轉(zhuǎn)    clk <= not clk;    -- 等待固定時間    wait for 50 ns;end process;

其次,由于 FPGA 和 PDIUSBD12 之間有數(shù)據(jù)讀寫,所以要模擬所有 FPGA 向 PDIUSBD12 讀取的數(shù)據(jù)。模擬數(shù)據(jù)讀寫的方法是將所有數(shù)據(jù)按照順序?qū)懭胍粋€大的測試數(shù)據(jù)數(shù)組中,使用一個變量作為該數(shù)組索引,再編寫一個對讀信號敏感的過程,在每次讀信號的下降沿將數(shù)據(jù)送到總線上,并且將數(shù)組索引變量增加 1。測試數(shù)據(jù)數(shù)組以及索引變量的定義方法如下:

-- 測試數(shù)據(jù)數(shù)組定義signal td : REG256x8 :=(-- 第一次獲取設(shè)備描述符測試數(shù)據(jù)X"01", X"00", X"20", X"00", X"08", -- 各寄存器數(shù)據(jù)以及端點 0 緩存前兩個字節(jié)X"80", X"06", X"00", X"01", X"00", X"00", X"40", X"00", -- 獲取設(shè)備描述符請求X"00",-- 設(shè)置地址請求測試數(shù)據(jù)X"01", X"00", X"20", X"00", X"08", -- 各寄存器數(shù)據(jù)以及端點 0 緩存前兩個字節(jié)X"00", X"05", X"02", X"00", X"00", X"00", X"00", X"00", -- 設(shè)置地址請求X"00",-- 獲取完整設(shè)備描述符測試數(shù)據(jù)X"01", X"00", X"20", X"00", X"08", -- 各寄存器數(shù)據(jù)以及端點 0 緩存前兩個字節(jié)X"80", X"06", X"00", X"01", X"00", X"00", X"12", X"00", -- 獲取配置描述符請求X"00",X"02", X"00", X"00", X"00", -- 各寄存器數(shù)據(jù)-- 獲取配置描述符請求測試數(shù)據(jù)X"01", X"00", X"20", X"00", X"08", -- 各寄存器數(shù)據(jù)以及端點 0 緩存前兩個字節(jié)X"80", X"06", X"00", X"02", X"00", X"00", X"09", X"00", --獲取配置描述符請求X"00",--獲取所有配置描述符請求測試數(shù)據(jù)X"01", X"00", X"20", X"00", X"08", -- 各寄存器數(shù)據(jù)以及端點 0 緩存前兩個字節(jié)X"80", X"06", X"00", X"02", X"00", X"00", X"FF", X"00", -- 獲取配置描述符請求X"00",X"02", X"00", X"00", X"00", -- 各寄存器數(shù)據(jù)X"02", X"00", X"00", X"00", -- 各寄存器數(shù)據(jù)-- 設(shè)置配置請求測試數(shù)據(jù)X"01", X"00", X"20", X"00", X"08", -- 各寄存器數(shù)據(jù)以及端點 0 緩存前兩個字節(jié)X"00", X"09", X"01", X"00", X"00", X"00", X"00", X"00", -- 設(shè)置配置請求X"00",others => X"00");-- 數(shù)組索引signal td_index : INTEGER8 := 255;

再次,需要處理好總線雙驅(qū)動的問題。前面介紹的輸入/輸出選擇模塊的功能就是在必要的時候關(guān)閉總線輸出來避免雙驅(qū)動的發(fā)生,同樣道理,在測試平臺中也應(yīng)該做到這一點,即當(dāng)測試平臺向 FPGA 固件系統(tǒng)讀取數(shù)據(jù)時,應(yīng)該關(guān)閉測試平臺的總線輸出,即將其設(shè)置為高阻。實現(xiàn)代碼如下:

process(d12_wr, td_index)begin    -- 當(dāng) FPGA 向 PDIUSBD12 些數(shù)據(jù)時,總線輸出變?yōu)楦咦?/code>    if d12_wr = '0' then        data <= "ZZZZZZZZ";    else        data <= td(td_index);    end if;end process;

最后,還需要編寫一個主流程,在主流程中需要進行系統(tǒng)復(fù)位和產(chǎn)生中斷信號,代碼如下:

-- main processmain: processvariable i : INTEGER8;begin    -- 復(fù)位    reset_n <= '0';    wait for 100 ns;    reset_n <= '1';    wait for 100 us;    -- 循環(huán)模擬產(chǎn)生 PDIUSBD12 中斷    for i in 0 to 10 loop        int_n_in <= '0';        wait for 3200 ns;        int_n_in <= '1';        wait for 300 us;    end loop;    wait;    end process;

七、USB 驅(qū)動和軟件開發(fā)

7.1 USB 驅(qū)動編寫

以上介紹的是 FPGA 固件的開發(fā)過程,由于本例中設(shè)計的不是一個類設(shè)備,所以要使設(shè)備正常工作,還需要編寫專門的驅(qū)動程序和軟件。由于驅(qū)動和軟件不是本篇的重點,故下面只簡要介紹其編寫方法。

1)USB 驅(qū)動模型

USB 體系的主機軟件可分為兩層,即 USB 系統(tǒng)軟件和客戶端驅(qū)動程序,如圖 48 所示。

圖 48 USB 接口軟件模型

USB 系統(tǒng)軟件根據(jù)功能可以分為 USBD 和 HCD 上下兩部分,其中 HCD 為上層提供了主機控制器的抽象以及數(shù)據(jù)在總線上的傳輸抽象。USBD 為上層的客戶端驅(qū)動程序提供了 USB 設(shè)備的抽象,并在客戶端驅(qū)動和所驅(qū)動的設(shè)備之間提供了數(shù)據(jù)傳輸的抽象。

客戶端驅(qū)動程序從用戶的角度來講相當(dāng)于傳統(tǒng)意義上的驅(qū)動程序。不過設(shè)備端不同的接口對應(yīng)不同的驅(qū)動程序,如果設(shè)備只有一個接口,那么從用戶的角度來講,兩者是一樣的,客戶端驅(qū)動程序通過 USB 系統(tǒng)軟件提供的接口與設(shè)備交互,而不是通過過去的 I/O 地址或者端口進行訪問。

2)使用 Driver Studio 開發(fā) USB 驅(qū)動

上面介紹的是 USB 軟件模型,對于驅(qū)動開發(fā)人員來說,需要編寫的就是客戶端驅(qū)動程序。編寫客戶端驅(qū)動程序需要安裝 DDK,即 Windows Driver Development Kit,通過 DDK 我們就能夠訪問 USB 系統(tǒng)軟件的接口從而實現(xiàn)與設(shè)備的交互。但是,如果只使用 DDK 開發(fā)驅(qū)動程序的話,會比較復(fù)雜,所以可以使用一些驅(qū)動開發(fā)的專用工具,例如 Driver Studio、WinDriver 等。本例選用的是 Driver Studio 2.7 進行開發(fā),下面介紹一下開發(fā)的基本步驟。安裝完 DDK 以及 Driver Studio 后,運行 Driver Studio 的 Driver Wizard。在第 1 步中輸入驅(qū)動工程名稱和路徑,如圖 49 所示。單擊 Next 按鈕進入如圖 50 所示對話框。

圖 49 Driver Wizard 第 1 步?

圖 50 Driver Wizard 第 2 步

第 2 步選擇工程類型 WDM Driver,單擊 Next 按鈕進入如圖 51 所示對話框。

第 3 步選擇驅(qū)動類型 WDM Function Driver。單擊 Next 按鈕進入如圖 52 所示對話框。

圖 51 Driver Wizard 第 3 步?

圖 52 Driver Wizard 第 4 步

第 4 步比較重要,是選擇驅(qū)動總線類型,應(yīng)該選擇 USB(WDM Only),并且注意要在 USB VendorID 和 USB Product ID 中輸入和固件中設(shè)備描述一致的信息。這里請注意 Vendor ID 一定是0x0471,因為使用的是 Philips 的 PDIUSBD12 芯片,其 Vendor ID 固定為 0x0471。單擊 Next按鈕,進入如圖 53 所示對話框。

圖?53?Driver Wizard 第 5 步

第 5 步是端點定義,可以根據(jù)需要定義端點的類型(輸入輸出)、端點號、緩存大小等。

第 6 步到第 9 步是一些開發(fā)輔助信息的定義,可以保持為默認(rèn)值,如圖 54~圖 57 所示。

圖 54 Driver Wizard 第 6 步?

圖 55 Driver Wizard 第 7 步

圖 56 Driver Wizard 第 8 步?

圖 57 Driver Wizard 第 9 步

第 10 步是設(shè)備類的定義,如圖 58 所示。定義打開設(shè)備的方式,Symbolic Link 表示按照設(shè)備名稱打開,Interface(WDM Only)表示按照設(shè)備的 GUID 打開,這里選擇使用設(shè)備名稱打開。

圖 58 Driver Wizard 第 10 步

第 11 步定義的是設(shè)備的 IO 控制接口,也就是驅(qū)動和應(yīng)用程序之間的接口,如圖 59 所示。單擊 Add 按鈕可以定義 IO 控制接口,如圖 60 所示。

圖 59 Driver Wizard 第 11 步?

圖 60 定義 IO 控制接口

最后,第 12 步進行一些額外的設(shè)置,如圖 61 所示,可以保持默認(rèn)值。

圖 61 Driver Wizard 第十二步

以上便是使用 Drive Studio 的 Driver Wizard 生成驅(qū)動框架的完整過程,現(xiàn)在我們已經(jīng)有了一個完成了大部分驅(qū)動工作的代碼框架,只需要增加一些自定義的處理代碼即可。

3)使用 Visual C++編譯驅(qū)動

運行 Visual C++ 6.0 打開 Driver Wizard 生成的工程文件,可看到在***Device 這個類中已經(jīng)有了很多設(shè)備操作的處理函數(shù),例如上電(OnDevicePowerUp)、休眠(OnDeviceSleep)啟動(OnDeviceStart)等,可以根據(jù)需要修改這些函數(shù),如果沒有特殊要求,可以保持默認(rèn)設(shè)置,如圖 62 所示。

圖 62 設(shè)備操作處理函數(shù)

另外還需要完成的工作就是對上面定義的 IO 控制接口函數(shù)進行處理,其功能就是建立一個廠商請求。由于本次設(shè)計的 USB 設(shè)備是一個加密設(shè)備,它不是類設(shè)備,所以會有一些特定的請求(廠商請求)。為了介紹廠商請求的實現(xiàn)方法,本系統(tǒng)用到了兩個廠商請求:設(shè)置密碼和獲取密碼。由 Driver Wizard 自動生成的驅(qū)動一般都已經(jīng)包括了標(biāo)準(zhǔn)請求的建立,但是不會包括廠商請求的建立。廠商請求是在 IO 控制接口函數(shù)中建立的,即 Driver Wizard 第 11 步所定義的兩個函數(shù),建立廠商請求的函數(shù)主要是 BuildVendorRequest 函數(shù),其格式如下:

PURB BuildVendorRequest(    PUCHAR TransferBuffer,    ULONG TransferBufferLength,    UCHAR RequestTypeReservedBits,    UCHAR Request,    USHORT Value,    BOOLEAN bIn=FALSE,    BOOLEAN bShortOk=FALSE,    PURB Link=NULL    UCHAR Index=0,    USHORT Function=URB_FUNCTION_VENDOR_DEVICE,    PURB pUrb=NULL??);

其中需要開發(fā)人員注意的是前 6 個參數(shù),其意義如下:

? PUCHAR TransferBuffe 數(shù)據(jù)緩沖。如果是數(shù)據(jù)輸入,用于存儲接收到的數(shù)據(jù);如果是數(shù)據(jù)輸出,則是待發(fā)送數(shù)據(jù)的數(shù)據(jù)源;如果沒有數(shù)據(jù)傳輸,此參數(shù)可是為空(NULL)。

? ULONG TransferBufferLength 發(fā)送或者接收數(shù)據(jù)的長度。

? UCHAR RequestTypeReservedBit 請求類型的位掩碼,一般為零。

? UCHAR Request 請求代碼。

? USHORT Value 即 USB 請求中的 wValue 位

? BOOLEAN bIn=FALSE 此參數(shù)為 TRUE 表示數(shù)據(jù)輸出,反之則表示數(shù)據(jù)輸入。

其余的參數(shù)可以保持默認(rèn)。下面就從 USBSOFTLOCK_IOCTL_GET_PASSWORD_Handler 處理函數(shù)為例介紹一下 BuildVendorRequest 函數(shù)的用法,代碼如下:

NTSTATUS USBSoftLockDevice::USBSOFTLOCK_IOCTL_GET_PASSWORD_Handler(KIrp I){    NTSTATUS status = STATUS_SUCCESS;    // 輸出提示信息    t << "Entering USBSoftLockDevice::USBSOFTLOCK_IOCTL_GET_PASSWORD_Handler, "      << I << EOL;    t << "IOctrlBuffer address is " << (LONG)(I.IoctlBuffer()) << EOL;    t << "BufferedReadDest address is " << (LONG)(I.BufferedReadDest()) << EOL;    t << "BufferedWriteSource address is " << (LONG)(I.BufferedWriteSource()) << EOL;    t << "IoctlOutputBufferSize is " << (LONG)(I.IoctlOutputBufferSize()) << EOL;    // 保存 8 字節(jié)密碼的緩存    UCHAR buffer[8];    // 創(chuàng)建廠商請求,請求的代碼是 REQUEST_GET_PASSWORD,數(shù)據(jù)長度為 8    PURB pUrb = m_Lower.BuildVendorRequest(        buffer, -- 數(shù)據(jù)緩沖        PASSWORD_LENGTH, -- 數(shù)據(jù)長度        0, -- 保留        REQUEST_GET_PASSWORD, -- 請求代碼        0, -- 即 USB 請求的 wValue 字段        TRUE -- TRUE 表示數(shù)據(jù)輸入,反之則是數(shù)據(jù)輸出    );    status = m_Lower.SubmitUrb(pUrb, NULL, NULL, OPERATION_TIMEOUT);    // 判斷返回值    if (status == STATUS_SUCCESS) {          t << "Received buffer is ";          for (int i=0;i<PASSWORD_LENGTH;i++) {              t << " " << buffer[i];          }          t << EOL;          PUCHAR output_buffer = (PUCHAR)(I.IoctlBuffer());          memcpy(output_buffer, buffer, PASSWORD_LENGTH);    }    else {    }    return status;}

完成廠商請求的編寫之后,就可以進行驅(qū)動程序編譯了。驅(qū)動編譯默認(rèn)有兩種版本,即Win32 Checked 和 Win32 Free,其中前者表示調(diào)試版本,而后者表示發(fā)布版本,發(fā)布版本相對調(diào)試版本去掉了大部分調(diào)試信息,比較簡化。

編 譯 驅(qū) 動 的 方 法 是 在 Visual C++ 中 打 開 Driver Studio 的 工 具 條 CompuwareDriverStudio,如圖 63 所示。

圖 63 Compuware DriverStudio 工具條

選擇合適的編譯版本,再單擊 Compuware DriverStudio 工具條的最后一個按鈕即可。請注意不能使用 Visual C++本身的編譯按鈕進行驅(qū)動編譯。編譯成功,如果是 Win32 Free 版本,則會在工程目錄的 sysobjfrei386 子目錄下生成驅(qū)動文件 USBSoftLock.sys;如果是 Win32Checked 版本,驅(qū)動文件會在工程目錄的 sysobjchki386 子目錄下。成功編譯驅(qū)動程序之后,將它和 Driver Studio 自動生成的.inf 文件(在工程目錄下)放在同一個目錄下,在查找驅(qū)動的時候指定這個目錄就可以了。

7.2 USB 軟件編寫

最后,再簡要介紹一下 USB 軟件的編寫,即軟件對 USB 設(shè)備訪問的實現(xiàn)方法。

USB 軟件通過 USB 驅(qū)動實現(xiàn)對 USB 設(shè)備的訪問,編寫 USB 軟件必須符合 USB 驅(qū)動定義的接口規(guī)范。一般來說,使用 Driver Wizard 生成一個驅(qū)動工程后,會同時生成一個***ioctl.h的文件,這個文件就是建立軟件和驅(qū)動之間通信的橋梁,它定義了訪問驅(qū)動程序的接口,在編寫軟件的時候需要將其引用進去。

USB 軟件的編寫一般有下面幾個步驟。

1) 打開設(shè)備

打開設(shè)備主要需要調(diào)用 CreateFile 函數(shù),它將設(shè)備作為一個文件來處理,代碼如下:

BOOL CSoftLock::OpenDevice(){    if (m_hDevice != INVALID_HANDLE_VALUE)        return TRUE;    const char *sLinkName = ".USBSoftLockDevice0";    m_hDevice = CreateFile(sLinkName,          GENERIC_READ | GENERIC_WRITE,          FILE_SHARE_READ,          NULL,          OPEN_EXISTING,          0,          NULL);    return m_hDevice != INVALID_HANDLE_VALUE;}

2) 調(diào)用設(shè)備 IO 接口

調(diào) 用 設(shè) 備 IO 接 口 使 用 DeviceIoControl 函 數(shù) 控 制 設(shè) 備 。 這 里 主 要 用 到 兩 次DeviceIOControl 函數(shù),即設(shè)置密碼和獲取密碼,它們分別對應(yīng)驅(qū)動中已經(jīng)定義的 IO 控制接口函數(shù)。例如,設(shè)置密碼接口函數(shù)的調(diào)用方法如下:

BOOL CSoftLock::SetPassword(char* password){// Note that Input and Output are named from the point of view// of the DEVICE:// bufInput supplies data to the device// bufOutput is written by the device to return data to this application    CHAR bufInput[IOCTL_INBUF_SIZE]; // Input to device    CHAR bufOutput[IOCTL_OUTBUF_SIZE]; // Output from device    ULONG nOutput; // Count written to bufOutput    memset(bufInput, 0, BUFFER_LENGTH);    memset(bufOutput, 0, BUFFER_LENGTH);    memcpy(bufInput, password, PASSWORD_LENGTH);    // Call device IO Control interface (USBSOFTLOCK_IOCTL_SET_PASSWORD) in driver    printf("Issuing Ioctl to device - ");    if (!DeviceIoControl( m_hDevice,              USBSOFTLOCK_IOCTL_SET_PASSWORD,              bufInput,              PASSWORD_LENGTH,              bufOutput,              PASSWORD_LENGTH,              &nOutput,              NULL) )    {              printf("ERROR: DeviceIoControl returns %0x.", GetLastError());              return FALSE;    }    else {            printf("input buffer is : %s, output buffer is %s, output buffer size is %d",                bufInput,                bufOutput,                nOutput);    }    return TRUE;}

3) 關(guān)閉設(shè)備

和打開設(shè)備對應(yīng),關(guān)閉設(shè)備就是調(diào)用 CloseHandle 函數(shù)關(guān)閉設(shè)備的句柄就可以了,例如:

void CSoftLock::CloseIfOpen(){    if (m_hDevice != INVALID_HANDLE_VALUE)    {        // Close the handle to the driver        if (!CloseHandle(m_hDevice))        {            printf("ERROR: CloseHandle returns %0x.n", GetLastError());        }        m_hDevice = INVALID_HANDLE_VALUE;    }}

USB軟件的詳細代碼請參考源代碼中的cube測試程序,它模擬了一個硬件加密設(shè)備的工作過程。cube程序運行后會出現(xiàn)一個立方體,使得立方體轉(zhuǎn)動表示正常的程序運行狀態(tài)。程序運行需要密碼,但是密碼不是保存在計算機上,而是保存在USB設(shè)備上,并且程序運行時需要及時校驗密碼,一旦密碼校驗失?。赡苁且驗槊艽a不正確或者USB設(shè)備被移除),程序都會停止運行。方法是首先選擇菜單File—>Open Device打開USB設(shè)備(如圖64所示),如果打開設(shè)備成功,選擇File—>Play Cube,在出現(xiàn)的密碼輸入框內(nèi)輸入密碼,如果密碼正確,立方體就會開始轉(zhuǎn)動,并且cube程序在不時地和USB設(shè)備之間進行密碼校驗(可以看到PDIUSBD12的GOODLINK燈會不停的閃,這表示有數(shù)據(jù)傳輸)。還可以通過選擇File—>Set Password設(shè)置密碼,此密碼會通過Set Password請求發(fā)送給設(shè)備。

圖 64 cube 程序運行界面

總結(jié)

本篇首先說明了 USB 系統(tǒng)的體系結(jié)構(gòu)以及 USB 協(xié)議相關(guān)的內(nèi)容,之后,詳細介紹了一下USB 接口器件 PDIUSBD12 的使用方法,最后,本章通過一個實例描述了使用 FPGA 接口 PDIUSBD12開發(fā) USB 接口的流程。本篇的學(xué)習(xí)要點可以總結(jié)如下:

首先,對 USB 協(xié)議的了解是最為重要的。雖然 PDIUSBD12 芯片能夠完成很多協(xié)議解析工作,但對 USB 協(xié)議的了解程度還是對整個開發(fā)過程起到了決定性的作用。USB 協(xié)議非常的復(fù)雜,熟悉 USB 協(xié)議的方法應(yīng)該是由大到小,即首先了解 USB 通信的基本原理,比如控制傳輸、批量傳輸?shù)脑砗吞攸c;然后再了解各個傳輸?shù)慕M成,即每個傳輸首先發(fā)送的是什么數(shù)據(jù)包,然后接受的是什么數(shù)據(jù)包;最后再去分析每個數(shù)據(jù)包的格式、意義等。

其次,需要對 PDIUSBD12 芯片的比較了解,比如它的各個信號引腳的功能、特性,更為重要的是其通信時序和控制命令。

最后,對各種語言以及各種開發(fā)工具熟悉也是非常重要的。在本次設(shè)計中,需要用到的開發(fā)語言很多,包括 VHDL、C++(Visual C++);此外,本次設(shè)計還用到了多種開發(fā)工具,包括EDA 開發(fā)、驅(qū)動開發(fā)、軟件開發(fā)等,只有熟悉這些工具才能夠快速的進行開發(fā)。USB 體系非常龐大,所以編寫本章也是為了夠幫助讀者跨入 USB 開發(fā)的大門,希望讀者通過本篇的學(xué)習(xí),能夠設(shè)計出更為完善、高效的 USB 接口。

本篇到此結(jié)束,各位大俠,有緣再見!

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險等級 參考價格 更多信息
XC2C384-10FTG256I 1 AMD Xilinx Flash PLD, 10ns, 384-Cell, CMOS, PBGA256, 17 X 17 MM, 1 MM PITCH, LEAD FREE, FTBGA-256
$596.21 查看
EPM570T100C5N 1 Intel Corporation Flash PLD, 8.7ns, 440-Cell, CMOS, PQFP100, 16 X 16 MM, 0.50 MM PITCH, LEAD FREE, TQFP-100

ECAD模型

下載ECAD模型
$23.38 查看
ICE40LP8K-CM225 1 Lattice Semiconductor Corporation Field Programmable Gate Array, 960 CLBs, 133MHz, 7680-Cell, CMOS, PBGA225, UCBGA-225
$10.96 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

任何技術(shù)的學(xué)習(xí)就好比一個江湖,對于每一位俠客都需要不斷的歷練,從初入江湖的小白到歸隱山林的隱世高人,需要不斷的自我感悟自己修煉,讓我們一起仗劍闖FPGA乃至更大的江湖。