概述
简介
"RTC"是Real Time Clock 的简称,意为实时时钟。即,提供类似于 PC 上的时间记录信息的功能。既然是实时时钟,则至少应该有秒、分、时等信息。也可以直观的把他理解成为一个计数器,一直累加。但又不同于 CPU 上电后的那些计数器,对于 RTC ,需要支持的是掉电后的继续计数(存在备用电源)。所谓掉电,是指电源Vpp断开的情况下,为了RTC外设掉电可以继续运行,必须给STM32芯片通过VBAT引脚街上锂电池.当主电源VDD有效时,由VDD给RTC外设供电.当VDD掉电后,由VBAT给RTC外设供电.无论由什么电源供电,RTC中的数据始终都保存在属于RTC的备份域中,如果主电源和VBA都掉电,那么备份域中保存的所有数据都将丢失.(备份域除了RTC模块的寄存器,还有42个16位的寄存器可以在VDD掉电的情况下保存用户程序的数序,系统复位或电源复位时,这些数据也不会被复位).
STM32 的 RTC 操作,主要功能由三部分组成:
电源配置
后备(BKP配置)
RTC 配置
时钟
RTC 时钟可以选择三种时钟源的输入:
1. 高速外部时钟 HSE 的 128 分频
2. 低速外部时钟 LSE
3. 低速内部时钟 LSI
针对单板上,我们选择 LSE 32.768kHz 的 LSE 作为 RTC 的时钟输入。
当主电源掉电的时候,由Vbat进行供电:
工作原理
灰色部分为备用区域,系统掉电后,由后备电源持续供电,寄存器相关的值会持续存在并持续工作。同时,此部分的复位也单独存在 BKP 复位部分,即通过对 BKP 的复位来进行此部分的复位。
可以看到, RTC CLK 供给 RTC 模块时,首先通过一个分频器,进行分频,经过分频器后的时钟 TR_CLK 用于后续的计数器使用。RTC 支持闹钟功能,即,在 RTC_ALR 寄存器设置一个期望的值,RTC_CNT 在 TR_CLK 下进行计数,当计数器的值到达了 RTC_ALR ,则产生闹钟事件。
注:普通情况下,通过计算,使得 RTC 计数器能以 1s 一次的方式进行计数,这样能够满足基本使用。
除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。
中断
RTC 支持 3 个中断:
1. 秒中断:即经过分频器的时钟 TR_CLK 每次计数产生的中断
2. 溢出中断 : 计数器是 32bits 的,当计数器达到上限的时候产生的中断
3. 闹钟中断:当计数器的值达到配置的期望值 (RTC_ALR) 的时候产生闹钟
这三个中断均需要配置对应的中断使能位,进行使能后,方可使用。
访问限制
由于 RTC 处于 STM32 的备用区域(BKP),同时 RTC 的核心独立于 APB1,同时又是使用了 APB1 进行访问,此处,在访问 RTC 相关寄存器的时候,硬件有跨时钟域的同步操作,需要有几点注意的地方:
A. 访问 RTC 相关寄存器之前,首先需要开启 BKP 和 PWR 相关的时钟
B. 设置 PWR 的 DBP 位,使能对后备寄存器和RTC的访问
C. 若在读取 RTC 寄存器时,RTC 的 APB1 接口曾经处于禁止状态,则软件首先必须等待 RTC_CRL 寄存器中的 RSF 位(寄存器同步标志)被硬件置’1’。
D. 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是’1’时,才可以写入RTC寄存器。
C. 使能 RTC 进入配置模式,在配置模式下进行寄存器更新,设置完后,退出配置模式
针对最后一项D,访问过程如下:
D1. 查询RTOFF位,直到RTOFF的值变为’1’
D2. 置 CNF 值为1,进入配置模式
D3. 对一个或多个RTC寄存器进行写操作
D4. 清除CNF标志位,退出配置模式
D5. 查询RTOFF,直至RTOFF位变为’1’以确认写操作已经完成。
配置过程
1. 开启 PWR,BKP 区域的时钟
2. 写 PWR 的 DBP 位,使能后备区域的访问权限
3. Deinit RTC 模块
4. 配置 LSE 的时钟开启,并等待 LSE 稳定(32.768kHz)
5. 选择 RTC 的时钟为 LSE(注意,此部分不在 RTC 寄存器,故,无需满足”访问限制”章节的限制)
6. 使能 RTC
7. 由于是上电配置,故 APB1 之前出于禁止状态,故需要等待 RSF 置位
8. 等待 RTOFF 置位,即等待 RTC 出于 no busy
9. 设置分频系数为 32768 - 1,并等待 RTOFF 置位(等待写完成)
10. 配置使能秒中断,并等待 RTOFF 置位(等待写完成)
11. 初始化当前时间(这个可以随意设置)
12. 配置 NVIC
注意:为了使上电后,RTC 只被配置一次,这里使用了一个备用寄存器来作为 RTC 是否被配置过的标志(如果使用软件的一个变量,掉电后,变量的值会丢掉)。每次上电的时候进行 check, 如果配置过 RTC,则不再配置。
这里测试的时候,使用一个全局变量进行读数据,每次在秒中断中,将数据读出并解析。(因为寄存器中的计数器的单位是s秒)
当然,在中断处理程序 RTC_IRQHandler ,对 clear 中断 pending 的标志,也是需要等待写完成的 (RTOFF 置位)
代码如下:
#define LSE_CLK_32768KHZ 32768
#define RTC_CFG_DONE 0xAAAA
#define LEAP_YEAR_SEC 31622400
#define NORMAL_YEAR_SEC 31536000
#define A_DAY_SEC 86400
#define A_HOUR_SEC 3600
#define A_MIN_SEC 60
#define MAX_DAY 10
#define ONE_DAY_HOURS 24
#define ONE_HOUR_MIN 60
#define ONE_MIN_SEC 60
#define MAX_SEC (MAX_DAY * ONE_DAY_HOURS * ONE_HOUR_MIN * ONE_MIN_SEC)
typedef struct {
uint32_t day;
uint32_t hour;
uint32_t min;
uint32_t sec;
} SK_TIME_t;
SK_TIME_t g_stCurrentTime;
static void _setCurrentTime(SK_TIME_t *cur_time)
{
// Test RTC At 2018.7.13 -- 00:26:00
RTC_SetCounter(0x00);
RTC_WaitForLastTask();
}
void SK_getCurrentTime(SK_TIME_t *cur_time)
{
uint32_t secCount = RTC_GetCounter();
uint32_t sec = secCount % A_DAY_SEC;
cur_time->day = secCount / A_DAY_SEC ;
cur_time->hour = sec / 3600;
cur_time->min = (sec % 3600) / 60;
cur_time->sec = (sec % 3600) % 60;
}
static uint8_t SK_RTCIsConfiged(void)
{
return ((BKP_ReadBackupRegister(BKP_DR1) == RTC_CFG_DONE) ? 1 : 0);
}
static void SK_RTCNVICConfig(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void SK_RTC_Configuration(void)
{
// Step 1 : Open Power & Backup zone Clock
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
// Step 2 : Set Power register to allow to access backup domain
PWR_BackupAccessCmd(ENABLE);
// Step 3 : Rest BackUp domain
BKP_DeInit();
// Step 4 : Enable LSE Clock and wait for ready
RCC_LSEConfig(RCC_LSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
// Step 5 : Configure the LSE as RTC Clock input
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
// Step 6 : Enable RTC Clock
RCC_RTCCLKCmd(ENABLE);
// Step 7 : Because APB1 was reset when power on, follow datasheet, must wait RSF
RTC_WaitForSynchro();
// Step 8 : Wait until last write was finished
RTC_WaitForLastTask();
// Step 9 : Set prescaler as 32767
// RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)
RTC_SetPrescaler(LSE_CLK_32768KHZ - 1);
RTC_WaitForLastTask();
// Step 10 : Enable second interrupt
RTC_ITConfig(RTC_IT_SEC, ENABLE);
RTC_WaitForLastTask();
}
void SK_RTCInit(void)
{
// First Configure RTC
if (!SK_RTCIsConfiged())
{
SK_RTC_Configuration();
_setCurrentTime(&stTestRTCTime);
BKP_WriteBackupRegister(BKP_DR1, RTC_CFG_DONE);
}
else
{
RTC_WaitForSynchro();
RTC_ITConfig(RTC_IT_SEC, ENABLE);
RTC_WaitForLastTask();
}
SK_RTCNVICConfig();
SK_getCurrentTime(&g_stCurrentTime);
}
void SK_RTCDeInit(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
BKP_DeInit();
}
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
if (RTC_GetCounter() >= MAX_SEC)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
RTC_WaitForLastTask();
RTC_SetCounter(0x0);
RTC_WaitForLastTask();
}
SK_getCurrentTime(&g_stCurrentTime);
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)
{
RTC_ClearITPendingBit(RTC_IT_ALR);
}
RTC_ClearITPendingBit(RTC_IT_SEC | RTC_IT_OW);
RTC_WaitForLastTask();
}
最后
以上就是干净荷花为你收集整理的STM32F103ZET6 — RTC的全部内容,希望文章能够帮你解决STM32F103ZET6 — RTC所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复