引言
有關通用串行總線(USB)的文章通常從USB是個人電腦的一個新連接標準開始講起。謝天謝地現在不再需要如此做,因此本引言可以簡短地寫為:如果你有一個嵌入式系統并且想連到PC,主流的連接通道是USB。本文介紹了一款Maxim最新推出的芯片,MAX3420E,它可以很容易地把USB加入到任何系統中。本文主要著重于SPI接口,提供了實現通用SPI的C例程。最后給出了一個簡單的USB HID (人機接口設備) ― 基于Windows的應急按鈕程序。
為任何系統增加USB
微控制器(μC)的選擇通常基于它所集成的外設。許多處理器集成了USB功能,但是大多數處理器,特別是一些低價位的處理器不含USB。有時,您可能選用了一個I/O和外設都很完美的微控制器,但它卻缺少USB。您是否希望只是添加USB功能而繼續使用當前的微控制器呢?利用Maxim的芯片MAX3420E,可以為任何處理器添加USB功能。MAX3420E集成了USB全速收發器、智能USB串行接口引擎(SIE)和一個可工作到26MHz時鐘的從SPI接口。MAX3420E的使用如同一個具有單個控制端點、兩個雙重緩沖的64字節數據端點和一個64字節的中斷端點的全速USB外設。
總線供電
圖1. USB供電
圖1是一個普通的USB外設結構。USB的VBUS電源線為3.3V穩壓器提供5V輸入,該穩壓器給微控制器和MAX3420E供電(無需墻上適配器)。SPI接口可以是3、4或5線。表1列出了5線接口。
表1. 使用3到5線的SPI接口
Signal | MAX3420E Direction | Description |
MOSI | In | SPI master out, slave in |
MISO | Out | SPI master in, slave out |
SCLK | In | Serial clock |
SS# | In | Slave select |
INT | Out | Interrupt (level or pulse) |
如果應用中不需要中斷(MAX3420E的中斷條件可以通過讀取寄存器直接檢測到)s,可以去掉INT引腳,得到一個4線接口。如果SPI主機具有雙向數據接口(MOSI/MISO),則可以省掉另外一條接口線,這樣,沒有中斷、支持雙向通信的SPI接口只需要3個引腳。
如果微控制器沒有SPI接口怎么辦呢?沒問題,可以很容易地設計一個直接觸發GPIO的、固件形式的SPI主控器。USB的一個非常強的特性是自控流量能力,它自動配合SPI側的任何速度(它利用在USB側插入NAK握手來提示“忙,重試”)。很多USB外設,特別是與人接口的,即使與最低速的SPI接口都能應答自如。
如果圖1中的微控制器非常小,只有10腳以下怎么辦呢?難道就不能把這些珍貴的I/O口用來連USB芯片了?不,這就是為什么MAX3420E提供了四個通用輸出口和四個通用輸入口的原因,它們可以替代被用掉的處理器上的I/O口,事實上你的系統在接上MAX3420E后還得到了更多的口線。
大規模集成芯片
圖2. 只連接大規模集成芯片的少許引腳
MAX3420E不僅僅只限用于小系統。圖2說明了如何給一個ASIC,FPGA,DSP和其他大芯片增加USB功能。這樣做的一個明顯原因是大芯片沒有內建的USB或內部的USB不是正好符合你的所需。另一個原因是隨著工藝尺寸的縮小,這些大片子不能兼容USB所需的3.3V “高”壓,此時使用外部帶低壓SPI接口的USB芯片就是一個很好的方案。MAX3420E內帶電平轉換器,VL腳設定接口電平的范圍,從1.7V到3.6V。
隔離USB
圖3. 隔離USB
如圖3所示,由于SPI接口信號是單向的,還很容易進行光隔。同時還可以設定較低的速率來支持廉價的光耦。
SPI接口
SPI (串行外設接口)是一個簡單的串行接口,它使用兩根數據線,一根串行時鐘和一個片選信號。SPI主控把SS#拉低來開始傳輸,然后驅動串行時鐘SCLK來把數據同步輸入和輸出從設備。SPI主控通過把SS#拉回到高電平,以終止傳輸。SPI接口有四種時鐘模式,反映了兩個信號CPOL (時鐘極性)和CPHA (時鐘相位)的兩個狀態。這兩信號以(CPOL, CPHA)的形式來表示。可以證明一個接口利用正沿SCLK且在第一個正沿時鐘到來以前MOSI數據已經準備就緒可以工作在(0,0)和(1,1)模式而無需任何改變。這一屬性使MAX3420E無需額外的模式引腳設置,就可以工作在(0,0)或(1,1)模式。
圖4和圖5顯示了利用SPI模式在微控制器(MAXQ2000,隨后介紹)和MAX3420E之間的數據傳輸。圖4所示為(0,0)模式,圖5所示為(1,1)模式。兩者的區別是SCLK信號的無效電平不同,(0,0)是低無效,(1,1)是高無效。
圖4. 工作在(0,0)模式的SPI接口
圖5. 工作在(1,1)模式的SPI接口
MAX3420E每次傳輸接收的第一個字節是命令字節。命令字節包含寄存器號和方向位,第二個字節和后續字節包含數據。在圖4和圖5中,移入命令字時,來自MAX3420E (MISO引腳)的8位數據是USB狀態位。此特性只在使用分離MISO和MOSI腳的接口中有效。
SPI代碼
編寫MAX3420E通用C代碼的竅門是把原始的最基本的SPI操作封裝到獨立的模塊中,然后針對不同的SPI接口的只需客戶化這一模塊。最基本的模塊只須做三件事:- 初始化SPI口
- 讀一個字節
- 寫一個字節
位仿真SPI
Init SPI
初始化SPI口的函數會隨處理器的不同而有很多變化。比較好的做法是先指定接口使用的I/O腳,設置它們的方向,然后設定SS=1和SCLK=0的初始條件(我們假定用SPI主控器的(00)模式)。
讀寄存器,寫寄存器
rreg是讀取MAX3420E寄存器的C函數,這個宏把功能從不同微控制器的不同I/O結構中獨立出來,使用宏使代碼易讀且與處理器無關。wreg是寫MAX3420E寄存器的例程。
更換處理器時,只需對宏進行修改即可使用這些例程。例如:下面是用于不帶硬件SPI單元的微控制器的宏。
#define SCLK_HI OUTA = PINSA | 0x02; #define SCLK_LO OUTA = PINSA & 0xFD; #define SS_HI OUTA = PINSA | 0x04; #define SS_LO OUTA = PINSA & 0xFB; #define MOSI(v) OUTA = (PINSA & 0x7F) | (v & 0x80); #define MISO inval |= PINSA & 0x01; BYTE rreg(BYTE r) // Read a register, return its value. { int j; BYTE bv,inval; inval = 0; SS_LO bv = r<<3; // Left-shift the reg number, WRITE=0 for (j=0; j<8; j++) // send the register number and direction bit { MOSI(bv) // put out a bit bv <<= 1; // shift one bit left SCLK_HI SCLK_LO } for (j=0; j<7; j++) // get 7 bits and shift left into 'inval' { SCLK_HI MISO inval <<= 1; // shift in one bit SCLK_LO } SCLK_HI // one more bit, but don't shift 'inval' this time MISO SCLK_LO SS_HI return inval; // return the byte we read in } void wreg(BYTE r,BYTE v) // register, value { int j; BYTE bv; SS_LO bv = (r<<3)+2; // Left-shift the reg number, set the WRITE direction bit for (j=0; j<8; j++) // send the register number and direction bit { MOSI(bv) // put out a bit bv <<= 1; // shift one bit left SCLK_HI SCLK_LO } for (j=0; j<8; j++) // send the register data { MOSI(v) // put out a bit v <<= 1; // shift one bit left SCLK_HI SCLK_LO } SS_HI }硬件SPI
這一部分討論上面提到的MAXQ2000微控制器,簡單地說,MAXQ2000是低功耗、16位、高性能RISC處理器家族中的第一個。“Q”代表安靜,表示這一結構能夠與敏感的模擬電路很好地協調工作。MAXQ2000內建了一個SPI口,它與MAX3420E配合特別合適,下面的例子使用MAXQ2000開發板和MAX3420E搭建了一個簡單、有趣的Windows小產品。
MAXQ2000硬件SPI單元提供了SCK、MOSI和MISO,但是沒有SS#。由于SS#的操作方式會變化(比如尋址一個字節與突發的字節串),最好用通用I/O腳做SS#。
MAXQ I/O單元
圖6. MAXQ I/O單元
圖6所示是一個基本的MAXQ I/O單元。I/O口以格式'port.bit'表示,'p'代表端口,'b'代表位。作為例子,我們主要討論I/O口5,第3位(用P53)表示。
每一個I/O單元有一個觸發器,本例中用一個稱為PO5.3的位來寫。'O'代表輸出。你一直可以寫這個觸發器,它的輸出有沒有與引腳相連由方向位決定。配置輸出腳時,實際應用時先寫觸發器再連到引腳比較好,這樣它可以避免引腳上出現毛刺。
P53腳的方向由稱作PD5.3的位來設定。'D'代表方向,D信號充當引腳驅動的輸出使能:1 = 驅動,0 = 浮空。引腳的狀態一直可以通過稱作PI5.3的位讀取,'I'代表輸入,無論引腳是如何驅動的,被內部觸發器(PD5.3 = 1)還是被外部的信號(PD5.3 = 0),PI位表示引腳狀態。
這種結構的一個好處是如果引腳被配制成輸入(PD5.3 = 0),觸發器的輸出沒有被用作輸出,那么它可以作為上拉電阻的開關重新利用。如果D = 0,0信號被重新定義,表示“連接一個上拉電阻”,如圖6中的點狀線所示。
許多I/O腳有中斷功能,如圖6下面的框圖所示,中斷模塊有三個信號:
- 一個中斷標志位,中斷請求有效時被置位,由CPU來復位。
- 一個邊沿選擇位,決定是正信號跳變還是負信號跳變引起中斷請求。
- 對每一個能引起中斷的引腳有一個中斷使能位。
初始化SPI
MAXQ2000的I/O引腳由通用I/O和像SPI單元這樣的特殊功能硬件共享。使用特殊功能硬件時,先配置硬件塊,然后把它連到I/O腳上。程序清單中的SPI_Init()過程設置了引腳方向,配置了SPI接口,最后使能它。
void SPI_Init(void) { // MAXQ2000 SPI port CKCN = 0x00; // system clock divisor is 1 SS_HI // SS# high PD5 |= 0x070; // Set SPI output pins (SS, SCLK, DOUT) as output. PD5 &= ~0x080; // Set SPI input pin (DIN) as input. SPICK = 0x00; // fastest SPI clock--div by 2 SPICF = 0x00; // mode(0,0), 8 bit data SPICN_bit.MSTM = 1; // Set Q2000 as the master. SPICN_bit.SPIEN = 1; // Enable SPI // MAX3420E INT pin is tied to MAXQ2000 P60; make it an input PD6 &= ~0x01; // PD6.0=0 (turn off output) }讀寄存器,寫寄存器
以下函數利用了MAXQ2000硬件SPI單元的優點,因此比起那些位仿真代碼尺寸小而且快。
// Read a MAX3420E register, return its value. BYTE rreg(BYTE reg) { BYTE dum; SS_LO SPIB = reg<<3; // reg number w. dir=0 (IN) while(SPICN_bit.STBY); // loop if data still being sent dum = SPIB; // read and toss the input byte SPIB=0x00; // data is don't care, we're clocking in MISO bits while(SPICN_bit.STBY); // loop if data still being sent SS_HI return(SPIB); } // Write a MAX3420E register. void wreg(BYTE reg, BYTE dat) { SS_LO // Set SS# low SPIB = (reg<<3)+2; // send reg. number w. DIR bit (b1) set to WRITE while(SPICN_bit.STBY); // loop if data still being sent SPIB = dat; // send the data while(SPICN_bit.STBY); // loop if data still being sent SS_HI // set SS# high }
例子:基于Windows的應急按鈕
這個USB小產品是一個USB HID,或人體學輸入設備-單個按鍵。當你按下按鍵,所有的活動窗口被最小化,你看到的僅剩桌面,再按一下它,所有的應用窗口又重新彈回來。USB鍵盤很有意思,如果插入幾個鍵盤,它們將同時有效。所以我們的小應急按鈕可以和你的正常鍵盤一起工作。
如果PC在待機,這個應急按鈕擔當了一個新角色-它可以充當PC的遠程喚醒按鍵。不過這取決于你的PC支持不支持USB喚醒,有些可以,有些不可以。這個按鈕可以幫你判斷你的PC可不可以。
此例程在帶有一個USB子卡(包含MAX3420E)的MAXQ2000開發板上運行。
USB詳細說明
這個應用代碼包含了USB做枚舉類型瑣碎工作的樣板代碼,此設備的屬性已經用Panic_Button_Enum_Data.h中的特性陣列完全描述。
這個應用使用了兩個端點,強制的CONTROL端點0,和EP3-IN,單緩沖64字節端點。雖然MAX3420E內含兩個雙重緩沖的64字節端點(EP1-OUT和EP2-OUT),在這個應用中并不需要雙重緩沖的吞吐優勢。
一個普遍存在HID錯誤概念是HID設備僅僅工作在低速下,本例展示了即使是像鍵盤這樣的東西也可以從全速運行中得到好處,通過發送12MHz的包來而不是1.5MHz包,它可以使用更低的總線帶寬。
圖7. 應急按鈕的流程圖
中斷端點有查詢間隔,它決定了USB主設備隔多久向IN端點要數據。每隔一段時間我們可以預計到主控制器發了一個IN請求給我們的設備端點3。圖7顯示了處理這些請求的一個簡單的狀態機。只要設備被例舉了,處理器重復地執行這一過程。為了簡單起見,該應用程序查詢中斷腳是否有效,當然,如果你還有其他事要微控制器處理,你會用中斷來激活Do_IN3函數。
狀態機使用了兩個全局變量:state和button。C宏定義了三個狀態:IDLE, RELEASE和 WAIT 。狀態變量初始化為IDLE。如果連在MAX3420E的GPIN0上的按鍵按下,變量button是高,否則為低。Main()中的無窮循環增加一個按鍵檢查定時器,當定時器到時它會讀一下MAX3420E中的GPIO寄存器來決定按鍵狀態。此方法省掉了不必要的SPI流量。
當按鍵處于彈起狀態時,狀態圖轉到左邊的兩個分支,不做任何事。如果按鍵在IDLE狀態被按下,就發一個清除桌面上活動窗口的鍵碼。鍵碼次序是08 (windows鍵) 00 (保留)和07 (字母d)。下一個狀態轉到RELEASE,這樣就完成了。
只要MAX3420E把數據包送到USB,它就產生另一個EP3-IN中斷請求來表示EP3-IN FIFO可以再一次裝載數據。然后再次進入圖7函數,此時狀態state = RELEASE ,因此函數發送序列00 00 00來表示“按鍵彈起”,下一個狀態進入WAIT,意思是“等待按鍵被釋放”。
現在函數要做的所有工作是利用WAIT狀態分支程序來檢測按鍵釋放。如果按鍵一直按著,程序不做任何事,當按鍵一被釋放,狀態圖就進到右邊的兩個分支,重新初始化state 變量為IDLE,使函數等候下一個按鍵按下。
占大部分運行時間的代碼只有少數幾行,圖7給出了流程圖:
void Do_IN3(void) { switch(state) { case IDLE: if (button) { wreg(rEP3INFIFO,0x08); // "Windows" prefix key wreg(rEP3INFIFO,0); wreg(rEP3INFIFO,0x07); // "D" key wreg(rEP3INBC,3); // arm it state = RELEASE; // next state sends the "keys up" code } break; // else do nothing (and the SIE will NAK) // case RELEASE: { wreg(rEP3INFIFO,0x00); // key up wreg(rEP3INFIFO,0x00); wreg(rEP3INFIFO,0x00); // key up wreg(rEP3INBC,3); // arm it state = WAIT; // next state waits for the PB to be unpressed } break; case WAIT: if (!button) state = IDLE; break; default: state = IDLE; } // end switch }
代碼關鍵
需要對代碼中的一些細節加以說明。時間敏感的USB事件
MAX3420E 通過在USB總線上送'K'狀態10ms時間發了一個遠程喚醒信號,為了避免用SPI主控器來做設定時間這種雜活,MAX3420E用自己內部來定時這個信號(事實上,所有的USB時間敏感事件),然后在時間到時給SPI主控器發一個中斷。SPI主控器對這些事件不必用上它自己的定時器-它只需啟動操作,然后等待完成中斷。
ACKSTAT位
函數rregAS和wregAS的功能與rreg和wreg只有一點不同,它們在SPI命令字中設了ACK STATUS位。SPI主控器(我們的例子中是MAXQ2000)用這一位表示MAX3420E已經完成了當前的CONTROL請求服務,因此用應答它的狀態情況的方式來終止CONTROL傳輸。ACKSTAT還扮演了一個內部寄存器位的角色,而且由于它含在SPI的命令字中,對它的操作能快速執行且只需少量代碼。
readbytes(), writebytes()函數 這些函數使用了MAX3420E的數據突發能力。與每次字節尋址發兩個字節(一個命令字節和一個數據字節)不同,這些函數先拉低SS#,送命令字,然后送入/送出一串字節,最后把SS#來拉高結束SPI傳輸。
哪里找到產品ID
圖8. 產品ID在這里顯示
產品ID碼(在Panic_Button_Enum_Data.h中)是第一次插入應急按鈕時出現的一小段信息。在確定應急按鈕為HID的枚舉過程中彈出來,并把它和Windows的內部驅動聯系起來。
除了插入任何USB設備時都會聽到一小聲“叭噠”外,后續的每個附件都不發聲。任何時候如果你想檢查設備狀態,請打開圖8所示屏幕。你可以右擊“我的電腦”,選擇“屬性”,“硬件”頁,“設備管理器”按鈕,展開“人機接口設備”項,右擊“USB人機接口設備”,選擇“屬性”。
USB兼容性
查看代碼后,你可能認為這對一個單鍵的USB設備來說要做很多工作,因為對任何USB設備都需要寫一段固定代碼。幸運的是我們對USB進行了仔細的歸納,枚舉代碼可作為任何USB設備的一個模板(拷貝-粘貼即可)。像所有認真的開發者一樣,我們希望自己的設計能夠通過USB-IF認證,以避免任何知識產權問題。這個應用已經通過了USB命令驗證(USBCV版本1.2.1.0)和USB-IF網站為開發者提供的HID測試。
圖9. USB和HID測試記錄和狀況報告
結論
如果需要設計一個USB外設,可選擇MAX3420E。該器件提供小尺寸封裝,并可提供許多免費程序。MAX3420E能夠為設計增加I/O口線,在支持SPI的系統中能很好地工作。由于SPI很容易通過位仿真實現,因此可以使用任何微控制器。如果需要更高性能,可以使用高達26MHz的SPI時鐘。類似文章發表于Circuit Cellar 2005年7月刊。
附錄. 代碼列表
下載代碼列表
評論
查看更多