CC2541 power saving

  1. 概述
    今天谈谈CC2541的低功耗,低功耗也是BLE的一个亮点,据TI的测试,在ultra power saving的时候,一颗纽扣电池可以做到待机一年。经过试验。在没有定时任务的时候,打开POWERSAVING这个宏之后,协议栈会自动检测当前的timeout事件,如果没有的定时任务话会进入PM3模式,该模式下,系统的所有数字部分和RF部分都进入了休眠,所有的时钟包括sleeptimer都处于休眠状态,这个时候只有复位操作可以唤醒系统,也就是最低的功耗模式。当OSAL中存在定时的事件,在时间间隔内,系统就会进入PM2,该模式下所有的数字电路部分、RF电路. 32M晶振都是处于休眠状态,保留有32K的手表晶振,和一个sleeptimer。PM2的唤醒方式是sleeptimer计时时间到,或者外部中断,或者是系统复位。该模式也是用到最多的,如何减少在PM2模式下电流是比较关心的问题。由于CC2541中sleeptimer的最大可计时时长是41s,这个中间唤醒的次数会影响功耗的大小。

  2. 测试
    先对比一下集中不同情况下的波形图。这里是利用实验室的DC电源分析仪来测试的,如果没有的话,可以在整个电路的输入端串联一个10欧姆的电阻,用示波器来测试电阻两端的电压,可以获得更加精确的波形图。
    CC2541 power saving_第1张图片
    可以看出在整个系统的平均电流在0.4ma即400uA。CC2541 power saving_第2张图片
    这个是蓝牙处于连接状态下,这个系统的电流波形图,可以看出会有很多的脉冲,蓝牙出于连接状态下,会根据设置的连接参数不同,当主机和从机之间没有数据发送时,主机会每隔30ms发送有一个确认帧,可以看到比较矮的脉冲就是确认帧。比较高的脉冲是系统中有事件timeout,唤醒进入active状态的波形。CC2541 power saving_第3张图片
    这幅图是连接情况下没有发送数据时的波形,在经过代码优化的情况下可以看出较上副图功耗有了明显的降低。
    我根据经验总结了几点可以减少功耗的方法:
    (1)IO引脚在不需要的情况下,应设置输出低电平状态
    (2)减少周期性事件的频率,尽量吧一下事件放在一起来处理。
    (3)将不需要的功能通过配置宏设置为DISABLE。

  3. 低功耗部分的代码
    程序运行到osal_start_system();后继而运行到osal_run_system();
    在里面有一个 osal_pwrmgr_powerconserve();函数,是系统自动检测当前是否有任务在执行,没哟的话将进入低功耗模式。

void osal_pwrmgr_powerconserve( void )  // 系统自动调用 如果当前没有任务,将进入低功耗模式  
{  
  uint32        next;  
  halIntState_t intState;  
  // Should we even look into power conservation  
  if ( pwrmgr_attribute.pwrmgr_device != PWRMGR_ALWAYS_ON )  
  {  
    // Are all tasks in agreement to conserve  
    if ( pwrmgr_attribute.pwrmgr_task_state == 0 )  
    {  
      // Hold off interrupts.  
      HAL_ENTER_CRITICAL_SECTION( intState );  
      // Get next time-out  
      next = osal_next_timeout();   //选取最近事件的Timeout 作为实际休眠时间   
      // Re-enable interrupts.  
      HAL_EXIT_CRITICAL_SECTION( intState );  
     // Put the processor into sleep mode  
      OSAL_SET_CPU_INTO_SLEEP( next );   // 进入低功耗模式     
    }  
  }  
}  

进入低功耗模式之后,会执行halsleep()函数进行低功耗模式设置。里面进行了低功耗时间等设置,进行电源控制的启动就在这里使能!

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

#ifdef DEBUG_GPIO  
  // TEMP  
  P1_0 = 1;  
#endif // DEBUG_GPIO  

  // 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;  // 选择进入PM 模式  

#ifdef DEBUG_GPIO  
  // TEMP  
  P1_0 = 0;  
#endif // DEBUG_GPIO  

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

#ifdef DEBUG_GPIO  
    // TEMP  
    P1_0 = 1;  /************************************************************************BLE 自带 与电源控制功能冲突*******/  
#endif // DEBUG_GPIO  
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
    HAL_ASSERT( HAL_INTERRUPTS_ARE_ENABLED());  
    HAL_DISABLE_INTERRUPTS();  

    // check if radio allows sleep, and if so, preps system for shutdown  
    // 检查radio 是否需允许进入低功耗,如果允许,提前进入低功耗状态   
    if ( halSleepPconValue && ( LL_PowerOffReq(halPwrMgtMode) == LL_SLEEP_REQUEST_ALLOWED ) )  
    {  


      /**************************************************************************************************************************/  
      /*  加入电源控制  此后进入低功耗状态  电平拉低 P1_0 =1 */  


      /**************************************************************************************************************************/  



#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);  // 模式设置 这里进入的PM2   

      // save interrupt enable registers and disable all interrupts  
      HAL_SLEEP_IE_BACKUP_AND_DISABLE(ien0, ien1, ien2);  
      HAL_ENABLE_INTERRUPTS();  
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////   
#ifdef DEBUG_GPIO  
      // TEMP  
      P1_0 = 0;  
#endif // DEBUG_GPIO  

      // 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();  

#ifdef DEBUG_GPIO  
      // TEMP  
      P1_0 = 1;  /************************************************************************BLE 自带 与电源控制功能冲突*******/  
#endif // DEBUG_GPIO  

      // 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();  
  }  

#ifdef DEBUG_GPIO  
      // TEMP  
      P1_0 = 0;  
#endif // DEBUG_GPIO  

  return;  
}  

跳出低功耗模式在此程序里是sleep timer 时间溢出,也就是在中断里退出低功耗模式

/******************************************************************************* 
 * @fn          halSleepTimerIsr 
 * 
 * @brief       Sleep timer ISR. 
 * 
 * input parameters 
 * 
 * None. 
 * 
 * output parameters 
 * 
 * @param       None. 
 * 
 * @return      None. 
 */  
HAL_ISR_FUNCTION(halSleepTimerIsr, ST_VECTOR)  
{  
  HAL_ENTER_ISR();  

  HAL_SLEEP_TIMER_CLEAR_INT();  

#ifdef HAL_SLEEP_DEBUG_POWER_MODE  
  halSleepInt = TRUE;  
#endif // HAL_SLEEP_DEBUG_POWER_MODE  
   ///////////////////////////////////////////////////////////////////////////////////////////// 拉高  


///////////////////////////////////////////////////////////////////////////////////////////////////  
  CLEAR_SLEEP_MODE();// 所有的任务退出Sleep模式时,调用CLEAR_SLEEP_MODE();  

  HAL_EXIT_ISR();  

  return;  
}  

你可能感兴趣的:(BLE-CC2541)