STM32F103ZET6 — RTC

简介

"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();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值