<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
上图很好地描述了枚举阶段“获取描述符”和“设置地址”两个阶段主机和设备数据交换的过程。
五、USB的“JoyStickMouse”工作过程详细分析
1、枚举第二步:设置地址
(1)重新从复位状态开始
在第一次获取设备描述符后,程序使端点0的发送和接收都无效,状态也设置为STALLED,所以主机先发一个复位,使得端点0接收有效。虽然说在NAK和STALL状态下,端点仍然可以响应和接收SETUP包。
(2)设置地址的建立阶段:
主机先发一个SETUP令牌包,设备端EP0的SETUP标志置位。然后主机发了一个OUT包,共8个字节,里面包含设置地址的要求。
设备在检验数据后,发一个ACK握手包。同时CTR_RX置位,CTR置位。数据已经保存到RxADDR所指向的缓冲区。此时USB产生数据接收中断。
由于CTR_RX和SETUP同时置位,终端处理程序调用Setup0_Process(),所做的工作仍然是先填充pInformation结构,获取请求特征码、请求代码和数据长度。
由于设置地址不会携带数据,所以接下来调用NoData_Setup0()。执行以下代码:
else if (RequestNo == SET_ADDRESS)
{
Result = USB_SUCCESS;
}
说明设置地址没有做任何工作。
ControlState = WAIT_STATUS_IN;/* After no data stage SETUP */
USB_StatusIn(); //这句话是一个关键,它是一个宏,实际是准备好发送0字节的状态数据包。因为地址设置没有数据过程,建立阶段后直接进入状态阶段,主机发IN令牌包,设备返回0字节数据包,主机再ACK。
它对应的宏是这样的:
#define USB_StatusIn() Send0LengthData() //准备发送0字节数据
#define Send0LengthData() { _SetEPTxCount(ENDP0, 0); \
vSetEPTxStatus(EP_TX_VALID); \ //设置发送有效,发送字节数为0
}
(3)设置地址的状态阶段:
而前面把状态设置为WAIT_STATUS_IN是给IN令牌包的处理提供指示。因为建立阶段结束以后,主机接着发一个IN令牌包,设备返回0字节数据包后,进入中断。
本次中断由IN0_Process()函数来处理,追踪进入,它执行以下代码:
else if (ControlState == WAIT_STATUS_IN)
{
if ((pInformation->USBbRequest == SET_ADDRESS) && (Type_Recipient==(STANDARD_REQUEST|DEVICE_RECIPIENT)))
{
SetDeviceAddress(pInformation->USBwValue0);
pUser_Standard_Requests->User_SetDeviceAddress(); //这个函数就一个赋值语句,bDeviceState = ADDRESSED。
}
(*pProperty->Process_Status_IN)(); //这是一个空函数。
ControlState = STALLED;
}
执行设置地址操作、采用新地址后,把设备的状态改为STALLED。而在处理的出口中调用Post0_Process()函数,这个所做的工作是:
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); //将端点0的缓冲区大小设置为64字节
if (pInformation->ControlState == STALLED)
{
vSetEPRxStatus(EP_RX_STALL);
vSetEPTxStatus(EP_TX_STALL);
}
将端点0的发送和接收都设置为:STALL,这种状态下只接受SETUP令牌包。
2、枚举第三步:从新地址获取设备描述符
(1)上一阶段末尾的状态
端点0的发送和接收都设置为:STALL,只接收SETUP令牌包。
(2)建立阶段:主机发令牌包、数据包、设备ACK
产生数据接收中断,且端点0的SETUP置位,调用Setup0_Process()函数进行处理。
在Setup0_Process()中,因为主机发送了请求数据8个字节。由调用Data_Setup0()函数进行处理。首先是获取设备描述符的长度,描述符的起始地址,传送的最大字节数,根据这些参数确定本次能够传输的字节数,然后调用DataStageIn()函数进行实际的数据传输操作,设备描述符必须在本次中断中就写入发送缓冲区,因为很快就要进入数据阶段了。
在函数处理的最后:
vSetEPTxStatus(EP_TX_VALID);
USB_StatusOut();/* 本来期待IN令牌包,但用户可以取消数据阶段,一般不会用到 */
(3)数据阶段:主机发IN包,设备返回数据,主机ACK
本次操作会产生数据发送完成中断,由In0_Process(void)来处理中断,它也调用DataStageIn()函数来进行处理。
如果数据已经发送完:
ControlState = WAIT_STATUS_OUT;
vSetEPTxStatus(EP_TX_STALL); //转入状态阶段。
有可能的话:
Send0LengthData();
ControlState = LAST_IN_DATA;
Data_Mul_MaxPacketSize = FALSE; //这一次发送0个字节,状态转为最后输入阶段。
否则,继续准备数据,调整剩余字节数、发送指针位置,等待主机的下一个IN令牌包。
(4)状态阶段:主机发OUT包、0字节包,设备ACK
数据发送完成中断,调用Out0_Process(void)函数进行处理,由于在数据阶段的末尾已经设置设备状态为:WAIT_STATUS_OUT,所以处理函数基本上没有做什么事,就退出了。并将状态设为STALLED。
3、对配置描述符、字符串描述符获取过程进行简单跟踪,过程就不再一一叙述了。
4、主机设置配置。
建立阶段:主机发SETUP包、发请求数据包(DATA0包)、用户ACK。
进入CTR中断,用户调用Setup0_Process()函数进行处理,取得请求数据后,由于没有数据传输阶段,该函数调用NoData_Setup0()函数进行处理。
判断为设置配置后,调用Standard_SetInterface()函数将设备状态结构体的当前配置改为主机数据中的配置参数。同时调用用户的设置配置函数,将设备状态改为“configured”。
退出时,将控制传输状态改为:ControlState = WAIT_STATUS_IN,进入状态阶段。设备期待主机的IN令牌包,返回状态数据。
状态阶段:主机发IN令牌、设备返回0字节DATA1、主机ACK。
主机ACK之后,设备进入CTR中断,调用函数In0_Process(void)来处理。根据当前控制传输状态,该函数把状态改为“STALLED”,退出时将端点状态改为“STALL”。状态阶段完成。
5、主机类特殊请求:设置空闲
建立阶段:主机发SETUP包、发请求数据包(DATA0包)、用户ACK。
进入CTR中断,用户调用Setup0_Process()函数进行处理,取得请求数据后,由于没有数据传输阶段,该函数调用NoData_Setup0()函数进行处理。
设置空闲时一个类特殊请求,其特征码为0x21,2表示类请求而不是标准请求,1表示接收对象是接口而不是设备。
USB的底层并不支持类特殊请求,它将调用上层函数提供的函数:
if (Result != USB_SUCCESS)
{
Result = (*pProperty->Class_NoData_Setup)(RequestNo); //这里就是调用用户提供的类特殊请求的处理函数。结果发现用户提供的类特殊请求(针对无数据情况)只支持SET_PROTOCOL。针对有数据情况只支持:GET_PROTOCOL。
if ((Type_Recipient==(CLASS_REQUEST | INTERFACE_RECIPIENT))
&& (RequestNo == SET_PROTOCOL))
{
return Joystick_SetProtocol();
}
}
6、主机获取报告描述符
建立阶段:主机发SETUP包、发请求数据包(DATA0包)、用户ACK。
进入CTR中断,获取描述符是一个标准请求,但是报告描述符并不是需要通用实现的,所以在底层函数中没有实现。跟踪Setup0_Process(void)——进入Data_Setup(void)函数,它是这么处理的:
if (Request_No == GET_DESCRIPTOR)
{
if(Type_Recipient==(STANDARD_REQUEST| EVICE_RECIPIENT))
{
u8 wValue1 = pInformation->USBwValue1;
if (wValue1 == DEVICE_DESCRIPTOR)
{
CopyRoutine = pProperty->GetDeviceDescriptor;
}
else if (wValue1 == CONFIG_DESCRIPTOR)
{
CopyRoutine = pProperty->GetConfigDescriptor;
}
else if (wValue1 == STRING_DESCRIPTOR)
{
CopyRoutine = pProperty->GetStringDescriptor;
} /* End of GET_DESCRIPTOR */
}
}
可见核心函数只支持设备描述符、配置描述符以及字符串描述符。最终该函数将调用:
Result= (*pProperty->Class_Data_Setup)(pInformation->USBbRequest);
调用用户的类特殊实现来获取报告描述符,同时HID类描述符也是通过这种方式取得的。
7、主机从中断端点读取鼠标操作数据
主机会轮询设备,设备数据的准备在主函数中,用Joystick_Send(JoyState())函数来实现。
Mouse_Buffer[1] = X;
Mouse_Buffer[2] = Y;
/*copy mouse position info in ENDP1 Tx Packet Memory Area*/
UserToPMABufferCopy(Mouse_Buffer, GetEPTxAddr(ENDP1), 4);
/* enable endpoint for transmission */
SetEPTxValid(ENDP1);
使能端点1的发送,当主机的IN令牌包来的时候,SIE将数据返回给主机。同时产生 CTR中断。
在中断处理程序中,执行下列代码:
if ((wEPVal & EP_CTR_TX) != 0)
{
/* clear int flag */
_ClearEP_CTR_TX(EPindex);
(*pEpInt_IN[EPindex-1])();
} /* if((wEPVal & EP_CTR_TX) != 0) */
这是在函数指针数组中调用函数,跟踪进入:发现这个函数什么也没有做。
经过对程序执行过程的跟踪和分析,我现在对USB设备HID类的工作有了大概的了解,对ST的USB库的工作也有了初步的概念。把所有文件的源代码粗略地浏览了一遍,心里大概有了些底。但现在我还不准备阅读源代码,我先把例程在智林开发板上移植好,再详细的阅读一遍源代码。
用户377235 2014-3-13 11:43
用户376159 2011-9-1 13:57
tengjingshu_112148725 2010-4-19 09:39