目录
1.USB Resume信号
USB设备进入挂起状态之后,将由Resume信号进行唤醒。Resume信号可以由USB主机发起,也可以由USB设备本身触发,但是只有USB主机可以结束Resume信号。
(1)主机在挂起设备后可通过翻转数据线上的极性并保持20ms来唤醒设备,并以低速EOP信号结尾。
(2)如果设备支持远程唤醒,设备可向主机发起远程唤醒请求,前提是设备已进入idle状态至少5ms,设备会驱动总线进入K状态,如下图,K状态必须维持1ms-15ms之内,此信号会在1ms内被主机接管,主机会继续驱动唤醒信号直到20ms,并以低速EOP信号结尾。
2.USB远程唤醒的方式
当主机控制器进入挂起后,所有设备是没办法向主机提交数据的,那主机是怎样知道设备的唤醒请求呢?答案是通过电信号。如下图(usb_20.pdf Page-333),设备需要生成一个1ms-15ms的K状态电信号(这里例子为10ms),hub会在1ms内捕获到该信号(此时设备其实就可以停止驱动K状态了),hub会进行和设备相同的操作把此信号传递给主机控制器,主机控制器对唤醒操作进行接管,主机会继续驱动唤醒信号直到20ms,并以低速EOP信号结尾,完成对设备的唤醒操作。如果是一个鼠标设备的话,休眠的系统有时会被恢复,其实这里大家可以看做是设备给主机系统发送一个IO中断信号,主机系统检测到后可以实现一系列的后续操作。
3.J状态和K状态
J状态 | LS(低速) | 差分0 |
FS(全速) | 差分1 | |
K状态 | LS(低速) | 差分1 |
FS(全速) | 差分0 |
4.STM32 USB设备远程唤醒机制详解
首先我们以我自己设计的STM32 USB鼠标例子来分析。
1.USB外设需支持远程唤醒操作
USB外设必须支持远程唤醒功能,当然STM32F103 USB外设符合USB2.0全速设备的技术规范,所以支持远程唤醒的功能。
2.USB设备描述符设置远程唤醒
USB设备配置描述符设备属性中必须Bit5必须为1(remote wake-up)。
STM32F103鼠标设备配置描述符源代码如下,从0数第7个字节0xE0(1110000b),标识设备是自供电且具有远程唤醒功能。
0x09, /* bLength: Configuation Descriptor size */
USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
JOYSTICK_SIZ_CONFIG_DESC,
/* wTotalLength: Bytes returned */
0x00,
0x01, /*bNumInterfaces: 1 interface*/
0x01, /*bConfigurationValue: Configuration value*/
0x00, /*iConfiguration: Index of string descriptor describing
the configuration*/
0xE0, /*bmAttributes: bus powered and support Remote wake-up */
0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/
3.STM32 USB设备远程唤醒代码剖析
实现的功能就是STM32按下一个按键后,STM32 USB设备发送一次远程唤醒请求。进入按键中断后代码如下,首先会判断设备是否存在远程唤醒功能,pInformation->Current_Feature = ConfigDescriptor[7];这句代码在USB初始化调用,如果我们配置描述符ConfigDescriptor[7]的Bit5设置为0,此分支不会进入,USB设备当然也就不会进行远程唤醒主机。一旦被设置为1,则会调用Resume(RESUME_INTERNAL);发送远程唤醒请求。
void EXTI9_5_IRQHandler(void)
{
if (EXTI_GetITStatus(GPIO_KEY_EXTI_Line) != RESET)
{
if (pInformation->Current_Feature & 0x20) //Remote wake-up enabled
{
Resume(RESUME_INTERNAL);
}
/* Clear the EXTI line 9 pending bit */
EXTI_ClearITPendingBit(GPIO_KEY_EXTI_Line);
}
}
Resume函数函数根据参数RESUME_INTERNAL会先使用Resume_Init唤醒自己,然后进入远程唤醒状态RESUME_START,远程唤醒的操作就是把USB控制寄存器的第4位置1,然后等待10ms把USB控制寄存器的第4位置为0,最后进入RESUME_OFF状态,设备的一次远程唤醒请求完成。下图为Resume函数原理和USB控制寄存器Resume位的含义。
大家可以看到,Resume函数竟然具有时间计数功能,这是因为每一个ESOF(帧结束包)中断都会调用一次Resume(RESUME_ESOF);函数,而我们知道对于全速设备ESOF(帧结束包)中断是1ms一次的,Resume函数原型上面已经给出,大家深入理解。下面为USB外设中断处理函数,它会处理USB外设所有的中断,我们这里只列举ESOF中断,而ESOF中断调用了Resume函数。
/*******************************************************************************
* Function Name : USB_Istr
* Description : STR events interrupt service routine
* Input :
* Output :
* Return :
*******************************************************************************/
void USB_Istr(void)
{
wIstr = _GetISTR();
....
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_ESOF)
if (wIstr & ISTR_ESOF & wInterrupt_Mask)
{
_SetISTR((uint16_t)CLR_ESOF);
/* resume handling timing is made with ESOFs */
Resume(RESUME_ESOF); /* request without change of the machine state */
#ifdef ESOF_CALLBACK
ESOF_Callback();
#endif
}
#endif
....
} /* USB_Istr */
紧接着设备会被主机唤醒,设备如果唤醒中断使能,则会进入USB唤醒中断处理程序,中断中首先会退出低功耗然后清除控制寄存器的FSUSP位,代码如下。
/*******************************************************************************
* Function Name : USB_Istr
* Description : ISTR events interrupt service routine
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void USB_Istr(void)
{
wIstr = _GetISTR();
...
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_WKUP)
if (wIstr & ISTR_WKUP & wInterrupt_Mask)
{
_SetISTR((uint16_t)CLR_WKUP);
Resume(RESUME_EXTERNAL);
}
#endif
...
} /* USB_Istr */
// 核心Resume(RESUME_EXTERNAL);--->Resume_Init
/*******************************************************************************
* Function Name : Resume_Init
* Description : Handles wake-up restoring normal operations
* Input : None.
* Output : None.
* Return : USB_SUCCESS.
*******************************************************************************/
void Resume_Init(void)
{
uint16_t wCNTR;
/* ------------------ ONLY WITH BUS-POWERED DEVICES ---------------------- */
/* restart the clocks */
/* ... */
/* CNTR_LPMODE = 0 */
wCNTR = _GetCNTR();
wCNTR &= (~CNTR_LPMODE);
_SetCNTR(wCNTR);
/* restore full power */
/* ... on connected devices */
Leave_LowPowerMode();
/* reset FSUSP bit */
_SetCNTR(IMR_MSK);
/* reverse suspend preparation */
/* ... */
}
上面唤醒中断里面调用了Resume(RESUME_EXTERNAL);函数,其实会把USB_CNTR寄存器的LP_MODE位置为’0’,先退出低功耗,紧接着会把CNTR_FSUSP清零。挂起的时候第①就是设备会先进行强制挂起,第②就是让设备进入低功耗完成挂起例程的处理,当然我们在唤醒的时候不但要退出低功耗,而且还要清除CNTR_FSUSP位,挂起处理程序如下图所示。
void Suspend(void)
{
u16 wCNTR;
/* macrocell enters suspend mode */
// 强制挂起
wCNTR = _GetCNTR();
wCNTR |= CNTR_FSUSP;
_SetCNTR(wCNTR);
/* force low-power mode in the macrocell */
// 进入低功耗
wCNTR = _GetCNTR();
wCNTR |= CNTR_LPMODE;
_SetCNTR(wCNTR);
/* switch-off the clocks */
/* ... */
Enter_LowPowerMode();
}
5.主机对设备远程唤醒功能状态的获取、清除和设置
1.主机可使用GetStatus请求USB设备的状态。
请求格式如下图(我们这里获取设备状态):
返回数据格式如下:
2.主机可使用ClearFeature请求清除设备的远程唤醒功能,当然这需要设备实现对ClearFeature请求的处理。
3.主机可使用SetFeature请求设置设备的远程唤醒功能,当然这需要设备实现对SetFeature请求的处理。
1.本文部分素材来源网络,版权归原作者所有,如涉及作品版权问题,请与我联系删除;
2.未经原作者允许不得转载本文内容,否则将视为侵权;
3.转载或者引用本文内容请注明来源及原作者;
4.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。
下面是我的个人微信公众号,关注【一个早起的程序员】精彩系列文章每天不断。