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