【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);
先配置喚醒用的功能 再進入低功耗
當然 喚醒用的外設也需要在最開始進行初始化配置