(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)
(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****/
进入stop1模式,通过PD11(AIN1)下降沿唤醒
进入stop1模式,通过PD11(AIN2)下降沿唤醒