[STM32] Establish your own low-power mode configuration library based on the HAL library (summary of all low-power configurations of the STM32L4 series)

[STM32] Establish your own low-power mode configuration library based on the HAL library (summary of all low-power configurations of the STM32L4 series)


This article discusses a library that integrates all previous low-power configuration functions (applicable to STM32L4 series)
Currently, in addition to the normal wake-up method, UART wake-up and RTC wake-up configurations are added.
If more wake-ups are added in the future (such as I2C, etc., they will be discussed directly in subsequent articles)
The library built in this article will no longer update other wake-up methods, but some BUGs will be fixed.

Relevant function calls and configurations can be found in my previous articles
gitee library

Low power consumption mode (you can skip this chapter directly)

[STM32 Notes] Summary of low-power mode configuration and pitfall avoidance

Introduction to Low Power Mode

The system provides several low-power modes to save power when the CPU does not need to run (such as when waiting for external events). It is up to the user to select the specific low-power mode based on the application to find the best balance between low power consumption, short startup time and available wake-up sources.

In sleep mode, stop mode and standby mode, if the backup domain power supply is powered normally, the RTC in the backup domain can run normally, and the data in the registers in the backup domain will be saved and will not be affected by the power consumption mode.

As can be seen from the table, these three low-power modes progress layer by layer, running fewer and fewer clocks or chip functions, so the power consumption is getting lower and lower.

model illustrate Entry method wake Effect on 1.8V region clock Impact on VDD region clock Regulator
sleep mode The core stops, and all peripherals including M3 core peripherals, such as NVIC, system clock (SysTick), etc. are still running. WFI, WFE commands (directly called by the HAL library) Any interrupt/event The core clock is turned off and has no effect on other clocks and ADC clocks. none open
stop mode all clocks have stopped Configure the PDDS+LPDS bit+SLEEPDEEP bit+WFI or WFE command of the PWR_CR register Any external interrupt EXTI (set in the external interrupt register) Turn off all clocks in the 1.8V region HSI and HSE oscillators are turned off Turn on or in low power mode (according to the settings of the power control register)
standby mode 1.8V power off Configure the PDDS+SLEEPDEEP bit of the PWR_CR register+WFI or WFE command WKUP, RTC alarm event on pin, external reset on NRST pin, IWDG reset Turn off all clocks in the 1.8V region HSI and HSE oscillators are turned off close

The general mode status table of L4 and L4+ can be found in the manual.
Insert image description here
Insert image description here

[STM32 Notes] RTC wake-up in low-power mode (not alarm clock wake-up, but using RTC_WAKEUPTIMER)

[STM32 Notes] GPIO power-saving configuration pitfall experiment in low-power mode (configuring idle pins as analog inputs actually consumes more power)

[STM32 Notes] Avoid pitfalls in power-saving configuration of GPIO and peripherals in low-power mode

sleep mode

In sleep mode, only the core clock is turned off and the core stops running, but its on-chip peripherals, including the peripherals of the CM3 core, all still run as usual. There are two ways to enter sleep mode. Its entry method determines the way to wake up from sleep, which are WFI (wait for interrupt) and WFE (wait for event), that is, waking up by waiting for "interrupt" and waking up by "event".

特性和说明:

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

You can start taking action after waking up and continue the program without configuring any registers.

Sleep mode and low-power sleep mode are two modes determined by PWR_MAINREGULATOR_ON and PWR_LOWPOWERREGULATOR_ON two variables

To enter low-power sleep mode, you must first enter low-power operating mode.

HAL_PWREx_EableLowPowerRunMode()

And the operating frequency is reduced to below 2MHz

Wake up directly from sleep mode

After waking up from the low-power sleep mode, it will enter the low-power running mode. If you want to work normally, you need to exit withHAL_PWREx_DisableLowPowerRunMode()

stop mode

In stop mode, all other clocks are further turned off, so all peripherals stop working. However, because part of the power supply in the 1.8V area is not turned off, the core registers and memory information are still retained, so from stop mode After waking up and restarting the clock, code execution can continue where it left off. Stop mode can be woken up by any external interrupt (EXTI). In stop mode, the voltage regulator can be selected to be in on mode or low power mode.

特性和说明:

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

Can only be woken up by an external interrupt. After waking up, the clock needs to be re-enabled (SystemClock_Config();)
It is recommended that an external interrupt line be dedicated as a wake-up interrupt. After executing the interrupt, enter the callback to enable the clock

Stop modes 0 and 1 are determined by two variablesPWR_MAINREGULATOR_ON and PWR_LOWPOWERREGULATOR_ON

Stop modes 0 and 1 can be awakened by serial port I2C and other devices (see the manual for details)

Stop mode 2 is entered in pwr_ex.c

Stop mode 2 can only be woken up by specific devices (such as LPUART and other devices that are internally linked to EXTI)

For details, see the subsequent article on STOP mode serial port wake-up.

[STM32 Notes] Serial port wake-up in low-power STOP stop mode of HAL library (solve the problem that serial port wake-up and callback cannot be used together)

standby mode

It is more appropriate to translate it into shutdown
Standby mode, in addition to turning off all clocks, it also completely turns off the power supply in the 1.8V area, that is, waking up from standby mode Finally, since there is no running record of the previous code, the chip can only be reset, the boot condition is re-detected, and the program is executed from the beginning. It has four wake-up methods, namely the rising edge of the WKUP (PA0) pin, RTC alarm event, reset of the NRST pin and IWDG (independent watchdog) reset.

特性和说明:

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

Build your own low power mode configuration library

First, in the previous article, we entered the low power mode through At the same time, we need to call the wake-up configuration function to exit before entering low power consumption. After low power consumption, the clock must also be initialized, etc. The library created is to integrate all configurations so that they can be directly replaced by a general function The configuration structure used to pass parameters in this article is a global variable In addition, the structure nested callback function is used, etc. This writing method is also commonly used in TI's SDKEnter_Low_PWR



Passing parameters through structures

The most important thing ismode_flagparameter
This parameter determines four low power consumption modes
Among them, stop mode The default is stop 1

typedef struct
{
    
    
	uint8_t mode_flag;  // 0/大于4 不进入任何模式,1 进入睡眠,2 进入停止1,3 进入待机,4 关机
	
	LOW_POWER_SLEEPEntry_Cfg SLEEPEntry_Cfg;  //进入睡眠模式的方式
	LOW_POWER_STOPEntry_Cfg STOPEntry_Cfg;  //进入停止模式的方式
	LOW_POWER_WakeUpPin_Cfg WakeUpPin_Cfg ;   //待机模式的唤醒引脚配置
	
	LOW_POWER_RTC_Cfg RTC_Cfg;  //RTC唤醒配置
	LOW_POWER_Device_Cfg Device_Cfg;	
	
	SystemClock_Config_Callback SystemClock_Config_Fxn;	 // 用于传入退出相关低功耗模式后 需要进行配置的系统时钟配置函数
}LOW_POWER_Entry_Cfg;

The secondary structure includes ways to enter low power consumption, standby mode wake-up pin configuration, and peripheral wake-up mode and clock configuration callbacks after wake-up.

Peripheral wake-up is divided into peripherals and RTC
The reason why RTC is listed separately is because there is only one RTC
and Other peripheral wake-ups may include UART, I2C SPI, etc.
So these functions I created are __weak declared and can be overridden in combination with different projects.

typedef void (*SystemClock_Config_Callback)(void);

typedef struct
{
    
    
	uint8_t SLEEPEntry;  //SLEEPEntry: 一般是 PWR_SLEEPENTRY_WFI 等待中断 也可以是 PWR_SLEEPENTRY_WFE
}LOW_POWER_SLEEPEntry_Cfg;

typedef struct
{
    
    
	uint8_t STOPEntry;  //STOPEntry: 一般是 PWR_STOPENTRY_WFI 等待中断 也可以是 PWR_STOPENTRY_WFE
}LOW_POWER_STOPEntry_Cfg;

typedef struct
{
    
    
	uint32_t WakeUpPinPolarity;  //WakeUpPinPolarity: 待机模式下WKUP唤醒引脚极性配置,其他模式无用 有的只能配置一个引脚 所以要看数据手册
	/*
	*    PWR_WAKEUP_PIN1_HIGH or PWR_WAKEUP_PIN1_LOW
  *    PWR_WAKEUP_PIN2_HIGH or PWR_WAKEUP_PIN2_LOW
  *    PWR_WAKEUP_PIN3_HIGH or PWR_WAKEUP_PIN3_LOW
  *    PWR_WAKEUP_PIN4_HIGH or PWR_WAKEUP_PIN4_LOW
  *    PWR_WAKEUP_PIN5_HIGH or PWR_WAKEUP_PIN5_LOW
	*/
}LOW_POWER_WakeUpPin_Cfg;

typedef struct
{
    
    
	bool EnableNotDisable;
	RTC_HandleTypeDef *rtc_handle;
	uint32_t counter;  //RTC计数值 由于进入低功耗模式会有约10ms消抖 所以建议减去这段时间
	uint32_t clock;  //RTC时钟源 一般是 RTC_WAKEUPCLOCK_RTCCLK_DIV16
}LOW_POWER_RTC_Cfg;

typedef struct
{
    
    
	bool EnableNotDisable;
	UART_HandleTypeDef *uart_handle;
	UART_WakeUpTypeDef UART_WakeUpStruct;  //UART唤醒的结构体配置 UART_WakeUpStruct.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY 就是接收数据不为空时唤醒
}LOW_POWER_UART_Cfg;

typedef struct
{
    
    
	LOW_POWER_UART_Cfg UART_Cfg[5];  //串口唤醒配置 有五个串口 所以最大buf长度为5
}LOW_POWER_Device_Cfg;

typedef struct
{
    
    
	uint8_t mode_flag;  // 0/大于4 不进入任何模式,1 进入睡眠,2 进入停止1,3 进入待机,4 关机
	
	LOW_POWER_SLEEPEntry_Cfg SLEEPEntry_Cfg;  //进入睡眠模式的方式
	LOW_POWER_STOPEntry_Cfg STOPEntry_Cfg;  //进入停止模式的方式
	LOW_POWER_WakeUpPin_Cfg WakeUpPin_Cfg ;   //待机模式的唤醒引脚配置
	
	LOW_POWER_RTC_Cfg RTC_Cfg;  //RTC唤醒配置
	LOW_POWER_Device_Cfg Device_Cfg;	
	
	SystemClock_Config_Callback SystemClock_Config_Fxn;	 // 用于传入退出相关低功耗模式后 需要进行配置的系统时钟配置函数
}LOW_POWER_Entry_Cfg;

In the function that enters low power consumption, each mode has a 10ms debounce. In fact, this can be omitted
But we use the HAL library. After the HAL library initializes the clock, SysTick will be turned on. If the interrupt is turned on, it will never be able to enter low power consumption (it will be woken up by the interrupt when entering), so I added a delay function I wrote myself, which includes a statement to turn off SysTick. If you don't use this to eliminate jitter, there will be no BUG. But I still recommend adding it, after all, it’s still 10ms

/*!
 * @brief       	进入低功耗模式   	
 *
 * @return				None
 */
__weak void Enter_Low_PWR(void)
{
    
    
	__HAL_RCC_PWR_CLK_ENABLE();
	switch(LP_Entry_Cfg.mode_flag)
	{
    
    
		case 0:
		{
    
    
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
		case 1:
		{
    
    
			printf("[INFO] 进入睡眠模式\n");
			delay_ms(10);  //消抖
			PWR_Device_Init(false);
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,LP_Entry_Cfg.SLEEPEntry_Cfg.SLEEPEntry);
			__HAL_RCC_PWR_CLK_ENABLE();			
			Ctrl_RTC_WakeUp(NULL,false);	
			PWR_Device_Init(true);
			break;
		}
		case 2:
		{
    
    
			printf("[INFO] 进入停止模式\n");
			delay_ms(10);  //消抖
			PWR_Device_Init(false);			
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			Ctrl_Stop_Mode_WakeUp_Device(true);			
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,LP_Entry_Cfg.STOPEntry_Cfg.STOPEntry);
			__HAL_RCC_PWR_CLK_ENABLE();			
			LP_Entry_Cfg.SystemClock_Config_Fxn();
			Ctrl_Stop_Mode_WakeUp_Device(false);
			Ctrl_RTC_WakeUp(NULL,false);	
			PWR_Device_Init(true);
			break;
		}
		case 3:
		{
    
    
			printf("[INFO] 三秒后进入待机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入待机模式\n");
			HAL_PWR_EnableWakeUpPin(LP_Entry_Cfg.WakeUpPin_Cfg.WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSTANDBYMode();
			break;
		}
		case 4:
		{
    
    
			printf("[INFO] 三秒后进入关机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入关机模式\n");
			HAL_PWR_EnableWakeUpPin(LP_Entry_Cfg.WakeUpPin_Cfg.WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWREx_EnterSHUTDOWNMode();
			break;
		}
		default:
		{
    
    
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
	}
}

RTC configuration

typedef struct
{
    
    
	bool EnableNotDisable;
	RTC_HandleTypeDef *rtc_handle;
	uint32_t counter;  //RTC计数值 由于进入低功耗模式会有约10ms消抖 所以建议减去这段时间
	uint32_t clock;  //RTC时钟源 一般是 RTC_WAKEUPCLOCK_RTCCLK_DIV16
}LOW_POWER_RTC_Cfg;

The clock source and count value here are the parameters passed inHAL_RTCEx_SetWakeUpTimer_IT


/*!
 * @brief       	配置RTC在低功耗模式下的唤醒   	
 *
 * @param 	[in]	RTC_Cfg: RTC配置
 *
 * @return				None
 */
__weak void Ctrl_RTC_WakeUp(LOW_POWER_RTC_Cfg *RTC_Cfg,bool EnableNotDisable)
{
    
    	
	if(EnableNotDisable)
	{
    
    
		HAL_RTCEx_SetWakeUpTimer_IT(RTC_Cfg->rtc_handle,RTC_Cfg->counter,RTC_Cfg->clock);
	}
	else
	{
    
    
		__HAL_RTC_WAKEUPTIMER_EXTI_DISABLE_IT();	
	}
}

[STM32 Notes] RTC wake-up in low-power mode (not alarm clock wake-up, but using RTC_WAKEUPTIMER)

UART configuration

[STM32 Notes] Serial port wake-up in low-power STOP stop mode of HAL library (solve the problem that serial port wake-up and callback cannot be used together)

typedef struct
{
    
    
	bool EnableNotDisable;
	UART_HandleTypeDef *uart_handle;
	UART_WakeUpTypeDef UART_WakeUpStruct;  //UART唤醒的结构体配置 UART_WakeUpStruct.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY 就是接收数据不为空时唤醒
}LOW_POWER_UART_Cfg;

UART_WakeUpStructGenerally,WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY is used in the structure to indicate waking up when the received data is not empty
. There are at most five serial ports, so one is defined in the structure. The array with a length of 5 is then judged to be NULL in the configuration function and skipped

/*!
 * @brief       	配置串口在停止模式下的唤醒   	
 *
 * @param 	[in]	UART_Cfg: UART配置
 *
 * @return				None
 */
__weak uint8_t Ctrl_UART_StopMode_WakeUp(LOW_POWER_UART_Cfg *UART_Cfg,bool EnableNotDisable)
{
    
    	
	if (!UART_Cfg->uart_handle)
	{
    
    
		return 0;
	}
	
	if(EnableNotDisable)
	{
    
    
		__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI);	//保留唤醒用的HSI线 串口初始化时钟也必须要配置为HSI
		HAL_UARTEx_StopModeWakeUpSourceConfig(UART_Cfg->uart_handle,UART_Cfg->UART_WakeUpStruct);
	
		__HAL_UART_ENABLE_IT(UART_Cfg->uart_handle,UART_IT_WUF);	//开启唤醒中断
		HAL_UARTEx_EnableStopMode(UART_Cfg->uart_handle);		//开启模式
	}
	else
	{
    
    
		__HAL_UART_DISABLE_IT(UART_Cfg->uart_handle,UART_IT_WUF);	//关闭唤醒中断
		HAL_UARTEx_DisableStopMode(UART_Cfg->uart_handle);		//关闭模式
	}
	
	return 1;
}

Configure the clock through callback function

typedef void (*SystemClock_Config_Callback)(void);

Declares a function pointer type
When calling, you need to pass in the function pointer, usually the system clock configuration
that isSystemClock_Config

Initialize low-power peripherals through fake callbacks

Before entering low power consumption and after exiting, you can reduce power consumption by turning off the peripherals that have been opened
Close:

PWR_Device_Init(false);

Open:

PWR_Device_Init(true);

This function also contains GPIO configuration
These two functions are written in a fake callback method
You need to make up for it yourself when calling Full code

[STM32 Notes] GPIO power-saving configuration pitfall experiment in low-power mode (configuring idle pins as analog inputs actually consumes more power)

[STM32 Notes] Avoid pitfalls in power-saving configuration of GPIO and peripherals in low-power mode

initialization function

The parameter passed here is the system clock configuration function. Of course, you can also write one yourself. Then there is the assignment of each variable. Here are the assignments of several common variables.

void Init_Enter_Low_PWR(SystemClock_Config_Callback SystemClock_Config_Fxn)
{
    
    	
	uint8_t i=0;
	memset(&LP_Entry_Cfg,0,sizeof(LP_Entry_Cfg));
	LP_Entry_Cfg.SystemClock_Config_Fxn=SystemClock_Config_Fxn;
	LP_Entry_Cfg.SLEEPEntry_Cfg.SLEEPEntry=PWR_SLEEPENTRY_WFI;
	LP_Entry_Cfg.STOPEntry_Cfg.STOPEntry=PWR_STOPENTRY_WFI;
	
	LP_Entry_Cfg.RTC_Cfg.counter=RTC_WAKEUPCLOCK_RTCCLK_DIV16;	
	
	for(i=0;i<sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg)/sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg[0]);i++)
	{
    
    
		LP_Entry_Cfg.Device_Cfg.UART_Cfg[i].UART_WakeUpStruct.WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY;
	}	
}


code integration

#ifndef __LOW_POWER_H__
#define __LOW_POWER_H__
#include "stm32l4xx_hal.h"
#include "DELAY.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

/*
进入低功耗的函数中 每个模式都有一个10ms的消抖 其实这个可以省略
但我们用的是HAL库 HAL库初始化时钟以后 会打开SysTick中断 如果这个开着 就一直无法进入低功耗(进入就被中断唤醒)
所以我这里加了个我自己写的延时函数 其中就有关闭SysTick的语句
如果你不用这个消抖 没啥BUG也行 但我还是建议加上去 毕竟也不差那10ms
*/

typedef void (*SystemClock_Config_Callback)(void);

typedef struct
{
    
    
	uint8_t SLEEPEntry;  //SLEEPEntry: 一般是 PWR_SLEEPENTRY_WFI 等待中断 也可以是 PWR_SLEEPENTRY_WFE
}LOW_POWER_SLEEPEntry_Cfg;

typedef struct
{
    
    
	uint8_t STOPEntry;  //STOPEntry: 一般是 PWR_STOPENTRY_WFI 等待中断 也可以是 PWR_STOPENTRY_WFE
}LOW_POWER_STOPEntry_Cfg;

typedef struct
{
    
    
	uint32_t WakeUpPinPolarity;  //WakeUpPinPolarity: 待机模式下WKUP唤醒引脚极性配置,其他模式无用 有的只能配置一个引脚 所以要看数据手册
	/*
	*    PWR_WAKEUP_PIN1_HIGH or PWR_WAKEUP_PIN1_LOW
  *    PWR_WAKEUP_PIN2_HIGH or PWR_WAKEUP_PIN2_LOW
  *    PWR_WAKEUP_PIN3_HIGH or PWR_WAKEUP_PIN3_LOW
  *    PWR_WAKEUP_PIN4_HIGH or PWR_WAKEUP_PIN4_LOW
  *    PWR_WAKEUP_PIN5_HIGH or PWR_WAKEUP_PIN5_LOW
	*/
}LOW_POWER_WakeUpPin_Cfg;

typedef struct
{
    
    
	bool EnableNotDisable;
	RTC_HandleTypeDef *rtc_handle;
	uint32_t counter;  //RTC计数值 由于进入低功耗模式会有约10ms消抖 所以建议减去这段时间
	uint32_t clock;  //RTC时钟源 一般是 RTC_WAKEUPCLOCK_RTCCLK_DIV16
}LOW_POWER_RTC_Cfg;

typedef struct
{
    
    
	bool EnableNotDisable;
	UART_HandleTypeDef *uart_handle;
	UART_WakeUpTypeDef UART_WakeUpStruct;  //UART唤醒的结构体配置 UART_WakeUpStruct.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY 就是接收数据不为空时唤醒
}LOW_POWER_UART_Cfg;

typedef struct
{
    
    
	LOW_POWER_UART_Cfg UART_Cfg[5];  //串口唤醒配置 有五个串口 所以最大buf长度为5
}LOW_POWER_Device_Cfg;

typedef struct
{
    
    
	uint8_t mode_flag;  // 0/大于4 不进入任何模式,1 进入睡眠,2 进入停止1,3 进入待机,4 关机
	
	LOW_POWER_SLEEPEntry_Cfg SLEEPEntry_Cfg;  //进入睡眠模式的方式
	LOW_POWER_STOPEntry_Cfg STOPEntry_Cfg;  //进入停止模式的方式
	LOW_POWER_WakeUpPin_Cfg WakeUpPin_Cfg ;   //待机模式的唤醒引脚配置
	
	LOW_POWER_RTC_Cfg RTC_Cfg;  //RTC唤醒配置
	LOW_POWER_Device_Cfg Device_Cfg;	
	
	SystemClock_Config_Callback SystemClock_Config_Fxn;	 // 用于传入退出相关低功耗模式后 需要进行配置的系统时钟配置函数
}LOW_POWER_Entry_Cfg;

extern LOW_POWER_Entry_Cfg LP_Entry_Cfg;

void GPIO_Reset_Init(bool EnableNotDisable);
void PWR_Device_Init(bool EnableNotDisable);
uint8_t Ctrl_UART_StopMode_WakeUp(LOW_POWER_UART_Cfg *UART_Cfg,bool EnableNotDisable);
void Ctrl_RTC_WakeUp(LOW_POWER_RTC_Cfg *RTC_Cfg,bool EnableNotDisable);

void Ctrl_Stop_Mode_WakeUp_Device(bool EnableNotDisable);
void Enter_Low_PWR(void);

void Init_Enter_Low_PWR(SystemClock_Config_Callback SystemClock_Config_Fxn);
	
#endif

#include "stm32l4xx_hal.h"
#include "LOW_POWER.h"

LOW_POWER_Entry_Cfg LP_Entry_Cfg={
    
    0};

/*!
 * @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
 */
__weak 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);
		*/
	}
}

/*!
 * @brief       	所有外设初始化配置,根据使用需求来写
 *
 * @param 	[in]	EnableNotDisable: 使能或者关闭
 *								true: 进行初始化外设(不包含时钟初始化)
 *								false: 或者关闭所有外设,所有GPIO配置为无上拉下拉且模拟输入,仅保留系统时钟和系统所需的GPIO口复用
 *								该函数在进入低功耗前调用(false)
 *								建议在进入该函数前(false)先配置用于唤醒的外设 如指定UART或RTC作为唤醒使用 然后再调用该函数 且不能关闭有唤醒功能的外设
 *								若用于唤醒后的初始化,则建议先初始化时钟,再执行该函数的初始化(true)
 *								在休眠期间使用的外设,不要关闭,也不要关闭GPIO等;相反,外设和GPIO等建议同时关闭(避免出现bug,并且也省电)
 *								未关闭,但唤醒时重复初始化外设并不受影响
 *								若未关闭的外设在运行中改变了初始化值,则建议不在唤醒时运行该初始化(前提是外设的GPIO等也没有作改动)
 *								若需要在初始化后更改初始化值,则建议要么不进行初始化且不关闭(也包括GPIO等),或重新设置新值
 *
 * @return				None
 */
__weak 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配置为复用
		
	}
}

/*!
 * @brief       	配置串口在停止模式下的唤醒   	
 *
 * @param 	[in]	UART_Cfg: UART配置
 *
 * @return				None
 */
__weak uint8_t Ctrl_UART_StopMode_WakeUp(LOW_POWER_UART_Cfg *UART_Cfg,bool EnableNotDisable)
{
    
    	
	if (!UART_Cfg->uart_handle)
	{
    
    
		return 0;
	}
	
	if(EnableNotDisable)
	{
    
    
		__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI);	//保留唤醒用的HSI线 串口初始化时钟也必须要配置为HSI
		HAL_UARTEx_StopModeWakeUpSourceConfig(UART_Cfg->uart_handle,UART_Cfg->UART_WakeUpStruct);
	
		__HAL_UART_ENABLE_IT(UART_Cfg->uart_handle,UART_IT_WUF);	//开启唤醒中断
		HAL_UARTEx_EnableStopMode(UART_Cfg->uart_handle);		//开启模式
	}
	else
	{
    
    
		__HAL_UART_DISABLE_IT(UART_Cfg->uart_handle,UART_IT_WUF);	//关闭唤醒中断
		HAL_UARTEx_DisableStopMode(UART_Cfg->uart_handle);		//关闭模式
	}
	
	return 1;
}

/*!
 * @brief       	配置停止模式下的外设唤醒函数 true为开启 false为关闭 (不包含RTC唤醒)
 *
 * @return				None
 */
__weak void Ctrl_Stop_Mode_WakeUp_Device(bool EnableNotDisable)
{
    
    
	uint8_t i=0;
	
	if(EnableNotDisable)
	{
    
    
		
		for(i=0;i<sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg)/sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg[0]);i++)
		{
    
    
			if(!Ctrl_UART_StopMode_WakeUp(&LP_Entry_Cfg.Device_Cfg.UART_Cfg[i],LP_Entry_Cfg.Device_Cfg.UART_Cfg[i].EnableNotDisable))
			{
    
    
				break;
			}
		}		
		
	}
	else
	{
    
    
		
		for(i=0;i<sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg)/sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg[0]);i++)
		{
    
    
			if(!Ctrl_UART_StopMode_WakeUp(&LP_Entry_Cfg.Device_Cfg.UART_Cfg[i],false))
			{
    
    
				break;	
			}				
		}
		
	}
}

/*!
 * @brief       	配置RTC在低功耗模式下的唤醒   	
 *
 * @param 	[in]	RTC_Cfg: RTC配置
 *
 * @return				None
 */
__weak void Ctrl_RTC_WakeUp(LOW_POWER_RTC_Cfg *RTC_Cfg,bool EnableNotDisable)
{
    
    	
	if(EnableNotDisable)
	{
    
    
		HAL_RTCEx_SetWakeUpTimer_IT(RTC_Cfg->rtc_handle,RTC_Cfg->counter,RTC_Cfg->clock);
	}
	else
	{
    
    
		__HAL_RTC_WAKEUPTIMER_EXTI_DISABLE_IT();	
	}
}

/*
进入低功耗的函数中 每个模式都有一个10ms的消抖 其实这个可以省略
但我们用的是HAL库 HAL库初始化时钟以后 会打开SysTick中断 如果这个开着 就一直无法进入低功耗(进入就被中断唤醒)
所以我这里加了个我自己写的延时函数 其中就有关闭SysTick的语句
如果你不用这个消抖 没啥BUG也行 但我还是建议加上去 毕竟也不差那10ms
*/

/*!
 * @brief       	进入低功耗模式   	
 *
 * @return				None
 */
__weak void Enter_Low_PWR(void)
{
    
    
	__HAL_RCC_PWR_CLK_ENABLE();
	switch(LP_Entry_Cfg.mode_flag)
	{
    
    
		case 0:
		{
    
    
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
		case 1:
		{
    
    
			printf("[INFO] 进入睡眠模式\n");
			delay_ms(10);  //消抖
			PWR_Device_Init(false);
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,LP_Entry_Cfg.SLEEPEntry_Cfg.SLEEPEntry);
			__HAL_RCC_PWR_CLK_ENABLE();			
			Ctrl_RTC_WakeUp(NULL,false);	
			PWR_Device_Init(true);
			break;
		}
		case 2:
		{
    
    
			printf("[INFO] 进入停止模式\n");
			delay_ms(10);  //消抖
			PWR_Device_Init(false);			
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			Ctrl_Stop_Mode_WakeUp_Device(true);			
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,LP_Entry_Cfg.STOPEntry_Cfg.STOPEntry);
			__HAL_RCC_PWR_CLK_ENABLE();			
			LP_Entry_Cfg.SystemClock_Config_Fxn();
			Ctrl_Stop_Mode_WakeUp_Device(false);
			Ctrl_RTC_WakeUp(NULL,false);	
			PWR_Device_Init(true);
			break;
		}
		case 3:
		{
    
    
			printf("[INFO] 三秒后进入待机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入待机模式\n");
			HAL_PWR_EnableWakeUpPin(LP_Entry_Cfg.WakeUpPin_Cfg.WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSTANDBYMode();
			break;
		}
		case 4:
		{
    
    
			printf("[INFO] 三秒后进入关机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入关机模式\n");
			HAL_PWR_EnableWakeUpPin(LP_Entry_Cfg.WakeUpPin_Cfg.WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWREx_EnterSHUTDOWNMode();
			break;
		}
		default:
		{
    
    
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
	}
}

void Init_Enter_Low_PWR(SystemClock_Config_Callback SystemClock_Config_Fxn)
{
    
    	
	uint8_t i=0;
	memset(&LP_Entry_Cfg,0,sizeof(LP_Entry_Cfg));
	LP_Entry_Cfg.SystemClock_Config_Fxn=SystemClock_Config_Fxn;
	LP_Entry_Cfg.SLEEPEntry_Cfg.SLEEPEntry=PWR_SLEEPENTRY_WFI;
	LP_Entry_Cfg.STOPEntry_Cfg.STOPEntry=PWR_STOPENTRY_WFI;
	
	LP_Entry_Cfg.RTC_Cfg.counter=RTC_WAKEUPCLOCK_RTCCLK_DIV16;	
	
	for(i=0;i<sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg)/sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg[0]);i++)
	{
    
    
		LP_Entry_Cfg.Device_Cfg.UART_Cfg[i].UART_WakeUpStruct.WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY;
	}	
}


Calling method

First, you need to call the initialization function
and pass in the system clock initialization function address

Init_Enter_Low_PWR(SystemClock_Config);

Then you need to configure it before entering low power consumption

		LP_Entry_Cfg.mode_flag=2;
		LP_Entry_Cfg.STOPEntry_Cfg.STOPEntry=PWR_STOPENTRY_WFI;
		LP_Entry_Cfg.SystemClock_Config_Fxn=SystemClock_Config;
		LP_Entry_Cfg.RTC_Cfg.EnableNotDisable=true;
		LP_Entry_Cfg.RTC_Cfg.rtc_handle=&hrtc;
		LP_Entry_Cfg.RTC_Cfg.clock=RTC_WAKEUPCLOCK_RTCCLK_DIV16;
		LP_Entry_Cfg.RTC_Cfg.counter=300;
		Enter_Low_PWR();	

Finally useEnter_Low_PWR(); function to enter low power consumption

Appendix: Cortex-M architecture SysTick system timer precise delay and MCU bit band operation

SysTick system timer precise delay

delay function

SysTick->The value in LOAD is the count value
The calculation method is the operating frequency value/frequency division value
For example, the operating frequency/1000 then The period is 1ms

Take ADuCM4050 as an example:

#include "ADuCM4050.h"

void delay_ms(unsigned int ms)
{
    
    
	SysTick->LOAD = 26000000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能52MHz的系统定时器
	while(ms--)
	{
    
    
		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
	}
	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{
    
    
	SysTick->LOAD = 26000000/1000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能52MHz的系统定时器
	while(us--)
	{
    
    
		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
	}
	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

The 52000000 represents the system timer frequency of the chip. The 32 series is generally twice the external timer frequency.

Cortex-M architecture SysTick system timer blocking and non-blocking delay

blocking delay

The first is the most commonly used blocking delay

void delay_ms(unsigned int ms)
{
    
    
	SysTick->LOAD = 50000000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	while(ms--)
	{
    
    
		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
	}
	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{
    
    
	SysTick->LOAD = 50000000/1000/1000-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	while(us--)
	{
    
    
		while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待
	}
	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

50000000 represents the operating frequency
After dividing the frequency, you can get different delay times
and so on

Then it can be written as: without two nested while loops:

void delay_ms(unsigned int ms)
{
    
    
	SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器

	while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{
    
    
	SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	
	while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

But this way of writing has a drawback
That is, after inputting ms, the maximum timing must not exceed the count value, that is, it cannot exceed the maximum value of LOAD, otherwise it will not work normally after overflow. work

And if the maximum size of LOAD is 32 bits, that is 4294967295

If the crystal oscillator is 50M, the count value of 50M is 1s and the count value of 4294967295 is about 85s.

Fixed maximum timing time is 85s

But using nested while, the maximum supported timing is 4294967295*85s.

non-blocking delay

If non-blocking is used, just rewrite the second method directly:

void delay_ms(unsigned int ms)
{
    
    
	SysTick->LOAD = 50000000/1000*ms-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器

	//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}
void delay_us(unsigned int us)
{
    
    
	SysTick->LOAD = 50000000/1000/1000*us-1; // Count from 255 to 0 (256 cycles)  载入计数值 定时器从这个值开始计数
	SysTick->VAL = 0; // Clear current value as well as count flag  清空计数值到达0后的标记
	SysTick->CTRL = 5; // Enable SysTick timer with processor clock  使能26MHz的系统定时器
	
	//while ((SysTick->CTRL & 0x00010000)==0);// Wait until count flag is set  等待

	//SysTick->CTRL = 0; // Disable SysTick  关闭系统定时器
}

Remove the wait and close timer statements
Add judgment when using it to become blocking:

delay_ms(500);
while ((SysTick->CTRL & 0x00010000)==0);
SysTick->CTRL = 0;

In a non-blocking state, you can submit the timer, do other things, and then wait again.

However, there is another drawback to this, that is, the timer will automatically reload. Maybe after doing other things, the timer will run out and you will have to wait 85 seconds to stop.

Therefore, non-blocking delay functions can be written through internal timers.

Basically, the internal timer of every MCU can be configured with automatic reloading and other functions. There is a lot of information online, so I won’t elaborate on it here.

Bitband operations

bitband code

The output address of the M3 and M4 architecture microcontroller is port address +20 and the input is +16
The output port address of the M0 architecture microcontroller is port address +12 and the input is +8
Taking ADuCM4050 as the column:

Bitband macro definition
#ifndef __GPIO_H__
#define __GPIO_H__
#include "ADuCM4050.h"
#include "adi_gpio.h"

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

#define GPIO0_ODR_Addr    (ADI_GPIO0_BASE+20) //0x40020014
#define GPIO0_IDR_Addr    (ADI_GPIO0_BASE+16) //0x40020010

#define GPIO1_ODR_Addr    (ADI_GPIO1_BASE+20) //0x40020054
#define GPIO1_IDR_Addr    (ADI_GPIO1_BASE+16) //0x40020050

#define GPIO2_ODR_Addr    (ADI_GPIO2_BASE+20) //0x40020094
#define GPIO2_IDR_Addr    (ADI_GPIO2_BASE+16) //0x40020090

#define GPIO3_ODR_Addr    (ADI_GPIO3_BASE+20) //0x400200D4
#define GPIO3_IDR_Addr    (ADI_GPIO3_BASE+16) //0x400200D0

#define P0_O(n)   	BIT_ADDR(GPIO0_ODR_Addr,n)  //输出 
#define P0_I(n)    	BIT_ADDR(GPIO0_IDR_Addr,n)  //输入 

#define P1_O(n)   	BIT_ADDR(GPIO1_ODR_Addr,n)  //输出 
#define P1_I(n)    	BIT_ADDR(GPIO1_IDR_Addr,n)  //输入 

#define P2_O(n)   	BIT_ADDR(GPIO2_ODR_Addr,n)  //输出 
#define P2_I(n)    	BIT_ADDR(GPIO2_IDR_Addr,n)  //输入 

#define P3_O(n)   	BIT_ADDR(GPIO3_ODR_Addr,n)  //输出 
#define P3_I(n)    	BIT_ADDR(GPIO3_IDR_Addr,n)  //输入 

#define Port0			(ADI_GPIO_PORT0)
#define Port1			(ADI_GPIO_PORT1)
#define Port2			(ADI_GPIO_PORT2)
#define Port3			(ADI_GPIO_PORT3)

#define Pin0			(ADI_GPIO_PIN_0)
#define Pin1			(ADI_GPIO_PIN_1)
#define Pin2			(ADI_GPIO_PIN_2)
#define Pin3			(ADI_GPIO_PIN_3)
#define Pin4			(ADI_GPIO_PIN_4)
#define Pin5			(ADI_GPIO_PIN_5)
#define Pin6			(ADI_GPIO_PIN_6)
#define Pin7			(ADI_GPIO_PIN_7)
#define Pin8			(ADI_GPIO_PIN_8)
#define Pin9			(ADI_GPIO_PIN_9)
#define Pin10			(ADI_GPIO_PIN_10)
#define Pin11			(ADI_GPIO_PIN_11)
#define Pin12			(ADI_GPIO_PIN_12)
#define Pin13			(ADI_GPIO_PIN_13)
#define Pin14			(ADI_GPIO_PIN_14)
#define Pin15			(ADI_GPIO_PIN_15)

void GPIO_OUT(unsigned int port,unsigned int pin,unsigned int flag);
void GPIO_BUS_OUT(unsigned int port,unsigned int num);

void P0_BUS_O(unsigned int num);
unsigned int P0_BUS_I(void);

void P1_BUS_O(unsigned int num);
unsigned int P1_BUS_I(void);

void P2_BUS_O(unsigned int num);
unsigned int P2_BUS_I(void);

void P3_BUS_O(unsigned int num);
unsigned int P3_BUS_I(void);

#endif

bus function
#include "ADuCM4050.h"
#include "adi_gpio.h"
#include "GPIO.h"

void GPIO_OUT(unsigned int port,unsigned int pin,unsigned int flag)
{
    
    
	switch(port)
	{
    
    
		case 0:{
    
    
			switch(pin)
			{
    
    
				case 0:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_0));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_0));};break;
				case 1:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_1));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_1));};break;
				case 2:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_2));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_2));};break;
				case 3:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_3));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_3));};break;
				case 4:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_4));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_4));};break;
				case 5:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_5));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_5));};break;
				case 6:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_6));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_6));};break;
				case 7:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_7));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_7));};break;
				case 8:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_8));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_8));};break;
				case 9:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_9));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_9));};break;
				case 10:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_10));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_10));};break;
				case 11:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_11));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_11));};break;
				case 12:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_12));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_12));};break;
				case 13:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_13));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_13));};break;
				case 14:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_14));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_14));};break;
				case 15:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT0),(ADI_GPIO_PIN_15));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT0),(ADI_GPIO_PIN_15));};break;
				default:pin=0;break;
			}
		}break;
		
		case 1:{
    
    
			switch(pin)
			{
    
    
				case 0:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_0));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_0));};break;
				case 1:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_1));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_1));};break;
				case 2:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_2));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_2));};break;
				case 3:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_3));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_3));};break;
				case 4:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_4));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_4));};break;
				case 5:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_5));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_5));};break;
				case 6:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_6));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_6));};break;
				case 7:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_7));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_7));};break;
				case 8:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_8));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_8));};break;
				case 9:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_9));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_9));};break;
				case 10:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_10));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_10));};break;
				case 11:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_11));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_11));};break;
				case 12:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_12));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_12));};break;
				case 13:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_13));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_13));};break;
				case 14:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_14));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_14));};break;
				case 15:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT1),(ADI_GPIO_PIN_15));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT1),(ADI_GPIO_PIN_15));};break;
				default:pin=0;break;
			}
		}break;
		
		case 2:{
    
    
			switch(pin)
			{
    
    
				case 0:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_0));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_0));};break;
				case 1:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_1));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_1));};break;
				case 2:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_2));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_2));};break;
				case 3:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_3));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_3));};break;
				case 4:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_4));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_4));};break;
				case 5:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_5));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_5));};break;
				case 6:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_6));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_6));};break;
				case 7:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_7));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_7));};break;
				case 8:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_8));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_8));};break;
				case 9:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_9));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_9));};break;
				case 10:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_10));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_10));};break;
				case 11:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_11));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_11));};break;
				case 12:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_12));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_12));};break;
				case 13:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_13));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_13));};break;
				case 14:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_14));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_14));};break;
				case 15:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT2),(ADI_GPIO_PIN_15));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT2),(ADI_GPIO_PIN_15));};break;
				default:pin=0;break;
			}
		}break;
		
		case 3:{
    
    
			switch(pin)
			{
    
    
				case 0:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_0));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_0));};break;
				case 1:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_1));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_1));};break;
				case 2:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_2));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_2));};break;
				case 3:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_3));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_3));};break;
				case 4:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_4));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_4));};break;
				case 5:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_5));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_5));};break;
				case 6:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_6));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_6));};break;
				case 7:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_7));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_7));};break;
				case 8:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_8));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_8));};break;
				case 9:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_9));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_9));};break;
				case 10:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_10));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_10));};break;
				case 11:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_11));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_11));};break;
				case 12:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_12));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_12));};break;
				case 13:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_13));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_13));};break;
				case 14:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_14));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_14));};break;
				case 15:if(flag==1){
    
    adi_gpio_SetHigh((ADI_GPIO_PORT3),(ADI_GPIO_PIN_15));}else{
    
    adi_gpio_SetLow((ADI_GPIO_PORT3),(ADI_GPIO_PIN_15));};break;
				default:pin=0;break;
			}
		}break;
		
		default:port=0;break;
	}	
}

void GPIO_BUS_OUT(unsigned int port,unsigned int num)  //num最大为0xffff
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		GPIO_OUT(port,i,(num>>i)&0x0001);
	}
}


void P0_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		P0_O(i)=(num>>i)&0x0001;
	}
}
unsigned int P0_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(P0_I(i)<<i)&0xFFFF;
	}
	return num;
}

void P1_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		P1_O(i)=(num>>i)&0x0001;
	}
}
unsigned int P1_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(P1_I(i)<<i)&0xFFFF;
	}
	return num;
}

void P2_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		P2_O(i)=(num>>i)&0x0001;
	}
}
unsigned int P2_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(P2_I(i)<<i)&0xFFFF;
	}
	return num;
}

void P3_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		P3_O(i)=(num>>i)&0x0001;
	}
}
unsigned int P3_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(P3_I(i)<<i)&0xFFFF;
	}
	return num;
}

1. Bit band operation theory and practice

The concept of bit band operation actually existed 30 years ago. It was CM3 that evolved this capability. The bit band operation here is a greatly enhanced version of the 8051 bit addressing area.

Bitband area: Address area that supports bitband operations

Bitband alias: Access to the alias address ultimately affects access to the bitband (note: there is an address mapping process in the middle)

Bitband operations are most useful for low-level programs that are hardware I/O intensive.

After supporting bit band operations, ordinary load/store instructions can be used to read and write single bits. In CM4, bitbanding is implemented in two zones. One of them is the lowest 1MB range of the SRAM area, and the second is the lowest 1MB range of the on-chip peripheral area. In addition to being used like ordinary RAM, the addresses in these two areas also have their own "bit band alias area", which expands each bit into a 32-bit word. When you access these words through the bitband alias area, you can access the original bits.

Bit operation means that you can read and write a bit individually, similar to the variable defined by sbit in 51. In stm32, the function of bit operation is realized by accessing the bit alias area
Bit strips are implemented in two places in STM32, one is SRAM and the other is on-chip peripherals.
Insert image description here
(1) The bit band is essentially an address area (for example, each address bit corresponds to a register) mapped to another address area (each address bit corresponds to a register) bit), this area is called the bit band alias area, and each bit is expanded into a 32-bit word.
(2) The 4 bytes of the bit band area correspond to one bit of the actual register or memory area. Although it becomes larger to 4 bytes, only the lowest bit is actually valid (representing 0 or 1)

Only the bit band can be directly operated using = assignment. The bit band expands each bit in the register to 32 bits and maps it to the bit band. For example, the 0th bit of the 0x4002 0000 address is mapped to the 0 address of the bit band. Then its corresponding The bit band mapping address is 0x00 - 0x04, a total of 32 bits, but only the LSB is valid in bit band mode. When assigning a value, the LSB corresponding to the bit band area is assigned, and then the MCU goes to the corresponding bit in the register. If the register operation does not change The values ​​​​on other bits can only be determined by &= or |=.

Insert image description here

To set the second bit bit2 of the byte 0x2000 0000 to 1, the steps to use the bit band operation are:
1. Write 1 to the corresponding bit band alias area Mapping address (i.e. 0x22000008, because 1 bit corresponds to 4 bytes);
2. Read the value of 0x2000 0000 into the internal buffer (this step is completed by the kernel and is an atomic operation. No user operation is required);
3. Set bit2 to 1, and then write the value back to 0x2000 0000 (it is an atomic operation and does not require user operation).

For the access address corresponding to the GPIO pin, you can refer to the following formula
Register bit with alias = 0x42000000 + (register address-0x40000000) 32 + pin number4

For example: the starting address GPIOF_BASE for port F access

#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)

Insert image description here

But fortunately, the official library has defined it for us. We only need to add cheap to the BASE address.

For example:

The address of the ODR register of GPIOF = GPIOF_BASE + 0x14

Register bit with alias = 0x42000000 + (register address-0x40000000)32 + pin number4

To set the PF9 pin:

uint32_t *PF9_BitBand =
*(uint32_t *)(0x42000000 + ((uint32_t )&GPIOF->ODR– 0x40000000) *32 + 9*4)

Encapsulate it:

#define PFout(x) *(volatile uint32_t *)(0x42000000 + ((uint32_t )&GPIOF->ODR – 0x40000000) *32 + x*4)

Now the common part can be encapsulated into a small definition:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

Then the function to set the PF pin can be defined:

#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414   
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 

#define PF_O(n)   	BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PF_I(n)    	BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

If PF9 is enabled for input and output:

PF_O(9)=1;  //输出高电平
uint8_t dat = PF_I(9);  //获取PF9引脚的值

Bus input and output:

void PF_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PF_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PF_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PF_I(i)<<i)&0xFFFF;
	}
	return num;
}

The following functions are available for STM32:

#ifndef __GPIO_H__
#define __GPIO_H__
#include "stm32l496xx.h"

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))

#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014     

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
 
#define PA_O(n)   	BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PA_I(n)    	BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PB_O(n)   	BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PB_I(n)    	BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PC_O(n)   	BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PC_I(n)    	BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PD_O(n)   	BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PD_I(n)    	BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PE_O(n)   	BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PE_I(n)    	BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PF_O(n)   	BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PF_I(n)    	BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PG_O(n)   	BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PG_I(n)    	BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

#define PH_O(n)   	BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PH_I(n)    	BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PI_O(n)			BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PI_I(n)   	BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

void PA_BUS_O(unsigned int num);
unsigned int PA_BUS_I(void);

void PB_BUS_O(unsigned int num);
unsigned int PB_BUS_I(void);

void PC_BUS_O(unsigned int num);
unsigned int PC_BUS_I(void);

void PD_BUS_O(unsigned int num);
unsigned int PD_BUS_I(void);

void PE_BUS_O(unsigned int num);
unsigned int PE_BUS_I(void);

void PF_BUS_O(unsigned int num);
unsigned int PF_BUS_I(void);

void PG_BUS_O(unsigned int num);
unsigned int PG_BUS_I(void);

void PH_BUS_O(unsigned int num);
unsigned int PH_BUS_I(void);

void PI_BUS_O(unsigned int num);
unsigned int PI_BUS_I(void);

#endif

#include "GPIO.h"

void PA_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PA_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PA_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PA_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PB_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PB_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PB_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PB_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PC_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PC_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PC_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PC_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PD_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PD_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PD_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PD_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PE_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PE_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PE_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PE_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PF_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PF_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PF_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PF_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PG_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PG_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PG_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PG_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PH_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PH_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PH_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PH_I(i)<<i)&0xFFFF;
	}
	return num;
}

void PI_BUS_O(unsigned int num)  //输入值num最大为0xFFFF
{
    
    
	int i;
	for(i=0;i<16;i++)
	{
    
    
		PI_O(i)=(num>>i)&0x0001;
	}
}
unsigned int PI_BUS_I(void)  //输出值num最大为0xFFFF
{
    
    
	unsigned int num;
	int i;
	for(i=0;i<16;i++)
	{
    
    
		num=num+(PI_I(i)<<i)&0xFFFF;
	}
	return num;
}

2. How to determine whether the MCU peripherals support bit banding

According to the description in Chapter 6, Section 7 of "ARM Cortex-M3 and Cortex-M4 Authoritative Guide (3rd Edition)"
Insert image description here
In other words, it is necessary to implement the bit banding of GPIO The operation must ensure that the GPIO is located in the first 1MB of the peripheral area
The first 1MB should be 0x4010. The bit band before 0000 is not a direct operation address but an operation address mapping. After the address mapping is operated, the MCU automatically Will modify the value of the corresponding register

The bit band area is only 1MB, so you can only change the registers 0x4000 0000 - 0x400F FFFF
For example, if the first address of the F4 series GPIO is 0x4002 0000, you can use the bit band to change it

The GPIO of STM32L476 does not work:
Insert image description here
AHB2 cannot use bitbands
Both ABP and AHB1 can be used
Insert image description here
But the GPIO and ADC in the L476 register are both AHB2

Guess you like

Origin blog.csdn.net/weixin_53403301/article/details/134182195