一、原理
睡眠模式不响应其他操作,比如烧写程序,烧写时按住复位键松手即可下载,在禁用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.hNVIC_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格式字符串,最好放入打印字符串变量Stringva_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){//RCCRCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//引脚时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//AFIO时钟使能//GPIOGPIO_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);//AFIOGPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);//EXTIEXTI_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使用的是MDNVIC_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=40000HzRTC_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);//根据计数值换算成日期时间,将值传给NowTimeUnixdataStructure.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)suint8_t minutes;//(0-59)minuint8_t hours;//(0-23)huint8_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。