手头有一块ST官方的Nucleo_L073RZ评估板,想看看这个L0系列进入停止模式功耗能做到多少,经过周末一天的写程序测试,从测试结果来看进入停止模式后功耗最低能降到1ua左右,还是不错的。后面在低功耗的基础上增加了串口打印,RTC定时唤醒功能,以及ADC采样功能(软件触发),都能使功耗稳定在2ua左右。
下面分享一下我的部分代码供需要调试低功耗应用的人一些参考,一些关键的地方在代码中强调出来了。要降功耗主要注意以下几点:
1.GPIO的配置(不用的引脚配置为模拟输入,使用到的引脚应保证低功耗状态时没有压降产生,即需要明确GPIO默认状态是高还是低)
2.要想达到极低功耗需要使内部调压器处在低功耗模式
3.如果使用了内部ADC通道例如VREFINT, 内部温度传感器,需要记得在进入低功耗模式时关闭(参考我的代码)
4.使用了ADC情况下,进入低功耗模式时记得关闭ADC的调压器以及关闭ADC外设(参考我的代码)
参考代码链接:https://download.csdn.net/download/qq_27718231/12918647
/*
* 函数名称: NUCLEO_GPIO_Init
* 函数说明: 初始化评估板的GPIO
* 输入参数: 无
* 返回参数: 无
*/
void NUCLEO_GPIO_Init(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct={0};
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOC);
GPIO_InitStruct.Pin=LL_GPIO_PIN_0|LL_GPIO_PIN_1|LL_GPIO_PIN_2|LL_GPIO_PIN_3
|LL_GPIO_PIN_4|LL_GPIO_PIN_5|LL_GPIO_PIN_6|LL_GPIO_PIN_7
|LL_GPIO_PIN_8|LL_GPIO_PIN_9|LL_GPIO_PIN_10|LL_GPIO_PIN_11
|LL_GPIO_PIN_12|LL_GPIO_PIN_13|LL_GPIO_PIN_14|LL_GPIO_PIN_15;
GPIO_InitStruct.Mode=LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin=LL_GPIO_PIN_0|LL_GPIO_PIN_1|LL_GPIO_PIN_2|LL_GPIO_PIN_3
|LL_GPIO_PIN_4|LL_GPIO_PIN_5|LL_GPIO_PIN_6|LL_GPIO_PIN_7
|LL_GPIO_PIN_8|LL_GPIO_PIN_9|LL_GPIO_PIN_10|LL_GPIO_PIN_11
|LL_GPIO_PIN_12|LL_GPIO_PIN_13|LL_GPIO_PIN_14|LL_GPIO_PIN_15;
GPIO_InitStruct.Mode=LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin=LL_GPIO_PIN_0|LL_GPIO_PIN_1|LL_GPIO_PIN_2|LL_GPIO_PIN_3
|LL_GPIO_PIN_4|LL_GPIO_PIN_5|LL_GPIO_PIN_6|LL_GPIO_PIN_7
|LL_GPIO_PIN_8|LL_GPIO_PIN_9|LL_GPIO_PIN_10|LL_GPIO_PIN_11
|LL_GPIO_PIN_12|LL_GPIO_PIN_13;
GPIO_InitStruct.Mode=LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* PA5-LED2 */
GPIO_InitStruct.Pin = LL_GPIO_PIN_5;
GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = LL_GPIO_PULL_DOWN;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/*
* 函数名称: Enter_DeepSleep
* 函数说明: 进入深度睡眠模式
* 输入参数: 无
* 返回参数: 无
*/
static void Enter_DeepSleep(void)
{
uint32_t tmpreg = 0U;
/* Clear all exti interrupt flag */
NVIC_DisableIRQ(SysTick_IRQn);
EXTI->PR = 0x007DFFFF;
LL_RTC_ClearFlag_WUT(RTC);
/* 使能超低功耗模式,此模式下会关闭VREFINT (下面这两句对降功耗有帮助)*/
LL_PWR_EnableUltraLowPower();
/* 使能快速唤醒 */
LL_PWR_EnableFastWakeUp();
/* 调压器模式:低功耗 (下面这一句对降功耗有帮助)*/
LL_PWR_SetRegulModeLP(LL_PWR_REGU_LPMODES_LOW_POWER);
/* Set STOP2 mode when CPU enters deepsleep */
LL_PWR_SetPowerMode(LL_PWR_MODE_STOP);
/* Set SLEEPDEEP bit of Cortex System Control Register */
LL_LPM_EnableDeepSleep();
/* Request Wait For Interrupt */
__WFI();
/* Reset SLEEPDEEP bit of Cortex System Control Register */
SCB->SCR &= (uint32_t)~((uint32_t)SCB_SCR_SLEEPDEEP_Msk);
/* 从停止模式唤醒后需要重新初始化时钟 */
SystemClock_Config();
}
/*
* 函数名称: Adc1_CH_Init
* 函数说明: ADC通道配置
* 输入参数: 无
* 返回参数: 无
*/
void Adc_CH_Init(void)
{
LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
LL_ADC_InitTypeDef ADC_InitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/* Peripheral clock enable */
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC1);
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOC);
/**ADC GPIO Configuration
PC4 ------> ADC_IN14
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_4;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
LL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/** 配置外部通道
*/
LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_14);
/** 配置内部通道VREFINT
*/
LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_VREFINT);
// /* 使能VREFINT(注只在需要使用内部通道VREFINT时才需要操作) */
// LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_VREFINT);
/** Common config
*/
ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE;
ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_39CYCLES_5);
LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE);
LL_ADC_REG_SetSequencerScanDirection(ADC1, LL_ADC_REG_SEQ_SCAN_DIR_FORWARD);
LL_ADC_SetCommonFrequencyMode(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_CLOCK_FREQ_MODE_HIGH);
LL_ADC_DisableIT_EOC(ADC1);
LL_ADC_DisableIT_EOS(ADC1);
ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; /* PCLK=16MHz */
ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;
ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
LL_ADC_Init(ADC1, &ADC_InitStruct);
// LL_ADC_DeInit(ADC1);
}
/*
* 函数名称: Start_Adc
* 函数说明: 使能ADC转换
* 输入参数: 无
* 返回参数: 无
*/
void Start_Adc(void)
{
uint32_t temp_buff=0, vref_buff=0;
uint32_t wait_loop_index;
/* 开启ADC调压器 */
LL_ADC_EnableInternalRegulator(ADC1);
/* 等待ADC稳压器稳定 */
wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
while(wait_loop_index != 0)
{
wait_loop_index--;
}
/* 校准ADC */
LL_ADC_StartCalibration(ADC1);
/* 等待校准完成 */
while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)
{
}
/* 校准完成之后需要等待一段时间才能使能ADC */
wait_loop_index = ((LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * 32) >> 1);
while(wait_loop_index != 0)
{
wait_loop_index--;
}
/* 使能之前清除ADRDY标志 */
LL_ADC_ClearFlag_ADRDY(ADC1);
/* 使能ADC */
LL_ADC_Enable(ADC1);
/* 等待ADC使能 */
while(LL_ADC_IsActiveFlag_ADRDY(ADC1) != SET)
{
}
/* 使能VREFINT(注只在需要使用内部通道VREFINT时才需要操作) */
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_VREFINT);
/* 开始转换(软件触发转换16次) */
for(uint8_t i=0; i<16; i++)
{
LL_ADC_REG_StartConversion(ADC1);
while(LL_ADC_IsActiveFlag_EOC(ADC1) != SET);
temp_buff += LL_ADC_REG_ReadConversionData12(ADC1);
LL_ADC_REG_StartConversion(ADC1);
while(LL_ADC_IsActiveFlag_EOC(ADC1) != SET);
vref_buff += LL_ADC_REG_ReadConversionData12(ADC1);
}
/* 求16次转换平均值 */
temp_buff >>= 4;
vref_buff >>= 4;
printf("Ch14=%d, Vrefint=%d\n", temp_buff, vref_buff);
/* 根据采样的内部参考值计算VDDA的值 */
printf("Vdda=%d\n", 3000*(*VREFINT_CAL)/vref_buff);
}
/*
* 函数名称: Start_Adc
* 函数说明: 停止ADC转换
* 输入参数: 无
* 返回参数: 无
*/
void Stop_Adc(void)
{
LL_ADC_REG_StopConversion(ADC1);
while(LL_ADC_REG_IsStopConversionOngoing(ADC1) != RESET)
{
}
LL_ADC_Disable(ADC1);
/* 失能VREFINT,降低功耗(注只在需要使用内部通道VREFINT时才需要操作) */
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_NONE);
}
下面是我的测试结果: