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。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深兰科技董事长陈海波应邀为华东师大心理学专业师生做AI专题讲座

12月28日&#xff0c;应上海华东师范大学的邀请&#xff0c;上海市科协常委、上海交通大学博士生导师、深兰科技创始人兼董事长陈海波专程到校&#xff0c;为该校心理学专业的全体师生做了一场关于人工智能推动个人数字化未来的专题讲座。 他在演讲中&#xff0c;首先详细讲述了…

ssh2-sftp-client uploadDir Upload error: getLocalStatus: Bad path: ./public

报错解释 这个错误表明在使用 ssh2-sftp-client 这个Node.js库进行目录上传时遇到了问题。具体来说&#xff0c;是指定的本地路径&#xff08;./public&#xff09;不正确或者不存在。 解决方法&#xff1a; 确认当前工作目录&#xff1a;确保你在执行上传操作时的当前工作目…

Vue指令的综合案例

Vue指令的综合案例 参考文献&#xff1a; Vue的快速上手 Vue指令上 Vue指令下 文章目录 Vue指令的综合案例记事本 列表渲染删除功能添加功能底部统计和清空总代码 结语 博客主页: He guolin-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能…

2025新春烟花代码(二)HTML5实现孔明灯和烟花效果

效果展示 源代码 <!DOCTYPE html> <html lang"en"> <script>var _hmt _hmt || [];(function () {var hm document.createElement("script");hm.src "https://hm.baidu.com/hm.js?45f95f1bfde85c7777c3d1157e8c2d34";var …

# 网络编程 - 轻松入门不含糊

网络编程 - 轻松入门 介绍 网络编程指的是&#xff0c;在网络通信协议下。实现 不同计算机之间 的数据传输&#xff0c;例如 通信、聊天、视频通话 等。 1. 网络编程概述 学习网络编程过程 中我们可以将网络编程理解为 计算机之间的数据交互 但 前提是通…

SAP BC 同服务器不同client之间的传输SCC1

源配置client不需要释放 登录目标client SCC1

【大数据基础】大数据概述

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈大数据技术原理与应用 ⌋ ⌋ ⌋专栏系统介绍大数据的相关知识&#xff0c;分为大数据基础篇、大数据存储与管理篇、大数据处理与分析篇、大数据应用篇。内容包含大数据概述、大数据处理架构Hadoop、分布式文件系统HDFS、分布式数…

【ROS2】☆ launch之Python

☆重点 ROS1和ROS2其中一个很大区别之一就是launch的编写方式。在ROS1中采用xml格式编写launch&#xff0c;而ROS2保留了XML 格式launch&#xff0c;还另外引入了Python和YAML 编写方式。选择哪种编写取决于每位开发人员的爱好&#xff0c;但是ROS2官方推荐使用Python方式编写…

Shell编程详解

文章目录 一、Linux系统结构二、Shell介绍1、Shell简介2、Shell种类3、Shell查询和切换 三、Shell基础语法1、注释2、本地变量3、环境变量3.1、查看环境变量3.2、临时设置环境变量3.3、永久设置环境变量 4、特殊变量5、控制语句5.1、shell中的中括号5.2、if语句5.3、for循环5.4…

Zemax 序列模式下的扩束器

扩束器结构原理 扩束器用于增加准直光束&#xff08;例如激光束&#xff09;的直径&#xff0c;同时保持其准直。它通常用于激光光学和其他需要修改光束大小或发散度的应用。 在典型的扩束器中&#xff0c;输入光束是准直激光器&#xff0c;或光束进入第一个光学元件。当光束开…

react-quill 富文本组件编写和应用

index.tsx文件 import React, { useRef, useState } from react; import { Modal, Button } from antd; import RichEditor from ./RichEditor;const AnchorTouchHistory: React.FC () > {const editorRef useRef<any>(null);const [isModalVisible, setIsModalVis…

【深度学习】多目标融合算法(二):底部共享多任务模型(Shared-Bottom Multi-task Model)

目录 一、引言 1.1 往期回顾 1.2 本期概要 二、Shared-Bottom Multi-task Model&#xff08;SBMM&#xff09; 2.1 技术原理 2.2 技术优缺点 2.3 业务代码实践 三、总结 一、引言 在朴素的深度学习ctr预估模型中&#xff08;如DNN&#xff09;&#xff0c;通常以一个行…

探秘MetaGPT:革新软件开发的多智能体框架(22/30)

一、MetaGPT 引发的 AI 变革浪潮 近年来&#xff0c;人工智能大模型领域取得了令人瞩目的进展&#xff0c;GPT-3、GPT-4、PaLM 等模型展现出了惊人的自然语言处理能力&#xff0c;仿佛为 AI 世界打开了一扇通往无限可能的大门。它们能够生成流畅的文本、回答复杂的问题、进行创…

LabVIEW软件Bug的定义与修改

在LabVIEW软件开发过程中&#xff0c;bug&#xff08;程序错误或缺陷&#xff09;指的是程序中导致不符合预期行为的任何问题。Bug可能是由于编码错误、逻辑漏洞、硬件兼容性问题、系统资源限制等因素引起的。它可能会导致程序崩溃、功能无法正常执行或输出结果不符合预期。理解…

高性能网络模式:Reactor 和 Proactor

Reactor Reactor 采用I/O多路复用监听事件&#xff0c;收到事件后&#xff0c;根据事件类型分配给某个进程/线程。 实际应用中用到的模型&#xff1a; 单 Reactor 单进程 单 Reactor 多线程 优点&#xff1a;能充分利用多核CPU性能。 缺点&#xff1a;存在多线程竞争共享资源…

扩散模型论文概述(三):Stability AI系列工作【学习笔记】

视频链接&#xff1a;扩散模型论文概述&#xff08;三&#xff09;&#xff1a;Stability AI系列工作_哔哩哔哩_bilibili 本期视频讲的是Stability AI在图像生成的工作。 同样&#xff0c;第一张图片是神作&#xff0c;总结的太好了&#xff01; 介绍Stable Diffusion之前&…

大数据技术-Hadoop(四)Yarn的介绍与使用

目录 一、Yarn 基本结构 1、Yarn基本结构 2、Yarn的工作机制 二、Yarn常用的命令 三、调度器 1、Capacity Scheduler&#xff08;容量调度器&#xff09; 1.1、特点 1.2、配置 1.2.1、yarn-site.xml 1.2.2、capacity-scheduler.xml 1.3、重启yarn、刷新队列 测试 向hi…

玩转大语言模型——ollama导入huggingface下载的模型

ollama导入huggingface模型 前言gguf模型查找相关模型下载模型 导入Ollama配置参数文件导入模型查看导入情况 safetensfors模型下载模型下载llama.cpp配置环境并转换 前言 ollama在大语言模型的应用中十分的方便&#xff0c;但是也存在一定的问题&#xff0c;比如不能使用自己…

apollo内置eureka dashboard授权登录

要确保访问Eureka Server时要求输入账户和密码&#xff0c;需要确保以下几点&#xff1a; 确保 eurekaSecurityEnabled 配置为 true&#xff1a;这个配置项控制是否启用Eureka的安全认证。如果它被设置为 false&#xff0c;即使配置了用户名和密码&#xff0c;也不会启用安全认…

【Dify】Dify自定义模型设置 | 对接DMXAPI使用打折 Openai GPT 或 Claude3.5系列模型方法详解

一、Dify & DMXAPI 1、Dify DIFY&#xff08;Do It For You&#xff09;是一种自动化工具或服务&#xff0c;旨在帮助用户简化操作&#xff0c;减少繁琐的手动操作&#xff0c;提升工作效率。通过DIFY&#xff0c;用户能够快速完成任务、获取所需数据&#xff0c;并且可以…