概述
今天谈谈CC2541的低功耗,低功耗也是BLE的一个亮点,据TI的测试,在ultra power saving的时候,一颗纽扣电池可以做到待机一年。经过试验。在没有定时任务的时候,打开POWERSAVING这个宏之后,协议栈会自动检测当前的timeout事件,如果没有的定时任务话会进入PM3模式,该模式下,系统的所有数字部分和RF部分都进入了休眠,所有的时钟包括sleeptimer都处于休眠状态,这个时候只有复位操作可以唤醒系统,也就是最低的功耗模式。当OSAL中存在定时的事件,在时间间隔内,系统就会进入PM2,该模式下所有的数字电路部分、RF电路. 32M晶振都是处于休眠状态,保留有32K的手表晶振,和一个sleeptimer。PM2的唤醒方式是sleeptimer计时时间到,或者外部中断,或者是系统复位。该模式也是用到最多的,如何减少在PM2模式下电流是比较关心的问题。由于CC2541中sleeptimer的最大可计时时长是41s,这个中间唤醒的次数会影响功耗的大小。
测试
先对比一下集中不同情况下的波形图。这里是利用实验室的DC电源分析仪来测试的,如果没有的话,可以在整个电路的输入端串联一个10欧姆的电阻,用示波器来测试电阻两端的电压,可以获得更加精确的波形图。
可以看出在整个系统的平均电流在0.4ma即400uA。
这个是蓝牙处于连接状态下,这个系统的电流波形图,可以看出会有很多的脉冲,蓝牙出于连接状态下,会根据设置的连接参数不同,当主机和从机之间没有数据发送时,主机会每隔30ms发送有一个确认帧,可以看到比较矮的脉冲就是确认帧。比较高的脉冲是系统中有事件timeout,唤醒进入active状态的波形。
这幅图是连接情况下没有发送数据时的波形,在经过代码优化的情况下可以看出较上副图功耗有了明显的降低。
我根据经验总结了几点可以减少功耗的方法:
(1)IO引脚在不需要的情况下,应设置输出低电平状态
(2)减少周期性事件的频率,尽量吧一下事件放在一起来处理。
(3)将不需要的功能通过配置宏设置为DISABLE。
低功耗部分的代码
程序运行到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;
}