PWR-STM32电源控制

一、原理

睡眠模式不响应其他操作,比如烧写程序,烧写时按住复位键松手即可下载,在禁用JTAG也可如此烧写程序。

对于低功耗模式可以通过RTC唤醒、外部中断唤醒、中断唤醒。

1、电源框图:

VDDA主要负责模拟部分的供电、Vref+和Vref-为模拟参考电压。有些芯片单独引出,有些内部接到了VDD和Vss。

VDD主要供电电压调节器、待机电路、独立看门狗等。电压调节器降压供电1.8V到CPU核心存储器、内置数字外设等。

后备供电区域:通过VBAT给LSE晶体振荡器、RCC BDCR寄存器、RTC等供电。通过低电压检测器,VDD有电时通过VDD供电,没电时通过VBAT供电。

2、低功耗模式

1、睡眠模式:只关闭CPU时钟,所有涉及运算和时序的操作都暂停,程序暂停运行。内部寄存器和存储器的数据保持不变。CPU不运行但是外设可以运行。可通过任一中断和唤醒时间唤醒。

2、停机模式:关闭所有1.8V的高速时钟。CPU核心存储器和内置数字外设(比如SPI、串口、NIVC、I2C等功能外设,但是外部中断GPIO可用)都不能运行。但是电压调节器1.8V不断电(CPU核心存储器和内置数字外设都是电压调节器供电,不断电恢复上电更快),停止前的状态保持,寄存器内容保存,唤醒可以继续运行。

PDDS用来区分是停机模式还是待机模式,PDDS=0进入停机模式,PDDS=1进入待机模式。

LPDS用来设置停机模式电压调节器(关于1.8V主区域供电),LPDS=0电压调节器开启,LPDS=1电压调节器进入低功耗模式。关乎到存储器和核心区域是否供电。

只能通过外部中断唤醒。

3、待机模式:SLEEPDEEP=1表示深度睡眠,PDDS=1进入待机模式。调用WFI|WFE进入待机模式。更难以唤醒,对比停机模式,待机模式更是将1.8V电压调节器供电都关闭了,只能通过待机电路唤醒。需要重新从程序起始位置运行,存储器的内容也丢失。

注意!!!停止模式和待机模式关闭了HSI和HSE,唤醒后若主频变低说明和HSE(默认启动后通过HSI时钟唤醒HSE通过PLL倍频得到72MHz)启动失败,会使用HSI(8Mhz时钟),所以可以在唤醒后启动HSE配置主频为72MHz。

2.1低功耗模式选择:

睡眠模式的立即睡眠和等待中断后睡眠(中断内使用),差别不大,注意使用位置即可。

停机模式的电压调节器开启和电压调节器低功耗差别也不大。更省电但是唤醒延迟更高。

2.2低功耗模式注意事项

2.2.1睡眠模式
2.2.2停止模式
2.2.3待机模式

3、详细功耗内容 ,详细见文件《STM32F103xx数据手册》

关闭外设和开启外设功耗对比

从RAM运行程序比闪存运行程序功耗要低一些

主频、温度、耗电的关系(正比)

睡眠模式功耗情况

停机模式(需要启动时间)和待机模式(待机模式需要更长的启动时间)和VBAT供电功耗

3、上电复位和掉电复位

迟滞的阈值1.92-1.88=40mV,所以复位为大于1.92V上电,小于1.88V下电。复位持续时间2.5ms。

4、可编程电压检测器(上下电阈值可编程调节)

可以看到PVD阈值可以通过编程调节为2.2V~2.9V左右。PVD上限和下限迟滞电压100mV左右。可以看到PVD的检测电压范围比Vpdr上电掉电复位阈值要高。

5、电压检测器原理

PVD中断使用外部中断,只有外部中断可以唤醒停止模式,所以RTC和PVD等接入到外部中断。

二、相关基础

标准库文件system_stm32f10x.c和.h中可以修改主频,两个文件主要用来配置RCC时钟树。

可以改变文件只读性质

通过SystemInit()可以查看系统初始化的内容。函数一开始默认配置使用HSI内部高速8MHz时钟。

在SystemInit()调用SetSysClock函数();可以看到SetSysClock();中通过不同的宏定义运行不同的函数,配置HSE。

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);//使能HSE外部高速时钟
 
  /* Wait till HSE is ready and if Time out is reached exit 超时退出*/
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)//根据HSE标志位获取是否启动成功
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)//如果启动成功,flash等待、配置AHB、APB1、APB2等
  {
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    


    /* HCLK = SYSCLK (AHB)*/
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;//AHB
      
    /* PCLK2 = HCLK (APB2)*/
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK (APB1)*/
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

#ifdef STM32F10X_CL
	  /* Configure PLLs ------------------------------------------------------CL为互联型*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
#else    
    /*  PLL configuration: PLLCLK = HSE(8MHz) * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error 
	  若HSE启动失败可以在此处做些动作以明确*/
	  
  }
}

STM32启动配置逻辑

三、程序实例

1、修改系统主频

分别在72MHz主频和36MHz主频下运行程序,查看OLED显示频率是否变慢。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

int main(void){
	OLED_Init();
	
	while(1){
		OLED_ShowString(2,1,"Ranning");
		Delay_ms(500);
		OLED_ShowString(2,1,"       ");
		Delay_ms(500);
	}
	return 0;
}

2、低功耗模式(睡眠模式),在有串口操作时运行,运行结束后睡眠。(睡眠模式不响应其他操作,比如烧写程序,烧写时按住复位键松手即可下载,在禁用JTAG也可如此烧写程序)

此处代码参考串口收发部分,接收数据包为:FF xx xx xx xx FE,接收后反回,可以看到程序现象,上位机发送一次睡眠模式可以解除(通过OLED显示的Ranning),串口部分需要发送多次才能返回和更新数据。

以下库内程序有很多冗余的部分,查看程序时根据调用的函数即可。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main(void){
	OLED_Init();
	Serial_Init();
	OLED_ShowString(1,1,"RX:");
	while(1){
		Serial1HexRx();
		OLED_ShowHexNum(2,1,Serial1_RxHexPacket[0],2);
		OLED_ShowHexNum(2,4,Serial1_RxHexPacket[1],2);
		OLED_ShowHexNum(2,7,Serial1_RxHexPacket[2],2);
		OLED_ShowHexNum(2,10,Serial1_RxHexPacket[3],2);
		OLED_ShowString(3,1,"Ranning");
		Delay_ms(500);
		OLED_ShowString(3,1,"       ");
		Delay_ms(500);
		__WFI();//睡眠模式
	}
	return 0;
}

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
#include "OLED.h"
#include "Button.h"
#include "String.h"
uint8_t Serial1_RxData;//接收数据字节
uint8_t Serial1_RxFlag;//接收完整数据包标志位
uint8_t Serial1_RxHexPacket[4];//接收Hex数据包
uint8_t Serial1_TxHexPacket[4];//发送Hex数据包
char Serial1_RxTextPacket[100];//接收文本数据包
/**
  * @brief 初始化USART1,通过USART1进行收发
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入.
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_Init(USART1,&USART_InitStructure);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	//NVIC配置-中断优先级和中断对应通道使能 stm32f10x.h
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//
	NVIC_InitStructure.NVIC_IRQChannelCmd  = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1,ENABLE);
}
/**
  * @brief 串口发送1byte
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendByte(uint8_t Byte){
	USART_SendData(USART1,Byte);
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}

/**
  * @brief 发送字节数组
  * @param  数组指针
  *     @arg 
  * @param  长度
  *     @arg 
  * @retval None
  */
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length){
	for(int i = 0 ; i<length ;  i++){
		Serial_SendByte(ByteArray[i]);
	}
}

/**
  * @brief 根据数据包发送Hex数据数组
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Tx_HexPacket(void){
	Serial_SendByte(0xFF);//发送包头
	Serial_SendByteArray(Serial1_TxHexPacket,4);
	Serial_SendByte(0xFE);//发送包尾
}



/**
  * @brief 配合USART1_IRQHandler串口1接收中断,对接收到的数据包进行处理
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1HexRx(void){
	if(Serial1_RxFlag){
		for(int i=0;i<4;i++){
			Serial1_TxHexPacket[i] = Serial1_RxHexPacket[i];
		}
		Serial1Tx_HexPacket();
		Serial1_RxFlag = 0;
	}
}

/**
  * @brief 配合USART1_IRQHandler串口1接收中断,接收到的Hex数据包进行判断,
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Rx_HexPacket(void){
	static uint8_t RxState = 0;//接收状态机
	static uint8_t RxDataFlag = 0;//接收数据下标
	/*
		0/1 :包头
		1 :数据
		2 :包尾
	*/
	Serial1_RxData = USART_ReceiveData(USART1);//接收数据
	if(RxState == 0){//等待接收包头
		if(Serial1_RxData == 0xFF){//如果获取包头
			RxState = 1;
			RxDataFlag = 0;//在每次接收数据前清0,更稳定
		}
	}else if(RxState == 1){//等待数据
		Serial1_RxHexPacket[RxDataFlag] = Serial1_RxData;
		RxDataFlag++;
		if(RxDataFlag>=4){
			RxState = 2;
		}
	}else if(RxState == 2){//等待包尾
		if(Serial1_RxData == 0xFE){//等待包尾
			RxState = 0;
			Serial1_RxFlag = 1;//标志接收到了正确的数据包标志位
		}else{
			RxState = 0;//如果接收的不是包尾,则丢弃数据包
		}
	}
	
}





/**
  * @brief 发送一个字符串
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendString(char *String){
	int i = 0;
	for(i=0;String[i]!='\0';i++){
		Serial_SendByte(String[i]);
	}
}

/**
  * @brief 以字符形式发送有符号数字
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendSignedNum(int32_t Num,uint16_t length){
	if(Num>=0){
		Serial_SendByte(0x2B);//加号
	}else{
		Serial_SendByte(0x2D);//减号
		Num = -Num;
	}
	//Num = abs(Num); abs处理int数据所以不适用
	for(int i=1;i<=length;i++){
		Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');
	}
}

/**
  * @brief 以字符形式发送无符号数字
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_SendNum(int32_t Num,uint16_t length){
	for(int i=1;i<=length;i++){
		Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');
	}
}

/**
  * @brief 重定向fputc函数,到串口1即Serial_SendByte函数,使用micor提供的精简库
  *     @arg fputc是printf函数的底层,printf打印时就是调用fputc单个打印,通过重定向可以在串口打印
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
int fputc(int ch,FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}


/**
  * @brief 重构v sprintf,完成printf打印串口
  *     @arg sprintf只能接收直接写的参数,vsprintf可以接收封装格式参数
  * @param  format:接收格式化字符串
  *     @arg 
  * @param  ...:可变参数列表,用来接收剩余参数
  *     @arg 
  * @retval None
  */
void Serial_printf(char *format,...){
	char String[100];
	va_list arg;//定义一个参数列表变量
	va_start(arg,format);//从format位置开始接收参数列表,放在arg中
	vsprintf(String,format,arg);//将打印参数arg格式化为format格式字符串,最好放入打印字符串变量String
	va_end(arg);//释放参数表
	Serial_SendString(String);//发送格式化后的字符串
}


/**
  * @brief 串口1接收,查询方式,接收到的数据返回
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial_Get(void){
	if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET){
		uint8_t Get_Data = USART_ReceiveData(USART1);
		Serial_SendByte(Get_Data);
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}


/**
  * @brief 根据数据包发送文本数据数组
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Tx_TextPacket(void){
	Serial_SendByte('@');//发送包头
	if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)==RESET){
		Serial_SendString("LED_ON");
	}else{
		Serial_SendString("LED_OFF");
	}
	Serial_SendByte('\r');//发送包尾
	Serial_SendByte('\n');//发送包尾
	
}


/**
  * @brief 配合USART1_IRQHandler串口1接收中断,对接收到的文本数据进行处理
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1TextRx(void){
	if(Serial1_RxFlag){
		OLED_ShowString(4,1,"                ");
		OLED_ShowString(4,1,Serial1_RxTextPacket);
		if(strcmp(Serial1_RxTextPacket,"LED_OFF") == 0){
			LED_OFF(GPIOA,GPIO_Pin_1);
		}else if(strcmp(Serial1_RxTextPacket,"LED_ON") == 0){
			LED_ON(GPIOA,GPIO_Pin_1);
			
		}else{
			OLED_ShowString(2,1,"Rx_Error");
			Serial_SendString("Rx_Error\r\n");
		}
		Serial1_RxFlag = 0;
	}
}

/**
  * @brief 配合USART1_IRQHandler串口1接收中断,接收到的Text数据包进行判断,
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Serial1Rx_TextPacket(void){
	static uint8_t RxState = 0;//接收状态机
	static uint8_t RxDataFlag = 0;//接收数据下标
	/*
		0/1 :包头
		1 :数据
		2 :包尾
	*/
	Serial1_RxData = USART_ReceiveData(USART1);//接收数据
	if(RxState == 0){//等待接收包头
		if(Serial1_RxData == '@'){//如果获取包头
			RxState = 1;
			RxDataFlag = 0;//在每次接收数据前清0,更稳定
		}
	}else if(RxState == 1){//等待数据
		if(Serial1_RxData != '\r'){
			Serial1_RxTextPacket[RxDataFlag] = Serial1_RxData;
			RxDataFlag++;
		}else{
			RxState = 2;
		}		
	}else if(RxState == 2){//等待包尾
		if(Serial1_RxData == '\n'){//等待包尾
			RxState = 0;
			Serial1_RxTextPacket[RxDataFlag] = '\0';
			Serial1_RxFlag = 1;//标志接收到了正确的数据包标志位
		}else{
			RxState = 0;//如果接收的不是包尾,则丢弃数据包
		}
	}
}

/**
  * @brief USART1中断函数,接收的数据返回
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None	
  *		@arg 中断函数名在startup_stm32f10x_md.s中
  */
void USART1_IRQHandler(void){
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){
		Serial1Rx_HexPacket();//开始接收数据
		//Serial1Rx_TextPacket();
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
extern uint8_t Serial1_RxData;
extern uint8_t Serial1_RxFlag;
extern uint8_t Serial1_RxHexPacket[];
extern uint8_t Serial1_TxHexPacket[];
extern char Serial1_RxTextPacket[];
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length);
void Serial_SendString(char *Char);
void Serial_SendSignedNum(int32_t Num,uint16_t length);
void Serial_SendNum(int32_t Num,uint16_t length);
void Serial_printf(char *format,...);
void Serial_Get(void);

void Serial1HexRx(void);
void Serial1Rx_HexPacket(void);
void Serial1Tx_HexPacket(void);

void Serial1TextRx(void);
void Serial1Tx_TextPacket(void);
void Serial1Rx_TextPacket(void);
#endif

3、低功耗模式,在外部中断时运行,用外部红外对管计次程序。

内核之外的电路操作,需要用到PWR外设。

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"
int main(void){
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	OLED_Init();
	CountSensor_Init();
	OLED_ShowString(1,1,"count:");
	while(1){
		OLED_ShowHexNum(2,1,Count,2);
		OLED_ShowString(3,1,"Ranning");
		Delay_ms(500);
		OLED_ShowString(3,1,"       ");
		Delay_ms(500);
		PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFI);//停机模式唤醒后使用HSI8MHz作为主频
		SystemInit();
	}
	return 0;
}

CountSensor.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
int Count = 0;
void CountSensor_Init(void){
	//RCC
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//引脚时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//AFIO时钟使能
	//GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	//AFIO
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
	//EXTI
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line14;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);
	//NVIC misc.h  stm32f10x.h - 根据实际使用的芯片选择,STM32F103C8T6使用的是MD
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//固定通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
}
//中断函数 startup_stm32f10x_md.s
void EXTI15_10_IRQHandler(){//固定名称
	if(EXTI_GetITStatus(EXTI_Line14)== SET){//防止中断判断错误,对中断标志位进行判断
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)== RESET){//红外对射传感器默认输出低电平
			Count++;
		};
		EXTI_ClearITPendingBit(EXTI_Line14);//清除中断标志位
	}
}

CountSensor.h

#ifndef __COUNTSENSOR_H
#define __COUNTSENSOR_H
#include "stm32f10x.h"                  // Device header
extern int  Count;
void CountSensor_Init(void);


#endif

4、停机模式+RTC时钟唤醒(+WKUP引脚唤醒)。定时5s唤醒一次,OLED显示时间。

WKUP引脚无需再次初始化。输入下拉,表示上升沿有效。

测试方法:

  • 烧写程序查看是否10s唤醒一次。
  • 通过WKUP-PA0接+3.3V,查看是否唤醒

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"

int main(void){
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//开启PWR的时钟,独立撰写减少耦合
	MyRTC_Init();
	OLED_Init();
	
	//使能WKUP引脚
	PWR_WakeUpPinCmd(ENABLE);
	
	OLED_ShowString(1,1,"CNT :");//秒计数器
	OLED_ShowString(2,1,"ALR :");//闹钟值
	OLED_ShowString(3,1,"ALRF:");//闹钟标志位
	
	
	uint32_t Alarm = GetCounter()+10;
	RTC_SetAlarm(Alarm);//设置闹钟值
	
	OLED_ShowNum(1,6,GetCounter(),10);
	OLED_ShowNum(2,6,Alarm,10);//闹钟值
	OLED_ShowNum(3,6,RTC_GetFlagStatus(RTC_FLAG_ALR),2);//闹钟标志位
		
	OLED_ShowString(4,1,"Ranning");
	Delay_ms(500);
	OLED_ShowString(4,1,"       ");
	Delay_ms(500);
	
		
	OLED_Clear();//STM32进入待机模式前需要把所有外挂模块清除,这样更能省电
	
	PWR_EnterSTANDBYMode();
	//SystemInit();无需初始化因为待机模式程序会从头开始执行
	while(1){
		
	}
	return 0;
}

MyRTC.c

#include "stm32f10x.h"                  // Device header
#include "time.h"
#include "MyRTC.h"
Unixdate SetTime;
void MyRTC_Init(void){
	//时钟配置
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	
	//使能RTC和BKP访问
	PWR_BackupAccessCmd(ENABLE);
	
	//开启LSE/LSI,并等待启动完成
	RCC_LSEConfig(RCC_LSE_ON);
	while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);
//	RCC_LSICmd(ENABLE);//备用配置LSI为内部时钟,并启动
//	while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY)!=SET);//等待启动完成
	
	//使用BKP来判断是否断电,若断电则进行初始化,相当于用BKP做了一个标志位
	if(BKP_ReadBackupRegister(BKP_DR1)!=0xA5A5){
		//选择LSE为时钟源,并使能时钟
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
	//	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);//备用选择LSI作为时钟源
		RCC_RTCCLKCmd(ENABLE);

		//等待时钟同步,等待RTC上一次操作完成
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
		
		//配置预分频器,LSE=32768Hz,分频32768后为1Hz,LSI=40000Hz
		RTC_SetPrescaler(32768-1);//函数内置写CNF=1/=0进入了配置模式/退出配置模式,只有配置模式可以写入寄存器
	//	RTC_SetPrescaler(40000-1);//备用使用LSI作为时钟源	
		RTC_WaitForLastTask();
		
		Time_Init(&SetTime);
		SetNowTime(SetTime);
		BKP_WriteBackupRegister(BKP_DR1,0xA5A5);
	}else{//若BKP不断电则不初始化
		//等待时钟同步,等待RTC上一次操作完成
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
	}
	
}

/**
  * @brief 获取当前CNT
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
uint32_t GetCounter(void){
	return RTC_GetCounter();
}

/**
  * @brief 获取当前余数值计数值
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
uint32_t GetDIV(void){
	return RTC_GetDivider();
}

/**
  * @brief 设置当前时间
  * @param  输入为Unixdate自定义日期类型
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void SetNowTime(Unixdate UnixdataStructure){
	struct tm NowTime;
	time_t count;
	NowTime.tm_min = UnixdataStructure.minutes;
	NowTime.tm_hour = UnixdataStructure.hours;
	NowTime.tm_mday = UnixdataStructure.day;
	NowTime.tm_mon = UnixdataStructure.months;
	NowTime.tm_year = UnixdataStructure.years;
	NowTime.tm_sec = UnixdataStructure.second;
	count = mktime(&NowTime)-8*60*60;//设置时间到RTC,输入东八区时间,偏移到0时区
	RTC_SetCounter(count);
	RTC_WaitForLastTask();//等待完成
}

/**
  * @brief 获取RTC当前时间
  * @param  
  *     @arg 
  * @param  
  *     @arg 
  * @retval 返回当前RTC对应的日期时间
  */
Unixdate GetNowTime(void){
	struct tm NowTime;
	Unixdate UnixdataStructure;
	time_t count;
	
	count = RTC_GetCounter()+8*60*60;//获取当前计数,偏移到东八区(STM32默认函数为0区时间)
	RTC_WaitForLastTask();//等待完成
	
	NowTime = *localtime(&count);//根据计数值换算成日期时间,将值传给NowTime
	UnixdataStructure.years = NowTime.tm_year+1900;
	UnixdataStructure.months = NowTime.tm_mon+1;
	UnixdataStructure.day = NowTime.tm_mday;
	UnixdataStructure.hours = NowTime.tm_hour;
	UnixdataStructure.minutes = NowTime.tm_min;
	UnixdataStructure.second = NowTime.tm_sec;
	return UnixdataStructure;
}

/**
  * @brief 日期变量初始化
  * @param  输入为日期变量结构体地址,直接对其进行改变
  *     @arg 
  * @param  
  *     @arg 
  * @retval None
  */
void Time_Init(Unixdate *UnixdataStructure){
	UnixdataStructure->years = 2025-1900;
	UnixdataStructure->months = 1-1;
	UnixdataStructure->day = 3;
	UnixdataStructure->hours = 23;
	UnixdataStructure->minutes = 59;
	UnixdataStructure->second = 56;
}

MyRTC.h

#ifndef __MYRTC_H
#define __MYRTC_H
#include "stm32f10x.h"                  // Device header

//#pragma pack(n)可修改编译器字节对齐数
typedef struct{
	uint8_t second;//(0-60)s
	uint8_t minutes;//(0-59)min
	uint8_t hours;//(0-23)h
	uint8_t months;//月(1-12)
	uint8_t day;//月中第几天(1-31)
	uint16_t years;//年
}Unixdate;


void MyRTC_Init(void);
uint32_t GetCounter(void);
uint32_t GetDIV(void);
Unixdate GetNowTime(void);
void Time_Init(Unixdate *UnixdataStructure);
void SetNowTime(Unixdate UnixdataStructure);
#endif

拆除LED和LDO线性稳压器可以查看STM32待机模式工作电流的确是3uA。

相关推荐
OpenVINO生态社区13 分钟前
【FPGA与单片机的区别】
单片机·嵌入式硬件·fpga开发
iFinder@1 小时前
【51单片机】01入门篇
单片机·嵌入式硬件·51单片机
通信小菜鸡@posedge clk1 小时前
FPGA实现UART对应的电路和单片机内部配合寄存器实现的电路到底有何区别?
单片机·嵌入式硬件·fpga开发
可喜~可乐2 小时前
总线通信接口I²C以及SPI入门指南:从原理到实践
c语言·stm32·单片机·嵌入式硬件
JaneZJW2 小时前
嵌入式岗位面试八股文(篇三 操作系统(下))
linux·stm32·面试·嵌入式·c
iFinder@4 小时前
【51单片机】02LED流水灯实验
嵌入式硬件·mongodb·51单片机
ElePower95274 小时前
STM32学习(十)
stm32·嵌入式硬件
Echo_cy_4 小时前
STM32 I2C硬件配置库函数
stm32·单片机·嵌入式硬件
mftang5 小时前
基于STM32G4(SPI接口)驱动DRV8353RS
嵌入式硬件
majingming1235 小时前
ST-LINK下载程序接线
stm32