STM32的USB枚举过程介绍 之前的说明: 文中大量引用网上资料,在文后已给出资料的引用说明。文件涉及到的USB各种传输包各个位的含义以及USB标准设备请求的含义都没有做说明,推荐看《圈圈教你玩USB》里面有详细的说明 一、枚举前的工作 系统上电后,程序开始运行,简单介绍一下USB的初始化 根据STM32的USB库做移植,介绍枚举过程 SetSystem函数是一些初始化化设置,因为我在之前已经做了初始化的操作,所以把这个函数就删了。 首先系统执行USB中断设置:USB_Interrupts_Config();//设置中断向量表,设置优先级 然后执行USB时钟设置:Set_USBClock();//时钟设置,USB使用48M时钟, 然后执行USB初始化设置:USB_Init();//这个函数比较重要 在执行USB初始化设置主要进行结构体,与函数指针的配置 [cpp] view plain copy
- void USB_Init(void)
- {
- pInformation = &Device_Info;//当前的连接状态与信息,关于Device_Info的信息,自己go to 去查看
- pInformation->ControlState = 2;//当前的控制状态设置为IN_DATA
- pProperty = &Device_Property;//设备本身支持的属性和方法
- pUser_Standard_Requests =&User_Standard_Requests;//主机请求的实现方法
- /* Initialize devices one by one */
- pProperty->Init();//回调设备的初始化例程
- }
这里面还调用了pProperty->Init();函数进行初始化,它的函数主体是Joystick_init();函数程序执行到Joystick_init();函数,首先Get_SerialNum();获取STM32内部唯一标识码。然后赋给Joystick_StringSerial,不过这个东西好像并没有什么用,因为后面我们在usb_desc.c文件中给改写了。。
好,接下来执行PowerOn();函数,这个函数首先把D+的上拉电阻上电(我使用的是神舟IIIstm32开发板,对应的D+上拉电阻控制位为PG11,我在之前的函数中已经进行了端口的初始化,你们不要忘记呀),这样电脑就可以检测到设备了。(集线器报告设备连接状态,并收到主机指令后,会复位 USB总线,这需要一定的时间(这段时间内设备应该准备好处理复位指令)。但是现在设备初始化程序将继续往下进行,因为它还没有使能复位中断)
接着执行语句
[cpp] view plain copy
- <span style="font-size:10px;"> /*** CNTR_PWDN = 0 ***/
- wRegVal = CNTR_FRES;
- _SetCNTR(wRegVal);</span>
这两句话的含义实际上是使能了USB模块电源,因为上电复位时,CNTR寄存器的断电控制位PNWN位是1,模块是断电的,语句将强制复位USB模块,直至
[cpp] view plain copy
- wInterrupt_Mask = 0;
- _SetCNTR(wInterrupt_Mask);
语句清除复位信号,然后终止复位操作,这个过程中因为复位中断允许位CNTR_RESETM没有被使能,所以,不会触发复位中断,而是间接使PDWN=0,模块开始工作。
[cpp] view plain copy
- wInterrupt_Mask =CNTR_RESETM | CNTR_SUSPM| CNTR_WKUPM;
- SetCNTR(wInterrupt_Mask);
执行语句后,复位中断,挂起中断,唤醒中断被允许,而此时集线器多半已经开始复位端口了,或者说稍微有限延迟,设备固件还能继续初始化一些部件,但已经不会影响整个工作流程了。那么实际上程序确实直接进入了复位中断。 程序直接进入了USB_LP_CAN1_RX0_IRQHandler的中断口,执行 USB_Istr();,通过匹配发现中断源为复位中断,程序运行到复位中断部分,开始执行复位程序(这里提一下,判断发生复位中断后,首先清中断位。在其它几个中断源也是先清中断位,但是CTR_LP();函数之前却没有清中断位是为什么呢?在STM32参考手册中是这样说的:端点在成功完成一次传输后, CTR位会被硬件置起,如果USB_CNTR上的相应位也被设置的话,就会产生中断。与端点相关的中断标志和USB_CNTR寄存器的CTRM位无关。这两个中断标志位将一直保持有效,直到应用程序清除了USB_EpnR寄存器中的相关中断挂起位(CTR位是个只读位)。) Device_Property.Reset();
这个函数指针,指向的函数是Joystick_Reset。我们跳过来继续看。
[cpp] view plain copy
- voidJoystick_Reset(void)
- {
- /* Set Joystick_DEVICE as not configured */
- pInformation->Current_Configuration = 0;
- pInformation->Current_Interface = 0;/*thedefault Interface*/
-
-
- /* Current Feature initialization */
- pInformation->Current_Feature =Joystick_ConfigDescriptor[7];//供电方式,总线供电,自供电
- SetBTABLE(BTABLE_ADDRESS);//设置包缓冲区地址
- /* Initialize Endpoint 0 */
- SetEPType(ENDP0, EP_CONTROL);//端点0为控制端点
- SetEPTxStatus(ENDP0, EP_TX_STALL);//端点状态为发送无效,也就是主机IN令牌包来的时候,回送一个STALL
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);//设置端点0的描述符表,包括接收缓冲区地址,最大允许接收的字节数、发送缓冲区地址三个量。
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);//发送缓冲区地址
- Clear_Status_Out(ENDP0);///清除EP_KIND的STATUS_OUT位,如果改位被设置, 在控制模式下只对0字节数据包相应。其它的都返回STALL。主要用于控制传输的状态过程
- SetEPRxCount(ENDP0,Device_Property.MaxPacketSize);///接收缓冲区支持64个字节
- SetEPRxValid(ENDP0);//使能端点0的接收,因为很快就要接收SETUP令牌包了
-
- /* Initialize Endpoint 1 */
- SetEPType(ENDP1, EP_INTERRUPT);//端点1设为中断端点
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);//设置发送缓冲区地址
- SetEPTxCount(ENDP1, 4);//每次发送4个字节
- SetEPRxStatus(ENDP1, EP_RX_DIS);///接收禁止,只发送 Mouse 信息,而不从主机接收
- SetEPTxStatus(ENDP1, EP_TX_NAK);///现在发送端点还不允许发送数据
-
- /* Set this device to response on defaultaddress */
- SetDeviceAddress(0);//址默认为 0.
- bDeviceState = ATTACHED;//接状态改为已经连接,默认地址状态
- }
这个复位的过程我们用一句话总结就是端点初始化。至此RESET中断处理完成,因为我们也没有设置RESET_CALLBACK,所以也不会有RESET_Callback();,不过它本身也是个空函数。在执行完复位函数后PC指针又回到main函数中来,Joystick_init函数往下执行, USB_SIL_Init();函数中使能所有常用中断前面说道,当开复位中断后,程序进入中断,那么复位中断执行完成以后,真正的枚举的过程(获取描述符)并没有开始。而是有一段空余的时间,这段时间内,程序执行了上面的提到的USB_SIL_Init();直至USB_Init();函数结束。那么真正的USB枚举过程都是在中断中完成。 通过我调试发现,在完成复位中断后,主机好像并没有发送获取描述符的命令,在这个过程中,系统两次进入挂起中断,然后触发唤醒中断,然后复位中断。我个人的理解是,这段时间因为stm32没有收到sof,所有触发挂起中断,当设备发送获取设备描述符命令时,触发唤醒中断,在唤醒中断中,系统设置恢复正常工作。至于最后的复位请求,我表示我也不知道了。。。(这里需要说一下,在移植的挂起中断处理程序中,针对stm32部分也做了挂起处理,我觉得(实际上是我看以前的前辈们都觉得)这样做没必要。所以,只保留了USB挂起,而对整个stm32的挂起被屏蔽掉了。) (此时pInformation->ControlState=IN_DATA, bDeviceState=UNCONNECTED)
|