欢迎来到我的博客https://www.cnblogs.com/veis/

https://www.cnblogs.com/veis/p/14182037.html

STM32G0低功耗介绍

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)下降沿唤醒

posted @ 2022-03-06 21:18  veis  阅读(1935)  评论(1编辑  收藏  举报