RTC实时时钟 & PWR电源控制 - STM32


STM32进入停止模式,并等待指定的唤醒事件(WKUP上升沿或RTC闹钟)

08 - STM32 - RTC实时时钟 & PWR电源控制

Unix时间戳

  • Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒。
  • 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量。
  • 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间。

UTC/GMT

  • •GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准。
  • UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致。

时间戳转换

  • C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换。
函数作用
time_t time(time_t*);获取系统时钟
struct tm gmtime(const time_t);秒计数器转换为日期时间(格林尼治时间)
struct tm localtime(const time_t);秒计数器转换为日期时间(当地时间)
time_t mktime(struct tm*);日期时间转换为秒计数器(当地时间)
char ctime(const time_t);秒计数器转换为字符串(默认格式)
char asctime(const struct tm);日期时间转换为字符串(默认格式)
size_t strftime(char, size_t, const char, const struct tm*);日期时间转换为字符串(自定义格式)

BKP备份寄存器 & RTC实时时钟

BKP简介

BKP基本结构

RTC简介

RTC框图

RTC基本结构

硬件电路

RTC操作注意事项

读写备份寄存器(程序)

接线图

代码

main.c

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

uint8_t KeyNum;                    //定义用于接收按键键码的变量

uint16_t ArrayWrite[] = {0x1234, 0x5678};    //定义要写入数据的测试数组
uint16_t ArrayRead[2];                        //定义要读取数据的测试数组

int main(void)
{
    /*模块初始化*/
    OLED_Init();                //OLED初始化
    Key_Init();                    //按键初始化
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "W:");
    OLED_ShowString(2, 1, "R:");
    
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);        //开启PWR的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);        //开启BKP的时钟
    
    /*备份寄存器访问使能*/
    PWR_BackupAccessCmd(ENABLE);                            //使用PWR开启对备份寄存器的访问
    
    while (1)
    {
        KeyNum = Key_GetNum();        //获取按键键码
        
        if (KeyNum == 1)            //按键1按下
        {
            ArrayWrite[0] ++;        //测试数据自增
            ArrayWrite[1] ++;
            
            BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);    //写入测试数据到备份寄存器
            BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);
            
            OLED_ShowHexNum(1, 3, ArrayWrite[0], 4);        //显示写入的测试数据
            OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);
        }
        
        ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);        //读取备份寄存器的数据
        ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);
        
        OLED_ShowHexNum(2, 3, ArrayRead[0], 4);                //显示读取的备份寄存器数据
        OLED_ShowHexNum(2, 8, ArrayRead[1], 4);
    }
}

测试

实时时钟(程序)

接线图

代码

System - MyRTC.c

#include "stm32f10x.h"                  // Device header
#include <time.h>

uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};    //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒

void MyRTC_SetTime(void);                //函数声明

/**
  * 函    数:RTC初始化
  * 参    数:无
  * 返 回 值:无
  */


//如果LSE无法起振导致程序卡死在初始化函数中
//可将初始化函数替换为下述代码,使用LSI当作RTCCLK
//LSI无法由备用电源供电,故主电源掉电时,RTC走时会暂停 
void MyRTC_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
    
    PWR_BackupAccessCmd(ENABLE);
    
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
    {
        RCC_LSICmd(ENABLE);
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);
        
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
        RCC_RTCCLKCmd(ENABLE);
        
        RTC_WaitForSynchro();
        RTC_WaitForLastTask();
        
        RTC_SetPrescaler(40000 - 1);
        RTC_WaitForLastTask();
        
        MyRTC_SetTime();
        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
    }
    else
    {
        RCC_LSICmd(ENABLE);                //即使不是第一次配置,也需要再次开启LSI时钟
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);
        
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
        RCC_RTCCLKCmd(ENABLE);
        
        RTC_WaitForSynchro();
        RTC_WaitForLastTask();
    }
}

/**
  * 函    数:RTC设置时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路
  */
void MyRTC_SetTime(void)
{
    time_t time_cnt;        //定义秒计数器数据类型
    struct tm time_date;    //定义日期时间数据类型
    
    time_date.tm_year = MyRTC_Time[0] - 1900;        //将数组的时间赋值给日期时间结构体
    time_date.tm_mon = MyRTC_Time[1] - 1;
    time_date.tm_mday = MyRTC_Time[2];
    time_date.tm_hour = MyRTC_Time[3];
    time_date.tm_min = MyRTC_Time[4];
    time_date.tm_sec = MyRTC_Time[5];
    
    time_cnt = mktime(&time_date) - 8 * 60 * 60;    //调用mktime函数,将日期时间转换为秒计数器格式
                                                    //- 8 * 60 * 60为东八区的时区调整
    
    RTC_SetCounter(time_cnt);                        //将秒计数器写入到RTC的CNT中
    RTC_WaitForLastTask();                            //等待上一次操作完成
}

/**
  * 函    数:RTC读取时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组
  */
void MyRTC_ReadTime(void)
{
    time_t time_cnt;        //定义秒计数器数据类型
    struct tm time_date;    //定义日期时间数据类型
    
    time_cnt = RTC_GetCounter() + 8 * 60 * 60;        //读取RTC的CNT,获取当前的秒计数器
                                                    //+ 8 * 60 * 60为东八区的时区调整
    
    time_date = *localtime(&time_cnt);                //使用localtime函数,将秒计数器转换为日期时间格式
    
    MyRTC_Time[0] = time_date.tm_year + 1900;        //将日期时间结构体赋值给数组的时间
    MyRTC_Time[1] = time_date.tm_mon + 1;
    MyRTC_Time[2] = time_date.tm_mday;
    MyRTC_Time[3] = time_date.tm_hour;
    MyRTC_Time[4] = time_date.tm_min;
    MyRTC_Time[5] = time_date.tm_sec;
}

System - MyRTC.h

#ifndef __MYRTC_H
#define __MYRTC_H

extern uint16_t MyRTC_Time[];

void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);

#endif

main.c

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

int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    MyRTC_Init();        //RTC初始化
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Date:XXXX-XX-XX");
    OLED_ShowString(2, 1, "Time:XX:XX:XX");
    OLED_ShowString(3, 1, "CNT :");
    OLED_ShowString(4, 1, "DIV :");
    
    while (1)
    {
        MyRTC_ReadTime();                            //RTC读取时间,最新的时间存储到MyRTC_Time数组中
        
        OLED_ShowNum(1, 6, MyRTC_Time[0], 4);        //显示MyRTC_Time数组中的时间值,年
        OLED_ShowNum(1, 11, MyRTC_Time[1], 2);        //月
        OLED_ShowNum(1, 14, MyRTC_Time[2], 2);        //日
        OLED_ShowNum(2, 6, MyRTC_Time[3], 2);        //时
        OLED_ShowNum(2, 9, MyRTC_Time[4], 2);        //分
        OLED_ShowNum(2, 12, MyRTC_Time[5], 2);        //秒
        
        OLED_ShowNum(3, 6, RTC_GetCounter(), 10);    //显示32位的秒计数器
        OLED_ShowNum(4, 6, RTC_GetDivider(), 10);    //显示余数寄存器
    }
}

测试

PWR电源控制

PWR简介

电源框图

上电复位和掉电复位

低功耗模式

模式选择

  • 执行WFI(Wait For Interrupt)或者WFE(Wait For Event)指令后,STM32进入低功耗模式。

睡眠模式

停止模式

待机模式

修改主频(程序)

接线图

代码

main.c

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

int main(void)
{
    OLED_Init();                            //OLED初始化
    
    OLED_ShowString(1, 1, "SYSCLK:");        //显示静态字符串
    OLED_ShowNum(1, 8, SystemCoreClock, 8);    //显示SystemCoreClock变量
                                            //SystemCoreClock的值表示当前的系统主频频率
    
    while (1)
    {
        OLED_ShowString(2, 1, "Running");    //闪烁Running,指示当前主循环运行的快慢
        Delay_ms(500);
        OLED_ShowString(2, 1, "       ");
        Delay_ms(500);
    }
}

测试

睡眠模式 + 串口发送 + 接收(程序)

接线图

代码

Serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;        //定义串口接收的数据变量
uint8_t Serial_RxFlag;        //定义串口接收的标志位变量

/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void Serial_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);    //开启USART1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    //开启GPIOA的时钟
    
    /*GPIO初始化*/
    GPIO_InitTypeDef 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);                    //将PA9引脚初始化为复用推挽输出
    
    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);                    //将PA10引脚初始化为上拉输入
    
    /*USART初始化*/
    USART_InitTypeDef USART_InitStructure;                    //定义结构体变量
    USART_InitStructure.USART_BaudRate = 9600;                //波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    //硬件流控制,不需要
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;    //模式,发送模式和接收模式均选择
    USART_InitStructure.USART_Parity = USART_Parity_No;        //奇偶校验,不需要
    USART_InitStructure.USART_StopBits = USART_StopBits_1;    //停止位,选择1位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;        //字长,选择8位
    USART_Init(USART1, &USART_InitStructure);                //将结构体变量交给USART_Init,配置USART1
    
    /*中断输出配置*/
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);            //开启串口接收数据的中断
    
    /*NVIC中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);            //配置NVIC为分组2
    
    /*NVIC配置*/
    NVIC_InitTypeDef NVIC_InitStructure;                    //定义结构体变量
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;        //选择配置NVIC的USART1线
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //指定NVIC线路使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;        //指定NVIC线路的抢占优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        //指定NVIC线路的响应优先级为1
    NVIC_Init(&NVIC_InitStructure);                            //将结构体变量交给NVIC_Init,配置NVIC外设
    
    /*USART使能*/
    USART_Cmd(USART1, ENABLE);                                //使能USART1,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
    USART_SendData(USART1, Byte);        //将字节数据写入数据寄存器,写入后USART自动生成时序波形
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);    //等待发送完成
    /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
    uint16_t i;
    for (i = 0; i < Length; i ++)        //遍历数组
    {
        Serial_SendByte(Array[i]);        //依次调用Serial_SendByte发送每个字节数据
    }
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
    uint8_t i;
    for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
    {
        Serial_SendByte(String[i]);        //依次调用Serial_SendByte发送每个字节数据
    }
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
    uint32_t Result = 1;    //设置结果初值为1
    while (Y --)            //执行Y次
    {
        Result *= X;        //将X累乘到结果
    }
    return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i ++)        //根据数字长度遍历数字的每一位
    {
        Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');    //依次调用Serial_SendByte发送每位数字
    }
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
    Serial_SendByte(ch);            //将printf的底层重定向到自己的发送字节函数
    return ch;
}

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
    char String[100];                //定义字符数组
    va_list arg;                    //定义可变参数列表数据类型的变量arg
    va_start(arg, format);            //从format开始,接收参数列表到arg变量
    vsprintf(String, format, arg);    //使用vsprintf打印格式化字符串和参数列表到字符数组中
    va_end(arg);                    //结束变量arg
    Serial_SendString(String);        //串口发送字符数组(字符串)
}

/**
  * 函    数:获取串口接收标志位
  * 参    数:无
  * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
    if (Serial_RxFlag == 1)            //如果标志位为1
    {
        Serial_RxFlag = 0;
        return 1;                    //则返回1,并自动清零标志位
    }
    return 0;                        //如果标志位为0,则返回0
}

/**
  * 函    数:获取串口接收的数据
  * 参    数:无
  * 返 回 值:接收的数据,范围:0~255
  */
uint8_t Serial_GetRxData(void)
{
    return Serial_RxData;            //返回接收的数据变量
}

/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)        //判断是否是USART1的接收事件触发的中断
    {
        Serial_RxData = USART_ReceiveData(USART1);                //读取数据寄存器,存放在接收的数据变量
        Serial_RxFlag = 1;                                        //置接收标志位变量为1
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);            //清除USART1的RXNE标志位
                                                                //读取数据寄存器会自动清除此标志位
                                                                //如果已经读取了数据寄存器,也可以不执行此代码
    }
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif

main.c

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

uint8_t RxData;            //定义用于接收串口数据的变量

int main(void)
{
    OLED_Init();        //OLED初始化
    OLED_ShowString(1, 1, "RxData:");    //显示静态字符串
    
    Serial_Init();        //串口初始化
    
    while (1)
    {
        if (Serial_GetRxFlag() == 1)            //检查串口接收数据的标志位
        {
            RxData = Serial_GetRxData();        //获取串口接收的数据
            Serial_SendByte(RxData);            //串口将收到的数据回传回去,用于测试
            OLED_ShowHexNum(1, 8, RxData, 2);    //显示串口接收的数据
        }
        
        OLED_ShowString(2, 1, "Running");        //OLED闪烁Running,指示当前主循环正在运行
        Delay_ms(100);
        OLED_ShowString(2, 1, "       ");
        Delay_ms(100);
        
        __WFI();                                //执行WFI指令,CPU睡眠,并等待中断唤醒
    }
}

测试

停止模式 + 对射式红外传感器计次(程序)

接线图

代码

CountSensor.c

#include "stm32f10x.h"                  // Device header

uint16_t CountSensor_Count;                //全局变量,用于计数

/**
  * 函    数:计数传感器初始化
  * 参    数:无
  * 返 回 值:无
  */
void CountSensor_Init(void)
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);        //开启GPIOB的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);        //开启AFIO的时钟,外部中断必须开启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);                        //将PB14引脚初始化为上拉输入
    
    /*AFIO选择中断引脚*/
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
    
    /*EXTI初始化*/
    EXTI_InitTypeDef EXTI_InitStructure;                        //定义结构体变量
    EXTI_InitStructure.EXTI_Line = EXTI_Line14;                    //选择配置外部中断的14号线
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;                    //指定外部中断线使能
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;            //指定外部中断线为中断模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;        //指定外部中断线为下降沿触发
    EXTI_Init(&EXTI_InitStructure);                                //将结构体变量交给EXTI_Init,配置EXTI外设
    
    /*NVIC中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                //配置NVIC为分组2
    
    /*NVIC配置*/
    NVIC_InitTypeDef NVIC_InitStructure;                        //定义结构体变量
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;        //选择配置NVIC的EXTI15_10线
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //指定NVIC线路使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;    //指定NVIC线路的抢占优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;            //指定NVIC线路的响应优先级为1
    NVIC_Init(&NVIC_InitStructure);                                //将结构体变量交给NVIC_Init,配置NVIC外设
}

/**
  * 函    数:获取计数传感器的计数值
  * 参    数:无
  * 返 回 值:计数值,范围:0~65535
  */
uint16_t CountSensor_Get(void)
{
    return CountSensor_Count;
}

/**
  * 函    数:EXTI15_10外部中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line14) == SET)        //判断是否是外部中断14号线触发的中断
    {
        /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
        {
            CountSensor_Count ++;                    //计数值自增一次
        }
        EXTI_ClearITPendingBit(EXTI_Line14);        //清除外部中断14号线的中断标志位
                                                    //中断标志位必须清除
                                                    //否则中断将连续不断地触发,导致主程序卡死
    }
}

CountSensor.h

#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H

void CountSensor_Init(void);
uint16_t CountSensor_Get(void);

#endif

main.c

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

int main(void)
{
    /*模块初始化*/
    OLED_Init();            //OLED初始化
    CountSensor_Init();        //计数传感器初始化
    
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);        //开启PWR的时钟
                                                            //停止模式和待机模式一定要记得开启
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Count:");
    
    while (1)
    {
        OLED_ShowNum(1, 7, CountSensor_Get(), 5);            //OLED不断刷新显示CountSensor_Get的返回值
        
        OLED_ShowString(2, 1, "Running");                    //OLED闪烁Running,指示当前主循环正在运行
        Delay_ms(100);
        OLED_ShowString(2, 1, "       ");
        Delay_ms(100);
        
        PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);    //STM32进入停止模式,并等待中断唤醒
        SystemInit();                                        //唤醒后,要重新配置时钟
    }
}

测试

待机模式 + 实时时钟(程序)

接线图

代码

MyRTC.c

#include "stm32f10x.h"                  // Device header
#include <time.h>

uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};    //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒

void MyRTC_SetTime(void);                //函数声明

/**
  * 函    数:RTC初始化
  * 参    数:无
  * 返 回 值:无
  */


//如果LSE无法起振导致程序卡死在初始化函数中
//可将初始化函数替换为下述代码,使用LSI当作RTCCLK
//LSI无法由备用电源供电,故主电源掉电时,RTC走时会暂停

void MyRTC_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
    
    PWR_BackupAccessCmd(ENABLE);
    
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
    {
        RCC_LSICmd(ENABLE);
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);
        
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
        RCC_RTCCLKCmd(ENABLE);
        
        RTC_WaitForSynchro();
        RTC_WaitForLastTask();
        
        RTC_SetPrescaler(40000 - 1);
        RTC_WaitForLastTask();
        
        MyRTC_SetTime();
        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
    }
    else
    {
        RCC_LSICmd(ENABLE);                //即使不是第一次配置,也需要再次开启LSI时钟
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);
        
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
        RCC_RTCCLKCmd(ENABLE);
        
        RTC_WaitForSynchro();
        RTC_WaitForLastTask();
    }
}

/**
  * 函    数:RTC设置时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路
  */
void MyRTC_SetTime(void)
{
    time_t time_cnt;        //定义秒计数器数据类型
    struct tm time_date;    //定义日期时间数据类型
    
    time_date.tm_year = MyRTC_Time[0] - 1900;        //将数组的时间赋值给日期时间结构体
    time_date.tm_mon = MyRTC_Time[1] - 1;
    time_date.tm_mday = MyRTC_Time[2];
    time_date.tm_hour = MyRTC_Time[3];
    time_date.tm_min = MyRTC_Time[4];
    time_date.tm_sec = MyRTC_Time[5];
    
    time_cnt = mktime(&time_date) - 8 * 60 * 60;    //调用mktime函数,将日期时间转换为秒计数器格式
                                                    //- 8 * 60 * 60为东八区的时区调整
    
    RTC_SetCounter(time_cnt);                        //将秒计数器写入到RTC的CNT中
    RTC_WaitForLastTask();                            //等待上一次操作完成
}

/**
  * 函    数:RTC读取时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组
  */
void MyRTC_ReadTime(void)
{
    time_t time_cnt;        //定义秒计数器数据类型
    struct tm time_date;    //定义日期时间数据类型
    
    time_cnt = RTC_GetCounter() + 8 * 60 * 60;        //读取RTC的CNT,获取当前的秒计数器
                                                    //+ 8 * 60 * 60为东八区的时区调整
    
    time_date = *localtime(&time_cnt);                //使用localtime函数,将秒计数器转换为日期时间格式
    
    MyRTC_Time[0] = time_date.tm_year + 1900;        //将日期时间结构体赋值给数组的时间
    MyRTC_Time[1] = time_date.tm_mon + 1;
    MyRTC_Time[2] = time_date.tm_mday;
    MyRTC_Time[3] = time_date.tm_hour;
    MyRTC_Time[4] = time_date.tm_min;
    MyRTC_Time[5] = time_date.tm_sec;
}

MyRTC.h

#ifndef __MYRTC_H
#define __MYRTC_H

extern uint16_t MyRTC_Time[];

void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);

#endif

main.c

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

int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    MyRTC_Init();        //RTC初始化
    
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);        //开启PWR的时钟
                                                            //停止模式和待机模式一定要记得开启
    
    /*显示静态字符串*/
    OLED_ShowString(1, 1, "CNT :");
    OLED_ShowString(2, 1, "ALR :");
    OLED_ShowString(3, 1, "ALRF:");
    
    /*使能WKUP引脚*/
    PWR_WakeUpPinCmd(ENABLE);                        //使能位于PA0的WKUP引脚,WKUP引脚上升沿唤醒待机模式
    
    /*设定闹钟*/
    uint32_t Alarm = RTC_GetCounter() + 10;            //闹钟为唤醒后当前时间的后10s
    RTC_SetAlarm(Alarm);                            //写入闹钟值到RTC的ALR寄存器
    OLED_ShowNum(2, 6, Alarm, 10);                    //显示闹钟值
    
    while (1)
    {
        OLED_ShowNum(1, 6, RTC_GetCounter(), 10);    //显示32位的秒计数器
        OLED_ShowNum(3, 6, RTC_GetFlagStatus(RTC_FLAG_ALR), 1);        //显示闹钟标志位
        
        OLED_ShowString(4, 1, "Running");            //OLED闪烁Running,指示当前主循环正在运行
        Delay_ms(100);
        OLED_ShowString(4, 1, "       ");
        Delay_ms(100);
        
        OLED_ShowString(4, 9, "STANDBY");            //OLED闪烁STANDBY,指示即将进入待机模式
        Delay_ms(1000);
        OLED_ShowString(4, 9, "       ");
        Delay_ms(100);
        
        OLED_Clear();                                //OLED清屏,模拟关闭外部所有的耗电设备,以达到极度省电
        
        PWR_EnterSTANDBYMode();                        //STM32进入停止模式,并等待指定的唤醒事件(WKUP上升沿或RTC闹钟)
        /*待机模式唤醒后,程序会重头开始运行*/
    }
}

测试

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - RTC实时时钟 & PWR电源控制 - STM32


三二一的一的二