【STM32】基于HAL库建立自己的低功耗模式配置库(STM32L4系列低功耗所有配置汇总)

【STM32】基于HAL库建立自己的低功耗模式配置库(STM32L4系列低功耗所有配置汇总)

文章目录

  • 低功耗模式(此章节可直接跳过)
    • 低功耗模式简介
      • 睡眠模式
      • 停止模式
      • 待机模式
  • 建立自己的低功耗模式配置库
    • 通过结构体的方式来进行传参
      • RTC配置
      • UART配置
    • 通过回调函数来配置时钟
    • 通过虚假的回调来初始化低功耗外设
    • 初始化函数
    • 代码整合
    • 调用方式
  • 附录:Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作
    • SysTick系统定时器精准延时
      • 延时函数
        • 阻塞延时
        • 非阻塞延时
    • 位带操作
      • 位带代码
        • 位带宏定义
        • 总线函数
      • 一、位带操作理论及实践
      • 二、如何判断MCU的外设是否支持位带

此文章是讨论将先前所有的低功耗配置功能整合起来的一个库(适用于STM32L4系列)
目前除了普通唤醒方式外 加入了UART唤醒和RTC唤醒配置
如果后续有更多唤醒加入(如I2C等 将直接在后续的文章中进行讨论)
本文所建的库将不再更新其他唤醒方式 但会对某些BUG进行修复

相关函数调用和配置 可以从我之前的文章里找到
gitee库

低功耗模式(此章节可直接跳过)

【STM32笔记】低功耗模式配置及避坑汇总

低功耗模式简介

系统提供了多个低功耗模式,可在 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+的通用模式状态表可见手册
在这里插入图片描述
在这里插入图片描述

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

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

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

睡眠模式

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

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

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

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

要进入低功耗睡眠模式 首先得进入低功耗运行模式

HAL_PWREx_EableLowPowerRunMode()

且工作频率降低到2MHz以下

唤醒时 睡眠模式直接唤醒

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

停止模式

在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 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_ONPWR_LOWPOWERREGULATOR_ON两个变量确定

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

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

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

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

【STM32笔记】HAL库低功耗STOP停止模式的串口唤醒(解决串口唤醒和回调无法一起使用的问题)

待机模式

翻译成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 口均工作在高阻态。
唤醒延迟: 芯片复位的时间。
唤醒后: 相当于芯片复位,在程序表现为从头开始执行代码。

建立自己的低功耗模式配置库

首先 在先前的文章中 我们通过Enter_Low_PWR来进入低功耗模式
同时 在进入低功耗之前 需要调用唤醒配置函数 退出低功耗后 也要初始化时钟等等
而建立的这个库 就是把所有配置整合到一起 从而使其能直接用一个通用函数代替
文中用于传参的配置结构体是一个全局变量
另外用到了结构体嵌套 回调函数等等 该写法也是TI的SDK常用写法

通过结构体的方式来进行传参

最主要的就是mode_flag参数
该参数决定了四种低功耗模式
其中 停止模式默认是停止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;

次级结构体包括进入低功耗的方式 待机模式唤醒引脚配置以及外设唤醒模式和唤醒后的时钟配置回调.

其中 外设唤醒分为外设和RTC
之所以要把RTC单独列出来 是因为RTC有且仅有一个
而其他外设唤醒 可能会有UART 也能有I2C SPI等等
所以我建立的这些函数都是__weak声明 可以结合不同的工程来覆写

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;
/*!* @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配置

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;

这里的时钟源和计数值就是HAL_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笔记】低功耗模式下的RTC唤醒(非闹钟唤醒,而是采用RTC_WAKEUPTIMER)

UART配置

【STM32笔记】HAL库低功耗STOP停止模式的串口唤醒(解决串口唤醒和回调无法一起使用的问题)

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_WakeUpStruct结构体中 一般把WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY 表示接收数据不为空时唤醒
串口最多有五个 所以在结构体中定义的是一个长度为5的数组 然后在配置函数中做判断 为NULL就跳过

/*!* @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线 串口初始化时钟也必须要配置为HSIHAL_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;
}

通过回调函数来配置时钟

typedef void (*SystemClock_Config_Callback)(void);

声明了一个函数指针类型
在调用时需要传入函数指针 一般是系统时钟配置
也就是SystemClock_Config

通过虚假的回调来初始化低功耗外设

在低功耗进入前和退出以后 都可以通过把已经打开的外设关掉来降低功耗
关闭:

PWR_Device_Init(false);

打开:

PWR_Device_Init(true);

同时 在此函数中 也包含GPIO的配置
这两个函数用的虚假回调方式来编写
在调用时 需要自己补全代码

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

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

初始化函数

这里传参是系统时钟配置函数 当然 你也可以自己写一个 然后就是各个变量的赋值 这里对几个常用变量进行了赋值

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;}	
}

代码整合

#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>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 PC2PC3 PC4 PC5 PC6PC7 PC8 PC9 PC10PC11 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 PA3PA4 PA5 PA6 PA7PA8 PA9 PA10 PA11PA12 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 PB10PB11 PB12 PB13 PB14PB15 PB3 PB4 PB5PB6 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线 串口初始化时钟也必须要配置为HSIHAL_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();	}
}/*!* @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;}	
}

调用方式

首先 需要调用初始化函数
同时传入系统时钟初始化函数地址

Init_Enter_Low_PWR(SystemClock_Config);

然后在进入低功耗前 需要对其进行配置

		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();	

最后用Enter_Low_PWR();函数来进入低功耗

附录:Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作

SysTick系统定时器精准延时

延时函数

SysTick->LOAD中的值为计数值
计算方法为工作频率值/分频值
比如工作频率/1000 则周期为1ms

以ADuCM4050为例:

#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  关闭系统定时器
}

其中的52000000表示芯片的系统定时器频率 32系列一般为外部定时器频率的两倍

Cortex-M架构SysTick系统定时器阻塞和非阻塞延时

阻塞延时

首先是最常用的阻塞延时

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表示工作频率
分频后即可得到不同的延时时间
以此类推

那么 不用两个嵌套while循环 也可以写成:

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  关闭系统定时器
}

但是这种写法有个弊端
那就是输入ms后,最大定时不得超过计数值,也就是不能超过LOAD的最大值,否则溢出以后,则无法正常工作

而LOAD如果最大是32位 也就是4294967295

晶振为50M的话 50M的计数值为1s 4294967295计数值约为85s

固最大定时时间为85s

但用嵌套while的话 最大可以支持定时4294967295*85s

非阻塞延时

如果采用非阻塞的话 直接改写第二种方法就好了:

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  关闭系统定时器
}

将等待和关闭定时器语句去掉
在使用时加上判断即可变为阻塞:

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

在非阻塞状态下 可以提交定时器后 去做别的事情 然后再来等待

不过这样又有一个弊端 那就是定时器会自动重载 可能做别的事情以后 定时器跑过了 然后就要等85s才能停下

故可以通过内部定时器来进行非阻塞延时函数的编写

基本上每个mcu的内部定时器都可以配置自动重载等功能 网上资料很多 这里就不再阐述了

位带操作

位带代码

M3、M4架构的单片机 其输出口地址为端口地址+20 输入为+16
M0架构的单片机 其输出口地址为端口地址+12 输入为+8
以ADuCM4050为列:

位带宏定义
#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
总线函数
#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;
}

一、位带操作理论及实践

位带操作的概念其实30年前就有了,那还是 CM3 将此能力进化,这里的位带操作是 8051 位寻址区的威力大幅加强版

位带区: 支持位带操作的地址区

位带别名: 对别名地址的访问最终作 用到位带区的访问上(注意:这中途有一个 地址映射过程)

位带操作对于硬件 I/O 密集型的底层程序最有用处

支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在CM4中,有两个区中实现了位带。其中一个是SRAM区的最低1MB范围,第二个则是片内外设区的最低1MB范围。这两个区中的地址除了可以像普通的RAM一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个32位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。

位操作就是可以单独的对一个比特位读和写,类似与51中sbit定义的变量,stm32中通过访问位带别名区来实现位操作的功能
STM32中有两个地方实现了位带,一个是SRAM,一个是片上外设。
在这里插入图片描述
(1)位带本质上是一块地址区(例如每一位地址位对应一个寄存器)映射到另一片地址区(实现每一位地址位对应一个寄存器中的一位),该区域就叫做位带别名区,将每一位膨胀成一个32位的字。
(2)位带区的4个字节对应实际寄存器或内存区的一个位,虽然变大到4个字节,但实际上只有最低位有效(代表0或1)

只有位带可以直接用=赋值的方式来操作寄存器 位带是把寄存器上的每一位 膨胀到32位 映射到位带区 比如0x4002 0000地址的第0个bit 映射到位带区的0地址 那么其对应的位带映射地址为0x00 - 0x04 一共32位 但只有LSB有效 采用位带的方式用=赋值时 就是把位带区对应的LSB赋值 然后MCU再转到寄存器对应的位里面 寄存器操作时 如果不改变其他位上面的值 那就只能通过&=或者|=的方式进行

在这里插入图片描述

要设置0x2000 0000这个字节的第二个位bit2为1,使用位带操作的步骤有:
1、将1写入位 带别名区对应的映射地址(即0x22000008,因为1bit对应4个byte);
2、将0x2000 0000的值 读取到内部的缓冲区(这一步骤是内核完成的,属于原子操作,不需要用户操作);
3、将bit2置1,再把值写 回到0x2000 0000(属于原子操作,不需要用户操作)。

关于GPIO引脚对应的访问地址,可以参考以下公式
寄存器位带别名 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

如:端口F访问的起始地址GPIOF_BASE

#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)

在这里插入图片描述

但好在官方库里面都帮我们定义好了 只需要在BASE地址加上便宜即可

例如:

GPIOF的ODR寄存器的地址 = GPIOF_BASE + 0x14

寄存器位带别名 = 0x42000000 + (寄存器的地址-0x40000000)32 + 引脚编号4

设置PF9引脚的话:

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

封装一下:

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

现在 可以把通用部分封装成一个小定义:

#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))

那么 设置PF引脚的函数可以定义:

#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)  //输入

若使PF9输入输出则:

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

总线输入输出:

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;
}

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;
}

二、如何判断MCU的外设是否支持位带

根据《ARM Cortex-M3与Cortex-M4权威指南(第3版)》中第6章第7节描述
在这里插入图片描述
也就是说 要实现对GPIO的位带操作 必须保证GPIO位于外设区域的第一个1MB中
第一个1MB应该是0x4010 0000之前 位带不是直接操作地址 而是操作地址映射 地址映射被操作以后 MCU自动会修改对应寄存器的值

位带区只有1MB 所以只能改0x4000 0000 - 0x400F FFFF的寄存器
像F4系列 GPIO的首地址为0x4002 0000 就可以用位带来更改

STM32L476的GPIO就不行:
在这里插入图片描述
AHB2的都不能用位带
ABP 还有AHB1都可以用
在这里插入图片描述
但是L476的寄存器里面 GPIO和ADC都是AHB2

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将Bean注入Spring容器的五种方式

将bean放入Spring容器中有哪些方式&#xff1f; 我们知道平时在开发中使用Spring的时候&#xff0c;都是将对象交由Spring去管理&#xff0c;那么将一个对象加入到Spring容器中&#xff0c;有哪些方式呢&#xff0c;下面我就来总结一下 1、Configuration Bean 这种方式其实也是…

数据结构 | 单链表专题【详解】

数据结构 | 单链表专题【详解】 文章目录 数据结构 | 单链表专题【详解】链表的概念及结构单链表的实现头文件打印尾插头插尾删头删查找在指定位置之前插入数据在指定位置之后插入数据删除pos节点删除pos之后的节点销毁链表 顺序表遗留下来的问题 中间/头部的插⼊删除&#xff…

vue3后台管理系统之实现分页功能

例子&#xff1a;用户 请求格式 返回数据类型 {"code": 200,"message": "获取所有用户成功","total": 19,"totalPages": 2,"currentPage": 1,"data": [{"id": 1,"username": &qu…

uniapp小程序九宫格抽奖

定义好奖品下标&#xff0c;计时器开始抽奖&#xff0c;请求接口&#xff0c;出现中奖奖品之后&#xff0c;获取中奖商品对应的奖品下标&#xff0c;再次计时器判断当前移动的小标是否为中奖商品的下标&#xff0c;并且是否转到3圈&#xff08;防止转1圈就停止&#xff09;&…

生成式人工智能:网络攻击者手中的破坏性力量

2022 年底&#xff0c;公开可用的生成式人工智能工具的推出使我们进入了人类历史上最大的技术革命之一。 一些人声称它的影响与互联网、手机、智能手机和社交媒体的引入一样大&#xff0c;甚至更大。这些新的生成式人工智能技术的采用和发展速度是我们以前从未见过的。 虽然这…

订单业务和系统设计(一)

一、背景简介 订单其实很常见&#xff0c;在电商购物、外卖点餐、手机话费充值等生活场景中&#xff0c;都能见到它的影子。那么&#xff0c;一笔订单的交易过程是什么样子的呢&#xff1f;文章尝试从订单业务架构和产品功能流程&#xff0c;描述对订单的理解。 二、订单业务…

VX-3R APRS发射试验

VX-3R本身是不带APRS功能的&#xff0c;不过可能通过外加TNC实现APRS功能。 有大佬已经用Arduino实现了相应的发射功能&#xff1a; https://github.com/handiko/Arduino-APRS 我要做的&#xff0c;就是简单修改一下代码&#xff0c;做一个转接板。 YEASU官方没有给出VX-3R的音…

YOLOv5:按每个类别的不同置信度阈值输出预测框

YOLOv5&#xff1a;按每个类别的不同置信度阈值输出预测框 前言前提条件相关介绍YOLOv5&#xff1a;按每个类别的不同置信度阈值输出预测框预测修改detect.py输出结果 验证修改val.py输出结果 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更…

走近Python爬虫(二):常见反爬虫机制的应对措施

文章目录 一、应对—异步加载1.一般措施2.Selenium 二、应对—登录验证1.使用Selenium模拟登录2.使用Cookies登录3.使用Session模拟表单登录 三、应对—验证码 本文是Python爬虫系列博客的第二篇&#xff0c;内容概览如下&#xff1a; 一、应对—异步加载 1.一般措施 AJAX技术…

在二维矩阵/数组中查找元素 Leetcode74, Leetcode240

这一类题型中二维数组的元素取值有序变化&#xff0c;因此可以用二分查找法。我们一起来看一下。 一、Leetcode 74 Leetcode 74. 搜索二维矩阵 这道题要在一个二维矩阵中查找元素。该二维矩阵有如下特点&#xff1a; 每行元素 从左到右 按非递减顺序排列。每行的第一个元素 …

【ArcGIS模型构建器】06:ArcGIS中DOM批量分幅教程

ArcGIS中利用模型构建器实现DOM批量分幅裁剪。 文章目录 1. 加载数据2. 批量分幅1. 加载数据 批量分幅通常是基于数字正射影像来实现。 数字正射影像(DOM.tif)CASS标准图幅(shp) 2. 批量分幅 单个图幅可以通过裁剪或者按掩膜提取工具来进行,批量分幅采用模型构建器进行。…

虹科案例 | AR内窥镜手术应用为手术节约45分钟?

相信医疗从业者都知道&#xff0c;在手术室中有非常多的医疗器械屏幕&#xff0c;特别是内窥镜手术室中医生依赖这些内窥镜画面来帮助病患进行手术。但手术室空间有限&#xff0c;屏幕缩放位置相对固定&#xff0c;在特殊场景下医生观看内窥镜画面时无法关注到病患的状态。这存…

Linux背景介绍与环境搭建

本章内容 认识 Linux, 了解 Linux 的相关背景学会如何使用云服务器掌握使用远程终端工具 xshell 登陆 Linux 服务器 Linux 背景介绍 发展史 本门课程学习Linux系统编程&#xff0c;你可能要问Linux从哪里来&#xff1f;它是怎么发展的&#xff1f;在这里简要介绍Linux的发展…

MFC 窗体插入图片

1.制作BMP图像1.bmp 放到res文件夹下&#xff0c;资源视图界面导入res文件夹下的1.bmp 2.添加控件 控件类型修改为Bitmap 图像&#xff0c;选择IDB_BITMAP1 3.效果

MySQL---搜索引擎

MySQL的存储引擎是什么 MySQL当中数据用各种不同的技术存储在文件中&#xff0c;每一种技术都使用不同的存储机制&#xff0c;索引技巧 锁定水平&#xff0c;以及最终提供的不同的功能和能力&#xff0c;这些就是我们说的存储引擎。 MySQL存储引擎的功能 1.MySQL将数据存储在文…

LabVIEW对多个同一类型控件进行操作

LabVIEW对多个同一类型控件进行操作 有时候LabVIEW要多多个同一类的控件进行操作&#xff0c;如对tab中某个page中所有String控件设为dissable。就可以用如下的方式。className是获取不同类型的控件。通过类型选择&#xff0c;可以选择所有的String控件&#xff0c;并可对特定…

双链表详解(初始化、插入、删除、遍历)(数据结构与算法)

1. 单链表与双链表的区别 单链表&#xff08;Singly Linked List&#xff09;和双链表&#xff08;Doubly Linked List&#xff09;是两种常见的链表数据结构&#xff0c;它们在节点之间的连接方式上有所区别。 单链表&#xff1a; 单链表的每个节点包含两个部分&#xff1a;数…

Synchronized与锁升级

一&#xff1a;java对象内存布局 对象在堆内存的存储布局可以划分为三个部分&#xff1a;对象头&#xff08;Header&#xff09;、实例数据&#xff08;Instance Data&#xff09; 和对齐填充 二&#xff1a;对象在堆内存中的存储布局 三&#xff1a;Sychronized的锁升级 S…

使用vscode实现远程开发,并通过内网穿透在公网环境下远程连接

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

中文大语言模型汇总

推荐一篇非常棒的github&#xff1a;Awesome-Chinese-LLM 另附语言模型排行榜&#xff1a;FastChat 里面总结了几乎所有目前主流的中文大语言模型。在此记录一下&#xff0c;方便以后慢慢学习。