新聞中心

EEPW首頁 > 模擬技術(shù) > 設(shè)計應(yīng)用 > 為任何系統(tǒng)增加USB

為任何系統(tǒng)增加USB

——
作者: 時間:2007-01-26 來源:《Maxim公司》 收藏

 


圖1. usb供電

圖1是一個普通的usb外設(shè)結(jié)構(gòu)。usb的vbus電源線為3.3v穩(wěn)壓器提供5v輸入,該穩(wěn)壓器給微控制器和max3420e供電(無需墻上適配器)。spi接口可以是3、4或5線。表1列出了5線接口。

表1. 使用3到5線的spi接口

如果應(yīng)用中不需要中斷(max3420e的中斷條件可以通過讀取寄存器直接檢測到)s,可以去掉int引腳,得到一個4線接口。如果spi主機(jī)具有雙向數(shù)據(jù)接口(mosi/miso),則可以省掉另外一條接口線,這樣,沒有中斷、支持雙向通信的spi接口只需要3個引腳。

如果微控制器沒有spi接口怎么辦呢?沒問題,可以很容易地設(shè)計一個直接觸發(fā)gpio的、固件形式的spi主控器。usb的一個非常強(qiáng)的特性是自控流量能力,它自動配合spi側(cè)的任何速度(它利用在usb側(cè)插入nak握手來提示“忙,重試”)。很多usb外設(shè),特別是與人接口的,即使與最低速的spi接口都能應(yīng)答自如。

如果圖1中的微控制器非常小,只有10腳以下怎么辦呢?難道就不能把這些珍貴的i/o口用來連usb芯片了?不,這就是為什么max3420e提供了四個通用輸出口和四個通用輸入口的原因,它們可以替代被用掉的處理器上的i/o口,事實上你的系統(tǒng)在接上max3420e后還得到了更多的口線。

大規(guī)模集成芯片


圖2. 只連接大規(guī)模集成芯片的少許引腳

max3420e不僅僅只限用于小系統(tǒng)。圖2說明了如何給一個asic,fpga,dsp和其他大芯片增加usb功能。這樣做的一個明顯原因是大芯片沒有內(nèi)建的usb或內(nèi)部的usb不是正好符合你的所需。另一個原因是隨著工藝尺寸的縮小,這些大片子不能兼容usb所需的3.3v “高”壓,此時使用外部帶低壓spi接口的usb芯片就是一個很好的方案。max3420e內(nèi)帶電平轉(zhuǎn)換器,vl腳設(shè)定接口電平的范圍,從1.7v到3.6v。

隔離usb


圖3. 隔離usb

如圖3所示,由于spi接口信號是單向的,還很容易進(jìn)行光隔。同時還可以設(shè)定較低的速率來支持廉價的光耦。


spi接口
spi (串行外設(shè)接口)是一個簡單的串行接口,它使用兩根數(shù)據(jù)線,一根串行時鐘和一個片選信號。spi主控把ss#拉低來開始傳輸,然后驅(qū)動串行時鐘sclk來把數(shù)據(jù)同步輸入和輸出從設(shè)備。spi主控通過把ss#拉回到高電平,以終止傳輸。
spi接口有四種時鐘模式,反映了兩個信號cpol (時鐘極性)和cpha (時鐘相位)的兩個狀態(tài)。這兩信號以(cpol, cpha)的形式來表示。可以證明一個接口利用正沿sclk且在第一個正沿時鐘到來以前mosi數(shù)據(jù)已經(jīng)準(zhǔn)備就緒可以工作在(0,0)和(1,1)模式而無需任何改變。這一屬性使max3420e無需額外的模式引腳設(shè)置,就可以工作在(0,0)或(1,1)模式。

圖4和圖5顯示了利用spi模式在微控制器(maxq2000,隨后介紹)和max3420e之間的數(shù)據(jù)傳輸。圖4所示為(0,0)模式,圖5所示為(1,1)模式。兩者的區(qū)別是sclk信號的無效電平不同,(0,0)是低無效,(1,1)是高無效。{{分頁}}

max3420e每次傳輸接收的第一個字節(jié)是命令字節(jié)。命令字節(jié)包含寄存器號和方向位,第二個字節(jié)和后續(xù)字節(jié)包含數(shù)據(jù)。在圖4和圖5中,移入命令字時,來自max3420e (miso引腳)的8位數(shù)據(jù)是usb狀態(tài)位。此特性只在使用分離miso和mosi腳的接口中有效。


spi代碼
編寫max3420e通用c代碼的竅門是把原始的最基本的spi操作封裝到獨立的模塊中,然后針對不同的spi接口的只需客戶化這一模塊。最基本的模塊只須做三件事:
初始化spi口
讀一個字節(jié)
寫一個字節(jié)
本文中的應(yīng)用例子使用硬件spi單元。對沒有此單元的spi主控器,我們先看看一些位仿真spi接口的通用c代碼。
位仿真spi
init spi
初始化spi口的函數(shù)會隨處理器的不同而有很多變化。比較好的做法是先指定接口使用的i/o腳,設(shè)置它們的方向,然后設(shè)定ss=1和sclk=0的初始條件(我們假定用spi主控器的(00)模式)。

讀寄存器,寫寄存器
rreg是讀取max3420e寄存器的c函數(shù),這個宏把功能從不同微控制器的不同i/o結(jié)構(gòu)中獨立出來,使用宏使代碼易讀且與處理器無關(guān)。wreg是寫max3420e寄存器的例程。

更換處理器時,只需對宏進(jìn)行修改即可使用這些例程。例如:下面是用于不帶硬件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 dont 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處理器家族中的第一個?!皅”代表安靜,表示這一結(jié)構(gòu)能夠與敏感的模擬電路很好地協(xié)調(diào)工作。maxq2000內(nèi)建了一個spi口,它與max3420e配合特別合適,下面的例子使用maxq2000開發(fā)板和max3420e搭建了一個簡單、有趣的windows小產(chǎn)品。

maxq2000硬件spi單元提供了sck、mosi和miso,但是沒有ss#。由于ss#的操作方式會變化(比如尋址一個字節(jié)與突發(fā)的字節(jié)串),最好用通用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單元有一個觸發(fā)器,本例中用一個稱為po5.3的位來寫。o代表輸出。你一直可以寫這個觸發(fā)器,它的輸出有沒有與引腳相連由方向位決定。配置輸出腳時,實際應(yīng)用時先寫觸發(fā)器再連到引腳比較好,這樣它可以避免引腳上出現(xiàn)毛刺。

p53腳的方向由稱作pd5.3的位來設(shè)定。d代表方向,d信號充當(dāng)引腳驅(qū)動的輸出使能:1 = 驅(qū)動,0 = 浮空。引腳的狀態(tài)一直可以通過稱作pi5.3的位讀取,i代表輸入,無論引腳是如何驅(qū)動的,被內(nèi)部觸發(fā)器(pd5.3 = 1)還是被外部的信號(pd5.3 = 0),pi位表示引腳狀態(tài)。

這種結(jié)構(gòu)的一個好處是如果引腳被配制成輸入(pd5.3 = 0),觸發(fā)器的輸出沒有被用作輸出,那么它可以作為上拉電阻的開關(guān)重新利用。如果d = 0,0信號被重新定義,表示“連接一個上拉電阻”,如圖6中的點狀線所示。

許多i/o腳有中斷功能,如圖6下面的框圖所示,中斷模塊有三個信號:

一個中斷標(biāo)志位,中斷請求有效時被置位,由cpu來復(fù)位。
一個邊沿選擇位,決定是正信號跳變還是負(fù)信號跳變引起中斷請求。
對每一個能引起中斷的引腳有一個中斷使能位。
我們的應(yīng)用例子把max3420e的中斷輸出配置成正邊沿觸發(fā)中斷,在maxq2000這邊,程序直接測試usb中斷的中斷觸發(fā)器,而不是使用maxq2000的中斷系統(tǒng)。程序除了檢測按鍵的狀態(tài)和響應(yīng)usb請求外什么都不干,因此只需一個查詢循環(huán)。
初始化spi
maxq2000的i/o引腳由通用i/o和像spi單元這樣的特殊功能硬件共享。使用特殊功能硬件時,先配置硬件塊,然后把它連到i/o腳上。程序清單中的spi_init()過程設(shè)置了引腳方向,配置了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)
}

讀寄存器,寫寄存器
以下函數(shù)利用了maxq2000硬件spi單元的優(yōu)點,因此比起那些位仿真代碼尺寸小而且快。
// 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 dont care, were 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的應(yīng)急按鈕

這個usb小產(chǎn)品是一個usb hid,或人體學(xué)輸入設(shè)備-單個按鍵。當(dāng)你按下按鍵,所有的活動窗口被最小化,你看到的僅剩桌面,再按一下它,所有的應(yīng)用窗口又重新彈回來。

usb鍵盤很有意思,如果插入幾個鍵盤,它們將同時有效。所以我們的小應(yīng)急按鈕可以和你的正常鍵盤一起工作。

如果pc在待機(jī),這個應(yīng)急按鈕擔(dān)當(dāng)了一個新角色-它可以充當(dāng)pc的遠(yuǎn)程喚醒按鍵。不過這取決于你的pc支持不支持usb喚醒,有些可以,有些不可以。這個按鈕可以幫你判斷你的pc可不可以。

此例程在帶有一個usb子卡(包含max3420e)的maxq2000開發(fā)板上運行。

usb詳細(xì)說明

這個應(yīng)用代碼包含了usb做枚舉類型瑣碎工作的樣板代碼,此設(shè)備的屬性已經(jīng)用panic_button_enum_data.h中的特性陣列完全描述。

這個應(yīng)用使用了兩個端點,強(qiáng)制的control端點0,和ep3-in,單緩沖64字節(jié)端點。雖然max3420e內(nèi)含兩個雙重緩沖的64字節(jié)端點(ep1-out和ep2-out),在這個應(yīng)用中并不需要雙重緩沖的吞吐優(yōu)勢。

一個普遍存在hid錯誤概念是hid設(shè)備僅僅工作在低速下,本例展示了即使是像鍵盤這樣的東西也可以從全速運行中得到好處,通過發(fā)送12mhz的包來而不是1.5mhz包,它可以使用更低的總線帶寬。


圖7. 應(yīng)急按鈕的流程圖

中斷端點有查詢間隔,它決定了usb主設(shè)備隔多久向in端點要數(shù)據(jù)。每隔一段時間我們可以預(yù)計到主控制器發(fā)了一個in請求給我們的設(shè)備端點3。圖7顯示了處理這些請求的一個簡單的狀態(tài)機(jī)。只要設(shè)備被例舉了,處理器重復(fù)地執(zhí)行這一過程。為了簡單起見,該應(yīng)用程序查詢中斷腳是否有效,當(dāng)然,如果你還有其他事要微控制器處理,你會用中斷來激活do_in3函數(shù)。

狀態(tài)機(jī)使用了兩個全局變量:state和button。c宏定義了三個狀態(tài):idle, release和 wait 。狀態(tài)變量初始化為idle。如果連在max3420e的gpin0上的按鍵按下,變量button是高,否則為低。main()中的無窮循環(huán)增加一個按鍵檢查定時器,當(dāng)定時器到時它會讀一下max3420e中的gpio寄存器來決定按鍵狀態(tài)。此方法省掉了不必要的spi流量。

當(dāng)按鍵處于彈起狀態(tài)時,狀態(tài)圖轉(zhuǎn)到左邊的兩個分支,不做任何事。如果按鍵在idle狀態(tài)被按下,就發(fā)一個清除桌面上活動窗口的鍵碼。鍵碼次序是08 (windows鍵) 00 (保留)和07 (字母d)。下一個狀態(tài)轉(zhuǎn)到release,這樣就完成了。

只要max3420e把數(shù)據(jù)包送到usb,它就產(chǎn)生另一個ep3-in中斷請求來表示ep3-in fifo可以再一次裝載數(shù)據(jù)。然后再次進(jìn)入圖7函數(shù),此時狀態(tài)state = release ,因此函數(shù)發(fā)送序列00 00 00來表示“按鍵彈起”,下一個狀態(tài)進(jìn)入wait,意思是“等待按鍵被釋放”。{{分頁}}

現(xiàn)在函數(shù)要做的所有工作是利用wait狀態(tài)分支程序來檢測按鍵釋放。如果按鍵一直按著,程序不做任何事,當(dāng)按鍵一被釋放,狀態(tài)圖就進(jìn)到右邊的兩個分支,重新初始化state 變量為idle,使函數(shù)等候下一個按鍵按下。

占大部分運行時間的代碼只有少數(shù)幾行,圖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
}

代碼關(guān)鍵
需要對代碼中的一些細(xì)節(jié)加以說明。
時間敏感的usb事件
max3420e 通過在usb總線上送k狀態(tài)10ms時間發(fā)了一個遠(yuǎn)程喚醒信號,為了避免用spi主控器來做設(shè)定時間這種雜活,max3420e用自己內(nèi)部來定時這個信號(事實上,所有的usb時間敏感事件),然后在時間到時給spi主控器發(fā)一個中斷。spi主控器對這些事件不必用上它自己的定時器-它只需啟動操作,然后等待完成中斷。

ackstat位
函數(shù)rregas和wregas的功能與rreg和wreg只有一點不同,它們在spi命令字中設(shè)了ack status位。spi主控器(我們的例子中是maxq2000)用這一位表示max3420e已經(jīng)完成了當(dāng)前的control請求服務(wù),因此用應(yīng)答它的狀態(tài)情況的方式來終止control傳輸。ackstat還扮演了一個內(nèi)部寄存器位的角色,而且由于它含在spi的命令字中,對它的操作能快速執(zhí)行且只需少量代碼。

readbytes(), writebytes()函數(shù) 這些函數(shù)使用了max3420e的數(shù)據(jù)突發(fā)能力。與每次字節(jié)尋址發(fā)兩個字節(jié)(一個命令字節(jié)和一個數(shù)據(jù)字節(jié))不同,這些函數(shù)先拉低ss#,送命令字,然后送入/送出一串字節(jié),最后把ss#來拉高結(jié)束spi傳輸。

哪里找到產(chǎn)品id


圖8. 產(chǎn)品id在這里顯示

產(chǎn)品id碼(在panic_button_enum_data.h中)是第一次插入應(yīng)急按鈕時出現(xiàn)的一小段信息。在確定應(yīng)急按鈕為hid的枚舉過程中彈出來,并把它和windows的內(nèi)部驅(qū)動聯(lián)系起來。

除了插入任何usb設(shè)備時都會聽到一小聲“叭噠”外,后續(xù)的每個附件都不發(fā)聲。任何時候如果你想檢查設(shè)備狀態(tài),請打開圖8所示屏幕。你可以右擊“我的電腦”,選擇“屬性”,“硬件”頁,“設(shè)備管理器”按鈕,展開“人機(jī)接口設(shè)備”項,右擊“usb人機(jī)接口設(shè)備”,選擇“屬性”。


usb兼容性

查看代碼后,你可能認(rèn)為這對一個單鍵的usb設(shè)備來說要做很多工作,因為對任何usb設(shè)備都需要寫一段固定代碼。幸運的是我們對usb進(jìn)行了仔細(xì)的歸納,枚舉代碼可作為任何usb設(shè)備的一個模板(拷貝-粘貼即可)。

像所有認(rèn)真的開發(fā)者一樣,我們希望自己的設(shè)計能夠通過usb-if認(rèn)證,以避免任何知識產(chǎn)權(quán)問題。這個應(yīng)用已經(jīng)通過了usb命令驗證(usbcv版本1.2.1.0)和usb-if網(wǎng)站為開發(fā)者提供的hid測試。


圖9. usb和hid測試記錄和狀況報告

結(jié)論

如果需要設(shè)計一個usb外設(shè),可選擇max3420e。該器件提供小尺寸封裝,并可提供許多免費程序。max3420e能夠為設(shè)計增加i/o口線,在支持spi的系統(tǒng)中能很好地工作。由于spi很容易通過位仿真實現(xiàn),因此可以使用任何微控制器。如果需要更高性能,可以使用高達(dá)26mhz的spi時鐘。

 


關(guān)鍵詞:

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉