大俠好,歡迎來(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)、鍵盤(pán)、閃存、U盤(pán)等等)都是采用了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ā),包括固件模塊劃分、自定義包編寫(xiě)、分頻器模塊的實(shí)現(xiàn)、沿控制模塊的實(shí)現(xiàn)、輸入/輸出切換模塊的實(shí)現(xiàn)、請(qǐng)求處理模塊的實(shí)現(xiàn)、設(shè)備收發(fā)器模塊的實(shí)現(xiàn)、測(cè)試平臺(tái)的編寫(xiě);USB 驅(qū)動(dòng)和軟件開(kāi)發(fā),包括USB 驅(qū)動(dòng)編寫(xiě)、USB 軟件編寫(xiě)以及總結(jié)等相關(guān)內(nèi)容。
六、FPGA 固件開(kāi)發(fā)
6.1 固件模塊劃分
在本例中,固件開(kāi)發(fā)指的就是 FPGA 開(kāi)發(fā),也就是使用硬件描述語(yǔ)言(VHDL 或者 VerilogHDL)編寫(xiě) 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 在讀寫(xiě)時(shí)序上有時(shí)間限制,例如每次讀寫(xiě)操作之間的間隔不能小于 500ns,而 FPGA 的系統(tǒng)時(shí)鐘一般頻率都比較高,所以不能直接使用系統(tǒng)時(shí)鐘控制 PDIUSBD12,必須進(jìn)行分頻。分頻器模塊的功能就是按照要求由系統(tǒng)時(shí)鐘生成所需頻率的時(shí)鐘信號(hào)。
(2)沿控制器模塊
PDIUSBD12 的讀寫(xiě)操作都各自有一個(gè)讀寫(xiě)控制信號(hào) WR_N 和 RD_N,每次讀寫(xiě)操作都在對(duì)應(yīng)的控制信號(hào)的下降沿觸發(fā),沿控制模塊的功能就是可控地產(chǎn)生一個(gè)下降沿信號(hào),用于控制讀寫(xiě)操作。
(3)輸入/輸出切換模塊
輸入/輸出切換模塊在整個(gè)系統(tǒng)中非常重要,因?yàn)?FPGA 芯片和 PDIUSBD12 芯片之間的數(shù)據(jù)總線是雙向的總線,所以當(dāng)讀寫(xiě)操作之一在進(jìn)行的時(shí)候另一個(gè)操作的信號(hào)源必須關(guān)閉,否則就會(huì)造成雙驅(qū)動(dòng),這不但不能得到正確的數(shù)據(jù)還會(huì)損害芯片。輸入/輸出切換模塊的功能就是根據(jù)當(dāng)前的讀寫(xiě)狀況控制信號(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è)模塊分別編寫(xiě)實(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 自定義包編寫(xiě)
在實(shí)際實(shí)現(xiàn)各個(gè)模塊功能之前,首先需要編寫(xiě)兩個(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_STATE
is ( 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 寫(xiě)數(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)寫(xiě)入數(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,而所需的讀寫(xiě)周期間隔要求大于 500ns,即讀寫(xiě)的時(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 is
begin
-- 輸出信號(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)行讀寫(xiě)操作的時(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ù)目前的讀寫(xiě)狀態(tài)關(guān)閉某一個(gè)驅(qū)動(dòng)源,也就是說(shuō)將其另一個(gè)驅(qū)動(dòng)源輸出設(shè)置為高阻態(tài)。由于讀寫(xiě)操作是由各自的控制信號(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)分別用于讀、寫(xiě)控制。
圖 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ù)讀寫(xiě),包括讀取中斷寄存器、讀取前次傳輸狀態(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ò)編寫(xiě)測(cè)試平臺(tái)仿真可以看到運(yùn)行的結(jié)果(測(cè)試平臺(tái)的編寫(xiě)將會(huì)在下面專門(mén)介紹),如圖 41 所示。
圖 41 器件配置仿真時(shí)序圖
通過(guò)上面的時(shí)序圖可以看出,8 位總線上傳輸?shù)氖?D12_CONNECT_DATA 定義的配置命令和數(shù)據(jù),而 a0 位表明了總線上的是命令還是數(shù)據(jù),通過(guò)一個(gè)下降沿的寫(xiě)信號(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í)控制傳輸是控制讀還是寫(xiě)。這可以通過(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ò)程。控制輸入就是設(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)代碼如下,由于篇幅原因,這里只提供部分參考代碼。
-- 寫(xiě)端點(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ā)送讀寫(xiě)端點(diǎn)的命令
when 2 =>
a0 <= D12_COMMAND;
data_out <= D12_COMMAND_RW_BUFFER;
wr_n_var := '0';
handle_step := handle_step+1;
-- 寫(xiě)入端點(diǎn)緩存第一個(gè)字節(jié),為保留字節(jié),值為 0
when 3 =>
a0 <= D12_DATA;
data_out <= X"00";
wr_n_var := '0';
handle_step := handle_step+1;
-- 寫(xiě)入端點(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)被寫(xiě)入
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
-- 寫(xiě)入數(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)的編寫(xiě)將在下面的章節(jié)進(jìn)行專門(mén)介紹)。如第一次發(fā)送設(shè)備描述符的仿真波形。此仿真過(guò)程可以分為兩個(gè)部分,第一部分(如圖 45 所示)是接收建立包(Setup Packet)以及讀取 PDIUSBD12 請(qǐng)求數(shù)據(jù)的過(guò)程;第二部分(如圖 46 所示)是將設(shè)備描述符數(shù)據(jù)寫(xiě)入 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)的編寫(xiě)
上面介紹的是整個(gè) FPGA 固件系統(tǒng)的實(shí)現(xiàn)方法,為了驗(yàn)證設(shè)計(jì)的正確性,還需要編寫(xiě)一個(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: process
begin
-- 翻轉(zhuǎn)
clk <= not clk;
-- 等待固定時(shí)間
wait for 50 ns;
end process;
其次,由于 FPGA 和 PDIUSBD12 之間有數(shù)據(jù)讀寫(xiě),所以要模擬所有 FPGA 向 PDIUSBD12 讀取的數(shù)據(jù)。模擬數(shù)據(jù)讀寫(xiě)的方法是將所有數(shù)據(jù)按照順序?qū)懭胍粋€(gè)大的測(cè)試數(shù)據(jù)數(shù)組中,使用一個(gè)變量作為該數(shù)組索引,再編寫(xiě)一個(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;
最后,還需要編寫(xiě)一個(gè)主流程,在主流程中需要進(jìn)行系統(tǒng)復(fù)位和產(chǎn)生中斷信號(hào),代碼如下:
-- main process
main: process
variable 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)編寫(xiě)
以上介紹的是 FPGA 固件的開(kāi)發(fā)過(guò)程,由于本例中設(shè)計(jì)的不是一個(gè)類設(shè)備,所以要使設(shè)備正常工作,還需要編寫(xiě)專門(mén)的驅(qū)動(dòng)程序和軟件。由于驅(qū)動(dòng)和軟件不是本篇的重點(diǎn),故下面只簡(jiǎn)要介紹其編寫(xiě)方法。
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ō),需要編寫(xiě)的就是客戶端驅(qū)動(dòng)程序。編寫(xiě)客戶端驅(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)求的編寫(xiě)之后,就可以進(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 軟件編寫(xiě)
最后,再簡(jiǎn)要介紹一下 USB 軟件的編寫(xiě),即軟件對(duì) USB 設(shè)備訪問(wèn)的實(shí)現(xiàn)方法。
USB 軟件通過(guò) USB 驅(qū)動(dòng)實(shí)現(xiàn)對(duì) USB 設(shè)備的訪問(wèn),編寫(xiě) 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)程序的接口,在編寫(xiě)軟件的時(shí)候需要將其引用進(jìn)去。
USB 軟件的編寫(xiě)一般有下面幾個(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 體系非常龐大,所以編寫(xiě)本章也是為了夠幫助讀者跨入 USB 開(kāi)發(fā)的大門(mén),希望讀者通過(guò)本篇的學(xué)習(xí),能夠設(shè)計(jì)出更為完善、高效的 USB 接口。
本篇到此結(jié)束,各位大俠,有緣再見(jiàn)!