RTC 案例2 :实时时钟 (掉电不丢失)
需求描述:
显示时间。通过串口把时间发送给电脑显示。即使关机很多天,再启动后也能正确显示时间。
思考:
1 通过时间戳设置初始时间
2 可视化的展示:年月日
实现:
1 时间戳
/* 在windows的powshell获取unix时间戳命令: [int][double]::Parse((Get-Date -UFormat %s)) */
/* 设置一次就行了,以后时钟就可以正常运行了 */
//RTC_SetUnixTimestampSecond(1700478670);
三种方式:
第一种:获得从1970 年 1 月 1 日 00:00:00 UTC(协调世界时)开始计算的秒钟
[int][double]::Parse((Get-Date -UFormat %s))
第二种:获得计时
Get-Date
第三种:获得精准的秒数
Get-Date -UFormat %s
2 可视化
C语言中有一个time.h的库提供一个tm的类型------函数:localtime();返回一个tm的类型的函数。
rtc.h
#ifndef __RTC_H
#define __RTC_H
#include "stm32f10x.h"
#include <time.h>
// 自定义日历时间结构体类型
typedef struct
{
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} DateTime;
// 初始化
void RTC_Init(void);
// 设置闹钟,s秒之后唤醒
void RTC_SetAlarm(uint32_t s);
// 设置当前时间(UNIX时间戳)
void RTC_SetTimestamp(uint32_t ts);
// 获取日历时间(年月日时分秒)
void RTC_GetDateTime(DateTime * dateTime);
#endif
rtc.c
#include "rtc.h"
// 初始化
void RTC_Init(void)
{
// 1. 后备域统一配置
// 1.1 开启PWR时钟
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
// 1.2 放开后备域的写保护
PWR->CR |= PWR_CR_DBP;
// // 1.3 软件复位整个备份域
// RCC->BDCR |= RCC_BDCR_BDRST;
// // 1.4 解除备份域复位
// RCC->BDCR &= ~RCC_BDCR_BDRST;
// 2. 配置RTC时钟源以及开启RTC
// 2.1 开启RTC时钟
RCC->BDCR |= RCC_BDCR_RTCEN;
// 2.2 打开LSE并等待启动完成
RCC->BDCR |= RCC_BDCR_LSEON;
while (!(RCC->BDCR & RCC_BDCR_LSERDY))
{
}
// 2.3 选择LSE作为RTC的时钟源
RCC->BDCR &= ~RCC_BDCR_RTCSEL;
RCC->BDCR |= RCC_BDCR_RTCSEL_0;
// 3. RTC寄存器的配置
// 3.1 查询RTOFF位,直到变为1
while (!(RTC->CRL & RTC_CRL_RTOFF))
{
}
// 3.2 进入配置模式
RTC->CRL |= RTC_CRL_CNF;
// 3.3 设置预分频系数 32767,产生秒脉冲
RTC->PRLH = 0;
RTC->PRLL = 0x7fff;
// 3.4 退出配置模式
RTC->CRL &= ~RTC_CRL_CNF;
// 3.5 查询RTOFF位,直到变为1
while (!(RTC->CRL & RTC_CRL_RTOFF))
{
}
}
// 设置闹钟,s秒之后唤醒
void RTC_SetAlarm(uint32_t s)
{
// 0. 先清除闹钟标志
RTC->CRL &= ~RTC_CRL_ALRF;
// 1. 查询RTOFF位,直到变为1
while (!(RTC->CRL & RTC_CRL_RTOFF))
{
}
// 2. 进入配置模式
RTC->CRL |= RTC_CRL_CNF;
// 3. 设置寄存器
// 3.1 计数器 CNT = 0
RTC->CNTH = 0;
RTC->CNTL = 0;
// 3.2 闹钟 ALR = s - 1
s -= 1;
RTC->ALRH = (s >> 16) & 0xffff;
RTC->ALRL = (s >> 0) & 0xffff;
// 4. 退出配置模式
RTC->CRL &= ~RTC_CRL_CNF;
// 5. 查询RTOFF位,直到变为1
while (!(RTC->CRL & RTC_CRL_RTOFF))
{
}
}
// 设置当前时间(UNIX时间戳)
void RTC_SetTimestamp(uint32_t ts)
{
// 1. 查询RTOFF位,直到变为1
while (!(RTC->CRL & RTC_CRL_RTOFF))
{
}
// 2. 进入配置模式
RTC->CRL |= RTC_CRL_CNF;
// 3. 设置CNT寄存器
RTC->CNTH = (ts >> 16) & 0xffff;
RTC->CNTL = (ts >> 0) & 0xffff;
// 4. 退出配置模式
RTC->CRL &= ~RTC_CRL_CNF;
// 5. 查询RTOFF位,直到变为1
while (!(RTC->CRL & RTC_CRL_RTOFF))
{
}
}
// 获取日历时间(年月日时分秒)
void RTC_GetDateTime(DateTime *dateTime)
{
// 1. 等待寄存器同步
while ( !(RTC->CRL & RTC_CRL_RSF) )
{}
// 2. 读取当前计数值(秒数)
uint32_t second = RTC->CNTH << 16 | RTC->CNTL;
// 3. 将秒数转换成tm结构体对象
struct tm* ptm = localtime(&second);
// 4. 基于tm构建自定义的结构体对象
dateTime->year = ptm->tm_year + 1900;
dateTime->month = ptm->tm_mon + 1;
dateTime->day = ptm->tm_mday;
dateTime->hour = ptm->tm_hour;
dateTime->minute = ptm->tm_min;
dateTime->second = ptm->tm_sec;
}
main.c
#include "usart.h"
#include "delay.h"
#include "rtc.h"
int main(void)
{
// 初始化
USART_Init();
RTC_Init();
printf("RTC实验:RTC实时时钟...\n");
// 设置一次当前的时间戳
// RTC_SetTimestamp(1736160789);
DateTime dateTime;
while (1)
{
// 每隔1s获取当前时间打印输出一次
RTC_GetDateTime(&dateTime);
printf("%04d年%02d月%02d日 %02d:%02d:%02d\n",
dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second);
Delay_ms(1000);
}
}