CC2540/CC2541进入PM3深度休眠的条件及其休眠机制的剖析

学习BLE已有一段时间,从开始时的一片空白到现在的略有领悟。下面来讲讲鄙人学习BLE中的一些领悟。

用BLE的人都知道,BLE的最大亮点是低功耗,实现低功耗的主要方式就是CC2540/CC2541的休眠机制。

       休眠的模式主要分为PM2和PM3两种模式(另一种PM1模式,是用于低于3ms时间的休眠,几乎不用)。唤醒方式主要分为ST睡眠定时器唤醒和外部中断唤醒。唤醒方式和休眠模式的对应关系为:PM2模式是由ST睡眠定时器唤醒,PM3模式是有外部中断唤醒。

许多开发者都想在不用BLE的时候让其进入最低功耗模式:PM3模式,但是在debug程序的时候,发现CC2540工程里的休眠函数都是进入PM2模式的,无法让其自己进入PM3模式。于是,有许多人就想直接编程电源寄存器,让其强行进入PM3。这样的方法是可以达到进入PM3模式,但如果你没有处理好休眠前的现场保护和唤醒后的恢复工作,是会出现溢出,唤醒后程序跑飞的危险,这样反而不会降低功耗的,吃力不讨好。

其实,TI的休眠处理函数已经处理得很好了,只要你掌握了它的休眠机制,你照样可以对其调配自如。下面是我总结的一些浅见。

一、CC2540 or CC2541进入PM3深度休眠的条件

①osal_timeout = 0;

②llTimeout = 0;

只要同时满足上面两个条件,BLE就会自己进入PM3模式,此时,所有内部电路都关闭,只有外部中断和复位电路可以工作,因此,功耗最低,且可通过外部中断对其唤醒。当中的一个不为0,都将进入PM2模式。

下面肯定会有人问上面这两个条件是什么,osal_timeout就是各层的定时事件的某个事件的超时时间,llTimeout是link layer最底层,也就是RF控制器层的定时事件的超时时间。只要其中的一个不等于0,就说明有定时事件存在,需要定时来处理这些事件,因此就需要定时来唤醒芯片来执行这些事件,所以芯片就不能进入PM3(PM3只能是外部中断唤醒),只能进入PM2模式。

所以,也就是说,要想进入PM3模式,就必须清空所有的定时事件,当不存在任何的定时事件时,即osal_timeoout和llTimeout都会为0,这样就直接导致进入PM3模式。清理定时事件,就是深度休眠的前处理。如此,使得整个BLE有良好的鲁棒性。下面从代码的角度来剖析BLE的休眠机制。

二、BLE的休眠函数

下面就是休眠处理函数的源码,里面timeout就是要设置休眠定时器的时间,这个值来自于osal_timeout或者是llTimeout,那个值小,就说明那个事件更紧急,则由小的来赋给timeout设置休眠定时器,执行定时任务。当timeout = 0时,说明没有定时任务了,就可进入PM3深度休眠了。

llTimeout是通过LL_TimeToNextRfEvent( &sleepTimer, &llTimeout )来得到底层的定时事件,LL_TimeToNextRfEvent( &sleepTimer, &llTimeout )函数是link layer的不开源的代码,因此看不到里面的细节,但是可以肯定的是,llTimeout是和RF定时控制事件相关。一般蓝牙广播、连接、初始化,此llTImeout的值就不会为零,当没有广播、断开连接可以使得llTImeout为零。

osal_timeout一般为应用层的定时事件,由osal_start_timeEX(task_id, event, timeout)设定的定时,只要用相应的osal_stop_timeEX( task_id, event )终止所有的事件,就可以使得osal_timeout为零。

void halSleep( uint32 osal_timeout )
{
  uint32 timeout;
  uint32 llTimeout;
  uint32 sleepTimer;

  // max allowed sleep time in ms
  if (osal_timeout > MAX_SLEEP_TIMEOUT)
  {
    osal_timeout = MAX_SLEEP_TIMEOUT;
  }

  // get LL timeout value already converted to 32kHz ticks
  LL_TimeToNextRfEvent( &sleepTimer, &llTimeout );

  // check if no OSAL timeout
  // Note: If the next wake event is due to an OSAL timeout, then wakeForRF
  //       will already be FALSE, and the call to LL_TimeToNExtRfEvent will
  //       already have taken a snapshot of the Sleep Timer.
  if (osal_timeout == 0)
  {
    // use common variable
    timeout = llTimeout;

    // check if there's time before the next radio event
    // Note: Since the OSAL timeout is zero, then if the radio timeout is
    //       not zero, the next wake (if one) will be due to the radio event.
    wakeForRF = (timeout != 0) ? TRUE : FALSE;
  }
  else // OSAL timeout is non-zero
  {
    // convet OSAL timeout to sleep time
    // Note: Could be early by one 32kHz timer tick due to rounding.
    timeout = HAL_SLEEP_MS_TO_32KHZ( osal_timeout );

    // so check time to radio event is non-zero, and if so, use shorter value
    if ((llTimeout != 0) && (llTimeout < timeout))
    {
      // use common variable
      timeout = llTimeout;

      // the next ST wake time is due to radio
      wakeForRF = TRUE;
    }
    else // OSAL timeout will be used to wake
    {
      // so take a snapshot of the sleep timer for sleep based on OSAL timeout
      sleepTimer = halSleepReadTimer();

      // the next ST wake time is not due to radio
      wakeForRF = FALSE;
    }
  }

  // HAL_SLEEP_PM3 is entered only if the timeout is zero
  halPwrMgtMode = (timeout == 0) ? HAL_SLEEP_DEEP : HAL_SLEEP_TIMER;

  // check if sleep should be entered
  if ( (timeout > PM_MIN_SLEEP_TIME) || (timeout == 0) )
  {
    halIntState_t ien0, ien1, ien2;

    HAL_ASSERT( HAL_INTERRUPTS_ARE_ENABLED() );
    HAL_DISABLE_INTERRUPTS();

    // check if radio allows sleep, and if so, preps system for shutdown
    if ( LL_PowerOffReq(halPwrMgtMode) == LL_SLEEP_REQUEST_ALLOWED )
    {
#if ((defined HAL_KEY) && (HAL_KEY == TRUE))
      // get peripherals ready for sleep
      HalKeyEnterSleep();
#endif // ((defined HAL_KEY) && (HAL_KEY == TRUE))

#ifdef HAL_SLEEP_DEBUG_LED
      HAL_TURN_OFF_LED3();
#else
      // use this to turn LEDs off during sleep
      HalLedEnterSleep();
#endif // HAL_SLEEP_DEBUG_LED

      // enable sleep timer interrupt
      if (timeout != 0)
      {
        // check if the time to next wake event is greater than max sleep time
        if (timeout > MAX_SLEEP_TIME )
        {
          // it is, so limit to max allowed sleep time (~510s)
          halSleepSetTimer( sleepTimer, MAX_SLEEP_TIME );
        }
        else // not more than allowed sleep time
        {
          // so set sleep time to actual amount
          halSleepSetTimer( sleepTimer, timeout );
        }
      }
      
      // prep CC254x power mode
      HAL_SLEEP_PREP_POWER_MODE(halPwrMgtMode);

      // save interrupt enable registers and disable all interrupts
      HAL_SLEEP_IE_BACKUP_AND_DISABLE(ien0, ien1, ien2);
      HAL_ENABLE_INTERRUPTS();


      // set CC254x power mode; interrupts are disabled after this function
      // Note: Any ISR that could wake the device from sleep needs to use
      //       CLEAR_SLEEP_MODE(), which will clear the halSleepPconValue flag
      //       used to enter sleep mode, thereby preventing the device from
      //       missing this interrupt.
      HAL_SLEEP_SET_POWER_MODE();

      // check if ST interrupt pending, and if not, clear wakeForRF flag
      // Note: This is needed in case we are not woken by the sleep timer but
      //       by for example a key press. In this case, the flag has to be
      //       cleared as we are not just before a radio event.
      // Note: There is the possiblity that we may wake from an interrupt just
      //       before the sleep timer would have woken us just before a radio
      //       event, in which case power will be wasted as we will probably
      //       enter this routine one or more times before the radio event.
      //       However, this is presumably unusual, and isn't expected to have
      //       much impact on average power consumption.
      if ( (wakeForRF == TRUE) && !(IRCON & 0x80) )
      {
        wakeForRF = FALSE;
      }

      // restore interrupt enable registers
      HAL_SLEEP_IE_RESTORE(ien0, ien1, ien2);

      // power on the LL; blocks until completion
      // Note: This is done here to ensure the 32MHz XOSC has stablized, in
      //       case it is needed (e.g. the ADC is used by the joystick).
      LL_PowerOnReq( (halPwrMgtMode == CC2540_PM3), wakeForRF );

#ifdef HAL_SLEEP_DEBUG_LED
      HAL_TURN_ON_LED3();
#else //!HAL_SLEEP_DEBUG_LED
      // use this to turn LEDs back on after sleep
      HalLedExitSleep();
#endif // HAL_SLEEP_DEBUG_LED

#if ((defined HAL_KEY) && (HAL_KEY == TRUE))
      // handle peripherals
      (void)HalKeyExitSleep();
#endif // ((defined HAL_KEY) && (HAL_KEY == TRUE))
    }

    HAL_ENABLE_INTERRUPTS();
  }

从上面的代码认真分析,就可以掌握BLE的休眠机制。BLE的所有事件都是间断的,而每个事件间的间歇就是定时休眠的时候。例如,广播可以是每个100ms广播一次,而这100ms就进入了PM2休眠的时间;连接的时候,传输数据是10ms或者100ms连接一次传输,数据的传输也是间断的,这个间歇也是PM2的定时休眠时间。BLE正是因为这种休眠机制而做到了很低的功耗,与传统蓝牙的连续传输相比,功耗降低了许多。

以上粗略地总结了TI源码下PM3深度休眠的条件和简单讲述了BLE的休眠机制,本人菜鸟,此文可能存在错误的理解,斗胆发此文,权当和大家交流学习经验,望吐槽。。。

你可能感兴趣的:(蓝牙BLE,低功耗,嵌入式)