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;
USB_StatusIn(); //这句话是一个关键,它是一个宏,实际是准备好发送0字节的状态数据包。因为地址设置没有数据过程,建立阶段后直接进入状态阶段,主机发IN令牌包,设备返回0字节数据包,主机再ACK。
它对应的宏是这样的: #defineUSB_StatusIn() Send0LengthData() //准备发送0字节数据 #defineSend0LengthData() { _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();
(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[size=12p]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))
{
returnJoystick_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;
}
}
}
可见核心函数只支持设备描述符、配置描述符以及字符串描述符。最终该函数将调用:
Result=(*pProperty->Class_Data_Setup)(pInformation->USBbRequest);
调用用户的类特殊实现来获取报告描述符,同时HID类描述符也是通过这种方式取得的。 7、主机从中断端点读取鼠标操作数据 主机会轮询设备,设备数据的准备在主函数中,用Joystick_Send(JoyState())函数来实现。
Mouse_Buffer[1] = X;
Mouse_Buffer[2] = Y;
UserToPMABufferCopy(Mouse_Buffer, GetEPTxAddr(ENDP1),4);
SetEPTxValid(ENDP1);
使能端点1的发送,当主机的IN令牌包来的时候,SIE将数据返回给主机。同时产生 CTR中断。 在中断处理程序中,执行下列代码:
if ((wEPVal & EP_CTR_TX) !=0)
{
_ClearEP_CTR_TX(EPindex);
(*pEpInt_IN[EPindex-1])();
}
这是在函数指针数组中调用函数,跟踪进入:发现这个函数什么也没有做。 经过对程序执行过程的跟踪和分析,我现在对USB设备HID类的工作有了大概的了解,对ST的USB库的工作也有了初步的概念。把所有文件的源代码粗略地浏览了一遍,心里大概有了些底。但现在我还不准备阅读源代码,我先把例程在智林开发板上移植好,再详细的阅读一遍源代码。
|