<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
一、追踪USB大容量设备的实现流程
1、从main.c开始
(1)main函数的执行流程
Set_System(); //设置时钟、端口等。
Set_USBClock(); //设置usb的时钟
USB_Interrupts_Config(); //设置中断
Led_Config(); //设置所使用的到的灯。
MSD_Init(); //SD卡初始化
Get_Medium_Characteristics(); //获取SD块总数、每块字节数。
USB_Init(); //USB_init.c提供的初始化函数。从这里开始USB设备被主机检测到。
while (1)
{ //USB的工作都是在中断中完成的,主执行流程什么也没做。
}
(2)与鼠标例程不同的地方
在中断配置中,使能了USB高优先级中断。
NVIC_InitStructure.NVIC_IRQChannel= USB_HP_CAN_TX_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
用到了几个灯指示,这个我的开发板上用不到,就不详细看了。
MSD_Init(),是对SD卡进行初始化。该函数在msd.c中,我看了一下它的SD卡实现的代码,比我的SD函数代码齐整多了。以后有时间要把我的USB驱动好好的整理一下。不过现在就先不管了。
接下来这个函数获取SD卡的容量,这样的函数我在SD卡驱动中也实现了,改变一下调用方式就行了。
USB_Init()函数在usb_init.c库函数中,但它最终会调用user_prop.c宏的用户初始化例程。下面就追踪进去看一看。
(3)大容量存储设备的初始化
void MASS_init()
{
pInformation->Current_Configuration = 0;
PowerOn(); 连接电缆主机很快发总线复位。
_SetISTR(0);
wInterrupt_Mask = IMR_MSK;
_SetCNTR(wInterrupt_Mask); 开启复位和传输中断。
pInformation->Current_Feature = MASS_ConfigDescriptor[7];
while (pInformation->Current_Configuration == 0)
{
NOP_Process();
}
bDeviceState = CONFIGURED; //这句执行完成后,设备处于已配置状态。我先在这里加一句调试语句。
#if usb_debug
Uart_PutString(“设备已配置”);
#endif
}
2、进入复位中断
(1)先列出中断处理代码
发生总线复位中断以后,处理是在usb_prop.c的Mass_Reset()函数中完成的。
void MASS_Reset()
{
Device_Info.Current_Configuration = 0;
SetBTABLE(BTABLE_ADDRESS);
SetEPType(ENDP0, EP_CONTROL); //端点0控制端点
SetEPTxStatus(ENDP0, EP_TX_NAK); //不响应IN
SetEPRxAddr(ENDP0, ENDP0_RXADDR); //设置接收缓冲区(OUT)
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); 接收长度。
SetEPTxAddr(ENDP0, ENDP0_TXADDR); //发送缓冲区(IN)
Clear_Status_Out(ENDP0);
SetEPRxValid(ENDP0); //使能端点0的接收。
SetEPType(ENDP1, EP_BULK); //端点1批量模式
SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置发送缓冲区(IN)
SetEPTxStatus(ENDP1, EP_TX_NAK); 发送不响应。
SetEPRxStatus(ENDP1, EP_RX_DIS); //接收无效。对OUT无效
SetEPType(ENDP2, EP_BULK); //端点2批量模式
SetEPRxAddr(ENDP2, ENDP2_RXADDR); //设置接收缓冲区OUT
SetEPRxCount(ENDP2, Device_Property.MaxPacketSize);
SetEPRxStatus(ENDP2, EP_RX_VALID);
SetEPTxStatus(ENDP2, EP_TX_DIS); //发送无效,对IN无效
SetDeviceAddress(0); //使能USB接口模块。
CBW.dSignature = BOT_CBW_SIGNATURE;
Bot_State = BOT_IDLE; //命令状态机初始化为空闲状态
}
在这里没有我没有看到将批量端点设置为双缓冲模式的迹象,难道这个例程没有用它?
3、进入枚举过程
因为在鼠标例程中已经详细分析过枚举过程,这里主要是大容量设备枚举过程中不同的地方做一下分析。
(1)获取设备描述符、设置地址。
(2)获取配置描述符
(3)获取配置描述符集合,这里主要讲接口描述符分析一下。
0x09, /* bLength: Interface Descriptor size */
0x04, /* bDescriptorType: */
0x00, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints*/ 使用两个端点
0x08, /* bInterfaceClass: MASS STORAGE Class,大容量存储类 */
0x06, /* bInterfaceSubClass : SCSI transparent,SCSI传输*/
0x50, /* nInterfaceProtocol ,仅批量传输*/
4, /* iInterface: */
(4)获取字符串描述符
(5)类请求实现:
类获取逻辑盘:
一般返回0
类请求复位:
ClearDTOG_TX(ENDP1);
ClearDTOG_RX(ENDP2);
CBW.dSignature = BOT_CBW_SIGNATURE;
Bot_State = BOT_IDLE;
(6)设置配置
在用户设置的回调函数中,又调用
void Mass_Storage_SetConfiguration(void)
{
if (pInformation->Current_Configuration)
{
ClearDTOG_TX(ENDP1);
ClearDTOG_RX(ENDP2);
Bot_State = BOT_IDLE; }
}
这个工作前面已经做过了。
4、主机发命令INQUIRY
(1)首先进入批量输出中断
该中断的回调函数调用Mass_Storage_Out()进行处理。
(2)追踪进入Mass_Storage_Out()
void Mass_Storage_Out (void)
{
u8 CMD;
CMD = CBW.CB[0]; //
Data_Len = GetEPRxCount(ENDP2);
PMAToUserBufferCopy(Bulk_Data_Buff,ENDP2_RXADDR, Data_Len);
switch (Bot_State)
{
case BOT_IDLE:
CBW_Decode(); //第一次收到命令肯定调用这个解码函数。
break; //它的作用应该是填充CBW命令块封包结构
case BOT_DATA_OUT:
if (CMD == SCSI_WRITE10)
{
SCSI_Write10_Cmd();
break;
}
}
(3)追踪进入CBW_Decode()
这个函数的代码较长,我就不列举在这里了,我就分析一下本次的主要工作。
首先将用户缓冲区的数据复制到命令封包结构里面。
然后准备好状态封包结构:
CSW.dTag = CBW.dTag; //这个标志由主机生成,可以用于检查设备是否正确收到该命令。
CSW.dDataResidue = CBW.dDataLength;
然后主要是根据命令操作码,调用相应的SCSI命令处理函数。
switch (CBW.CB[0])
{
case SCSI_REQUEST_SENSE:
SCSI_RequestSense_Cmd ();
break;
case SCSI_INQUIRY:
SCSI_Inquiry_Cmd();
break;
我这里就列出了两项,实际的命令是很多的。本次主要是查询处理。
(4)追踪进入SCSI_Inquiry_Cmd()
以上函数是在usb_bot.c里面,现在跳转到usb_scsi.c里面。
这个函数的主要工作是调用Transfer_Data_Request(Inquiry_Data, Inquiry_Data_Length)来完成。
这个Inquiry_Data = Standard_Inquiry_Data,后面这个Standard_Inquiry_Data是scsi_data.c里面定义的一个数据结构,专门用于inquiry命令的返回。
u8 Standard_Inquiry_Data[] =
{
0x00, /* Direct Access Device */
0x80, /* RMB = 1: Removable Medium */
0x02, /* Version: No conformance claim to standard */
0x02, //这里圈圈的书上说应该为 0x01
36 - 4, //这里圈圈的书上说应该为 31
0x00, 0x00, 0x00, /* SCCS = 1: Storage Controller Component */
'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ',//厂商信息
'S', 'T', 'R', ' ', ' ', 'F', 'l', 'a', 's', 'h', ' ', 'D', 'i', 's', 'k', ' ',//产品信息
'1', '.', '0', ' ' //版本信息。
};
(5)追踪进入Transfer_Data_Request ()
void Transfer_Data_Request(u8* Data_Pointer, u16 Data_Len)
{
UserToPMABufferCopy(Data_Pointer, ENDP1_TXADDR, Data_Len);
SetEPTxCount(ENDP1, Data_Len);
SetEPTxStatus(ENDP1, EP_TX_VALID);
Bot_State = BOT_DATA_IN_LAST;
CSW.dDataResidue -= Data_Len;
CSW.bStatus = CSW_CMD_PASSED; //设置好命令状态封包信息。
}
(6)接下来,主机会发IN,取走查询信息。并进入批量输入中断。
在该中断中,将调用函数Mass_Storage_In (void)
void Mass_Storage_In (void)
{
switch (Bot_State)
{
case BOT_CSW_Send:
case BOT_ERROR:
Bot_State = BOT_IDLE;
SetEPRxStatus(ENDP2, EP_RX_VALID);/* enable the Endpoint to recive the next cmd*/
break;
case BOT_DATA_IN_LAST:
Set_CSW (CSW_CMD_PASSED, SEND_CSW_ENABLE);
SetEPRxStatus(ENDP2, EP_RX_VALID);
break;
default:
break;
}
}
然后设备的命令状态机状态变为Set_CSW()这个函数所设置的状态,一般为BOT_CSW_Send。
(7)追踪进入Set_CSW()
在该函数中:
void Set_CSW (u8 CSW_Status, u8 Send_Permission)
{
CSW.dSignature = BOT_CSW_SIGNATURE;
CSW.bStatus = CSW_Status; //命令状态封包数据已经准备好
UserToPMABufferCopy((&CSW),ENDP1_TXADDR, DATA_LENGTH);
SetEPTxCount(ENDP1, CSW_DATA_LENGTH);
Bot_State = BOT_ERROR;
if (Send_Permission)
{
Bot_State = BOT_CSW_Send;
SetEPTxStatus(ENDP1, EP_TX_VALID);
}
}
然后,主机再次发IN令牌包,取走命令状态封包。
case BOT_ERROR:
Bot_State = BOT_IDLE;
SetEPRxStatus(ENDP2, EP_RX_VALID);
端点2的接收又被使能,重新进入接收命令状态。
用户156578 2010-5-13 10:35