【STM32笔记】低功耗模式下GPIO、外设、时钟省电配置避坑
前文:
blog.csdn.net/weixin_53403301/article/details/128216064
【STM32笔记】HAL库低功耗模式配置(ADC唤醒无法使用、低功耗模式无法烧录解决方案)
blog.csdn.net/weixin_53403301/article/details/129055530
【STM32笔记】低功耗模式下GPIO省电配置避坑实验(闲置引脚配置为模拟输入其实更耗电)
在进入低功耗模式前 可以通过降低时钟频率 关闭GPIO口(通常是配置为模拟输入 或降低GPIO时钟) 以及关闭不需要的外设来降低功耗(尤其是在SLEEP、STOP模式)
时钟的话 如果用不上 或者进入STOP等模式 则也不需要配置 进入模式时 时钟本身就会被配置
关闭外设的函数在进入低功耗的前一步执行 相关唤醒配置需要在关闭外设前执行 且要注意 不能关闭用于唤醒的外设及其GPIO口
而用不到的外设和对应的GPIO口 建议同时关闭
以我的为例:
/*!
* @brief 所有外设初始化配置,根据使用需求来写
*
* @param [in] EnableNotDisable: 使能或者关闭
* true: 进行初始化外设(不包含时钟初始化)
* false: 或者关闭所有外设,所有GPIO配置为无上拉下拉且模拟输入,仅保留系统时钟和系统所需的GPIO口复用
* 该函数在进入低功耗前调用(false)
* 建议在进入该函数前(false)先配置用于唤醒的外设 如指定UART或RTC作为唤醒使用 然后再调用该函数 且不能关闭有唤醒功能的外设
* 若用于唤醒后的初始化,则建议先初始化时钟,再执行该函数的初始化(true)
* 在休眠期间使用的外设,不要关闭,也不要关闭GPIO等;相反,外设和GPIO等建议同时关闭(避免出现bug,并且也省电)
* 未关闭,但唤醒时重复初始化外设并不受影响
* 若未关闭的外设在运行中改变了初始化值,则建议不在唤醒时运行该初始化(前提是外设的GPIO等也没有作改动)
* 若需要在初始化后更改初始化值,则建议要么不进行初始化且不关闭(也包括GPIO等),或重新设置新值
*
* @return None
*/
void PWR_Device_Init(bool EnableNotDisable)
{
if(EnableNotDisable)
{
//这里是系统最初的初始化值
GPIO_Reset_Init(false); //重置GPIO
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_UART4_Init();
MX_ADC1_Init();
MX_ADC2_Init();
MX_TIM6_Init();
MX_RTC_Init();
MX_ADC3_Init();
//这里放初始化后还要更改的配置,若要重新初始化,建议先运行外设DeInit
// HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8,GPIO_PIN_SET);
// HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4,GPIO_PIN_SET);
}
else
{
HAL_ADC_DeInit(&hadc1);
HAL_ADC_DeInit(&hadc2);
HAL_ADC_DeInit(&hadc3);
// HAL_UART_DeInit(&huart2); //唤醒用的串口 最好不要关闭:若不用于唤醒 则可以关闭 GPIO等同步关闭;若用于唤醒 则不能关闭 GPIO等也不能关闭
HAL_UART_DeInit(&huart4);
HAL_TIM_Base_DeInit(&htim6);
// HAL_RTC_DeInit(&hrtc); //唤醒用的RTC 最好不要关闭
GPIO_Reset_Init(true); //GPIO配置为复用
}
}
-
true: 进行初始化外设(不包含时钟初始化)
-
false: 或者关闭所有外设,所有GPIO配置为无上拉下拉且模拟输入,仅保留系统时钟和系统所需的GPIO口复用
-
该函数在进入低功耗前调用(false)
-
建议在进入该函数前(false)先配置用于唤醒的外设 如指定UART或RTC作为唤醒使用 然后再调用该函数 且不能关闭有唤醒功能的外设
-
若用于唤醒后的初始化,则建议先初始化时钟,再执行该函数的初始化(true)
-
在休眠期间使用的外设,不要关闭,也不要关闭GPIO等;相反,外设和GPIO等建议同时关闭(避免出现bug,并且也省电)
-
未关闭,但唤醒时重复初始化外设并不受影响
-
若未关闭的外设在运行中改变了初始化值,则建议不在唤醒时运行该初始化(前提是外设的GPIO等也没有作改动)
-
若需要在初始化后更改初始化值,则建议要么不进行初始化且不关闭(也包括GPIO等),或重新设置新值
GPIO配置也是个坑
(相关配置及唤醒功能等 见前文)
/*!
* @brief 重置GPIO(都会进行),或再将除外部高低速晶振复用、SWCLK、SWDIO复用的所有GPIO配置为模拟输入(false)
* 注意:用于串口唤醒等的引脚,不可配置为模拟输入,也不可关闭
* 在进行GPIO初始化前,先将GPIO_DeInit,但是不做也不影响,不过还是建议跑一下
* 以优先级顺序来看:
* 如果这一组GPIO都没用到过 那么直接不开启时钟就最省电
* 如果这一组GPIO有引脚用过了 时钟不能关 那么就将用过的引脚配置为模拟输入
* 切记!!!:
* 不要将没用过的引脚配置为模拟输入 耗电量其实会稍微增加一点!
* 不要将没用过的GPIO时钟打开以后再配置为模拟输入 耗电量会增加很多 就算配置后再关时钟也没用!
* 尽量不要勾选CubeMX中的配置闲置引脚为模拟输入的选项 没用到的时钟还开启了会增加很多耗电
* 低功耗模式配置:
* 在进入STOP模式时 GPIO会保留原本的状态 所以把开启后不需要再保留的GPIO配置为模拟输入确实省电 时钟的话不用的肯定关 其他的反正都会关(除了保留的时钟)
* 在进入SLEEP模式时 时钟并不会关闭 所以时钟应手动关闭 且将开启后的GPIO配置为模拟输入
* 待机模式和关机模式就更不用在意GPIO口耗电了
* https://blog.csdn.net/weixin_53403301/article/details/129055530
*
* @param [in] EnableNotDisable: 使所有GPIO变成模拟输入或不进行模拟配置
*
* @return None
*/
void GPIO_Reset_Init(bool EnableNotDisable)
{
// HAL_GPIO_DeInit(GPIOA,GPIO_PIN_2|GPIO_PIN_3); //用于串口唤醒的引脚 不可变动
HAL_GPIO_DeInit(GPIOA,GPIO_PIN_0|GPIO_PIN_1
|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7
|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
|GPIO_PIN_12|GPIO_PIN_15);
HAL_GPIO_DeInit(GPIOB,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9);
HAL_GPIO_DeInit(GPIOC,GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2
|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6
|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12);
HAL_GPIO_DeInit(GPIOD,GPIO_PIN_2);
HAL_GPIO_DeInit(GPIOH,GPIO_PIN_3);
if(EnableNotDisable)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/*Configure GPIO pins : PC13 PC0 PC1 PC2
PC3 PC4 PC5 PC6
PC7 PC8 PC9 PC10
PC11 PC12 */
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2
|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6
|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pins : PA0 PA1 PA2 PA3
PA4 PA5 PA6 PA7
PA8 PA9 PA10 PA11
PA12 PA15 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1
|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7
|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
|GPIO_PIN_12|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// //用于串口唤醒的 不可变动
// GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
// GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
// GPIO_InitStruct.Pull = GPIO_NOPULL;
// HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PB0 PB1 PB2 PB10
PB11 PB12 PB13 PB14
PB15 PB3 PB4 PB5
PB6 PB7 PB8 PB9 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pin : PD2 */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/*Configure GPIO pin : PH3 */
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
}
}
-
注意:用于串口唤醒等的引脚,不可配置为模拟输入,也不可关闭
-
在进行GPIO初始化前,先将GPIO_DeInit,但是不做也不影响,不过还是建议跑一下
-
以优先级顺序来看:
-
如果这一组GPIO都没用到过 那么直接不开启时钟就最省电
-
如果这一组GPIO有引脚用过了 时钟不能关 那么就将用过的引脚配置为模拟输入
-
切记!!!:
-
不要将没用过的引脚配置为模拟输入 耗电量其实会稍微增加一点!
-
不要将没用过的GPIO时钟打开以后再配置为模拟输入 耗电量会增加很多 就算配置后再关时钟也没用!
-
尽量不要勾选CubeMX中的配置闲置引脚为模拟输入的选项 没用到的时钟还开启了会增加很多耗电
-
低功耗模式配置:
-
在进入STOP模式时 GPIO会保留原本的状态 所以把开启后不需要再保留的GPIO配置为模拟输入确实省电 时钟的话不用的肯定关 其他的反正都会关(除了保留的时钟)
-
在进入SLEEP模式时 时钟并不会关闭 所以时钟应手动关闭 且将开启后的GPIO配置为模拟输入
-
待机模式和关机模式就更不用在意GPIO口耗电了
而低功耗进入则改成了:
(相关配置及唤醒功能等 见前文)
printf("[INFO] 进入停止模式\n");
delay_ms(10); //消抖
PWR_Device_Init(false);
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_SLEEPENTRY_WFI);
break;
同样 在唤醒后 唤醒回调里面也得先配置时钟再进行外设初始化
void HAL_UARTEx_WakeupCallback(UART_HandleTypeDef *huart)
{
if(huart==&huart2)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_Init();
SystemClock_Config();
Ctrl_UART_StopMode_WakeUp(huart,false);
PWR_Device_Init(true);
}
}
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_Init();
SystemClock_Config();
Ctrl_RTC_WakeUp(0,0,false);
PWR_Device_Init(true);
}
不过 HAL_Init可以省略 但是为了避免出现bug 还是放在这里.
调用的时候:
Ctrl_UART_StopMode_WakeUp(&huart2,true);
Ctrl_RTC_WakeUp(20000,RTC_WAKEUPCLOCK_RTCCLK_DIV16,true);
Enter_Low_PWR(2,0);
先配置唤醒用的功能 再进入低功耗
当然 唤醒用的外设也需要在最开始进行初始化配置