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

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

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

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

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

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

2019年9月4日,USB-IF終于正式公布USB 4規(guī)范。它引入了Intel此前捐獻(xiàn)給USB推廣組織的Thunderbolt雷電協(xié)議規(guī)范,雙鏈路運(yùn)行(Two-lane),傳輸帶寬因此提升,與雷電3持平,都是40Gbps。需要注意的是,你想要體驗(yàn)最高傳輸速度,就必須使用經(jīng)過(guò)認(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ā)布時(shí)間至今暫未公布。值得注意的是,此次發(fā)布的USB4是規(guī)范,而并非USB4.0。在此之前,USB Implementers Forum(USB-IF)計(jì)劃取消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ū)動(dòng),而你的系統(tǒng)有USB通用驅(qū)動(dòng)的話(比如XP就內(nèi)建了USB通用驅(qū)動(dòng))就能用。而有些USB設(shè)備是需要特殊驅(qū)動(dòng)的,比如某些手機(jī),連接到電腦的USB口,是需要安裝驅(qū)動(dòng)才能使用的。下面我們一起動(dòng)手做一做USB接口控制器設(shè)計(jì),了解一下如何設(shè)計(jì)。

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

六、FPGA 固件開(kāi)發(fā)

6.1 固件模塊劃分

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

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

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

(1)分頻器模塊

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

(2)沿控制器模塊

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

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

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

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

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

(5)請(qǐng)求處理模塊

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

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

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

ISE 14.7 安裝教程及詳細(xì)說(shuō)明

6.2 自定義包編寫

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

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

- 定義設(shè)備的工作狀態(tài)機(jī)type TRANSEIVER_STATEis ( TS_DISCONNECTED, -- 未連接TS_CONNECTING, -- 正在連接TS_IDLE, -- 閑置TS_END_REQUESTHANDLER, -- 請(qǐng)求處理完成TS_READ_IR, -- 讀取中斷寄存器TS_READ_LTS, -- 讀取最后處理狀態(tài)TS_BUSRESET, -- 總線復(fù)位TS_SUSPENDCHANGE, -- 掛起改變TS_EP0_RECEIVE, -- 端點(diǎn) 0 接收完成TS_EP0_TRANSMIT, -- 端點(diǎn) 0 發(fā)送完成TS_EP2_RECEIVE, -- 端點(diǎn) 2 接收完成TS_EP2_TRANSMIT, -- 端點(diǎn) 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, -- 從端點(diǎn)讀取數(shù)據(jù)TS_WRITE_ENDPOINT, -- 向端點(diǎn)寫入數(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); -- 錯(cuò)誤

請(qǐng)求類型以及請(qǐng)求的代碼定義如下:

-- 描述符類型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";

另一個(gè)包是 PDIUSBD12 包,它定義的則是和 PDIUSBD12 相關(guān)的內(nèi)容,比如 PDIUSBD12 的命令代碼值、中斷代碼值等內(nèi)容。對(duì) 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?分頻器模塊的實(shí)現(xiàn)

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

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

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

圖 35 分頻器模塊的示意圖

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

-- 申明所使用的包library IEEE;use IEEE.STD_LOGIC_1164.all;use WORK.USB_PACKAGE.all;-- 申明實(shí)體entity FrequencyDivider is      generic(              div_factor : INTEGER8 := 0 -- 分頻系數(shù)屬性        );      port(              reset_n : in STD_LOGIC; -- 復(fù)位端口              clk_origin : in STD_LOGIC; -- 輸入時(shí)鐘端口              clk : out STD_LOGIC -- 輸出時(shí)鐘端口        );end FrequencyDivider;architecture FrequencyDivider of FrequencyDivider is-- 內(nèi)部信號(hào),在內(nèi)部隨時(shí)改變同時(shí)又輸出給輸出時(shí)鐘端口signal clk_tmp: STD_LOGIC;begin    -- 信號(hào)連接    clk <= clk_tmp;    -- 主過(guò)程    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        -- 計(jì)數(shù)到達(dá)分頻系數(shù)時(shí)翻轉(zhuǎn)輸出,并且重置計(jì)數(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 沿控制模塊的實(shí)現(xiàn)

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

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

圖 36 沿控制模塊的示意圖和時(shí)序圖

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

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

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

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

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

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

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

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

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

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

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

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

6.6 請(qǐng)求處理模塊的實(shí)現(xiàn)

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

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

圖 39 請(qǐng)求處理模塊的示意圖

請(qǐng)求處理模塊的實(shí)現(xiàn)代碼如下:

-- 申明要使用的庫(kù)library IEEE;use IEEE.STD_LOGIC_1164.all;use WORK.USB_PACKAGE.all;use WORK.PDIUSBD12_PACKAGE.all;-- 申明實(shí)體entity RequestHandler is    port(            reset_n : in STD_LOGIC; -- 復(fù)位端口            clk : in STD_LOGIC; -- 輸入時(shí)鐘            recv_n : in STD_LOGIC; -- 接收事件輸入端口            req_type : in STD_LOGIC_VECTOR(7 downto 0); -- 請(qǐng)求類型輸入端口            cmd : out STD_LOGIC_VECTOR(7 downto 0); -- 命令輸出端口            exec_n : out STD_LOGIC -- 命令中斷端口        );end RequestHandler;
architecture RequestHandler of RequestHandler is-- 狀態(tài)機(jī),已在 USB 包中有定義signal rh_state: REQUEST_HANDLER_STATE := RH_IDLE;-- 寄存器,用于標(biāo)示是否已分配地址signal address_set: STD_LOGIC := '0';begin    -- 主進(jìn)程    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 為低時(shí)候表示需要進(jìn)行請(qǐng)求處理                if recv_n = '0' then                    -- req_type 就是請(qǐng)求的代碼                    case req_type is                    -- 獲取描述符請(qǐng)求                    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)請(qǐng)求                    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';                    -- 啟用特性請(qǐng)求                    when REQUEST_SET_FEATURE =>                        cmd <= RH_SET_FEATURE;                        exec_n <= '0';                    -- 清除特性請(qǐng)求                    when REQUEST_CLEAR_FEATURE =>                        cmd <= RH_CLEAR_FEATURE;                        exec_n <= '0';                    -- 設(shè)置配置請(qǐng)求和設(shè)置描述符請(qǐng)求                    when                        REQUEST_SET_CONFIGURATION | REQUEST_SET_DESCRIPTOR =>                        cmd <= RH_SET_CONFIGURATION;                        exec_n <= '0';                    -- 獲取配置請(qǐng)求                    when REQUEST_GET_CONFIGURATION =>                        cmd <= RH_SEND_CONFIGURATION;                        exec_n <= '0';                    -- 設(shè)置接口請(qǐng)求                    when REQUEST_SET_INTERFACE =>                        cmd <= RH_SET_INTERFACE;                        exec_n <= '0';                    -- 獲取密碼請(qǐng)求                    when REQUEST_GET_PASSWORD =>                        cmd <= RH_SEND_PASSWORD;                        exec_n <= '0';                    -- 獲取密碼高位請(qǐng)求                    when REQUEST_SET_PASSWORD_HIGH =>                        cmd <= RH_SET_PASSWORD_HIGH;                        exec_n <= '0';                    -- 獲取密碼低位請(qǐng)求                    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ā)器模塊的實(shí)現(xiàn)

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

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

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

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

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

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

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

1)初始化器件

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

由于對(duì)寄存器配置的命令以及時(shí)序都是確定的,所以可以在自定義包中將配置數(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ù),第一個(gè)常數(shù)數(shù)組是配置命令和數(shù)據(jù),第二個(gè)數(shù)組表示命令、數(shù)據(jù)的順序,最后一個(gè)參數(shù)是配置參數(shù)的總長(zhǎng)度。定義的過(guò)程是首先向 PDIUSBD12發(fā)送命令 D12_COMMAND_SET_DMA(設(shè)置 DMA 命令),然后發(fā)送此命令的數(shù)據(jù) D12_DMA(D12_DMA定義為 0xC0,其意義請(qǐng)參考圖 23);之后發(fā)送設(shè)置模式命令和此命令的兩個(gè)數(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";

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

-- TS_CONNECT 狀態(tài),對(duì) PDIUSBD12 進(jìn)行配置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;

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

圖 41 器件配置仿真時(shí)序圖

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

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

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

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

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

當(dāng)監(jiān)測(cè)到 PDIUSBD12 的中斷時(shí),設(shè)備收發(fā)器首先讀取中斷寄存器,然后就會(huì)進(jìn)入數(shù)據(jù)收發(fā)狀態(tài),如果監(jiān)測(cè)到的是請(qǐng)求處理模塊的命令中斷,則進(jìn)入的是請(qǐng)求回復(fù)狀態(tài)。請(qǐng)求回復(fù)狀態(tài)包括了發(fā)送描述符、發(fā)送配置信息等,這些內(nèi)容將在下面一個(gè)小節(jié)介紹。數(shù)據(jù)收發(fā)狀態(tài)包括讀取中斷寄存器、控制端點(diǎn)數(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è)置讀信號(hào)為低,讀取第一個(gè)返回參數(shù),即中斷寄存器第一個(gè)字節(jié)    elsif handle_step = 1 then        a0 <= D12_DATA;        rd_n_var := '0';    -- 第三步,保存中斷寄存器第一個(gè)字節(jié)并讀取第二個(gè)返回參數(shù)(中斷寄存器第二個(gè)字節(jié))    elsif handle_step = 2 then        -- 保存中斷寄存器第一個(gè)字節(jié)        ir_0 := data_in;        -- 讀取第二個(gè)參數(shù)        a0 <= D12_DATA;        rd_n_var := '0';        -- 最后,保存第二個(gè)參數(shù),進(jìn)入下一處理狀態(tài)    else        -- 保存中斷寄存器第二個(gè)字節(jié)        ir_1 := data_in(0);        -- 根據(jù)中斷寄存器選擇進(jìn)入下一處理狀態(tài)        ts_state <= GetInterruptHandler(ir_0, ir_1);        ih_state <= IH_START;    end if;    handle_step := handle_step+1;

下面介紹一下控制輸出的處理流程。控制輸出的輸出是相對(duì)主機(jī)來(lái)說(shuō)的,所以相對(duì)于設(shè)備來(lái)說(shuō),就是接收主機(jī)的數(shù)據(jù)。當(dāng)一次控制輸出發(fā)生時(shí),設(shè)備首先會(huì)判斷接收到的是不是建立包(Setup Packet),如果是則開(kāi)始接收下面的數(shù)據(jù),否則,接收前次傳輸所剩余的數(shù)據(jù)??刂苽鬏?shù)奶幚砹鞒虉D如圖 43 所示。

圖 43 控制輸出流程圖

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

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

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

-- 讀取端點(diǎn)數(shù)據(jù)狀態(tài)when TS_READ_ENDPOINT =>    -- handle_step 表示操作步驟    case handle_step is    -- 首先,發(fā)送選擇端點(diǎn)命令,選擇端點(diǎn)    when 0 =>        a0 <= D12_COMMAND;        data_out <= active_ep;        wr_n_var := '0';        handle_step := handle_step+1;    -- 發(fā)送讀取端點(diǎn)數(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ù)的前兩個(gè)字節(jié),第一個(gè)字節(jié)為保留數(shù)據(jù),第二個(gè)字節(jié)表示數(shù)據(jù)長(zhǎng)度    when 2 | 3 =>        a0 <= D12_DATA;        rd_n_var := '0';        handle_step := handle_step+1;    -- 保存第二個(gè)字節(jié)(數(shù)據(jù)長(zhǎng)度),準(zhǔn)備接收有效數(shù)據(jù)    when 4 =>        -- 保留第二個(gè)字節(jié)        read_in := conv_integer(data_in);        -- 判斷數(shù)據(jù)長(zhǎng)度是否為零        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 =>        -- 保存前一個(gè)周期要求獲取的數(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ù)要求獲取下一個(gè)數(shù)據(jù)            a0 <= D12_DATA;            rd_n_var := '0';        end if;    -- 最后,發(fā)送清除端點(diǎn)緩沖的命令    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;

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

圖 44 控制輸入流程圖

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

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

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

-- 寫端點(diǎn)緩存數(shù)據(jù)的狀態(tài)when TS_WRITE_ENDPOINT =>    case handle_step is    -- 首先,發(fā)送選擇端點(diǎn)的命令,選擇端點(diǎn) 0    when 0 =>        a0 <= D12_COMMAND;        data_out <= active_ep;        wr_n_var := '0';        handle_step := handle_step+1;    -- 讀取選擇端點(diǎn)命令的一個(gè)返回參數(shù)(可選)    when 1 =>        a0 <= D12_DATA;        rd_n_var := '0';        handle_step := handle_step+1;    -- 發(fā)送讀寫端點(diǎn)的命令    when 2 =>        a0 <= D12_COMMAND;        data_out <= D12_COMMAND_RW_BUFFER;        wr_n_var := '0';        handle_step := handle_step+1;    -- 寫入端點(diǎn)緩存第一個(gè)字節(jié),為保留字節(jié),值為 0    when 3 =>        a0 <= D12_DATA;        data_out <= X"00";        wr_n_var := '0';        handle_step := handle_step+1;    -- 寫入端點(diǎn)緩存第二個(gè)字節(jié),為有效數(shù)據(jù)的長(zhǎng)度    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ù)訪問(wèn)狀態(tài)的實(shí)現(xiàn)方法,在測(cè)試平臺(tái)中可以對(duì)以上代碼進(jìn)行測(cè)試,測(cè)試時(shí)的輸入數(shù)據(jù)應(yīng)該由測(cè)試平臺(tái)產(chǎn)生(測(cè)試平臺(tái)的編寫將在下面的章節(jié)進(jìn)行專門介紹)。如第一次發(fā)送設(shè)備描述符的仿真波形。此仿真過(guò)程可以分為兩個(gè)部分,第一部分(如圖 45 所示)是接收建立包(Setup Packet)以及讀取 PDIUSBD12 請(qǐng)求數(shù)據(jù)的過(guò)程;第二部分(如圖 46 所示)是將設(shè)備描述符數(shù)據(jù)寫入 PDIUSBD12 端點(diǎn)緩存并且使緩沖區(qū)有效。

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

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

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

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

獲取描述符請(qǐng)求是最為重要的請(qǐng)求,因?yàn)檫@在設(shè)備枚舉過(guò)程中是必需的,它是主機(jī)了解設(shè)備的第一個(gè)步。獲取描述符請(qǐng)求的處理流程如圖 47 所示。

圖 47 獲取描述符處理流程

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

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

6.8 測(cè)試平臺(tái)的編寫

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

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

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

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

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

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

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

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

-- 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ū)動(dòng)和軟件開(kāi)發(fā)

7.1 USB 驅(qū)動(dòng)編寫

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

1)USB 驅(qū)動(dòng)模型

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

圖 48 USB 接口軟件模型

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

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

2)使用 Driver Studio 開(kāi)發(fā) USB 驅(qū)動(dòng)

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

圖 49 Driver Wizard 第 1 步?

圖 50 Driver Wizard 第 2 步

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

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

圖 51 Driver Wizard 第 3 步?

圖 52 Driver Wizard 第 4 步

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

圖?53?Driver Wizard 第 5 步

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

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

圖 54 Driver Wizard 第 6 步?

圖 55 Driver Wizard 第 7 步

圖 56 Driver Wizard 第 8 步?

圖 57 Driver Wizard 第 9 步

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

圖 58 Driver Wizard 第 10 步

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

圖 59 Driver Wizard 第 11 步?

圖 60 定義 IO 控制接口

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

圖 61 Driver Wizard 第十二步

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

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

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

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

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

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

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

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

? UCHAR RequestTypeReservedBit 請(qǐng)求類型的位掩碼,一般為零。

? UCHAR Request 請(qǐng)求代碼。

? USHORT Value 即 USB 請(qǐng)求中的 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)建廠商請(qǐng)求,請(qǐng)求的代碼是 REQUEST_GET_PASSWORD,數(shù)據(jù)長(zhǎng)度為 8    PURB pUrb = m_Lower.BuildVendorRequest(        buffer, -- 數(shù)據(jù)緩沖        PASSWORD_LENGTH, -- 數(shù)據(jù)長(zhǎng)度        0, -- 保留        REQUEST_GET_PASSWORD, -- 請(qǐng)求代碼        0, -- 即 USB 請(qǐng)求的 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ǐng)求的編寫之后,就可以進(jìn)行驅(qū)動(dòng)程序編譯了。驅(qū)動(dòng)編譯默認(rèn)有兩種版本,即Win32 Checked 和 Win32 Free,其中前者表示調(diào)試版本,而后者表示發(fā)布版本,發(fā)布版本相對(duì)調(diào)試版本去掉了大部分調(diào)試信息,比較簡(jiǎn)化。

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

圖 63 Compuware DriverStudio 工具條

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

7.2 USB 軟件編寫

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

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

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

1) 打開(kāi)設(shè)備

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

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è)置密碼和獲取密碼,它們分別對(duì)應(yīng)驅(qū)動(dòng)中已經(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è)備

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

圖 64 cube 程序運(yùn)行界面

總結(jié)

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

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

其次,需要對(duì) PDIUSBD12 芯片的比較了解,比如它的各個(gè)信號(hào)引腳的功能、特性,更為重要的是其通信時(shí)序和控制命令。

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

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

推薦器件

更多器件
器件型號(hào) 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊(cè) ECAD模型 風(fēng)險(xiǎn)等級(jí) 參考價(jià)格 更多信息
10M08SCE144C8G 1 Intel Corporation Field Programmable Gate Array, 8000-Cell, CMOS, PQFP144, 22 X 22 MM, 0.50 MM PITCH, ROHS COMPLIANT, PLASTIC, EQFP-144

ECAD模型

下載ECAD模型
$17.46 查看
M1A3P1000-PQG208I 1 Microchip Technology Inc Field Programmable Gate Array, 24576 CLBs, 1000000 Gates, 350MHz, CMOS, PQFP208
$393.19 查看
XC7A200T-3FFG1156E 1 AMD Xilinx Field Programmable Gate Array, 16825 CLBs, 1412MHz, 215360-Cell, CMOS, PBGA1156, FBGA-1156
$527.73 查看

相關(guān)推薦

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

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