欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

STM32 USB 设备远程唤醒机制详情

最编程 2024-07-13 10:59:54
...

目录

1.USB Resume信号

2.USB远程唤醒的方式

3.J状态和K状态

4.STM32 USB设备远程唤醒机制详解

5.主机对设备远程唤醒功能状态的获取、清除和设置


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.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。

下面是我的个人微信公众号,关注【一个早起的程序员】精彩系列文章每天不断。