1. 程式人生 > 其它 >STM32G0低功耗介紹與自定義介面實現

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引腳復位和IWDG復位(只打開了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);
    // 重新初始化CAN
    CAN_Init(500);

	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 By veis