天天看點

【STM32筆記】低功耗模式下GPIO、外設、時鐘省電配置避坑

【STM32筆記】低功耗模式下GPIO、外設、時鐘省電配置避坑

前文:

blog.csdn.net/weixin_53403301/article/details/128216064

【STM32筆記】HAL庫低功耗模式配置(ADC喚醒無法使用、低功耗模式無法燒錄解決方案)

blog.csdn.net/weixin_53403301/article/details/129055530

【STM32筆記】低功耗模式下GPIO省電配置避坑實驗(閑置引腳配置為模拟輸入其實更耗電)

【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);
           

先配置喚醒用的功能 再進入低功耗

當然 喚醒用的外設也需要在最開始進行初始化配置

繼續閱讀