嵌入式USB主機(jī)設(shè)計(jì)(硬件設(shè)計(jì)和軟件設(shè)計(jì))
嵌入式USB主機(jī)硬件設(shè)計(jì)
選用廉價(jià)的51系列單片機(jī)(89C52)控制USB主機(jī)接口芯片SL811HS,同時(shí),通過(guò)MAX232芯片與PC機(jī)通信。硬件接線(xiàn)示意圖如下所示:
嵌入式USB主機(jī)軟件設(shè)計(jì)
下面將從底層到高層詳細(xì)地介紹本系統(tǒng)的軟件設(shè)計(jì)。
4.2.1單片機(jī)讀寫(xiě)SL811HS
4.2.1.1讀取SL811HS內(nèi)存的數(shù)據(jù)
根據(jù)SL811HS的讀寫(xiě)時(shí)序要求,讀取數(shù)據(jù)前首先發(fā)送地址:
void SetHostAddress(char AddressP)
{
P_BUS=AddressP;/*數(shù)據(jù)總線(xiàn)發(fā)送地址,但此時(shí)地址還不會(huì)被SL811HS接收*/
P_CTRL=0x90;/*設(shè)置總線(xiàn)控制信號(hào)為SL811HS接收地址信號(hào),具體含義如下所示:*/
/*P_CTRL ;0x90, 98, F0
P_CTRL.0 ; - - -
P_CTRL.1 ; - - -
P_CTRL.2 ; - - -
A0 ; 0 1 0
nRST ; 1 1 1
nCS ; 0 0 1
nWR ; 0 0 1
nRD ; 1 1 1 */
nWR=1;
nCS=1; /*地址傳輸完畢后,關(guān)閉片選、寫(xiě)等信號(hào)*/
P_CTRL=0xF0;
}
地址發(fā)送完畢之后,SL811HS就接到了需要讀取的內(nèi)存單元地址(包括寄存器的地址)。緊接著單片機(jī)就可以讀取數(shù)據(jù):
unsigned char HostRead(void)
{
A0=1; /* 滿(mǎn)足SL811HS的時(shí)序要求,先保證A0和nCS的有效*/
NCS=0;
P_CTRL=0x58; /*設(shè)置控制位信號(hào),讀取SL811HS內(nèi)的數(shù)據(jù)*/
/*P_CTRL ;0x90, 58, F0
P_CTRL.0 ; - - -
P_CTRL.1 ; - - -
P_CTRL.2 ; - - -
A0 ; 0 1 0
nRST ; 1 1 1
nCS ; 0 0 1
nWR ; 0 1 1
nRD ; 1 0 1 */
return P_BUS; /*函數(shù)返回讀取的SL811HS內(nèi)存的數(shù)據(jù)*/
}
4.2.1.2寫(xiě)入數(shù)據(jù)到SL811HS內(nèi)存
與讀數(shù)據(jù)類(lèi)似,單片機(jī)要往SL811HS的內(nèi)存單元寫(xiě)數(shù)據(jù)時(shí),也要首先發(fā)送地址,然后再發(fā)送要寫(xiě)入的數(shù)據(jù)。為了簡(jiǎn)便起見(jiàn),寫(xiě)數(shù)據(jù)過(guò)程中發(fā)送地址和數(shù)據(jù)的功能都放在一個(gè)函數(shù)中執(zhí)行。
Void HostWrite(char HostWriteAddress, char WriteConstent)
{ /*參數(shù)HostWriteAddress輸入要寫(xiě)入數(shù)據(jù)的SL811HS內(nèi)存的地址,WriteConstent為要寫(xiě)入的地址*/
P_BUS=HostWriteAddress; /*準(zhǔn)備好需要發(fā)送的地址*/
P_CTRL=0x90;
/*P_CTRL ;0x90, 98, F0
P_CTRL.0 ; - - -
P_CTRL.1 ; - - -
P_CTRL.2 ; - - -
A0 ; 0 1 0
nRST ; 1 1 1
nCS ; 0 0 1
nWR ; 0 0 1
nRD ; 1 1 1 */
nWR=1;
nCS=1;
P_BUS=WriteContent; /*準(zhǔn)備好需要發(fā)送的數(shù)據(jù)*/
P_CTRL=0x98; /*重新安排好控制信號(hào),發(fā)送數(shù)據(jù)*/
P_CTRL=0xF4;
}
4.2.1.3讀寫(xiě)批量數(shù)據(jù)
有時(shí)單片機(jī)和SL811HS之間要進(jìn)行批量數(shù)據(jù)的傳輸,為方便操作,設(shè)計(jì)了能夠批量進(jìn)行數(shù)據(jù)讀或?qū)懙暮瘮?shù):
/*批量寫(xiě)*/
void HostBulkWrite(char addr, unsigned char *s, char c)
{ /*參數(shù)addr為SL811HS中寫(xiě)入數(shù)據(jù)的起始地址,*s為單片機(jī)內(nèi)存放的需要寫(xiě)入的數(shù)據(jù)緩沖區(qū),c為總共要寫(xiě)入的字節(jié)數(shù)*/
if(c<=0) return;
while(c--)
{
HostWrite(addr++,*s++);
}
}
/*批量讀*/
void HostBulkRead(char addr, unsigned char *s, char c)
{ /*參數(shù)addr為SL811HS中讀取數(shù)據(jù)的起始地址,*s為單片機(jī)內(nèi)存放讀取來(lái)的數(shù)據(jù)的數(shù)據(jù)緩沖區(qū),c為總共要讀入的字節(jié)數(shù)*/
if(c<=0) return;
while(c--)
{
SetHostAddress(addr++);
*s++=HostRead();
}
}
4.2.2階段USB傳輸?shù)膶?shí)現(xiàn)
4.2.2.1 SL811HS的初始化
初始化主要是對(duì)SL811HS的部分內(nèi)部寄存器進(jìn)行設(shè)置:
Void SL811HS_Init(void)
{
HostWrite(IntEna,0x20);
HostWrite(CSOFcnt, 0xAE);
HostWrite(CtrlReg, 0x08);
HostWrite(CtrlReg, 0x00);
HostWrite(CSOFcnt, 0xAE);
HostWrite(CtrlReg, 0x08);
DelayMs(10);
HostWrite(CtrlReg, 0x00);
DelayMs(1);
HostWrite(IntStatus, 0xFF);
}
4.2.2.2三種階段USB傳輸?shù)膶?shí)現(xiàn)
三種階段USB傳輸都可以由這個(gè)函數(shù)(下稱(chēng)“階段傳輸實(shí)現(xiàn)函數(shù)”)實(shí)現(xiàn):
void USB_Transaction(unsigned char PID, unsigned char EP_Address, unsigned char Address, int Length, char *pDataBuf);
4.2.2.2.1發(fā)送或接收前的準(zhǔn)備工作
4.2.2.2.1.1設(shè)置EP0Status寄存器
本設(shè)計(jì)涉及到了多種階段的USB傳輸,但這里需要考慮的只有3種,分別是建立(SETUP)、數(shù)據(jù)輸入(IN)和數(shù)據(jù)輸出(OUT)階段。階段傳輸實(shí)現(xiàn)函數(shù)的輸入?yún)?shù)中,PID就是用來(lái)區(qū)別這3種傳輸階段。
#define PID_SETUP 0x2D
#define PID_IN 0x69
#define PID_OUT 0xE1
階段傳輸實(shí)現(xiàn)函數(shù)的第二個(gè)需要輸入的參數(shù)就是端點(diǎn)號(hào)EP_Address,大小為1字節(jié)(實(shí)際只有低4位有效,高4位為0),類(lèi)型為unsigned char 。U盤(pán)等類(lèi)似的USB Mass Storage類(lèi)設(shè)備一般具有3個(gè)端點(diǎn):一個(gè)是端點(diǎn)0,用于處理控制傳輸;另一個(gè)是批量輸出Bulk_OUT端點(diǎn),該端點(diǎn)用于接收主機(jī)發(fā)來(lái)的批量數(shù)據(jù),端點(diǎn)號(hào)有設(shè)備定義;還有一個(gè)就是批量輸入Bulk_IN端點(diǎn),用于給÷向主機(jī)發(fā)送批量數(shù)據(jù),其端點(diǎn)號(hào)也由設(shè)備定義。
從硬件角度來(lái)講,程序需要把PID和EP_Address組合在一起后寫(xiě)入EP0Status寄存器。
unsigned char PID_EPA; /*用于儲(chǔ)存PIN和EP_Address的組合值*/
PID_EPA=PID&0x0F; /*PID的高4位位校驗(yàn)碼,低4位為有效值*/
PID_EPA=(PID_EPA<<4)+EP_Address; /*按照EP0Status寄存器的要求合并PID和EP_Address*/
HostWrite(EP0Status,PID_EPA);
4.2.2.2.2設(shè)置EP0Counter寄存器
階段傳輸實(shí)現(xiàn)函數(shù)的第三個(gè)需要輸入的參數(shù)是設(shè)備的地址Address,大小為1字節(jié),類(lèi)型為unsigned char。
HostWrite(EP0Counter,Address);/*設(shè)備地址的D7位值為0,D6~D0位代表地址*/
4.2.2.2.3設(shè)置EP0XferLen寄存器
第四個(gè)參數(shù)是發(fā)送或接收的數(shù)據(jù)的長(zhǎng)度Length,大小為2字節(jié),類(lèi)型為int。這個(gè)長(zhǎng)度還需要和相應(yīng)端點(diǎn)的最大包尺寸MaxPacketSize進(jìn)行比較。如果Length小于MaxPacketSize,就說(shuō)明需要發(fā)送或接收的數(shù)據(jù)長(zhǎng)度比相應(yīng)端點(diǎn)的最大包尺寸還小,因此,主機(jī)和該端點(diǎn)之間只要進(jìn)行一次數(shù)據(jù)傳輸就可以實(shí)現(xiàn)數(shù)據(jù)的發(fā)送或接收。反之,如果Length大于MaxPacketSize,那么就需要將發(fā)送或接收的數(shù)據(jù)進(jìn)行分割,第一批發(fā)送或接收的數(shù)據(jù)長(zhǎng)度就是MaxPacketSize,剩下的數(shù)據(jù)就利用 SL811HS的“乒乓”機(jī)制進(jìn)行發(fā)送。最后需要把實(shí)際要發(fā)送的數(shù)據(jù)長(zhǎng)度寫(xiě)入SL811HS的EP0XferLen寄存器中。
/*定義變量CurentLength,用于保存當(dāng)前需要發(fā)送的數(shù)據(jù)長(zhǎng)度*/
if(Length>MaxPacketSize)
{
CurrentLength=MaxPacketSize;
}
else
CurrentLength=Length;
HostWrith(EP0XferLen,(unsigned char)CurrentLength);/*注意這里的變量類(lèi)型轉(zhuǎn)換*/
4.2.2.2.4設(shè)置EP0Address寄存器
最后一個(gè)需要確定是發(fā)送或接收數(shù)據(jù)的緩沖地址*pDataBuf,大小為1字節(jié)。
在這里pDataBuf是指向單片機(jī)內(nèi)存單元的指針,但實(shí)際讀寫(xiě)數(shù)據(jù)是要以SL811HS的數(shù)據(jù)緩沖區(qū)作為中介的。
為加快數(shù)據(jù)傳輸,把SL811HS的數(shù)據(jù)緩沖區(qū)分成兩部分:SL811HS_Buf0和SL811HS_Buf1。SL811HS_Buf0的起始地址就可以定為0x10。而SL811HS_Buf1的地址就根據(jù)端點(diǎn)最大包尺寸進(jìn)行調(diào)整。
Unsigned char SL811_HS_Buf0 =0x10, SL811HS_Buf1;
If(Length>MaxPackeSize)
{
SL811HS_Buf1= SL811HS_Buf0+MaxPacketSize;
}
HostWrite(EP0Address, SL811HS_Buf0);/*當(dāng)前數(shù)據(jù)發(fā)送從SL811HS_Buf 0開(kāi)始*/
如果主機(jī)要發(fā)送數(shù)據(jù)給設(shè)備,就需要把*pDataBuf中的數(shù)據(jù)復(fù)制到SL811HS的數(shù)據(jù)緩沖區(qū)中:
HostBulkWrite(SL811HS_Buf0,pDataBuf,CurrentLength);
如果是主機(jī)接收數(shù)據(jù),那么在以下的處理中,就會(huì)把SL811HS緩沖區(qū)中的接收到的設(shè)備的數(shù)據(jù)通過(guò)HostBulkRead()函數(shù)復(fù)制到單片機(jī)的緩沖區(qū)中。
HostBulkRead(SL811HS_Buf0,pDataBuf,CurrentLength);
4.2.2.2.5啟動(dòng)發(fā)送或接收
啟動(dòng)USB數(shù)據(jù)的發(fā)送或接收實(shí)際上是通過(guò)向SL811HS的EP0Control寄存器發(fā)送命令字CmdWord來(lái)實(shí)現(xiàn)的。
首先,PID等參數(shù)的不同,CmdWord的值也不同,根據(jù)EP0Control寄存器每一位的屬性,有如下配置程序:
unsigned char CmdWord;
if(PID==PID_SETUP)
{
CmdWord=0x03; /*控制傳輸?shù)腟ETUP事務(wù)*/
}
else
{
if(EP_Address==0)
{
if(PID==PID_IN)
{
CmdWord=0x47; /*控制傳輸?shù)妮斎隝N事務(wù)*/
}
else
{
CmdWord=0x43; /*控制傳輸?shù)腛UT事務(wù)*/
}
}
else
{
if(PID==PID_IN)
{
CmdWord=0x07; /*批量傳輸IN事務(wù)*/
}
else
{
CmdWord=0x03; /*批量傳輸OUT事務(wù)*/
}
}
}
將CmdWord命令字發(fā)送到SL811HS的EP0Control寄存器后,就啟動(dòng)了數(shù)據(jù)包的發(fā)送或接收了:
HostWrite(IntStatus, 0xFF); /*清除中斷狀態(tài)位*/
HostWrite(EP0Control, CmdWord);
剩下的工作就是查詢(xún)SL811HS的IntStatus寄存器,以查看發(fā)送或接收的完成情況,有需要時(shí),最后還可以查看EP0status獲取握手包的有關(guān)信息,但其實(shí)所有的握手包信息都是有硬件自動(dòng)完成的。
4.2.3事務(wù)USB傳輸?shù)膶?shí)現(xiàn)
4.2.3.1控制傳輸
包含了三個(gè)階段:建立階段、可選數(shù)據(jù)階段以及狀態(tài)階段。
Void Control_Transfer(pRequestCMD RequestCMD, unsigned char* pDataBuf_x);
4.2.3.1.1建立階段的實(shí)現(xiàn)
任務(wù)就是發(fā)送建立的8字節(jié)請(qǐng)求命令,命令的數(shù)據(jù)結(jié)構(gòu)為(注意該段定義是放在Control_Transfer()函數(shù)之外的):
typedef struct{
unsigned char bmRequest Type;
unsigned char bRequest;
unsigned int wValue;
unsigned int wIndex;
unsigned int wLength;
}REQUESTCMD,*pRequestCMD;
在這里只需調(diào)用一次USB_Transaction()函數(shù)即可:
USB_Transaction(PID_SETUP, 0 , Device_Address, 0x08, (char *)RequestCMD);
/*發(fā)送的令牌為PID_SETUP,端點(diǎn)號(hào)為0,設(shè)備地址為Device_Address,發(fā)送數(shù)據(jù)長(zhǎng)度為8字節(jié),發(fā)送內(nèi)容為相應(yīng)的請(qǐng)求命令*/
4.2.3.1.2可選數(shù)據(jù)階段的實(shí)現(xiàn)
注意數(shù)據(jù)傳輸方向,實(shí)現(xiàn)過(guò)程如下:
if(RequestCMD->wLength)
{
if(RequestCMD->bmRequestType & 0x80) /*判斷為PID_OUT*/
{
USB_Transaction(PID_OUT, 0, Device_Address, RequestCMD->wLength, pDataBuf_x);
}
else /*判斷為PID_IN*/
{
USB_Transaction(PID_IN, 0, Device_Address, RequestCMD->wLength, pDataBuf_x);
}
}
4.2.3.1.3狀態(tài)信息階段的實(shí)現(xiàn)
在需要時(shí)(如可選數(shù)據(jù)階段為IN),主機(jī)發(fā)送控制傳輸?shù)臓顟B(tài)信息:
USB_Transaction(PID_OUT, 0, Device_Address, 0, pDataBuf_x);
4.2.3.2批量傳輸
類(lèi)似與控制傳輸中的可選數(shù)據(jù)階段,有兩個(gè)函數(shù),分別對(duì)應(yīng)于批量傳輸IN和批量傳輸OUT:
void Bulk_Transfer_IN(int Length_bi, unsigned char* pDataBuf_bi)
{
USB_Transaction(PID_IN,EP_Bulk_IN,Device_Address, Length_bi, pDataBuf_bi);
}
void Bulk_Transfer_OUT(int Length_bo, unsigned char* pDataBuf_bo)
{
USB_Transaction(PID_OUT,EP_Bulk_OUT,Device_Address, Length_bi, pDataBuf_bo);
}
4.2.4 USB設(shè)備枚舉的實(shí)現(xiàn)
現(xiàn)在,各種枚舉所需的傳輸實(shí)現(xiàn)函數(shù)已經(jīng)好了,要實(shí)現(xiàn)USB請(qǐng)求動(dòng)作進(jìn)而實(shí)現(xiàn)設(shè)備枚舉是很容易的了。下面是兩個(gè)典型請(qǐng)求動(dòng)作的實(shí)現(xiàn)函數(shù):
/*獲取描述符請(qǐng)求命令*/
void Get_Descriptor(unsigned int wValue_d, unsigned char Length_d, unsigned char * pDataBuf_d)
{
REQUESTCMD RequestCMD; /*建立該請(qǐng)求命令的結(jié)構(gòu)*/
RequestCMD.bmRequestType=0x80; /*填入該請(qǐng)求命令的內(nèi)容*/
RequestCMD.bRequest=GET_DESCRIPTOR;
RequestCMD.wValue=wValue_d;
RequestCMD.wIndex=0;
RequestCMD.wLength=Length_d;
Control_Transfer(&RequstCMD,pDataBuf_d);
}
/*設(shè)置設(shè)備地址請(qǐng)求命令*/
void Set_Address(unsigned int Device_address_e)
{
REQUESTCMD RequestCMD; /*建立該請(qǐng)求命令的結(jié)構(gòu)*/
RequestCMD.bmRequestType=0x00; /*填入該請(qǐng)求命令的內(nèi)容*/
RequestCMD.bRequest=SET_ADDRESS;
RequestCMD.wValue=w Device_address_e;
RequestCMD.wIndex=0;
RequestCMD.wLength=0;
Control_Transfer(&RequstCMD,0);
}
評(píng)論
查看更多