STM32G0低功耗介紹
一、低功耗模式介紹
1、STM32G0按照分類可以分為4種模式
(1)sleep(sleep和low-power sleep)模式:功耗高,支持任意中斷/事件喚醒
(2)stop(stop0和stop1)模式:功耗較低,支持任意外部中斷和RTC鬧鍾喚醒
(3)standby模式:功耗更低,只支持RTC鬧鍾喚醒、WKUP喚醒、NRST引腳復位和IWDG復位(打開了LSI和LSE)
(4)shutdown模式:功耗最低,只支持RTC鬧鍾喚醒、WKUP喚醒、NRST引腳復位(只打開了LSE)
2、官方參考手冊介紹如下圖
3、工作模式轉換圖
4、掃盲知識
(1)STOP模式下,只要有外部中斷進來就可以喚醒,無需用戶自己配置具體代碼去實現喚醒操作
(2)STOP模式下被喚醒之后,單片機先執行外部中斷回調函數,然后再接着剛剛進入STOP模式下的語句繼續執行
(3)待機模式下被喚醒之后,單片機是類似於REST,從頭開始執行的
(4)RTC鬧鍾喚醒實質也就是外部中斷喚醒,是由片內自己解決了
(5)外部中斷喚醒之后,在重新初始化一些引腳配置
(6)對於串口喚醒這些特殊喚醒方式,其實使用的還是外部中斷,進入低功耗之前需要將串口引腳重置然后配置成外部中斷輸入引腳,外部中斷觸發喚醒之后,再重新將引腳配置為串口即可
(7)對於一些輸入腳進入低功耗之前可以全部配置為浮空輸入,或者Anglog模式,是最省電的
(8)低功耗喚醒之后,默認時鍾用的是HSI 8M,用戶需要自己重新配置時鍾,否則時鍾不准確
(9)對於ADC腳想要外部中斷喚醒,進入低功耗之前重新配置的之前需要使用HAL_ADC_DeInit(&hadc1);,否則可能不成功
喚醒調用流程:中斷產生->中斷服務函數->中斷回調->低功耗函數->上下文繼續執行
二、示例代碼
PowerManagement.h,移植時候只需要增加用戶需要的喚醒源到eAwakeupSrc
#ifndef __POWERMANAGEMENT_H__
#define __POWERMANAGEMENT_H__
#include "main.h"
typedef enum
{
LP_SLEEP = (uint8_t)0x01,
LP_DEEP_SLEEP,
LP_STOP0, /*!< Stop 0: stop mode with main regulator */
LP_STOP1, /*!< Stop 1: stop mode with low power regulator */
LP_STANDBY, /*!< Standby mode */
LP_SHUTDOWN, /*!< Shutdown mode */
} PowerMode;
// 喚醒源定義
typedef enum
{
NONE_WAKE = (uint32_t)0x00000000,
AIN1_WAKE = (uint32_t)0x00000001,
AIN2_WAKE = (uint32_t)0x00000002,
} eAwakeupSrc;
// 喚醒中斷處理狀態
typedef enum
{
no_Processed = (uint8_t)0,
Processed,
} ProcStatus;
#define WAKE_MASK (uint32_t)0xffffffff
typedef struct
{
__IO uint32_t flag; // 中斷喚醒標志位,支持32個中斷標志
__IO uint32_t mask; // 掩碼
ProcStatus isProcessed; // 是否已處理
} sAwakeupFlag;
extern sAwakeupFlag wakeFlag;
// 進入低功耗模式
void SystemEnterLowerPower(PowerMode mode);
#endif /* __POWERMANAGEMENT_H__ */
PowerManagement.c,移植時候根據實際應用場景編寫LowPowerPreProc函數
#include "PowerManagement.h"
#include "debug.h"
#include "usart.h"
#include "gpio.h"
static void ExitLowPowerMode(void);
static void LowPowerPreProc(void);
static void EnterSleepMode(PowerMode mode);
// 定義喚醒標志變量
sAwakeupFlag wakeFlag =
{
NONE_WAKE,
WAKE_MASK,
Processed,
};
// 進入低功耗
void SystemEnterLowerPower(PowerMode mode)
{
p_info("enter low power mode!\r\n");
LowPowerPreProc();
HAL_Delay(1000); // 等待1s進入低功耗,便於打印跟蹤
__HAL_RCC_PWR_CLK_ENABLE();
switch(mode)
{
case LP_SLEEP:
p_info("enter LP_SLEEP mode!\r\n");
EnterSleepMode(LP_SLEEP);
break;
case LP_DEEP_SLEEP:
p_info("enter LP_DEEP_SLEEP mode!\r\n");
EnterSleepMode(LP_DEEP_SLEEP);
break;
case LP_STOP0:
p_info("enter LP_STOP0 mode!\r\n");
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFE);
break;
case LP_STOP1:
p_info("enter LP_STOP1 mode!\r\n");
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFE);
break;
case LP_STANDBY:
p_info("enter LP_STANDBY mode!\r\n");
HAL_PWR_EnterSTANDBYMode();
break;
case LP_SHUTDOWN:
p_info("enter LP_SHUTDOWN mode!\r\n");
HAL_PWREx_EnterSHUTDOWNMode();
break;
}
ExitLowPowerMode();
}
/*
程序以WFI指令進入睡眠模式,所以只要產生任意中斷都會退出睡眠模式。所以進入睡眠模式前先調?
肏AL_SuspendTick()函數掛起系統滴答定時器,否則將會被系統滴答定時器(SysTick
)中斷在1ms內喚醒。程序運行到HAL_PWR_EnterSLEEPMode
()函數時,系統進入睡眠模式,程序停止運行。當按下WAKEUP按鍵時,觸發外部中斷0
,此時系統被喚醒。繼續執行HAL_ResumeTick()語句回復系統滴答定時器。
*/
static void EnterSleepMode(PowerMode mode)
{
/* Suspend Tick increment to prevent wakeup by Systick interrupt.
Otherwise the Systick interrupt will wake up the device within 1ms (HAL time base) */
HAL_SuspendTick();
/* Request to enter SLEEP mode */
if(mode == LP_SLEEP)
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
else if(mode == LP_DEEP_SLEEP)
HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
/* Resume Tick interrupt if disabled prior to sleep mode entry */
HAL_ResumeTick();
HAL_PWREx_DisableLowPowerRunMode();
}
// 低功耗前預處理:把不需要維持的端口反初始化為模擬輸入模式,降低功耗
static void LowPowerPreProc(void)
{
p_dbg_enter;
HAL_GPIO_WritePin(GSM_POW_GPIO_Port, GSM_POW_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(WIFI_LED_SW_GPIO_Port, WIFI_LED_SW_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPRS_LED_SW_GPIO_Port, GPRS_LED_SW_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(CAN_LED_SW_GPIO_Port, CAN_LED_SW_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPS_LED_SW_GPIO_Port, GPS_LED_SW_Pin, GPIO_PIN_RESET);
HAL_GPIO_DeInit(GSM_POW_GPIO_Port, GSM_POW_Pin);
HAL_GPIO_DeInit(WIFI_LED_SW_GPIO_Port, WIFI_LED_SW_Pin);
HAL_GPIO_DeInit(GPRS_LED_SW_GPIO_Port, GPRS_LED_SW_Pin);
HAL_GPIO_DeInit(CAN_LED_SW_GPIO_Port, CAN_LED_SW_Pin);
HAL_GPIO_DeInit(GPS_LED_SW_GPIO_Port, GPS_LED_SW_Pin);
p_dbg_exit; // 此處再關閉串口前打印退出日志
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9 | GPIO_PIN_10);
HAL_UART_DeInit(&huart1);
}
// 退出低功耗
static void ExitLowPowerMode(void)
{
SystemClock_Config(); // 重新初始化為外部晶振
MX_USART1_UART_Init(); // 初始化TRACE串口
p_dbg_enter; // 此處在打開串口后打印跟蹤日志
MX_GPIO_Init();
HAL_GPIO_WritePin(GSM_POW_GPIO_Port, GSM_POW_Pin, GPIO_PIN_SET);
switch(wakeFlag.flag)
{
case AIN1_WAKE:
p_dbg("AIN1 WAKE!");
break;
case AIN2_WAKE:
p_dbg("AIN2 WAKE!");
break;
}
wakeFlag.flag = (uint32_t)(wakeFlag.mask & NONE_WAKE);
wakeFlag.isProcessed = Processed; // 退出前標記已經重新初始化相關外設
p_dbg_exit;
}
debug.c 調試接口文件,可以使用AT+cmdCfg進行調試,例如進入STOP1模式發送:AT+cmdCfg=101,1,4,序號見PowerMode
調試接口文件之前有分享過,詳情可以見:https://www.cnblogs.com/veis/p/15086204.html
#include "debug.h"
#include "usart.h"
#include <string.h>
#include "PowerManagement.h"
#define LOWPWR_CMD 101
#define ENTER_LWP 0x01
uint8_t DataRxBuffer[RX_BUF_MAX_LEN] = {0};
uint8_t dbg_rxdata = 0;
static uint32_t count = 0;
// debug串口接收記錄集
STRUCT_USARTx_Fram dbg_Fram_Record =
{
DataRxBuffer,
0
};
// 調試等級
int dbg_level = Monitor;
static int OnCfgDebug(uint32_t vp_Type, uint32_t vp_P1, uint32_t vp_P2, uint32_t vp_P3)
{
p_info("info:OnCfgDebug:Type=%d,P1=%d,P2=%d,P3=%d.", vp_Type, vp_P1, vp_P2, vp_P3);
switch(vp_Type)
{
case ENTER_LWP:
{
p_dbg("OK");
SystemEnterLowerPower(vp_P1);
break;
}
default:
p_info("warn:PARAM INVALID!");
break;
}
memset(DataRxBuffer, 0, RX_BUF_MAX_LEN);
return 0;
}
// 格式:AT+cmdCfg=vl_CmdId,vl_Type,vl_P1,vl_P2,vl_P3
// 設置定時器命令:666
// 設置關閉和開始時間類型:1
// 開啟時間和關閉時間:vl_P1,vl_P2
static int AT_DeviceHandle(const unsigned char *data_buf)
{
count = 0;
uint32_t i, vl_CmdId, vl_Type, vl_P1, vl_P2, vl_P3;
uint32_t nlen = strlen((const char *)data_buf);
char vl_FormateStr[64];
vl_CmdId = 0;
vl_Type = 0;
vl_P1 = 0;
vl_P2 = 0;
vl_P3 = 0;
// p_dbg("data_buf=%s", data_buf);
if(!strstr((const char *)data_buf, "="))
goto RETURN;
memset(vl_FormateStr, 0, sizeof(vl_FormateStr)/sizeof(vl_FormateStr[0]));
memcpy(vl_FormateStr, "AT+cmdCfg=%d", strlen("AT+cmdCfg=%d"));
// p_dbg("nlen=%d", nlen);
for (i = 0; i < nlen; i++)
{
if ((',' == data_buf[i]) && (i < nlen - 1))
memcpy(vl_FormateStr + strlen(vl_FormateStr), ",%d", strlen(",%d"));
}
// p_dbg("vl_FormateStr=%s", vl_FormateStr);
sscanf((const char *)data_buf, vl_FormateStr, &vl_CmdId,
&vl_Type, &vl_P1, &vl_P2, vl_P3);
memset((char *)data_buf, 0, nlen);
p_dbg("vl_CmdId=%d, vl_Type=%d, vl_P1=%d, vl_P2=%d, vl_P3=%d", vl_CmdId, vl_Type, vl_P1, vl_P2, vl_P3);
if (LOWPWR_CMD == vl_CmdId)
return OnCfgDebug(vl_Type, vl_P1, vl_P2, vl_P3);
RETURN:
return -1;
}
/**
* @brief 獲取系統時間基准
*
* @return 返回系統CPU運行時間
*/
uint32_t os_time_get(void)
{
return HAL_GetTick();
}
/**
* @brief 串口接收回調函數
*
* @param 串口實例
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == DEBUG_USART)
{
if(dbg_rxdata != 0x0d && dbg_rxdata != 0x0a)
{
DataRxBuffer[count++] = dbg_rxdata;
}
else if(dbg_rxdata != 0x0a)
{
DataRxBuffer[count] = '\0';
AT_DeviceHandle(DataRxBuffer); // 調用解析接口函數
}
// HAL_UART_Transmit(&huart1, &dbg_rxdata, 1, 0);
}
HAL_UART_Receive_IT(huart, &dbg_rxdata, 1);
}
/**
* @brief 重寫fputc
*
* @param[in] ch 待發送參數
* @param f 設備文件
*
* @return 返回發送的字符
*/
int fputc(int ch, FILE *f)
{
/* 重定向fputc函數到串口1 */
HAL_UART_Transmit(&huart1, (unsigned char *)&ch, 1, 100);
return (ch);
}
#ifndef _DEBUG_H
#define _DEBUG_H
#include "main.h"
/* keil V5工具鏈中默認不支持匿名聯合體,故需要聲明下 */
//#pragma anon_unions
#define RX_BUF_MAX_LEN 1024 //最大接收緩存字節數
#define DEBUG_USART USART1
enum DebugLevel
{
Release,
Monitor
};
#define DEBUG 1
#define RELEASE_VERSION 0 // 置1后將關閉所有打印信息
#if RELEASE_VERSION
#undef DEBUG
#endif
#ifdef DEBUG
// 打印運行信息,定位標識:I
#define p_info(...) \
do \
{ \
if(!dbg_level) \
break; \
printf("[I: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);\
printf(__VA_ARGS__); \
printf("\r\n"); \
}while(0)
// 打印錯誤信息,定位標識:E
#define p_err(...) \
do \
{ \
if(!dbg_level) \
break; \
printf("[E: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);\
printf(__VA_ARGS__); \
printf("\r\n"); \
}while(0)
// 打印調試信息,定位標識:D
#define p_dbg(...) \
do \
{ \
if(!dbg_level) \
break; \
printf("[D: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);\
printf(__VA_ARGS__); \
printf("\r\n"); \
}while(0)
// 打印時間戳
#define ERR_PRINT_TIME printf("[E: %d.%03d] ", os_time_get()/1000, os_time_get()%1000)
#define DBG_PRINT_TIME printf("[D: %d.%03d] ", os_time_get()/1000, os_time_get()%1000)
// 定位具體位置(函數、行、狀態)
#define p_dbg_track do{if(!dbg_level)break;printf("[D: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);printf("%s,%d", __FUNCTION__, __LINE__); printf("\r\n");}while(0)
#define p_dbg_enter do{if(!dbg_level)break;printf("[D: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);printf("enter %s\n", __FUNCTION__); printf("\r\n");}while(0)
#define p_dbg_exit do{if(!dbg_level)break;printf("[D: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);printf("exit %s\n", __FUNCTION__); printf("\r\n");}while(0)
#define p_dbg_status do{if(!dbg_level)break;printf("[D: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);printf("status %d\n", status); printf("\r\n");}while(0)
// 定位錯誤位置
#define p_err_miss do{printf("[E: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);printf("%s miss\n", __FUNCTION__); printf("\r\n");}while(0)
#define p_err_mem do{printf("[E: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);printf("%s mem err\n", __FUNCTION__); printf("\r\n");}while(0)
#define p_err_fun do{printf("[E: %d.%03d] ", os_time_get()/1000, os_time_get()%1000);printf("%s err in %d\n", __FUNCTION__, __LINE__); printf("\r\n");}while(0)
#else
#define ERR_PRINT_TIME
#define DBG_PRINT_TIME
#define p_info(...)
#define p_err(...)
#define p_dbg_track
#define p_dbg(...)
#define p_dbg_enter
#define p_dbg_exit
#define p_dbg_status
#define p_err_miss
#define p_err_mem
#define p_err_fun
#endif
typedef struct // 串口數據幀的處理結構體
{
uint8_t *pRxBuffer;
union
{
__IO uint16_t InfAll;
struct
{
__IO uint16_t FramLength : 15; // 14:0
__IO uint16_t FramFinishFlag : 1; // 15
} InfBit;
};
} STRUCT_USARTx_Fram;
extern uint8_t dbg_rxdata;
extern STRUCT_USARTx_Fram dbg_Fram_Record;
extern int dbg_level;
uint32_t os_time_get(void);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); // 串口接收回調函數
#endif
主函數:main.c,主函數只是打印1Hz的Trace日志,便於觀測MCU是否喚醒
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "PowerManagement.h"
#include "debug.h"
void SystemClock_Config(void);
int main(void)
{
uint32_t nCount = 0;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
// 使能串口接收
HAL_UART_Receive_IT(&huart1, &dbg_rxdata, 1);
while (1)
{
nCount++;
if(nCount != 0 && nCount % 10 == 0)
{
p_info("DEBUG_1HZ_TRACE:%d\r\n", HAL_GetTick());
nCount = 0;
}
HAL_Delay(100);
}
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Configure the main internal regulator output voltage */
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
RCC_OscInitStruct.PLL.PLLN = 16;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV6;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV3;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
/** Initializes the peripherals clocks */
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1;
PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief 下降沿中斷回調函數
*
* @param[in] GPIO_Pin gpio引腳序號
*
* @return 空
*/
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case AIN1_Pin:
p_info("AIN1=1");
wakeFlag.flag = (uint32_t)(wakeFlag.mask & AIN1_WAKE);
wakeFlag.isProcessed = no_Processed;
break;
case AIN2_Pin:
p_info("AIN2=1");
wakeFlag.flag = (uint32_t)(wakeFlag.mask & AIN2_WAKE);
wakeFlag.isProcessed = no_Processed;
break;
}
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return
state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line
number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line
) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
三、測試log
進入stop1模式,通過PD11(AIN1)下降沿喚醒
進入stop1模式,通過PD11(AIN2)下降沿喚醒