1、枚举第一步:获取设备的描述符
1)先要允许数据传输完成中断;
从USB_init()开始
PowerOn(); //这句话执行完,主机检测到设备,并且能够响应复位中断了 _SetISTR(0); //清除挂起的中断
wInterupt_Mask = IMR_MSK; _SetCNTR(wInterrupt_Mask); //设置中断屏蔽 //上面两句将允许所有USB中断
bDeviceState = UNCONNECTED; //设备状态值,当前为未连接状态
|
2)主机获取描述符;
主机进入控制传输的第一阶段:建立事务、发送setup令牌包、发送请求数据包、设备发送ACK包。
主机对地址0、端点0发出SETUP令牌包,首先端点0寄存器的第11位SETUP位置位,表明收到了setup令牌包。
由于此时端点0数据接收有效,所以接下来主机的请求数据包被SIE保存到端点0描述符表的
RxADDR里面,收到的字节数保存到RxCount里面。
端点0寄存器的CTR_RX被置位为1,ISTR的CTR置位为1,DIR=1,EP_ID=0,表示端点0
接收到主机来的请求数据。此时设备已经ACK主机,将触发正确传输完成中断,下面进入中断看一看。
//* Function Name : CTR_LP. //* Description : Low priority Endpoint Correct Transfer interrupt's service routine. void CTR_LP(void) { __IO uint16_t wEPVal = 0; while (((wIstr = _GetISTR()) & ISTR_CTR) != 0) /* stay in loop while pending interrupts */ { EPindex = (uint8_t)(wIstr & ISTR_EP_ID); /* extract low priority endpoint number :获取指针传输针对的端点号*/ if (EPindex == 0) /*如果是端点0,这里确实是端点0*/ { /* Decode and service control endpoint interrupt calling related service routine - (Setup0_Process, In0_Process, Out0_Process) */
SaveRState = _GetENDPOINT(ENDP0); /* save RX & TX status and set both to NAK:保存端点0状态,原本是有效状态 */ SaveTState = SaveRState & EPTX_STAT; SaveRState &= EPRX_STAT; _SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK); /*在本次数据处理好之前,对主机发来的数据包以NAK回应*/ if ((wIstr & ISTR_DIR) == 0) /* DIR bit = origin of the interrupt:如果是IN令牌包,数据被取走*/ { /* DIR = 0 */ /* DIR = 0 => IN int :DIR = 0时,是输入中断*/ /* DIR = 0 implies that (EP_CTR_TX = 1) always :DIR为0总是意味着EP_CTR_TX为1*/ _ClearEP_CTR_TX(ENDP0); /*清除传输完成标志*/ In0_Process(); _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState); /* before terminate set Tx & Rx status */ return; } else /*DIR = 1时,要么是SETUP包,要么是OUT包*/ { /* DIR = 1 */ /* DIR = 1 & CTR_RX => SETUP or OUT int:DIR = 1时,是输出中断 */ /* DIR = 1 & (CTR_TX | CTR_RX) => 2 int pending */ wEPVal = _GetENDPOINT(ENDP0); //获取端点0的状态 if ((wEPVal & EP_SETUP) != 0) //我们的程序会执行到这里 { _ClearEP_CTR_RX(ENDP0); /* SETUP bit kept frozen while CTR_RX = 1 :*/ Setup0_Process(); //主要是调用该程序来处理主机请求 _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);/* before terminate set Tx & Rx status */ return; } else if ((wEPVal & EP_CTR_RX) != 0) //暂时不执行的代码先删除掉 { _ClearEP_CTR_RX(ENDP0); /* clear int flag:清除接收端点0标志 */ Out0_Process(); _SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);/* before terminate set Tx & Rx status */ return; } } } ................................//其他端点处理代码 } }
|
//* Function Name : CTR_HP. //* Description : High Priority Endpoint Correct Transfer interrupt's service routine.void CTR_HP(void) { uint32_t wEPVal = 0;
while (((wIstr = _GetISTR()) & ISTR_CTR) != 0) { _SetISTR((uint16_t)CLR_CTR); /* clear CTR flag:首先清除传输完成标志 */ EPindex = (uint8_t)(wIstr & ISTR_EP_ID); /* extract highest priority endpoint number */ wEPVal = _GetENDPOINT(EPindex); /* process related endpoint register: */
if ((wEPVal & EP_CTR_RX) != 0) { _ClearEP_CTR_RX(EPindex); /* clear int flag */ (*pEpInt_OUT[EPindex-1])(); /* call OUT service function */- }
else if ((wEPVal & EP_CTR_TX) != 0) /* if((wEPVal & EP_CTR_RX) */ { _ClearEP_CTR_TX(EPindex); /* clear int flag */ (*pEpInt_IN[EPindex-1])(); /* call IN service function */- }
- }
}
|
3)Setup0_Process()函数的执行分析;
//* Function Name : Setup0_Process //* Description : Get the device request data and dispatch to individual process. //* Return : Post0_Process. uint8_t Setup0_Process(void) { - union
{ uint8_t* b; uint16_t* w; } pBuf; uint16_t offset = 1;
pBuf.b = PMAAddr + (uint8_t *)(_GetEPRxAddr(ENDP0) * 2); /* *2 for 32 bits addr :这是取得端点0接收缓冲区的起始地址*/ -
//PMAAddr是包缓冲区起始地址,_GetEPRxAddr(ENDP0)获得端点0描述符表里的接收缓冲区地址。 //乘以2的原因是,描述符里地址项为16位,使用的是相对偏移。
if (pInformation->ControlState != PAUSE) { pInformation->USBbmRequestType = *pBuf.b++; /* bmRequestType:请求类型,表示方向和接收对象(设备、接口还是端点)此时为80,表明设备到主机。*/ pInformation->USBbRequest = *pBuf.b++; /* bRequest:请求代码,第一次时应该为6,表明主机要获取设备描述符。 */ pBuf.w += offset; /* word not accessed because of 32 bits addressing */ pInformation->USBwValue = ByteSwap(*pBuf.w++); /* wValue */ pBuf.w += offset; /* word not accessed because of 32 bits addressing */ pInformation->USBwIndex = ByteSwap(*pBuf.w++); /* wIndex */ pBuf.w += offset; /* word not accessed because of 32 bits addressing */ pInformation->USBwLength = *pBuf.w; /* wLength */ }
pInformation->ControlState = SETTING_UP;
if (pInformation->USBwLength == 0) { NoData_Setup0(); /* Setup with no data stage */ } else { Data_Setup0(); /* Setup with data stage:这次是数据传输的,所以有进入该函数 */ }
return Post0_Process(); }
|
这个函数执行的时候,主机发来的请求数据包已经存在RxADDR缓冲区了。大部分的标志位已经清除,除了SETUP位,这个位将由下一个令牌包自动清除。
4)Data_Setup0()函数的执行分析;
//* Function Name : Data_Setup0. //* Description : Proceed the processing of setup request with data stage. void Data_Setup0(void) { uint8_t *(*CopyRoutine)(uint16_t); RESULT Result; uint32_t Request_No = pInformation->USBbRequest; uint32_t Related_Endpoint, Reserved; uint32_t wOffset, Status;
CopyRoutine = NULL; //这是一个函数指针,由用户提供 wOffset = 0; if (Request_No == GET_DESCRIPTOR) //如果是获取描述符 { if (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)) { uint8_t 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 */ } } /*GET STATUS*/ else if ((Request_No == GET_STATUS) && (pInformation->USBwValue == 0) && (pInformation->USBwLength == 0x0002) && (pInformation->USBwIndex1 == 0)) { .... } /*GET CONFIGURATION*/ else if (Request_No == GET_CONFIGURATION) { .... } /*GET INTERFACE*/ else if (Request_No == GET_INTERFACE) { .... } if (CopyRoutine) { pInformation->Ctrl_Info.Usb_wOffset = wOffset; pInformation->Ctrl_Info.CopyData = CopyRoutine; /* sb in the original the cast to word was directly */ /* now the cast is made step by step */ (*CopyRoutine)(0); //这个函数在这里调用的目的是设置pInformation中需要写入的描述符的长度 Result = USB_SUCCESS; } else { .... } if (pInformation->Ctrl_Info.Usb_wLength == 0xFFFF) { .... } if ((Result == USB_UNSUPPORT) || (pInformation->Ctrl_Info.Usb_wLength == 0)) { .... } if (ValBit(pInformation->USBbmRequestType, 7)) /* Device ==> Host :此时为80,说明方向是IN*/ { __IO uint32_t wLength = pInformation->USBwLength; //这个一般是64 if (pInformation->Ctrl_Info.Usb_wLength > wLength) /* Restrict the data length to be the one host asks */ { pInformation->Ctrl_Info.Usb_wLength = wLength; //设备描述符长度18 } else if (pInformation->Ctrl_Info.Usb_wLength < pInformation->USBwLength) { ...... } pInformation->Ctrl_Info.PacketSize = pProperty->MaxPacketSize; DataStageIn(); //调用这个函数实现描述符的输出准备 } else { .... } return; }
|
5)DataStageIn()函数的执行分析;
- //* Function Name : DataStageIn.
//* Description : Data stage of a Control Read Transfer. void DataStageIn(void) { ENDPOINT_INFO *pEPinfo = &pInformation->Ctrl_Info; uint32_t save_wLength = pEPinfo->Usb_wLength; uint32_t ControlState = pInformation->ControlState;
uint8_t *DataBuffer; uint32_t Length;
if ((save_wLength == 0) && (ControlState == LAST_IN_DATA)) { if(Data_Mul_MaxPacketSize == TRUE) { /* No more data to send and empty packet */ Send0LengthData(); ControlState = LAST_IN_DATA; Data_Mul_MaxPacketSize = FALSE; } else { /* No more data to send so STALL the TX Status*/ ControlState = WAIT_STATUS_OUT; vSetEPTxStatus(EP_TX_STALL); } goto Expect_Status_Out; }
Length = pEPinfo->PacketSize; ControlState = (save_wLength <= Length) ? LAST_IN_DATA : IN_DATA;
if (Length > save_wLength) { Length = save_wLength; }
//以下是主要执行代码: DataBuffer = (*pEPinfo->CopyData)(Length); //取得用户描述符缓冲区的地址,这里共18个字节 - UserToPMABufferCopy(DataBuffer, GetEPTxAddr(ENDP0), Length); //将设备描述符复制到用户的发送缓冲区
- SetEPTxCount(ENDP0, Length); //设置发送字节的数目:18
pEPinfo->Usb_wLength -= Length; //等于0 pEPinfo->Usb_wOffset += Length; //偏移到18 vSetEPTxStatus(EP_TX_VALID); //使能端点发送,只要主机的IN令牌包一来,SIE就会将描述符返回给主机
USB_StatusOut();/* Expect the host to abort the data IN stage :使能接收也有效,主机可以取消IN*/
Expect_Status_Out: pInformation->ControlState = ControlState; }
|
6)执行流程返回到CTR_LP(void);
_SetEPRxStatus(ENDP0, SaveRState);
_SetEPTxStatus(ENDP0, SaveTState); //因为vSetEPTxStatus(EP_TX_VALID)实际改变了SaveTState,所以此时端点发送已经使能了。
return;
7)主机的IN令牌包;
获取描述符的控制传输进入第二阶段,主机首先发送一个IN令牌包,由于端点0发送有效,SIE将数据返回主机。
主机方返回一个ACK后,主机发送数据的CTR标志置位,DIR = 0,EP_ID = 0,表明主机正确收到了用户发过去的描述符。固件程序由此进入中断。
此时是由IN引起的。
主要是调用In0_Process()完成剩下的工作。
8)追踪进入函数In0_Process();
此时实际上设备返回描述符已经成功了。
这一次还是调用DataStageIn()函数,但是目的只是期待主机的0状态字节输出了。
//* Function Name : In0_Process //* Description : Process the IN token on all default endpoint. //* Return : Post0_Process. uint8_t In0_Process(void) { uint32_t ControlState = pInformation->ControlState;
if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA)) { DataStageIn(); //第一次取设备描述符,只取一次。此次调用后,当前 //状态变为WAIT_STATUS_OUT,表明设备等待状态过程,主机输出0字节 ControlState = pInformation->ControlState; /* ControlState may be changed outside the function */ } - else if (ControlState == WAIT_STATUS_IN)
{ - ....
} - else
{ - ....
- }
- pInformation->ControlState = ControlState;
- return Post0_Process(); //返回时调用这个函数,没做什么事
}
|
9)进入状态过程;
主机收到18字节描述符后,进入状态事务过程,此时过程的令牌包为OUT,字节数为0,只需要用户返回一个ACK。
所以中断处理程序会进入Out0_Process()。
//* Function Name : Out0_Process //* Description : Process the OUT token on all default endpoint. //* Return : Post0_Process. uint8_t Out0_Process(void) { uint32_t ControlState = pInformation->ControlState;
if ((ControlState == OUT_DATA) || (ControlState == LAST_OUT_DATA)) { DataStageOut(); ControlState = pInformation->ControlState; /* may be changed outside the function */ } //由于此时状态为WAIT_STATUS_OUT,所以执行下面代码
- else if (ControlState == WAIT_STATUS_OUT)
{ (*pProperty->Process_Status_OUT)(); //这个是空函数,什么都不做 ControlState = STALLED; //状态转为STALLED } - else if ((ControlState == IN_DATA) || (ControlState == LAST_IN_DATA))
{ /* host aborts the transfer before finish */ ControlState = STALLED; } - /* Unexpect state, STALL the endpoint */
else { ControlState = STALLED; }
pInformation->ControlState = ControlState;
return Post0_Process(); }
|
获取设备描述符后,主机再次复位设备,设备又进入初始状态。
五、USB的“JoyStickMouse”工作过程详细分析(3)
2、枚举第二步:设置地址
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()。执行以下代码:
//* Function Name : NoData_Setup0. //* Description : Proceed the processing of setup request without data stage. void NoData_Setup0(void) { RESULT Result = USB_UNSUPPORT; uint32_t RequestNo = pInformation->USBbRequest; uint32_t ControlState;
if (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)) { /* Device Request*/ if (RequestNo == SET_CONFIGURATION) /* SET_CONFIGURATION*/ { Result = Standard_SetConfiguration(); } else if (RequestNo == SET_ADDRESS) /*SET ADDRESS*/ { if ((pInformation->USBwValue0 > 127) || (pInformation->USBwValue1 != 0) || (pInformation->USBwIndex != 0) || (pInformation->Current_Configuration != 0)) /* Device Address should be 127 or less*/ { ControlState = STALLED; goto exit_NoData_Setup0; } //由于设置地址不会携带数据,执行以下代码 else { Result = USB_SUCCESS; } } else if (RequestNo == SET_FEATURE) /*SET FEATURE for Device*/ { - ......
} else if (RequestNo == CLEAR_FEATURE) /*Clear FEATURE for Device */ { - ......
} - }
else if (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT)) /* Interface Request*/ { .... } else if (Type_Recipient == (STANDARD_REQUEST | ENDPOINT_RECIPIENT)) /* EndPoint Request*/ { - ....
} else { Result = USB_UNSUPPORT; } -
- if (Result != USB_SUCCESS)
{ Result = (*pProperty->Class_NoData_Setup)(RequestNo); if (Result == USB_NOT_READY) { ControlState = PAUSE; goto exit_NoData_Setup0; } }
if (Result != USB_SUCCESS) { ControlState = STALLED; goto exit_NoData_Setup0; }
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}
exit_NoData_Setup0: pInformation->ControlState = ControlState; return; }
|
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()函数,这个所做的工作是:
//* Function Name : Post0_Process //* Description : Stall the Endpoint 0 in case of error. //* Return : - 0 if the control State is in PAUSE; - 1 if not.uint8_t Post0_Process(void) { SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
if (pInformation->ControlState == STALLED) //将端点0的缓冲区大小设置为64字节 { vSetEPRxStatus(EP_RX_STALL); vSetEPTxStatus(EP_TX_STALL); //将端点0的发送和接收都设置为:STALL,这样只接收SETUP令牌包。 }
return (pInformation->ControlState == PAUSE); }
|
3、枚举第三步:从新地址获取设备描述符
1)上一阶段末尾的状态
端点0的发送和接收都设置为:STALL、只接收SETUP令牌包。
2)建立阶段:主机发送令牌包、数据包、设备ACK;
产生数据接收中断,且端点0的SETUP置位,调用Setup0_Process()函数进行处理。
在Setup0_Process()中,因为主机发送了请求数据8个字节,由调用Data_Setup()函数进行处理。首先是获取
设备描述符的长度、描述符起始地址、传送的最大字节数,根据这些参数确定本次能够传输的字节数,然后调用
DataSetageIn()函数进行实际的数据传输操作,设备描述符必须在本次中断中就写入发送缓冲区,因为很快就要
进入数据阶段了。
在函数处理的最后:
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_Precess(void)函数进行处理,由于在数据阶段的末尾已经设置设备状态为:
WAIT_STATUS_OUT,所以处理函数基本上没做什么,就退出了,并将状态改为STALLED。
4、对配置描述符、字符串描述符获取
过程进行简单跟踪,过程就不一一叙述了
5、主机设置配置
1)建立阶段:主机发SETUP包、发请求数据包(DATA0包)、用户ACK;
进入CTR中断,用户调用Setup0_Precess()函数进行处理,取得请求数据后,由于没有数据传输阶段,
该函数调用NoData_Setup()函数进行处理。
判断为设置配置后,调用Standard_SetInterface()函数将设备状态结构体的当前配置改为主机数据中的
配置参数。同时调用用户的设置配置函数,将设备状态改为“ configured”。
退出时,将控制传输状态改为:ControlState = WAIT_STATUS_IN,进入状态阶段。设备期待主机的IN
令牌包,返回状态数据。
2)状态阶段:主机发IN令牌,设备返回0。
Setup0_Process()函数进行处理,取得请求数据后,由于没有数据传输阶段,该函数调用NoData_Setup()函数进行处理。
设置空闲时一个类特殊请求,其特征码为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。
if (Request_No == GET_DESCRIPTOR) { if (Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)) { uint8_t 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 */ } }
|
进入CTR中断,获取描述符时一个标准请求,但是报告描述符并不是需要通用实现的,所以在底层函数中没有实现。
跟踪Setup0_Process(void)——进入Data_Setup(void)函数,它按照上面程序处理。
可见核心程序只支持设备描述符、配置描述符、字符串描述符。
最终该函数调用:
Result = (*pProperty->Class_Data_Setup)(pInformation->USBbRequest);
调用用户的类特殊实现来获取报告描述符,同时HID类描述符也是通过这种方式获取的。
7、主机从中断端点读取鼠标操作数据