【极海APM32替代笔记】低功耗模式配置及配置汇总

news2024/11/29 8:44:18

【极海APM32替代笔记】低功耗模式配置及配置汇总

文章总结:(后续更新以相关文章为准)
【STM32笔记】低功耗模式、WFI命令等进入不了休眠的可能原因(系统定时器SysTick一直产生中断)

【STM32笔记】HAL库低功耗模式配置(ADC唤醒无法使用、低功耗模式无法烧录解决方案)

【STM32笔记】低功耗模式下GPIO省电配置避坑实验(闲置引脚配置为模拟输入其实更耗电)

【STM32笔记】低功耗模式下GPIO、外设、时钟省电配置避坑

【STM32笔记】低功耗模式下的RTC唤醒(非闹钟唤醒,而是采用RTC_WAKEUPTIMER)

【STM32笔记】HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用、接收数据不全的问题)

HAL库低功耗模式配置(ADC唤醒无法使用、低功耗模式无法烧录解决方案)

一、低功耗模式简介

系统提供了多个低功耗模式,可在 CPU 不需要运行时(例如等待外部事件时)节省功耗。由用户根据应用选择具体的低功耗模式,以在低功耗、短启动时间和可用唤醒源之间寻求最佳平衡。

睡眠模式、停止模式及待机模式中,若备份域电源正常供电,备份域内的 RTC 都可以正常运行,备份域内的寄存器的数据会被保存,不受功耗模式影响。

从表中可以看到,这三种低功耗模式层层递进,运行的时钟或芯片功能越来越少,因而功耗越来越低。

模式说明进入方式唤醒对1.8V区域时钟的影响对VDD区域时钟的影响调压器
睡眠模式内核停止,所有外设包括M3核心的外设,如NVIC、系统时钟(SysTick)等仍在运行WFI、WFE命令(HAL库直接调用)任意中断/事件内核时钟关,对其他时钟和ADC时钟无影响
停止模式所有的时钟都已停止配置PWR_CR寄存器的PDDS+LPDS位+SLEEPDEEP位+WFI或WFE命令任意外部中断EXTI(在外部中断寄存器中设置)关闭所有1.8V区域的时钟HSI和HSE的振荡器关闭开启或处于低功耗模式(依据电源控制寄存器的设定)
待机模式1.8V电源关闭配置PWR_CR寄存器的PDDS+SLEEPDEEP位+WFI或WFE命令WKUP、引脚的RTC闹钟事件、NRST引脚上的外部复位、IWDG复位关闭所有1.8V区域的时钟HSI和HSE的振荡器关闭

L4及L4+的通用模式状态表可见手册
在这里插入图片描述
在这里插入图片描述

blog.csdn.net/weixin_53403301/article/details/129031935
【STM32笔记】低功耗模式下的RTC唤醒(非闹钟唤醒,而是采用RTC_WAKEUPTIMER)

blog.csdn.net/weixin_53403301/article/details/129055530
【STM32笔记】低功耗模式下GPIO省电配置避坑实验(闲置引脚配置为模拟输入其实更耗电)

blog.csdn.net/weixin_53403301/article/details/129060093
【STM32笔记】低功耗模式下GPIO、外设省电配置避坑

1.1 睡眠模式

在睡眠模式中,仅关闭了内核时钟,内核停止运行,但其片上外设,CM3 核心的外设全都还照常运行。有两种方式进入睡眠模式,它的进入方式决定了从睡眠唤醒的方式,分别是 WFI(wait for interrupt) 和 WFE(wait for event),即由等待“中断”唤醒和由“事件”唤醒。

特性和说明:

立即睡眠: 在执行 WFI 或 WFE 指令时立即进入睡眠模式。
退出时睡眠: 在退出优先级最低的中断服务程序后才进入睡眠模式。
进入方式: 内核寄存器的 SLEEPDEEP=0 ,然后调用 WFI 或 WFE 指令即可进入睡眠模式;SLEEPONEXIT=1 时,进入“退出时睡眠”模式。
唤醒方式: 如果是使用 WFI 指令睡眠的,则可使用任意中断唤醒;如果是使用 WFE 指令睡眠的,则由事件唤醒。
睡眠时: 关闭内核时钟,内核停止,而外设正常运行,在软件上表现为不再执行新的代码。这个状态会保留睡眠前的内核寄存器、内存的数据。
唤醒延迟: 无延迟。
唤醒后: 若由中断唤醒,先进入中断,退出中断服务程序后,接着执行 WFI 指令后的程序;若由事件唤醒,直接接着执行 WFE 后的程序。

唤醒后即可开始行动 继续程序 无需配置任何寄存器

睡眠模式和低功耗睡眠模式是两个模式 由PWR_MAINREGULATOR_ON和PWR_LOWPOWERREGULATOR_ON两个变量确定

要进入低功耗睡眠模式 首先得进入低功耗运行模式
HAL_PWREx_EableLowPowerRunMode()
且工作频率降低到2MHz以下

唤醒时 睡眠模式直接唤醒

而低功耗睡眠模式唤醒后 会进入到低功耗运行模式 若想正常工作 需用HAL_PWREx_DisableLowPowerRunMode()退出

1.2 停止模式

在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。停止模式可以由任意一个外部中断(EXTI)唤醒,在停止模式中可以选择电压调节器为开模式或低功耗模式。

特性和说明:

调压器低功耗模式: 在停止模式下调压器可工作在正常模式或低功耗模式,可进一步降低功耗。
进入方式: 内核寄存器的 SLEEPDEEP=1,PWR_CR 寄存器中的 PDDS=0,然后调用 WFI 或 WFE 指令即可进入停止模式;PWR_CR 寄存器的 LPDS=0 时,调压器工作在正常模式,LPDS=1 时工作在低功耗模式。
唤醒方式: 如果是使用 WFI 指令睡眠的,可使用任意 EXTI 线的中断唤醒;如果是使用 WFE 指令睡眠的,可使用任意配置为事件模式的 EXTI 线事件唤醒。
停止时: 内核停止,片上外设也停止。这个状态会保留停止前的内核寄存器、内存的数据。
唤醒延迟: 基础延迟为 HSI 振荡器的启动时间,若调压器工作在低功耗模式,还需要加上调压器从低功耗切换至正常模式下的时间。
唤醒后: 若由中断唤醒,先进入中断,退出中断服务程序后,接着执行 WFI 指令后的程序;若由事件唤醒,直接接着执行 WFE 后的程序。唤醒后,STM32 会使用 HSI 作为系统时钟。

只能由外部中断唤醒 唤醒后需要重新使能时钟(SystemClock_Config();)
建议将一条外部中断线专门作为唤醒中断,执行中断后进入回调进行时钟使能

停止模式0和1由PWR_MAINREGULATOR_ON和PWR_LOWPOWERREGULATOR_ON两个变量确定

停止模式0和1可以被串口 I2C等设备唤醒(具体看手册)

停止模式2则在pwr_ex.c中进入

停止模式2 只能被特定器件(如LPUART等在内部与EXTI有链接的器件)唤醒

详情见后续关于STOP模式串口唤醒的文章

blog.csdn.net/weixin_53403301/article/details/129014963
【STM32笔记】HAL库低功耗STOP停止模式的串口唤醒(解决串口唤醒和回调无法一起使用的问题)

1.3 待机模式

翻译成shutdown更为合适
待机模式,它除了关闭所有的时钟,还把 1.8V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 boot 条件,从头开始执行程序。它有四种唤醒方式,分别是 WKUP(PA0)引脚的上升沿,RTC 闹钟事件,NRST 引脚的复位和 IWDG(独立看门狗)复位。

特性和说明:

进入方式: 内核寄存器的 SLEEPDEEP=1,PWR_CR 寄存器中的 PDDS=1,PWR_CR 寄存器中的唤醒状态位 WUF=0,然后调用 WFI 或 WFE 指令即可进入待机模式。
唤醒方式: 通过 WKUP ,RTC 闹钟、唤醒、入侵、时间戳事件或 NRST 引脚外部复位及 IWDG 复位唤醒。
待机时: 内核停止,片上外设也停止;内核寄存器、内存的数据会丢失;除复位引脚、RTC_AF1 引脚及 WKUP 引脚,其它 I/O 口均工作在高阻态。
唤醒延迟: 芯片复位的时间。
唤醒后: 相当于芯片复位,在程序表现为从头开始执行代码。

代码实操

SYS配置:
在这里插入图片描述
选择Serial Wire模式可以在某些情况下进行调试如(SWD)
在这里插入图片描述
配置外部中断就不说了

进入低功耗模式的函数

引脚指定只对待机模式有效
HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);
该函数指定的引脚不受引脚其他的配置影响(比如已经被配成了复用)

__HAL_RCC_PWR_CLK_ENABLE(); 可要可不要

/*!
 * @brief       	进入低功耗模式   	
 *
 * @param 	[in]	mode_flag: 模式标志
 * 								0/大于4 不进入任何模式,1 进入睡眠,2 进入停止,3 进入待机,4 关机
 *  				[in]	WakeUpPinPolarity: 待机模式下WKUP唤醒引脚极性配置,其他模式无用
 *
 * @return				None
 */
void Enter_Low_PWR(uint8_t mode_flag,uint32_t WakeUpPinPolarity)
{
	__HAL_RCC_PWR_CLK_ENABLE();
	switch(mode_flag)
	{
		case 0:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
		case 1:
		{
			printf("[INFO] 进入睡眠模式\n");
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);
			break;
		}
		case 2:
		{
			printf("[INFO] 进入停止模式\n");
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_SLEEPENTRY_WFI);
			break;
		}
		case 3:
		{
			printf("[INFO] 三秒后进入待机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入待机模式\n");
			HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWR_EnterSTANDBYMode();
			break;
		}
		case 4:
		{
			printf("[INFO] 三秒后进入关机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入关机模式\n");
			HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWREx_EnterSHUTDOWNMode();
			break;
		}
		default:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
	}
}

外部中断回调(只对STOP模式有效果 SLEEP模式可以省略):

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	switch(GPIO_Pin)
	{
		case WAKE_UP_Pin:
		{
			__HAL_RCC_PWR_CLK_ENABLE();
			SystemClock_Config();
		}
		default:
		{			
			break;
		}
	}
	__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
}

ADC唤醒无法使用的解决方案

从STOP模式唤醒 会导致L4系列等MCU的ADC无法使用
原因就是PLLSAI时钟被关闭了 就算从头开始初始化也没用
所以只需要更换时钟源即可
在这里插入图片描述

低功耗模式无法烧录解决方案

在进入到待机模式后 相当于强行死机了 无法进行烧录 只能手动复位后烧录
但在调试时 将进入待机模式的函数放得太前 则完全无法烧录 这时候就需要从硬件方面解决

把烧写程序用的软件复位NREST引脚与硬件复位短接 那么在烧写时 复位引脚就会被拉低 这样的话 就算是shutdown模式 也能正常烧写程序 就不用像之前那样一直按复位卡时间点了

省电优化

在CubeMX里面有一项设置,就是把没有用到的引脚全部设置为省电(模拟输入) 这样可以更省电
在这里插入图片描述

HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用的问题)

低功耗模式如图所示

停止模式有三种 分别是0 1 2
其中 0 1可以由串口唤醒
2只能由LPUART唤醒
在这里插入图片描述
在这里插入图片描述
在手册里可以查到

进入也很简单:

/*!
 * @brief       	进入低功耗模式   	
 *
 * @param 	[in]	mode_flag: 模式标志
 * 								0/大于4 不进入任何模式,1 进入睡眠,2 进入停止,3 进入待机,4 关机
 *  				[in]	WakeUpPinPolarity: 待机模式下WKUP唤醒引脚极性配置,其他模式无用
 *
 * @return				None
 */
void Enter_Low_PWR(uint8_t mode_flag,uint32_t WakeUpPinPolarity)
{	
	__HAL_RCC_PWR_CLK_ENABLE();
	switch(mode_flag)
	{
		case 0:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
		case 1:
		{
			printf("[INFO] 进入睡眠模式\n");
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);
			break;
		}
		case 2:
		{
			printf("[INFO] 进入停止模式\n");
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_SLEEPENTRY_WFI);
			break;
		}
		case 3:
		{
			printf("[INFO] 三秒后进入待机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入待机模式\n");
			HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWR_EnterSTANDBYMode();
			break;
		}
		case 4:
		{
			printf("[INFO] 三秒后进入关机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入关机模式\n");
			HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWREx_EnterSHUTDOWNMode();
			break;
		}
		default:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
	}
}

要进入停止2模式则需要在pwr_ex.c中配置
HAL_PWREx_EnterSTOP2Mode();函数

其中
HAL_PWR_EnterSTOPMode中的PWR_MAINREGULATOR_ON、PWR_LOWPOWERREGULATOR_ON分别是开启稳压器和关闭稳压器 分别对应STOP 0和1

在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。停止模式可以由任意一个外部中断(EXTI)唤醒,在停止模式中可以选择电压调节器为开模式或低功耗模式。

特性和说明:

调压器低功耗模式: 在停止模式下调压器可工作在正常模式或低功耗模式,可进一步降低功耗。
进入方式: 内核寄存器的 SLEEPDEEP=1,PWR_CR 寄存器中的 PDDS=0,然后调用 WFI 或 WFE 指令即可进入停止模式;PWR_CR 寄存器的 LPDS=0 时,调压器工作在正常模式,LPDS=1 时工作在低功耗模式。
唤醒方式: 如果是使用 WFI 指令睡眠的,可使用任意 EXTI 线的中断唤醒;如果是使用 WFE 指令睡眠的,可使用任意配置为事件模式的 EXTI 线事件唤醒。
停止时: 内核停止,片上外设也停止。这个状态会保留停止前的内核寄存器、内存的数据。
唤醒延迟: 基础延迟为 HSI 振荡器的启动时间,若调压器工作在低功耗模式,还需要加上调压器从低功耗切换至正常模式下的时间。
唤醒后: 若由中断唤醒,先进入中断,退出中断服务程序后,接着执行 WFI 指令后的程序;若由事件唤醒,直接接着执行 WFE 后的程序。唤醒后,STM32 会使用 HSI 作为系统时钟。

只能由外部中断唤醒 唤醒后需要重新使能时钟(SystemClock_Config();)
建议将一条外部中断线专门作为唤醒中断,执行中断后进入回调进行时钟使能

停止模式0和1由PWR_MAINREGULATOR_ON和PWR_LOWPOWERREGULATOR_ON两个变量确定

停止模式0和1可以被串口 I2C等设备唤醒(具体看手册)

停止模式2则在pwr_ex.c中进入

停止模式2 只能被特定器件(如LPUART等在内部与EXTI有链接的器件)唤醒

若要配置UART唤醒 则需要:

/*!
 * @brief       	配置串口在停止模式下的唤醒   	
 *
 * @param 	[in]	huart: UART_HandleTypeDef类型的器件
 *					[in]	EnableNotDisable: 使能或者关闭
 *
 * @return				None
 */
void Ctrl_UART_StopMode_WakeUp(UART_HandleTypeDef *huart,bool EnableNotDisable)
{	
	if(EnableNotDisable)
	{
		__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI);	//保留唤醒用的HSI线 串口初始化时钟也必须要配置为HSI
	
		UART_WakeUpTypeDef UART_WakeUpStruct={0};
		UART_WakeUpStruct.WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY;  //接收数据不为空时唤醒
		HAL_UARTEx_StopModeWakeUpSourceConfig(huart,UART_WakeUpStruct);
	
		__HAL_UART_ENABLE_IT(&huart2,UART_IT_WUF);	//开启唤醒中断
		HAL_UARTEx_EnableStopMode(huart);		//开启模式
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart2,UART_IT_WUF);	//关闭唤醒中断
		HAL_UARTEx_DisableStopMode(huart);		//关闭模式
	}
}

配置为接收数据就唤醒

若要使用 则UART必须为HSI或MSI时钟 配置太麻烦 所以我建议直接在HAL里面配置
在这里插入图片描述
串口回调一般是:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart==&huart2)
  {		
		HAL_UART_Transmit(&huart2,&RxBuffer,1,0xFFFF);
		HAL_UART_Receive_IT(&huart2,&RxBuffer,1);
  }
	if(huart==&huart4)
  {		
		HAL_UART_Transmit(&huart4,&RxBuffer,1,0xFFFF);
		HAL_UART_Receive_IT(&huart4,&RxBuffer,1);
  }
}

接收数据后发送数据
而唤醒回调则是:

void HAL_UARTEx_WakeupCallback(UART_HandleTypeDef *huart)
{
	if(huart==&huart2)
  {		
		__HAL_RCC_PWR_CLK_ENABLE();
		SystemClock_Config();		
		Ctrl_UART_StopMode_WakeUp(huart,false);
  }
}

进入以后立马唤醒

若是串口悬空 或硬件设计问题 串口数据不定 则可能进入以后立马被唤醒
在这里插入图片描述
在外部硬件上加下拉 或者软件配置下拉(或上拉)即可 不过下拉更省电

串口唤醒和回调无法一起使用的问题

在调试时 发现串口唤醒和回调无法一起使用 进入了回调以后就退出了 不会进入串口唤醒

其实就是

__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI);	//保留唤醒用的HSI线 串口初始化时钟也必须要配置为HSI

的问题

若不使用这个语句 虽然串口可以用 也能接收数据并返回 但是进不了唤醒回调

其实就是因为时序被改变了

进入低功耗以后 接收数据唤醒 则先进入接收回调 然后发一次数据 但不会进行唤醒 因为时序有问题 mcu认为接收的数据不正常

保留这个语句以后就好了

另外 每次进入低功耗前 都要先调用

Ctrl_UART_StopMode_WakeUp(huart,true);

语句 否则无法正常唤醒 也不能再次进入低功耗模式

为了避免出错 每次唤醒以后都应该清空调唤醒中断

STOP模式会关闭时钟 所以建议是回调以后就初始化时钟一次

Ctrl_UART_StopMode_WakeUp(&huart2,true);
Enter_Low_PWR(2,0);

LPUART的配置同理 完全一模一样的语句

低功耗模式下的RTC唤醒(非闹钟唤醒,而是采用RTC_WAKEUPTIMER)

低功耗模式如图所示

停止模式有三种 分别是0 1 2
其中 0 1可以由串口唤醒
2只能由LPUART唤醒
在这里插入图片描述
在这里插入图片描述
在手册里可以查到

进入也很简单:

/*!
 * @brief       	进入低功耗模式   	
 *
 * @param 	[in]	mode_flag: 模式标志
 * 								0/大于4 不进入任何模式,1 进入睡眠,2 进入停止,3 进入待机,4 关机
 *  				[in]	WakeUpPinPolarity: 待机模式下WKUP唤醒引脚极性配置,其他模式无用
 *
 * @return				None
 */
void Enter_Low_PWR(uint8_t mode_flag,uint32_t WakeUpPinPolarity)
{	
	__HAL_RCC_PWR_CLK_ENABLE();
	switch(mode_flag)
	{
		case 0:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
		case 1:
		{
			printf("[INFO] 进入睡眠模式\n");
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);
			break;
		}
		case 2:
		{
			printf("[INFO] 进入停止模式\n");
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_SLEEPENTRY_WFI);
			break;
		}
		case 3:
		{
			printf("[INFO] 三秒后进入待机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入待机模式\n");
			HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWR_EnterSTANDBYMode();
			break;
		}
		case 4:
		{
			printf("[INFO] 三秒后进入关机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入关机模式\n");
			HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			HAL_PWREx_EnterSHUTDOWNMode();
			break;
		}
		default:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
	}
}

要进入停止2模式则需要在pwr_ex.c中配置
HAL_PWREx_EnterSTOP2Mode();函数

其中
HAL_PWR_EnterSTOPMode中的PWR_MAINREGULATOR_ON、PWR_LOWPOWERREGULATOR_ON分别是开启稳压器和关闭稳压器 分别对应STOP 0和1

所有的模式都可以用RTC唤醒
在这里插入图片描述
在这里插入图片描述
可以在手册里面找到外部中断线 所以RTC可以唤醒任一模式
在这里插入图片描述
RTC开启Internal WakeUp

在Wake Up配置中 完全不用设置(后面的语句可以直接配)
另外开启唤醒中断
在这里插入图片描述
在进入低功耗前 需要先调用RTC唤醒中断配置

HAL_RTCEx_SetWakeUpTimer_IT(&hrtc,counter,clock);

同样 退出时 要在注册的回调里面关闭中断

void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
	__HAL_RCC_PWR_CLK_ENABLE();
	SystemClock_Config();
	__HAL_RTC_WAKEUPTIMER_EXTI_DISABLE_IT();	
}

打包一下就是:

/*!
 * @brief       	配置RTC在低功耗模式下的唤醒   	
 *
 * @param 	[in]	counter: 计数值
 *					[in]	clock: 时钟值
 *					[in]	EnableNotDisable: 使能或者关闭
 *
 * @return				None
 */
void Ctrl_RTC_WakeUp(uint32_t counter,uint32_t clock,bool EnableNotDisable)
{	
	if(EnableNotDisable)
	{
		HAL_RTCEx_SetWakeUpTimer_IT(&hrtc,counter,clock);
	}
	else
	{
		__HAL_RTC_WAKEUPTIMER_EXTI_DISABLE_IT();	
	}
}

调用:

	Ctrl_UART_StopMode_WakeUp(&huart2,true);
	Ctrl_RTC_WakeUp(2000,RTC_WAKEUPCLOCK_RTCCLK_DIV16,true);
	Enter_Low_PWR(2,0);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		printf("[INFO] ADC: %f\n",Get_Real_ADC_Value(&hadc2));
		Count_ADXL345();
		Count_TMP75();
		TIM_Delay_ms(1000,&htim6);
		Ctrl_UART_StopMode_WakeUp(&huart2,true);
		Ctrl_RTC_WakeUp(2000,RTC_WAKEUPCLOCK_RTCCLK_DIV16,true);
		Enter_Low_PWR(2,0);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

低功耗模式下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);

先配置唤醒用的功能 再进入低功耗

当然 唤醒用的外设也需要在最开始进行初始化配置

GPIO省电模式实验

众所周知 GPIO配置为模拟输入最省电

在CubeMX中 有一项可以将未用到的引脚全部配置为模拟输入

在这里插入图片描述
若是不开启任何GPIO 以STM32L496RGT6为例 函数为:

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  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_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_PIN_15;
  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时钟

另外 如果配置了外部晶振或系统调试 则仅会开启时钟 而不会有任何引脚复用的配置

  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

但无论是官方手册 还是官方例程 引脚配置前都应开启时钟 再进行配置
所以在进行模拟输入配置时 都会把所有的引脚时钟开启

以我做的最小系统板进行测试:
download.csdn.net/download/weixin_53403301/86930297

在这里插入图片描述
其主要耗电为两个LED灯
去掉这两个LED灯 并且进入待机模式后 实测只有8uA以下的功耗

这两个LED灯也是为了方便测试

在恒压源3.3V供电的前提下 从以下四个方面进行测试 每次都在电流稳定后读取 三次复位以后取平均值 精度10uA:

  1. 按CubeMX生成的所有闲置引脚模拟输入代码 直接烧录进去
  2. 按CubeMX生成的代码 去掉GPIOB和GPIOD的时钟(仅保留系统调试和高低速外部晶振) 再进行初始化
  3. 按CubeMX生成的代码 进行初始化以后 再去掉GPIOB和GPIOD的时钟(仅保留系统调试和高低速外部晶振)
  4. 不进行GPIO初始化 GPIO时钟仅保留系统调试和高低速外部晶振时钟 关闭GPIOB和GPIOD的时钟
  5. 只开启GPIOC、GPIOA、GPIOH的时钟,并且将C、A、H剩下的引脚配置为模拟输入 关闭GPIOB和GPIOD的时钟且不对其进行任何配置

其实测电流如下:

12345
按CubeMX生成的所有闲置引脚模拟输入代码 直接烧录进去按CubeMX生成的代码 去掉GPIOB和GPIOD的时钟(仅保留系统调试和高低速外部晶振) 再进行初始化按CubeMX生成的代码 进行初始化以后 再去掉GPIOB和GPIOD的时钟(仅保留系统调试和高低速外部晶振)不进行GPIO初始化 GPIO时钟仅保留系统调试和高低速外部晶振时钟 关闭GPIOB和GPIOD的时钟只开启GPIOC、GPIOA、GPIOH的时钟,并且将C、A、H剩下的引脚配置为模拟输入 关闭GPIOB和GPIOD的时钟且不对其进行任何配置
1.27mA1.12mA1.29mA1.08mA1.13mA(同2)

其中 功耗最低的是不进行模拟输入初始化的方案 仅关闭不需要的时钟 其次是仅配置需要保留的GPIO组时钟 并把其他引脚配置为模拟输入

在关闭时钟时 GPIO配置其实是无效的(还是会进行配置 将相关参数写入寄存器 但GPIO不会工作 所以无效)

所以方案2和方案5等价的 这两者都是在初始化之前没开启时钟 方案2虽然将寄存器写入 但功耗其实和5差不多 实验也存在误差 所以可以忽略不计

而方案4则更加直接了 没有进行任何GPIO配置 直接关闭不用的时钟

重点是1和3
1的话就是开启了所有时钟 然后把闲置引脚都配置为模拟输入
3则是进行1之后 再关闭闲置时钟
其中 3的耗电量是最高的 尽管初始化后面关闭了时钟 但其实最多就是跟1差不多 完全比不上2 4 5 甚至功耗比1还高(虽然可能存在误差)

所以 综合来说 以省电优先级顺序来看:
如果这一组GPIO都没用到过 那么直接不开启时钟就最省电
如果这一组GPIO有引脚用过了 时钟不能关 那么就将用过的引脚配置为模拟输入
尽量不要勾选CubeMX中的配置闲置引脚为模拟输入的选项 没用到的时钟还开启了会增加很多耗电

切记!!!:
不要将没用过的引脚配置为模拟输入 耗电量其实会稍微增加一点!
不要将没用过的GPIO时钟打开以后再配置为模拟输入 耗电量会增加很多 就算配置后再关时钟也没用!

低功耗模式扩展

在进入STOP模式时 GPIO会保留原本的状态 所以把开启后不需要再保留的GPIO配置为模拟输入确实省电 时钟的话不用的肯定关 其他的反正都会关(除了保留的时钟)

在进入SLEEP模式时 时钟并不会关闭 所以时钟应手动关闭 且将开启后的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);
	}
}

__WFI();进入不了休眠的可能原因

__WFI();为汇编指令

/**
  \brief   Wait For Interrupt
  \details Wait For Interrupt is a hint instruction that suspends execution until one of a number of events occurs.
 */
#define __WFI                             __wfi

其作用就是设备休眠 并等待任意中断实践唤醒
在这里插入图片描述

实际调用:

	__WFI();

但是 基本上直接都执行不了

最常见的就是中断没清理掉

在这里插入图片描述
在Keil的调试中可以看到活跃的中断
在这里插入图片描述
EPA分别表示Enable Pending Active

前两个表示开启但未发生 Active表示正在发生

所以进入休眠前需要调用中断清理
如:

__disable_irq(); 

或:

__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	  //清理唤醒标志 防止立刻唤醒

等标志

另外 实测发现 即使没有中断或唤醒标志 也会导致__WFI();无法进入 特别是程序刚开始运行的时候 这里其实就是没消抖 需要延时一会(哪怕1us)

	delay_us(1);
	__WFI();

再者 在进行调试时 如果采用单步调试(也相当于一种中断) 则会执行__WFI();后立马执行下一句

所以可以在__WFI();之前和之后打一个断点 用全速跑来判断是否进入

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/363931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

万字长文解析!复现和使用GPT-3/ChatGPT,你所应该知道的

关于作者 英文原版作者:杨靖锋,现任亚马逊科学家,本科毕业于北大,硕士毕业于佐治亚理工学院,师从 Stanford 杨笛一教授。 杨昊桐 译,王骁 修订 感谢靳弘业对第一版稿件的建议,感谢陈三星&am…

timm使用swin-transformer

1.安装 pip install timm2.timm中有多少个预训练模型 #timm中有多少个预训练模型 model_pretrain_list timm.list_models(pretrainedTrue) print(len(model_pretrain_list), model_pretrain_list[:3])3加载swin模型一般准会出错 model_ft timm.create_model(swin_base_pat…

Head First设计模式---4.工厂方法模式

2.1工厂方法模式 亦称: 虚拟构造函数、Virtual Constructor、Factory Method 工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。 [外链图片转存失败,源站可能有防盗链机制,建议将图片…

Linux 终端复用器Tmux

目录 Tmux讲解 配置tmux 配置tmux会话 配置tmux窗口(在会话界面进行配置) 配置tmux面板 配置窗口共享同步 Tmux讲解 RHEL5/6/7使用的是screen软件包 RHEL8使用的是tumx软件包(功能更强大,更易用) tmux的三个基本…

阿里云物联网平台设备模拟器

在使用阿里云物联网平台过程中,如果开始调试没有实际的物理设备,可以考虑在阿里云物联网平台使用官方自带的模拟器进行调试。不过也可以通过叶帆科技开发的阿里云物联网平台设备模拟器AliIoTSimulator进行调试,AliIoTSimulator可以独立运行&a…

第49章 API统一集中管理

1 关于统一集中管理API的一些思考 1、统一集中管理是保证工程性项目得保质、保量、成功实施,并对后期维护提供数据支撑的最有效,最节省资源和时间的技能和做法,软件做为一种特殊的工程性项目,也符合上述特性。 2、由于在前台实现中…

Leetcode6. N字形变换

一、题目描述: 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下: 之后,你的输出需要从左往右逐行读取,产…

【数据结构与算法】算法的时间复杂度和空间复杂度

文章目录前言1.算法效率1.1.如何衡量一个算法的好坏1.2.算法的复杂度2.时间复杂度2.1.时间复杂度的概念2.2.大O的渐进表示法2.3.常见时间复杂度计算举例2.4.常见时间复杂度3.空间复杂度4.复杂度oj练习Practice.1 消失的数字Practice.2 旋转数组写在最后前言 关于时空复杂度的分…

到底什么样的条件才能被浙大MBA录取?攻略集合

新一年管理类联考已悄然启动,很多考生把目标也都放在了浙江大学MBA项目上,那么浙江大学MBA项目好考吗?报考流程是怎样的?杭州达立易考教育在这里给大家汇总整理了浙大MBA项目相关资讯,分享给想要报考浙大MBA的同学&…

每天一道大厂SQL题【Day12】微众银行真题实战(二)

每天一道大厂SQL题【Day12】微众银行真题实战(二) 大家好,我是Maynor。相信大家和我一样,都有一个大厂梦,作为一名资深大数据选手,深知SQL重要性,接下来我准备用100天时间,基于大数据岗面试中的经典SQL题&…

Wgcloud安装和使用(性能监控)

一、Wgcloud说明 官网:https://www.wgstart.com/ WGCLOUD支持主机各种指标监测(cpu使用率,cpu温度,内存使用率,磁盘容量,磁盘IO,硬盘SMART健康状态,系统负载,连接数量&…

结合YOLOv8实现目标追踪

博主使用YOLOv8在自制数据集上取得了不错的效果,考虑到后期的安排,需要在完成目标检测后完成目标追踪功能。因此在本篇博文中主要介绍结合YOLOv8实现目标追踪。 项目源码: https://github.com/mikel-brostrom/yolov8_tracking 接下来便是调试…

CSS变量

前端的开发工作中,CSS 是不可或缺的部分;实际工作中,我们通过JavaScript 来进行数据和交互工作,CSS 为用户呈现可视化的界面。有时,CSS 来进行部分交互效果是不是会比 JavaScript 更高效、更省事呢? 一、变…

python并发编程(并发与并行,同步和异步,阻塞与非阻塞)

最近在学python的网络编程,学了socket通信,并利用socket实现了一个具有用户验证功能,可以上传下载文件、可以实现命令行功能,创建和删除文件夹,可以实现的断点续传等功能的FTP服务器。但在这当中,发现一些概…

《JeecgBoot系列》 如何设计表单实现“下拉组件二级联动“ ? 以省市二级联动为例

《JeecgBoot系列》 如何设计表单实现"下拉组件二级联动" ? 以省市二级联动为例 一、准备字典表 1.1 创建字典表 CREATE TABLE sys_link_table ( id int NULL, pid int NULL, name varchar(64) null );1.2 准备数据 idpidname1全国21浙江省32杭州市42宁波市51江苏…

postgres 源码解析50 LWLock轻量锁--1

简介 postgres LWLock(轻量级锁)是由SpinLock实现,主要提供对共享存储器的数据结构的互斥访问。LWLock有两种锁模式,一种为排他模式,另一种是共享模式,如果想要读取共享内存中的内容,需要在读取…

kafka如何动态消费新增topic主题

一、背景使用spring-kafka客户端,每次新增topic主题,都需要硬编码客户端并重新发布服务,操作麻烦耗时长,对于业务逻辑相似场景,创建新主题动态监听可以用kafka-batch-starter组件二、核心设计点1、动态接入消息&#x…

Scala模式匹配详解(第八章:基本语法、模式守卫、模式匹配类型)(尚硅谷笔记)

模式匹配第 8 章 模式匹配8.1 基本语法8.2 模式守卫8.3 模式匹配类型8.3.1 匹配常量8.3.2 匹配类型8.3.3 匹配数组8.3.4 匹配列表8.3.5 匹配元组8.3.6 匹配对象及样例类8.4 变量声明中的模式匹配8.5 for 表达式中的模式匹配8.6 偏函数中的模式匹配(了解)第 8 章 模式匹配 Scal…

Redis:缓存穿透、缓存雪崩和缓存击穿(未完待续)

Redis的缓存穿透、缓存雪崩和缓存击穿一. 缓存穿透1.1 概念1.2 造成的问题1.3 解决方案1.4 案例:查询商铺信息(缓存穿透的实现)二. 缓存雪崩2.1 概念2.2 解决方案三. 缓存击穿(热点key)3.1 概念3.2 解决方案3.3 案例&a…

网络基础概述

1.计算机网络背景 ​ 计算机刚刚发展的时候,是没有网络的,每一台计算机都是相互独立的。后来,人们有了多人协作的需求,人们就想办法把多台计算机用“线”连接起来,实现数据共享。后来,连接到一起的电脑越来…