嵌入式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的数据缓冲区作为中介的。