今天給大俠帶來基于 FPGA 的 USB 接口控制器設(shè)計(jì)(VHDL),由于篇幅較長,分三篇。今天帶來第三篇,下篇,F(xiàn)PGA 固件開發(fā)、USB驅(qū)動和軟件開發(fā)。話不多說,上貨。
?
導(dǎo)讀
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)過認(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ū)動,而你的系統(tǒng)有USB通用驅(qū)動的話(比如XP就內(nèi)建了USB通用驅(qū)動)就能用。而有些USB設(shè)備是需要特殊驅(qū)動的,比如某些手機(jī),連接到電腦的USB口,是需要安裝驅(qū)動才能使用的。下面我們一起動手做一做USB接口控制器設(shè)計(jì),了解一下如何設(shè)計(jì)。
第三篇內(nèi)容摘要:本篇會介紹FPGA 固件開發(fā),包括固件模塊劃分、自定義包編寫、分頻器模塊的實(shí)現(xiàn)、沿控制模塊的實(shí)現(xiàn)、輸入/輸出切換模塊的實(shí)現(xiàn)、請求處理模塊的實(shí)現(xiàn)、設(shè)備收發(fā)器模塊的實(shí)現(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 進(jìn)行通信,從 PDIUSBD12 中獲取數(shù)據(jù)并且根據(jù)主機(jī)的要求發(fā)送數(shù)據(jù)。PDIUSBD12 和 FPGA 之間的通信就是 8 位數(shù)據(jù)總線加上若干控制信號(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í)鐘信號。
(2)沿控制器模塊
PDIUSBD12 的讀寫操作都各自有一個(gè)讀寫控制信號 WR_N 和 RD_N,每次讀寫操作都在對應(yīng)的控制信號的下降沿觸發(fā),沿控制模塊的功能就是可控地產(chǎn)生一個(gè)下降沿信號,用于控制讀寫操作。
(3)輸入/輸出切換模塊
輸入/輸出切換模塊在整個(gè)系統(tǒng)中非常重要,因?yàn)?FPGA 芯片和 PDIUSBD12 芯片之間的數(shù)據(jù)總線是雙向的總線,所以當(dāng)讀寫操作之一在進(jìn)行的時(shí)候另一個(gè)操作的信號源必須關(guān)閉,否則就會造成雙驅(qū)動,這不但不能得到正確的數(shù)據(jù)還會損害芯片。輸入/輸出切換模塊的功能就是根據(jù)當(dāng)前的讀寫狀況控制信號源,保證在一個(gè)時(shí)刻只有一個(gè)信號源在驅(qū)動總線。
(4)設(shè)備收發(fā)器模塊
這個(gè)模塊是整個(gè)固件的核心模塊,它完成的工作包括配置 PDIUSBD12 芯片、處理 PDIUSBD12產(chǎn)生的中斷、完成從緩存讀取數(shù)據(jù),并且根據(jù)需要將數(shù)據(jù)通過 PDIUSBD12 發(fā)送。設(shè)備收發(fā)器模塊完成對每個(gè)主機(jī)請求的解析工作,此外,還要將解析完成的請求數(shù)據(jù)傳遞給請求處理模塊。
(5)請求處理模塊
請求處理模塊的作用是接收設(shè)備收發(fā)器模塊解析完成的主機(jī)請求,并且決定如何處理此請求。
模塊劃分完畢之后就可以使用 ISE 創(chuàng)建工程了,然后就各個(gè)模塊分別編寫實(shí)現(xiàn)代碼和測試平臺,最后將所有模塊整合起來作為一個(gè)實(shí)體并且對其進(jìn)行仿真、測試,這樣就是一次完整的FPGA 開發(fā)過程。
ISE 的一些基本使用方法在前面的文章已有詳細(xì)介紹,這里放超鏈接,在此不詳細(xì)說明。下面詳細(xì)介紹一下各個(gè)模塊的實(shí)現(xiàn)方法。
ISE 14.7 安裝教程及詳細(xì)說明
?
6.2 自定義包編寫
在實(shí)際實(shí)現(xiàn)各個(gè)模塊功能之前,首先需要編寫兩個(gè)自定義包,分別是 USB 包和 PDIUSBD12包。
USB 包定義了 USB 協(xié)議以及 USB 設(shè)備相關(guān)的數(shù)據(jù)類型、常量等內(nèi)容,比如自定義數(shù)據(jù)類型、設(shè)備類型代碼值、請求代碼值、設(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, -- 請求處理完成 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ò)誤
請求類型以及請求的代碼定義如下:
-- 描述符類型 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)容。對 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 倍分頻。所以,我們設(shè)定一個(gè)計(jì)數(shù)器,工作在系統(tǒng)時(shí)鐘下,每個(gè)系統(tǒng)時(shí)鐘周期計(jì)數(shù)減一,減到零后恢復(fù)到 13,這樣,每經(jīng)過 13×2=26個(gè)系統(tǒng)時(shí)鐘周期,計(jì)數(shù)器的輸出會是一個(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)部信號,在內(nèi)部隨時(shí)改變同時(shí)又輸出給輸出時(shí)鐘端口 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 -- 計(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è)使能信號 CE_N 控制輸出。輸入為分頻后的時(shí)鐘,當(dāng) CE_N 輸入為高的時(shí)候,輸出保持高電平,而當(dāng) CE_N 輸入變?yōu)榈偷臅r(shí)候,將時(shí)鐘接到輸出上,這樣就能得到連續(xù)的下降沿信號(和時(shí)鐘的下降沿同步)。只要對 CE_N 進(jìn)行適當(dāng)?shù)目刂疲湍艿玫叫枰南陆笛亍?/p>
沿控制模塊的示意圖和時(shí)序圖如圖 36 所示。輸入時(shí)鐘連接到分頻器模塊的輸出時(shí)鐘上,使能信號控制沿輸出信號,只要在某一個(gè)時(shí)鐘周期內(nèi)將使能信號保持低電平,就可以得到一個(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 -- 沿信號輸出端口 ); end EdgeController; architecture EdgeController of EdgeController is begin -- 輸出信號賦值 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ū)動。雙驅(qū)動的意思就是在總線兩邊同時(shí)往總線上加輸出信號,這樣總線數(shù)據(jù)就處于一種不定態(tài)(用 X 表示),并且還容易損壞器件。例如,沒有處理好雙驅(qū)動的仿真波形就會如圖 37 所示,這種情況下無法得到正確的數(shù)據(jù)的。
圖 37 仿真不定態(tài)時(shí)序圖
信號的 4 種基本狀態(tài)是高電平(1)、低電平(0)、不定態(tài)(X)和高阻態(tài)(Z),當(dāng)一個(gè)總線上同時(shí)加有兩個(gè)信號時(shí),組合起來的結(jié)果如表 35 所示。
表 35 信號狀態(tài)表
可見,當(dāng)一個(gè)總線上同時(shí)有兩個(gè)驅(qū)動的時(shí)候,很有可能產(chǎn)生不定態(tài) X,但是如果其中一個(gè)信號為高阻態(tài) Z 的話,則是一個(gè)確定的狀態(tài)(即另一個(gè)信號的狀態(tài))。所以,避免雙驅(qū)動的基本思想就是根據(jù)目前的讀寫狀態(tài)關(guān)閉某一個(gè)驅(qū)動源,也就是說將其另一個(gè)驅(qū)動源輸出設(shè)置為高阻態(tài)。由于讀寫操作是由各自的控制信號(WR_N、RD_N)控制的,所以可以將這兩個(gè)信號作為互斥關(guān)系的信號來控制總線數(shù)據(jù)的信號源。例如,當(dāng) RD_N 為低時(shí),要從 PDIUSBD12 讀取數(shù)據(jù),就應(yīng)該關(guān)閉 FPGA 對總線的輸出,即將 FPGA 的總線輸出信號變?yōu)楦咦钁B(tài) Z。反過來也一樣,當(dāng) WR_N 為低時(shí),要向 PDIUSBD12 發(fā)送數(shù)據(jù),此時(shí) PDIUSBD12 也會自動關(guān)閉它在總線上的輸出。以上思想可用公式表示為:
輸入/輸出切換模塊的示意圖如圖 6-38 所示。其中左邊的總線表示連接到 PDIUSBD12 的總線,右邊的輸入、輸出總線是在 FPGA 內(nèi)部的總線信號,表示在 FPGA 內(nèi)部將總線的輸入和輸出區(qū)分開來;RD_N 和 WR_N 信號分別用于讀、寫控制。
圖 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; -- 總線輸入控制信號 sel_out_n : in STD_LOGIC -- 總線輸出控制信號 ); end IOSwitch; architecture IOSwitch of IOSwitch is -- 創(chuàng)建一個(gè)內(nèi)部信號,用作數(shù)據(jù)傳遞 signal data_tmp : STD_LOGIC_VECTOR(7 downto 0); begin -- 信號連接 data <= data_tmp; dout <= data; -- 主進(jìn)程 process(sel_in_n, sel_out_n, data, din) begin -- 當(dāng)輸出控制信號有效時(shí),將 data_tmp 賦值高阻 if sel_out_n = '0' then data_tmp <= "ZZZZZZZZ"; -- 當(dāng)輸入控制信號有效時(shí),將輸入的信號賦值給 data_tmp elsif sel_in_n = '0' then data_tmp <= din; else data_tmp <= "ZZZZZZZZ"; end if; end process; end IOSwitch;
6.6 請求處理模塊的實(shí)現(xiàn)
請求處理模塊的功能是根據(jù)主機(jī)的請求控制設(shè)備收發(fā)器模塊的處理狀態(tài)。在本例中,請求處理模塊實(shí)際的功能就是根據(jù)目前接收到的主機(jī)請求控制設(shè)備收發(fā)器模塊發(fā)送數(shù)據(jù),所以請求處理模塊的實(shí)現(xiàn)就是一個(gè)簡單的狀態(tài)機(jī)。
請求處理模塊的示意圖如圖 39 所示。時(shí)鐘信號是由分頻器的輸出時(shí)鐘提供;請求類型輸入是一個(gè) 8 位端口,它和接收事件輸入?yún)f(xié)同工作,當(dāng)設(shè)備收發(fā)器接收到一個(gè)請求時(shí),就會將請求代碼發(fā)送到請求類型輸入端口,在接收事件輸入端口輸出一個(gè)時(shí)鐘周期的低電平,表示一次新的請求處理;命令輸出端口和命令中斷端口則用于控制設(shè)備收發(fā)器模塊的操作狀態(tài)。
圖 39 請求處理模塊的示意圖
請求處理模塊的實(shí)現(xiàn)代碼如下:
-- 申明要使用的庫 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); -- 請求類型輸入端口 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)行請求處理 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ā)器模塊的實(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í)序要求對其進(jìn)行數(shù)據(jù)訪問和控制。
設(shè)備收發(fā)器模塊的示意圖如圖 40 所示。
圖 40 設(shè)備收發(fā)器模塊的示意圖
由于 USB 協(xié)議很復(fù)雜并且 PDIUSBD12 的控制也比較復(fù)雜,所以設(shè)備收發(fā)器狀態(tài)機(jī)的狀態(tài)量會較多。根據(jù)設(shè)備收發(fā)器的功能,可以將狀態(tài)機(jī)各個(gè)狀態(tài)的功能分為 3 類。
? 初始化器件:初始化器件就是對 PDIUSBD12 器件進(jìn)行配置的狀態(tài),需要配置的內(nèi)容包括設(shè)置地址/使能、設(shè)置 DMA 以及設(shè)置模式等。
? 數(shù)據(jù)訪問:數(shù)據(jù)訪問即實(shí)現(xiàn) PDIUSBD12 和 FPGA 之間的數(shù)據(jù)讀寫,包括讀取中斷寄存器、讀取前次傳輸狀態(tài)、由端點(diǎn)讀取數(shù)據(jù)、由端點(diǎn)發(fā)送數(shù)據(jù)等。
? 請求回復(fù):請求回復(fù)是指根據(jù)各種類型請求的數(shù)據(jù)格式提取所需要的數(shù)據(jù),并且在解析完成后通知請求處理模塊。下面詳細(xì)介紹一下以上 3 種狀態(tài)的實(shí)現(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í)序都是確定的,所以可以在自定義包中將配置數(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ù)的總長度。定義的過程是首先向 PDIUSBD12發(fā)送命令 D12_COMMAND_SET_DMA(設(shè)置 DMA 命令),然后發(fā)送此命令的數(shù)據(jù) D12_DMA(D12_DMA定義為 0xC0,其意義請參考圖 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ù)定義請參考 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),對 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)過 5 個(gè)時(shí)鐘周期,F(xiàn)PGA 完成向 PDIUSBD12 輸出的一系列命令以及數(shù)據(jù),通過編寫測試平臺仿真可以看到運(yùn)行的結(jié)果(測試平臺的編寫將會在下面專門介紹),如圖 41 所示。
圖 41 器件配置仿真時(shí)序圖
通過上面的時(shí)序圖可以看出,8 位總線上傳輸?shù)氖?D12_CONNECT_DATA 定義的配置命令和數(shù)據(jù),而 a0 位表明了總線上的是命令還是數(shù)據(jù),通過一個(gè)下降沿的寫信號可以將命令或者數(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í)會通過中斷來通知設(shè)備收發(fā)器,此外,請求處理模塊也會通過命令中斷信號控制設(shè)備收發(fā)器模塊。所以,中斷監(jiān)測就是在每個(gè)時(shí)鐘周期讀取一次 PDIUSBD12 的中斷信號和請求處理模塊的命令中斷信號,如果發(fā)現(xiàn)其中的一個(gè)中斷信號為低,則轉(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í),設(shè)備收發(fā)器首先讀取中斷寄存器,然后就會進(jìn)入數(shù)據(jù)收發(fā)狀態(tài),如果監(jiān)測到的是請求處理模塊的命令中斷,則進(jìn)入的是請求回復(fù)狀態(tài)。請求回復(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è)置讀信號為低,讀取第一個(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;
下面介紹一下控制輸出的處理流程。控制輸出的輸出是相對主機(jī)來說的,所以相對于設(shè)備來說,就是接收主機(jī)的數(shù)據(jù)。當(dāng)一次控制輸出發(fā)生時(shí),設(shè)備首先會判斷接收到的是不是建立包(Setup Packet),如果是則開始接收下面的數(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è)備請求的有效性,如果是一個(gè)有效的請求,設(shè)備收發(fā)器必須向控制輸出端點(diǎn)發(fā)送應(yīng)答建立命令以重新使能下一個(gè)建立階段。
接下來,設(shè)備收發(fā)器需要證實(shí)控制傳輸是控制讀還是寫。這可以通過讀建立包中bmRequestType 的第 8 位來判斷。如果控制傳輸是一個(gè)控制讀類型,那就是說器件需要在下一個(gè)數(shù)據(jù)階段向主機(jī)發(fā)回?cái)?shù)據(jù)包。設(shè)備收發(fā)器會設(shè)置一個(gè)標(biāo)志以指示設(shè)備現(xiàn)在正處于傳輸模式,即準(zhǔn)備在主機(jī)發(fā)送請求時(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ù)長度 when 2 | 3 => a0 <= D12_DATA; rd_n_var := '0'; handle_step := handle_step+1; -- 保存第二個(gè)字節(jié)(數(shù)據(jù)長度),準(zhǔn)備接收有效數(shù)據(jù) when 4 => -- 保留第二個(gè)字節(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 => -- 保存前一個(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;
下面介紹一下控制輸入的處理過程。控制輸入就是設(shè)備向主機(jī)發(fā)送數(shù)據(jù),最為典型的就是設(shè)備向主機(jī)發(fā)送描述符,圖 44 所示是控制輸入的流程圖。
圖 44 控制輸入流程圖
從控制輸入的流程圖可以看出,設(shè)備收發(fā)器首先需要通過讀 PDIUSBD12 的最后處理狀態(tài)寄存器清零中斷標(biāo)志位。接著設(shè)備收發(fā)器在確認(rèn) PDIUSBD12 處于傳輸模式后進(jìn)行數(shù)據(jù)包的發(fā)送。PDIUSBD12 的控制端點(diǎn)只有 16 字節(jié) FIFO,如果傳輸?shù)拈L度大于 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ā)送中斷來到時(shí),設(shè)備收發(fā)器將確定剩余的字節(jié)是否為零。如果已經(jīng)沒有數(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 很類似,只不過是將讀取數(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ù)的長度 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)的實(shí)現(xiàn)方法,在測試平臺中可以對以上代碼進(jìn)行測試,測試時(shí)的輸入數(shù)據(jù)應(yīng)該由測試平臺產(chǎn)生(測試平臺的編寫將在下面的章節(jié)進(jìn)行專門介紹)。如第一次發(fā)送設(shè)備描述符的仿真波形。此仿真過程可以分為兩個(gè)部分,第一部分(如圖 45 所示)是接收建立包(Setup Packet)以及讀取 PDIUSBD12 請求數(shù)據(jù)的過程;第二部分(如圖 46 所示)是將設(shè)備描述符數(shù)據(jù)寫入 PDIUSBD12 端點(diǎn)緩存并且使緩沖區(qū)有效。
圖 45 發(fā)送設(shè)備描述符仿真波形 1
圖 46 發(fā)送設(shè)備描述符仿真波形 2
3)請求回復(fù)狀態(tài)
請求回復(fù)狀態(tài)的功能就是對各個(gè)請求作出響應(yīng)。USB 的標(biāo)準(zhǔn)請求已經(jīng)在前面做了介紹,下面就以獲取描述符請求為例介紹一下請求響應(yīng)的實(shí)現(xiàn)方法,其他的標(biāo)準(zhǔn)請求以及廠商請求(獲取、設(shè)置密碼)相對來說比較簡單,實(shí)現(xiàn)的方法請讀者參考源代碼。
獲取描述符請求是最為重要的請求,因?yàn)檫@在設(shè)備枚舉過程中是必需的,它是主機(jī)了解設(shè)備的第一個(gè)步。獲取描述符請求的處理流程如圖 47 所示。
圖 47 獲取描述符處理流程
獲取設(shè)備描述符請求響應(yīng)的實(shí)現(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; -- 判斷描述符長度是否超過端點(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ù)長度 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ù)長度,LED 輸出,提示作用 if data_length > LENGTH_CONFIGURATION_DESCRIPTOR then data_length := LENGTH_CONFIGURATION_DESCRIPTOR; led(2) <= '0'; else led(1) <= '0'; end if; -- 判斷描述符長度是否超過端點(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ù)長度 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 測試平臺的編寫
上面介紹的是整個(gè) FPGA 固件系統(tǒng)的實(shí)現(xiàn)方法,為了驗(yàn)證設(shè)計(jì)的正確性,還需要編寫一個(gè)測試平臺對整個(gè)系統(tǒng)進(jìn)行仿真。由于實(shí)際情況下 FPGA 是和 PDIUSBD12 進(jìn)行通信,所以在測試平臺中需要虛擬一個(gè) PDIUSBD12,來實(shí)現(xiàn)仿真的目的。
首先,在測試平臺中需要產(chǎn)生一個(gè)虛擬的時(shí)鐘信號,產(chǎn)生的方法就是使用 wait for 語句等待固定時(shí)間后將信號值翻轉(zhuǎn)。時(shí)鐘信號的實(shí)現(xiàn)代碼如下:
-- 時(shí)鐘信號生成代碼 clk_gen: process begin -- 翻轉(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è)大的測試數(shù)據(jù)數(shù)組中,使用一個(gè)變量作為該數(shù)組索引,再編寫一個(gè)對讀信號敏感的過程,在每次讀信號的下降沿將數(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ù)以及端點(diǎn) 0 緩存前兩個(gè)字節(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ù)以及端點(diǎn) 0 緩存前兩個(gè)字節(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ù)以及端點(diǎn) 0 緩存前兩個(gè)字節(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ù)以及端點(diǎn) 0 緩存前兩個(gè)字節(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ù)以及端點(diǎn) 0 緩存前兩個(gè)字節(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ù)以及端點(diǎn) 0 緩存前兩個(gè)字節(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ū)動的問題。前面介紹的輸入/輸出選擇模塊的功能就是在必要的時(shí)候關(guān)閉總線輸出來避免雙驅(qū)動的發(fā)生,同樣道理,在測試平臺中也應(yīng)該做到這一點(diǎn),即當(dāng)測試平臺向 FPGA 固件系統(tǒng)讀取數(shù)據(jù)時(shí),應(yīng)該關(guān)閉測試平臺的總線輸出,即將其設(shè)置為高阻。實(shí)現(xiàn)代碼如下:
process(d12_wr, td_index) begin -- 當(dāng) FPGA 向 PDIUSBD12 些數(shù)據(jù)時(shí),總線輸出變?yōu)楦咦? if d12_wr = '0' then data <= "ZZZZZZZZ"; else data <= td(td_index); end if; end process;
最后,還需要編寫一個(gè)主流程,在主流程中需要進(jìn)行系統(tǒng)復(fù)位和產(chǎn)生中斷信號,代碼如下:
-- 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ū)動和軟件開發(fā)
?
7.1 USB 驅(qū)動編寫
以上介紹的是 FPGA 固件的開發(fā)過程,由于本例中設(shè)計(jì)的不是一個(gè)類設(shè)備,所以要使設(shè)備正常工作,還需要編寫專門的驅(qū)動程序和軟件。由于驅(qū)動和軟件不是本篇的重點(diǎn),故下面只簡要介紹其編寫方法。
1)USB 驅(qū)動模型
USB 體系的主機(jī)軟件可分為兩層,即 USB 系統(tǒng)軟件和客戶端驅(qū)動程序,如圖 48 所示。
圖 48 USB 接口軟件模型
USB 系統(tǒng)軟件根據(jù)功能可以分為 USBD 和 HCD 上下兩部分,其中 HCD 為上層提供了主機(jī)控制器的抽象以及數(shù)據(jù)在總線上的傳輸抽象。USBD 為上層的客戶端驅(qū)動程序提供了 USB 設(shè)備的抽象,并在客戶端驅(qū)動和所驅(qū)動的設(shè)備之間提供了數(shù)據(jù)傳輸?shù)某橄蟆?/p>
客戶端驅(qū)動程序從用戶的角度來講相當(dāng)于傳統(tǒng)意義上的驅(qū)動程序。不過設(shè)備端不同的接口對應(yīng)不同的驅(qū)動程序,如果設(shè)備只有一個(gè)接口,那么從用戶的角度來講,兩者是一樣的,客戶端驅(qū)動程序通過 USB 系統(tǒng)軟件提供的接口與設(shè)備交互,而不是通過過去的 I/O 地址或者端口進(jìn)行訪問。
2)使用 Driver Studio 開發(fā) USB 驅(qū)動
上面介紹的是 USB 軟件模型,對于驅(qū)動開發(fā)人員來說,需要編寫的就是客戶端驅(qū)動程序。編寫客戶端驅(qū)動程序需要安裝 DDK,即 Windows Driver Development Kit,通過 DDK 我們就能夠訪問 USB 系統(tǒng)軟件的接口從而實(shí)現(xiàn)與設(shè)備的交互。但是,如果只使用 DDK 開發(fā)驅(qū)動程序的話,會比較復(fù)雜,所以可以使用一些驅(qū)動開發(fā)的專用工具,例如 Driver Studio、WinDriver 等。本例選用的是 Driver Studio 2.7 進(jìn)行開發(fā),下面介紹一下開發(fā)的基本步驟。安裝完 DDK 以及 Driver Studio 后,運(yùn)行 Driver Studio 的 Driver Wizard。在第 1 步中輸入驅(qū)動工程名稱和路徑,如圖 49 所示。單擊 Next 按鈕進(jìn)入如圖 50 所示對話框。
圖 49 Driver Wizard 第 1 步?
圖 50 Driver Wizard 第 2 步
第 2 步選擇工程類型 WDM Driver,單擊 Next 按鈕進(jìn)入如圖 51 所示對話框。
第 3 步選擇驅(qū)動類型 WDM Function Driver。單擊 Next 按鈕進(jìn)入如圖 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,因?yàn)槭褂玫氖?Philips 的 PDIUSBD12 芯片,其 Vendor ID 固定為 0x0471。單擊 Next按鈕,進(jìn)入如圖 53 所示對話框。
圖?53?Driver Wizard 第 5 步
第 5 步是端點(diǎn)定義,可以根據(jù)需要定義端點(diǎn)的類型(輸入輸出)、端點(diǎn)號、緩存大小等。
第 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 步進(jìn)行一些額外的設(shè)置,如圖 61 所示,可以保持默認(rèn)值。
圖 61 Driver Wizard 第十二步
以上便是使用 Drive Studio 的 Driver Wizard 生成驅(qū)動框架的完整過程,現(xiàn)在我們已經(jīng)有了一個(gè)完成了大部分驅(qū)動工作的代碼框架,只需要增加一些自定義的處理代碼即可。
3)使用 Visual C++編譯驅(qū)動
運(yùn)行 Visual C++ 6.0 打開 Driver Wizard 生成的工程文件,可看到在***Device 這個(gè)類中已經(jīng)有了很多設(shè)備操作的處理函數(shù),例如上電(OnDevicePowerUp)、休眠(OnDeviceSleep)啟動(OnDeviceStart)等,可以根據(jù)需要修改這些函數(shù),如果沒有特殊要求,可以保持默認(rèn)設(shè)置,如圖 62 所示。
圖 62 設(shè)備操作處理函數(shù)
另外還需要完成的工作就是對上面定義的 IO 控制接口函數(shù)進(jìn)行處理,其功能就是建立一個(gè)廠商請求。由于本次設(shè)計(jì)的 USB 設(shè)備是一個(gè)加密設(shè)備,它不是類設(shè)備,所以會有一些特定的請求(廠商請求)。為了介紹廠商請求的實(shí)現(xiàn)方法,本系統(tǒng)用到了兩個(gè)廠商請求:設(shè)置密碼和獲取密碼。由 Driver Wizard 自動生成的驅(qū)動一般都已經(jīng)包括了標(biāo)準(zhǔn)請求的建立,但是不會包括廠商請求的建立。廠商請求是在 IO 控制接口函數(shù)中建立的,即 Driver Wizard 第 11 步所定義的兩個(gè)函數(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 個(gè)參數(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?
?
完成廠商請求的編寫之后,就可以進(jìn)行驅(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 工具條的最后一個(gè)按鈕即可。請注意不能使用 Visual C++本身的編譯按鈕進(jìn)行驅(qū)動編譯。編譯成功,如果是 Win32 Free 版本,則會在工程目錄的 sysobjfrei386 子目錄下生成驅(qū)動文件 USBSoftLock.sys;如果是 Win32Checked 版本,驅(qū)動文件會在工程目錄的 sysobjchki386 子目錄下。成功編譯驅(qū)動程序之后,將它和 Driver Studio 自動生成的.inf 文件(在工程目錄下)放在同一個(gè)目錄下,在查找驅(qū)動的時(shí)候指定這個(gè)目錄就可以了。
?
7.2 USB 軟件編寫
最后,再簡要介紹一下 USB 軟件的編寫,即軟件對 USB 設(shè)備訪問的實(shí)現(xiàn)方法。
USB 軟件通過 USB 驅(qū)動實(shí)現(xiàn)對 USB 設(shè)備的訪問,編寫 USB 軟件必須符合 USB 驅(qū)動定義的接口規(guī)范。一般來說,使用 Driver Wizard 生成一個(gè)驅(qū)動工程后,會同時(shí)生成一個(gè)***ioctl.h的文件,這個(gè)文件就是建立軟件和驅(qū)動之間通信的橋梁,它定義了訪問驅(qū)動程序的接口,在編寫軟件的時(shí)候需要將其引用進(jìn)去。
USB 軟件的編寫一般有下面幾個(gè)步驟。
1) 打開設(shè)備
打開設(shè)備主要需要調(diào)用 CreateFile 函數(shù),它將設(shè)備作為一個(gè)文件來處理,代碼如下:
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. ", GetLastError()); } m_hDevice = INVALID_HANDLE_VALUE; } }USB軟件的詳細(xì)代碼請參考源代碼中的cube測試程序,它模擬了一個(gè)硬件加密設(shè)備的工作過程。cube程序運(yùn)行后會出現(xiàn)一個(gè)立方體,使得立方體轉(zhuǎn)動表示正常的程序運(yùn)行狀態(tài)。程序運(yùn)行需要密碼,但是密碼不是保存在計(jì)算機(jī)上,而是保存在USB設(shè)備上,并且程序運(yùn)行時(shí)需要及時(shí)校驗(yàn)密碼,一旦密碼校驗(yàn)失?。赡苁且?yàn)槊艽a不正確或者USB設(shè)備被移除),程序都會停止運(yùn)行。方法是首先選擇菜單File—>Open Device打開USB設(shè)備(如圖64所示),如果打開設(shè)備成功,選擇File—>Play Cube,在出現(xiàn)的密碼輸入框內(nèi)輸入密碼,如果密碼正確,立方體就會開始轉(zhuǎn)動,并且cube程序在不時(shí)地和USB設(shè)備之間進(jìn)行密碼校驗(yàn)(可以看到PDIUSBD12的GOODLINK燈會不停的閃,這表示有數(shù)據(jù)傳輸)。還可以通過選擇File—>Set Password設(shè)置密碼,此密碼會通過Set Password請求發(fā)送給設(shè)備。
圖 64 cube 程序運(yùn)行界面
?
總結(jié)
?
本篇首先說明了 USB 系統(tǒng)的體系結(jié)構(gòu)以及 USB 協(xié)議相關(guān)的內(nèi)容,之后,詳細(xì)介紹了一下USB 接口器件 PDIUSBD12 的使用方法,最后,本章通過一個(gè)實(shí)例描述了使用 FPGA 接口 PDIUSBD12開發(fā) USB 接口的流程。本篇的學(xué)習(xí)要點(diǎn)可以總結(jié)如下:
首先,對 USB 協(xié)議的了解是最為重要的。雖然 PDIUSBD12 芯片能夠完成很多協(xié)議解析工作,但對 USB 協(xié)議的了解程度還是對整個(gè)開發(fā)過程起到了決定性的作用。USB 協(xié)議非常的復(fù)雜,熟悉 USB 協(xié)議的方法應(yīng)該是由大到小,即首先了解 USB 通信的基本原理,比如控制傳輸、批量傳輸?shù)脑砗吞攸c(diǎn);然后再了解各個(gè)傳輸?shù)慕M成,即每個(gè)傳輸首先發(fā)送的是什么數(shù)據(jù)包,然后接受的是什么數(shù)據(jù)包;最后再去分析每個(gè)數(shù)據(jù)包的格式、意義等。
其次,需要對 PDIUSBD12 芯片的比較了解,比如它的各個(gè)信號引腳的功能、特性,更為重要的是其通信時(shí)序和控制命令。
最后,對各種語言以及各種開發(fā)工具熟悉也是非常重要的。在本次設(shè)計(jì)中,需要用到的開發(fā)語言很多,包括 VHDL、C++(Visual C++);此外,本次設(shè)計(jì)還用到了多種開發(fā)工具,包括EDA 開發(fā)、驅(qū)動開發(fā)、軟件開發(fā)等,只有熟悉這些工具才能夠快速的進(jìn)行開發(fā)。USB 體系非常龐大,所以編寫本章也是為了夠幫助讀者跨入 USB 開發(fā)的大門,希望讀者通過本篇的學(xué)習(xí),能夠設(shè)計(jì)出更為完善、高效的 USB 接口。
?
審核編輯:黃飛
?
評論
查看更多