為任何系統(tǒng)增加USB
圖1. usb供電
圖1是一個(gè)普通的usb外設(shè)結(jié)構(gòu)。usb的vbus電源線為3.3v穩(wěn)壓器提供5v輸入,該穩(wěn)壓器給微控制器和max3420e供電(無(wú)需墻上適配器)。spi接口可以是3、4或5線。表1列出了5線接口。
表1. 使用3到5線的spi接口
如果應(yīng)用中不需要中斷(max3420e的中斷條件可以通過(guò)讀取寄存器直接檢測(cè)到)s,可以去掉int引腳,得到一個(gè)4線接口。如果spi主機(jī)具有雙向數(shù)據(jù)接口(mosi/miso),則可以省掉另外一條接口線,這樣,沒(méi)有中斷、支持雙向通信的spi接口只需要3個(gè)引腳。
如果微控制器沒(méi)有spi接口怎么辦呢?沒(méi)問(wèn)題,可以很容易地設(shè)計(jì)一個(gè)直接觸發(fā)gpio的、固件形式的spi主控器。usb的一個(gè)非常強(qiáng)的特性是自控流量能力,它自動(dòng)配合spi側(cè)的任何速度(它利用在usb側(cè)插入nak握手來(lái)提示“忙,重試”)。很多usb外設(shè),特別是與人接口的,即使與最低速的spi接口都能應(yīng)答自如。
如果圖1中的微控制器非常小,只有10腳以下怎么辦呢?難道就不能把這些珍貴的i/o口用來(lái)連usb芯片了?不,這就是為什么max3420e提供了四個(gè)通用輸出口和四個(gè)通用輸入口的原因,它們可以替代被用掉的處理器上的i/o口,事實(shí)上你的系統(tǒng)在接上max3420e后還得到了更多的口線。
大規(guī)模集成芯片
圖2. 只連接大規(guī)模集成芯片的少許引腳
max3420e不僅僅只限用于小系統(tǒng)。圖2說(shuō)明了如何給一個(gè)asic,fpga,dsp和其他大芯片增加usb功能。這樣做的一個(gè)明顯原因是大芯片沒(méi)有內(nèi)建的usb或內(nèi)部的usb不是正好符合你的所需。另一個(gè)原因是隨著工藝尺寸的縮小,這些大片子不能兼容usb所需的3.3v “高”壓,此時(shí)使用外部帶低壓spi接口的usb芯片就是一個(gè)很好的方案。max3420e內(nèi)帶電平轉(zhuǎn)換器,vl腳設(shè)定接口電平的范圍,從1.7v到3.6v。
隔離usb
圖3. 隔離usb
如圖3所示,由于spi接口信號(hào)是單向的,還很容易進(jìn)行光隔。同時(shí)還可以設(shè)定較低的速率來(lái)支持廉價(jià)的光耦。
spi接口
spi (串行外設(shè)接口)是一個(gè)簡(jiǎn)單的串行接口,它使用兩根數(shù)據(jù)線,一根串行時(shí)鐘和一個(gè)片選信號(hào)。spi主控把ss#拉低來(lái)開(kāi)始傳輸,然后驅(qū)動(dòng)串行時(shí)鐘sclk來(lái)把數(shù)據(jù)同步輸入和輸出從設(shè)備。spi主控通過(guò)把ss#拉回到高電平,以終止傳輸。
spi接口有四種時(shí)鐘模式,反映了兩個(gè)信號(hào)cpol (時(shí)鐘極性)和cpha (時(shí)鐘相位)的兩個(gè)狀態(tài)。這兩信號(hào)以(cpol, cpha)的形式來(lái)表示。可以證明一個(gè)接口利用正沿sclk且在第一個(gè)正沿時(shí)鐘到來(lái)以前mosi數(shù)據(jù)已經(jīng)準(zhǔn)備就緒可以工作在(0,0)和(1,1)模式而無(wú)需任何改變。這一屬性使max3420e無(wú)需額外的模式引腳設(shè)置,就可以工作在(0,0)或(1,1)模式。
圖4和圖5顯示了利用spi模式在微控制器(maxq2000,隨后介紹)和max3420e之間的數(shù)據(jù)傳輸。圖4所示為(0,0)模式,圖5所示為(1,1)模式。兩者的區(qū)別是sclk信號(hào)的無(wú)效電平不同,(0,0)是低無(wú)效,(1,1)是高無(wú)效。{{分頁(yè)}}
max3420e每次傳輸接收的第一個(gè)字節(jié)是命令字節(jié)。命令字節(jié)包含寄存器號(hào)和方向位,第二個(gè)字節(jié)和后續(xù)字節(jié)包含數(shù)據(jù)。在圖4和圖5中,移入命令字時(shí),來(lái)自max3420e (miso引腳)的8位數(shù)據(jù)是usb狀態(tài)位。此特性只在使用分離miso和mosi腳的接口中有效。
spi代碼
編寫(xiě)max3420e通用c代碼的竅門(mén)是把原始的最基本的spi操作封裝到獨(dú)立的模塊中,然后針對(duì)不同的spi接口的只需客戶化這一模塊。最基本的模塊只須做三件事:
初始化spi口
讀一個(gè)字節(jié)
寫(xiě)一個(gè)字節(jié)
本文中的應(yīng)用例子使用硬件spi單元。對(duì)沒(méi)有此單元的spi主控器,我們先看看一些位仿真spi接口的通用c代碼。
位仿真spi
init spi
初始化spi口的函數(shù)會(huì)隨處理器的不同而有很多變化。比較好的做法是先指定接口使用的i/o腳,設(shè)置它們的方向,然后設(shè)定ss=1和sclk=0的初始條件(我們假定用spi主控器的(00)模式)。
讀寄存器,寫(xiě)寄存器
rreg是讀取max3420e寄存器的c函數(shù),這個(gè)宏把功能從不同微控制器的不同i/o結(jié)構(gòu)中獨(dú)立出來(lái),使用宏使代碼易讀且與處理器無(wú)關(guān)。wreg是寫(xiě)max3420e寄存器的例程。
更換處理器時(shí),只需對(duì)宏進(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微控制器,簡(jiǎn)單地說(shuō),maxq2000是低功耗、16位、高性能risc處理器家族中的第一個(gè)?!皅”代表安靜,表示這一結(jié)構(gòu)能夠與敏感的模擬電路很好地協(xié)調(diào)工作。maxq2000內(nèi)建了一個(gè)spi口,它與max3420e配合特別合適,下面的例子使用maxq2000開(kāi)發(fā)板和max3420e搭建了一個(gè)簡(jiǎn)單、有趣的windows小產(chǎn)品。
maxq2000硬件spi單元提供了sck、mosi和miso,但是沒(méi)有ss#。由于ss#的操作方式會(huì)變化(比如尋址一個(gè)字節(jié)與突發(fā)的字節(jié)串),最好用通用i/o腳做ss#。
maxq i/o單元
圖6. maxq i/o單元
圖6所示是一個(gè)基本的maxq i/o單元。i/o口以格式port.bit表示,p代表端口,b代表位。作為例子,我們主要討論i/o口5,第3位(用p53)表示。{{分頁(yè)}}
每一個(gè)i/o單元有一個(gè)觸發(fā)器,本例中用一個(gè)稱為po5.3的位來(lái)寫(xiě)。o代表輸出。你一直可以寫(xiě)這個(gè)觸發(fā)器,它的輸出有沒(méi)有與引腳相連由方向位決定。配置輸出腳時(shí),實(shí)際應(yīng)用時(shí)先寫(xiě)觸發(fā)器再連到引腳比較好,這樣它可以避免引腳上出現(xiàn)毛刺。
p53腳的方向由稱作pd5.3的位來(lái)設(shè)定。d代表方向,d信號(hào)充當(dāng)引腳驅(qū)動(dòng)的輸出使能:1 = 驅(qū)動(dòng),0 = 浮空。引腳的狀態(tài)一直可以通過(guò)稱作pi5.3的位讀取,i代表輸入,無(wú)論引腳是如何驅(qū)動(dòng)的,被內(nèi)部觸發(fā)器(pd5.3 = 1)還是被外部的信號(hào)(pd5.3 = 0),pi位表示引腳狀態(tài)。
這種結(jié)構(gòu)的一個(gè)好處是如果引腳被配制成輸入(pd5.3 = 0),觸發(fā)器的輸出沒(méi)有被用作輸出,那么它可以作為上拉電阻的開(kāi)關(guān)重新利用。如果d = 0,0信號(hào)被重新定義,表示“連接一個(gè)上拉電阻”,如圖6中的點(diǎn)狀線所示。
許多i/o腳有中斷功能,如圖6下面的框圖所示,中斷模塊有三個(gè)信號(hào):
一個(gè)中斷標(biāo)志位,中斷請(qǐng)求有效時(shí)被置位,由cpu來(lái)復(fù)位。
一個(gè)邊沿選擇位,決定是正信號(hào)跳變還是負(fù)信號(hào)跳變引起中斷請(qǐng)求。
對(duì)每一個(gè)能引起中斷的引腳有一個(gè)中斷使能位。
我們的應(yīng)用例子把max3420e的中斷輸出配置成正邊沿觸發(fā)中斷,在maxq2000這邊,程序直接測(cè)試usb中斷的中斷觸發(fā)器,而不是使用maxq2000的中斷系統(tǒng)。程序除了檢測(cè)按鍵的狀態(tài)和響應(yīng)usb請(qǐng)求外什么都不干,因此只需一個(gè)查詢循環(huán)。
初始化spi
maxq2000的i/o引腳由通用i/o和像spi單元這樣的特殊功能硬件共享。使用特殊功能硬件時(shí),先配置硬件塊,然后把它連到i/o腳上。程序清單中的spi_init()過(guò)程設(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)
}
讀寄存器,寫(xiě)寄存器
以下函數(shù)利用了maxq2000硬件spi單元的優(yōu)點(diǎn),因此比起那些位仿真代碼尺寸小而且快。
// 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)急按鈕
這個(gè)usb小產(chǎn)品是一個(gè)usb hid,或人體學(xué)輸入設(shè)備-單個(gè)按鍵。當(dāng)你按下按鍵,所有的活動(dòng)窗口被最小化,你看到的僅剩桌面,再按一下它,所有的應(yīng)用窗口又重新彈回來(lái)。
usb鍵盤(pán)很有意思,如果插入幾個(gè)鍵盤(pán),它們將同時(shí)有效。所以我們的小應(yīng)急按鈕可以和你的正常鍵盤(pán)一起工作。
如果pc在待機(jī),這個(gè)應(yīng)急按鈕擔(dān)當(dāng)了一個(gè)新角色-它可以充當(dāng)pc的遠(yuǎn)程喚醒按鍵。不過(guò)這取決于你的pc支持不支持usb喚醒,有些可以,有些不可以。這個(gè)按鈕可以幫你判斷你的pc可不可以。
此例程在帶有一個(gè)usb子卡(包含max3420e)的maxq2000開(kāi)發(fā)板上運(yùn)行。
usb詳細(xì)說(shuō)明
這個(gè)應(yīng)用代碼包含了usb做枚舉類型瑣碎工作的樣板代碼,此設(shè)備的屬性已經(jīng)用panic_button_enum_data.h中的特性陣列完全描述。
這個(gè)應(yīng)用使用了兩個(gè)端點(diǎn),強(qiáng)制的control端點(diǎn)0,和ep3-in,單緩沖64字節(jié)端點(diǎn)。雖然max3420e內(nèi)含兩個(gè)雙重緩沖的64字節(jié)端點(diǎn)(ep1-out和ep2-out),在這個(gè)應(yīng)用中并不需要雙重緩沖的吞吐優(yōu)勢(shì)。
一個(gè)普遍存在hid錯(cuò)誤概念是hid設(shè)備僅僅工作在低速下,本例展示了即使是像鍵盤(pán)這樣的東西也可以從全速運(yùn)行中得到好處,通過(guò)發(fā)送12mhz的包來(lái)而不是1.5mhz包,它可以使用更低的總線帶寬。
圖7. 應(yīng)急按鈕的流程圖
中斷端點(diǎn)有查詢間隔,它決定了usb主設(shè)備隔多久向in端點(diǎn)要數(shù)據(jù)。每隔一段時(shí)間我們可以預(yù)計(jì)到主控制器發(fā)了一個(gè)in請(qǐng)求給我們的設(shè)備端點(diǎn)3。圖7顯示了處理這些請(qǐng)求的一個(gè)簡(jiǎn)單的狀態(tài)機(jī)。只要設(shè)備被例舉了,處理器重復(fù)地執(zhí)行這一過(guò)程。為了簡(jiǎn)單起見(jiàn),該應(yīng)用程序查詢中斷腳是否有效,當(dāng)然,如果你還有其他事要微控制器處理,你會(huì)用中斷來(lái)激活do_in3函數(shù)。
狀態(tài)機(jī)使用了兩個(gè)全局變量:state和button。c宏定義了三個(gè)狀態(tài):idle, release和 wait 。狀態(tài)變量初始化為idle。如果連在max3420e的gpin0上的按鍵按下,變量button是高,否則為低。main()中的無(wú)窮循環(huán)增加一個(gè)按鍵檢查定時(shí)器,當(dāng)定時(shí)器到時(shí)它會(huì)讀一下max3420e中的gpio寄存器來(lái)決定按鍵狀態(tài)。此方法省掉了不必要的spi流量。
當(dāng)按鍵處于彈起狀態(tài)時(shí),狀態(tài)圖轉(zhuǎn)到左邊的兩個(gè)分支,不做任何事。如果按鍵在idle狀態(tài)被按下,就發(fā)一個(gè)清除桌面上活動(dòng)窗口的鍵碼。鍵碼次序是08 (windows鍵) 00 (保留)和07 (字母d)。下一個(gè)狀態(tài)轉(zhuǎn)到release,這樣就完成了。
只要max3420e把數(shù)據(jù)包送到usb,它就產(chǎn)生另一個(gè)ep3-in中斷請(qǐng)求來(lái)表示ep3-in fifo可以再一次裝載數(shù)據(jù)。然后再次進(jìn)入圖7函數(shù),此時(shí)狀態(tài)state = release ,因此函數(shù)發(fā)送序列00 00 00來(lái)表示“按鍵彈起”,下一個(gè)狀態(tài)進(jìn)入wait,意思是“等待按鍵被釋放”。{{分頁(yè)}}
現(xiàn)在函數(shù)要做的所有工作是利用wait狀態(tài)分支程序來(lái)檢測(cè)按鍵釋放。如果按鍵一直按著,程序不做任何事,當(dāng)按鍵一被釋放,狀態(tài)圖就進(jìn)到右邊的兩個(gè)分支,重新初始化state 變量為idle,使函數(shù)等候下一個(gè)按鍵按下。
占大部分運(yùn)行時(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)鍵
需要對(duì)代碼中的一些細(xì)節(jié)加以說(shuō)明。
時(shí)間敏感的usb事件
max3420e 通過(guò)在usb總線上送k狀態(tài)10ms時(shí)間發(fā)了一個(gè)遠(yuǎn)程喚醒信號(hào),為了避免用spi主控器來(lái)做設(shè)定時(shí)間這種雜活,max3420e用自己內(nèi)部來(lái)定時(shí)這個(gè)信號(hào)(事實(shí)上,所有的usb時(shí)間敏感事件),然后在時(shí)間到時(shí)給spi主控器發(fā)一個(gè)中斷。spi主控器對(duì)這些事件不必用上它自己的定時(shí)器-它只需啟動(dòng)操作,然后等待完成中斷。
ackstat位
函數(shù)rregas和wregas的功能與rreg和wreg只有一點(diǎn)不同,它們?cè)趕pi命令字中設(shè)了ack status位。spi主控器(我們的例子中是maxq2000)用這一位表示max3420e已經(jīng)完成了當(dāng)前的control請(qǐng)求服務(wù),因此用應(yīng)答它的狀態(tài)情況的方式來(lái)終止control傳輸。ackstat還扮演了一個(gè)內(nèi)部寄存器位的角色,而且由于它含在spi的命令字中,對(duì)它的操作能快速執(zhí)行且只需少量代碼。
readbytes(), writebytes()函數(shù) 這些函數(shù)使用了max3420e的數(shù)據(jù)突發(fā)能力。與每次字節(jié)尋址發(fā)兩個(gè)字節(jié)(一個(gè)命令字節(jié)和一個(gè)數(shù)據(jù)字節(jié))不同,這些函數(shù)先拉低ss#,送命令字,然后送入/送出一串字節(jié),最后把ss#來(lái)拉高結(jié)束spi傳輸。
哪里找到產(chǎn)品id
圖8. 產(chǎn)品id在這里顯示
產(chǎn)品id碼(在panic_button_enum_data.h中)是第一次插入應(yīng)急按鈕時(shí)出現(xiàn)的一小段信息。在確定應(yīng)急按鈕為hid的枚舉過(guò)程中彈出來(lái),并把它和windows的內(nèi)部驅(qū)動(dòng)聯(lián)系起來(lái)。
除了插入任何usb設(shè)備時(shí)都會(huì)聽(tīng)到一小聲“叭噠”外,后續(xù)的每個(gè)附件都不發(fā)聲。任何時(shí)候如果你想檢查設(shè)備狀態(tài),請(qǐng)打開(kāi)圖8所示屏幕。你可以右擊“我的電腦”,選擇“屬性”,“硬件”頁(yè),“設(shè)備管理器”按鈕,展開(kāi)“人機(jī)接口設(shè)備”項(xiàng),右擊“usb人機(jī)接口設(shè)備”,選擇“屬性”。
usb兼容性
查看代碼后,你可能認(rèn)為這對(duì)一個(gè)單鍵的usb設(shè)備來(lái)說(shuō)要做很多工作,因?yàn)閷?duì)任何usb設(shè)備都需要寫(xiě)一段固定代碼。幸運(yùn)的是我們對(duì)usb進(jìn)行了仔細(xì)的歸納,枚舉代碼可作為任何usb設(shè)備的一個(gè)模板(拷貝-粘貼即可)。
像所有認(rèn)真的開(kāi)發(fā)者一樣,我們希望自己的設(shè)計(jì)能夠通過(guò)usb-if認(rèn)證,以避免任何知識(shí)產(chǎn)權(quán)問(wèn)題。這個(gè)應(yīng)用已經(jīng)通過(guò)了usb命令驗(yàn)證(usbcv版本1.2.1.0)和usb-if網(wǎng)站為開(kāi)發(fā)者提供的hid測(cè)試。
圖9. usb和hid測(cè)試記錄和狀況報(bào)告
結(jié)論
如果需要設(shè)計(jì)一個(gè)usb外設(shè),可選擇max3420e。該器件提供小尺寸封裝,并可提供許多免費(fèi)程序。max3420e能夠?yàn)樵O(shè)計(jì)增加i/o口線,在支持spi的系統(tǒng)中能很好地工作。由于spi很容易通過(guò)位仿真實(shí)現(xiàn),因此可以使用任何微控制器。如果需要更高性能,可以使用高達(dá)26mhz的spi時(shí)鐘。
評(píng)論