单片机
返回首页

STM32 RTC实时时钟(一)

2024-08-21 来源:elecfans

STM32处理器内部集成了实时时钟控制器(RTC),因此在实现实时时钟功能时,无须外扩时钟芯片即可构建实时时钟系统。


实时时钟 (RTC) 是一个独立的 BCD 定时器/计数器。RTC 提供一个日历时钟、两个可编程闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC 还包含用于管理低功耗模式的自动唤醒单元。


两个 32 位寄存器包含二进码十进数格式 (BCD) 的秒、分钟、小时(12 或 24 小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。系统可以自动将月份的天数补偿为 28、29(闰年)、30 和 31天。并且还可以进行夏令时补偿。其它 32 位寄存器还包含可编程的闹钟亚秒、秒、分钟、小时、星期几和日期。


此外,还可以使用数字校准功能对晶振精度的偏差进行补偿。

上电复位后,所有 RTC 寄存器都会受到保护,以防止可能的非正常写访问。无论器件状态如何(运行模式、低功耗模式或处于复位状态),只要电源电压保持在工作范围内,RTC 便不会停止工作。


STM32F4XX的RTC模块和时钟配置可设置在备份区域,这样系统复位或者从待机模式唤醒后RTC的设置和时间依然维持不变,只要备份域供电正常,RTC模块就能持续地走时。为防止备份域被意外写操作,备份区域也是被写保护的。


备份区域包含20个备份寄存器,共80个字节。可利用备份寄存器模拟EEPROM,实现非易失功能,但是必须保证备份区域供电正常。通常,为保证系统掉电后RTC能够正常走时且备份寄存器非易失,都会为RTC模块配备一枚纽扣电池。

RTC框图

图片

时钟和预分频器

RTC 时钟源 (RTCCLK) 通过时钟控制器从 LSE 时钟、LSI 振荡器时钟以及 HSE 时钟三者中选择。

可编程的预分频器阶段可生成 1 Hz 的时钟,用于更新日历。为最大程度地降低功耗,预分频器分为 2 个可编程的预分频器:

● 一个通过 RTC_PRER 寄存器的 PREDIV_A 位配置的 7 位异步预分频器。

● 一个通过 RTC_PRER 寄存器的 PREDIV_S 位配置的 15 位同步预分频器。

实时时钟和日历

RTC 日历时间和日期寄存器可通过与 PCLK1(APB1 时钟)同步的影子寄存器来访问。这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。

● RTC_SSR 对应于亚秒

● RTC_TR 对应于时间

● RTC_DR 对应于日期

每隔两个 RTCCLK 周期,便将当前日历值复制到影子寄存器,并将 RTC_ISR 寄存器的 RSF位置 1。在停机和待机模式下不会执行复制操作。退出这两种模式时,影子寄存器会在最长 2 个 RTCCLK 周期后进行更新。

当应用读取日历寄存器时,它会访问影子寄存器的内容。也可以通过将 RTC_CR 寄存器的

BYPSHAD 控制位置 1 来直接访问日历寄存器。默认情况下,该位被清零,用户访问影子寄存器。

在 BYPSHAD=0 模式下读取 RTC_SSR、RTC_TR 或 RTC_DR 寄存器时,APB 时钟频率(f APB ) 必须至少为 RTC 时钟频率 (f RTCCLK ) 的 7 倍。

影子寄存器通过系统复位来复位。

RTC 寄存器写保护

系统复位后,可通过 PWR 电源控制寄存器 (PWR_CR) 的 DBP 位保护 RTC 寄存器以防止非正常的写访问。必须将 DBP 位置 1 才能使能 RTC 寄存器的写访问。

上电复位后,所有 RTC 寄存器均受到写保护。通过向写保护寄存器 (RTC_WPR) 写入一个密钥来使能对 RTC 寄存器的写操作。

要解锁所有 RTC 寄存器(RTC_ISR[13:8]、RTC_TAFCR 和 RTC_BKPxR 除外)的写保护,需要执行以下步骤:

  1. 将“0xCA”写入 RTC_WPR 寄存器。

  2. 将“0x53”写入 RTC_WPR 寄存器。

写入一个错误的关键字会再次激活写保护。保护机制不受系统复位影响。

日历初始化和配置

要编程包括时间格式和预分频器配置在内的初始时间和日期日历值,需按照以下顺序操作:

  1. 将 RTC_ISR 寄存器中的 INIT 位置 1 以进入初始化模式。在此模式下,日历计数器将停止工作并且其值可更新。

  2. 轮询 RTC_ISR 寄存器中的 INITF 位。当 INITF 置 1 时进入初始化阶段模式。大约需要2 个 RTCCLK 时钟周期(由于时钟同步)。

  3. 要为日历计数器生成 1 Hz 时钟,应首先编程 RTC_PRER 寄存器中的同步预分频系数,然后编程异步预分频系数。即使只需要更改这两个字段中之一,也必须对 RTC_PRER寄存器执行两次单独的写访问。

  4. 在影子寄存器(RTC_TR 和 RTC_DR)中加载初始时间和日期值,然后通过 RTC_CR寄存器中的 FMT 位配置时间格式(12 或 24 小时制)。

  5. 通过清零 INIT 位退出初始化模式。随后,自动加载实际日历计数器值,在 4 个 RTCCLK时钟周期后重新开始计数。

当初始化序列完成之后,日历开始计数。

注意:系统复位后,应用可读取 RTC_ISR 寄存器中的 INITS 标志,以检查日历是否已初始化。如果该标志为 0 ,表明日历尚未初始化,因为年份字段设置为其上电复位时的默认值 (0x00) 。

要在初始化之后读取日历,必须首先用软件检查 RTC_ISR 寄存器的 RSF 标志是否置 1 。

接着查看寄存器描述:

RTC 时间寄存器 (RTC_TR)

RTC_TR 是日历时间影子寄存器。只能在初始化模式下对该寄存器执行写操作。

图片

位 22 PM:AM/PM 符号 (AM/PM notation)

0:AM 或 24 小时制

1:PM

位 21:20 HT[1:0]: 小时的十位(BCD 格式)(Hour tens in BCD format)

位 16:16 HU[3:0]: 小时的个位(BCD 格式)(Hour units in BCD format)

位 14:12 MNT[2:0]: 分钟的十位(BCD 格式)(Minute tens in BCD format)

位 11:8 MNU[3:0]: 分钟的个位(BCD 格式)(Minute units in BCD format)

位 6:4 ST[2:0]: 秒的十位(BCD 格式)(Second tens in BCD format)

位 3:0 SU[3:0]: 秒的个位(BCD 格式)(Second units in BCD format)

RTC 日期寄存器 (RTC_DR)

RTC_DR 是日历日期影子寄存器。只能在初始化模式下对该寄存器执行写操作。

图片

位 23:20 YT[3:0]: 年份的十位(BCD 格式)(Year tens in BCD format)

位 19:16 YU[3:0]: 年份的个位(BCD 格式)(Year units in BCD format)

位 15:13 WDU[2:0]: 星期几的个位 (Week day units)

000:禁止

001:星期一

...

111:星期日

位 12 MT: 月份的十位(BCD 格式)(Month tens in BCD format)

位 11:8 MU: 月份的个位(BCD 格式)(Month units in BCD format)

位 5:4 DT[1:0]: 日期的十位(BCD 格式)(Date tens in BCD format)

位 3:0 DU[3:0]: 日期的个位(BCD 格式)(Date units in BCD format)

RTC 控制寄存器 (RTC_CR)

图片

位 23 COE:校准输出使能 (Calibration output enable)

该位使能 RTC_CALIB 输出

0:禁止校准输出

1:使能校准输出

位 22:21 OSEL[1:0]:输出选择 (Output selection)

这些位用于选择要连接到 RTC_ALARM 输出的标志

00:禁止输出

01:使能闹钟 A 输出

10:使能闹钟 B 输出

11:使能唤醒输出

位 20 POL:输出极性 (Output polarity)

该位用于配置 RTC_ALARM 输出的极性

0:当 ALRAF/ALRBF/WUTF 置 1 时(取决于 OSEL[1:0]),该引脚为高电平

1:当 ALRAF/ALRBF/WUTF 置 1 时(取决于 OSEL[1:0]),该引脚为低电平

位 19 COSEL:校准输出选择 (Calibration output selection)

当 COE=1 时,该位可选择 RTC_CALIB 上输出的信号。

0:校准输出为 512 Hz

1:校准输出为 1 Hz

在 RTCCLK 为 32.768 kHz 且预分频器为其默认值(PREDIV_A=127 且 PREDIV_S=255)的条件下,这些频率有效。

位 18 BKP:备份 (Backup)

用户可对此位执行写操作以记录是否已对夏令时进行更改。

位 17 SUB1H:减少 1 小时(冬季时间更改)(Subtract 1 hour (winter time change))

当该位在初始化模式以外的模式下置 1 时,如果当前小时不是 0,则日历时间将减少 1 小时。此位始终读为 0。当前小时为 0 时,将此位置 1 没有任何作用。

0:无作用。

1:将当前时间减少 1 小时。这可用于冬季时间更改。

位 16 ADD1H:增加 1 小时(夏季时间更改)(Add 1 hour (summer time change))

当该位在初始化模式以外的模式下置 1 时,日历时间将增加 1 小时。此位始终读为 0。

0:无作用。

1:将当前时间增加 1 小时。这可用于夏季时间更改

位 15 TSIE:时间戳中断使能 (Timestamp interrupt enable)

0:禁止时间戳中断

1:使能时间戳中断

位 14 WUTIE:使能唤醒定时器使能 (Wakeup timer interrupt enable)

0:禁止唤醒定时器中断

1:使能唤醒定时器中断

位 13 ALRBIE: ** 闹钟 B 中断使能 (Alarm B interrupt enable)**

0:闹钟 B 中断禁止

1:闹钟 B 中断使能

位 12 ALRAIE:闹钟 A 中断使能 (Alarm A interrupt enable)

0:禁止闹钟 A 中断

1:使能闹钟 A 中断

位 11 TSE:时间戳使能 (Time stamp enable)

0:禁止时间戳

1:使能时间戳

位 10 WUTE:唤醒定时器使能 (Wakeup timer enable)

0:禁止唤醒定时器

1:使能唤醒定时器

位 9 ALRBE: ** 闹钟 B 使能 (Alarm B enable)**

0:禁止闹钟 B

1:使能闹钟 B

位 8 ALRAE :闹钟 A 使能 (Alarm A enable)

0:禁止闹钟 A

1:使能闹钟 A

位 7 DCE :粗略数字校准使能 (Coarse digital calibration enable)

0:禁止数字校准

1:使能数字校准

PREDIV_A 必须大于或等于 6

位 6 FMT:小时格式 (Hour format)

0:24 小时/天格式

1:AM/PM 小时格式

位 5 BYPSHAD:旁路影子寄存器 (Bypass the shadow registers)

0:日历值(从 RTC_SSR、RTC_TR 和 RTC_DR 读取时)取自影子寄存器,该影子寄存器每两个 RTCCLK 周期更新一次。

1:日历值(从 RTC_SSR、RTC_TR 和 RTC_DR 读取时)直接取自日历计数器。

注意:如果 APB1 时钟的频率低于 7 倍的 RTCCLK 频率,则必须将 BYPSHAD 置“ 1 ”。

位 4 REFCKON: **参考时钟检测使能(50 Hz 或 60 Hz)(Reference clock detection enable (50 or **60 Hz))

0:禁止参考时钟检测

1:使能参考时钟检测

注意:PREDIV_S 必须为 0x00FF 。

位 3 TSEDGE:时间戳事件有效边沿 (Timestamp event active edge)

0:TIMESTAMP 上升沿生成时间戳事件

1:TIMESTAMP 下降沿生成时间戳事件

TSEDGE 发生更改时,必须复位 TSE 以避免将 TSF 意外置 1

位 2:0 WUCKSEL[2:0]:唤醒时钟选择 (Wakeup clock selection)

000:选择 RTC/16 时钟

001:选择 RTC/8 时钟

010:选择 RTC/4 时钟

011:选择 RTC/2 时钟

10x:选择 ck_spre 时钟(通常为 1 Hz)

11x:选择 ck_spre 时钟(通常为 1 Hz)并将 WUT 计数器值增加 216

RTC 初始化和状态寄存器 (RTC_ISR)

图片

位 16 RECALPF:重新校准挂起标志 (Recalibration pending Flag)

当软件对 RTC_CALR 寄存器执行写操作时,RECALPF 状态标志将自动置“1”,指示RTC_CALR 寄存器已屏蔽。当采用新的校准设置时,该位恢复为“0”。

位 14 TAMP2F:TAMPER2 检测标志 (TAMPER2 detection flag)

在入侵输入 2 上检测到入侵检测事件时,由硬件将此标志置 1。

该标志由软件写零清除。

位 13 TAMP1F:入侵检测标志 (Tamper detection flag)

当检测到入侵检测事件时,由硬件将此标志置 1。

该标志由软件写零清除。

位 12 TSOVF:时间戳溢出标志 (Timestamp overflow flag)

当在 TSF 已置 1 的情况下发生时间戳事件时,由硬件将此标志置 1。

该标志由软件写零清除。建议仅在 TSF 位清零之后再检查并清TSOVF 位。否则,如果时间戳事件恰好在清零 TSF 位之前刚刚发生,则溢出事件可能会被漏掉。

位 11 TSF:时间戳标志 (Timestamp flag)

发生时间戳事件时,由硬件将此标志置 1。

该标志由软件写零清除。

位 10 WUTF:唤醒定时器标志 (Wakeup timer flag)

当唤醒自动重载计数器计数到 0 时,由硬件将此标志置 1。

该标志由软件写零清除。

软件必须在 WUTF 再次置 1 的 1.5 个 RTCCLK 周期之前将该标志清零。

位 9 ALRBF:闹钟 B 标志 (Alarm B flag)

当时间/日期寄存器(RTC_TR 和 RTC_DR)与闹钟 B 寄存器 (RTC_ALRMBR) 匹配时,由硬件将该标志置 1。该标志由软件写零清除。

位 8 ALRAF:闹钟 A 标志 (Alarm A flag)

当时间/日期寄存器(RTC_TR 和 RTC_DR)与闹钟 A 寄存器 (RTC_ALRMAR) 匹配时,由

硬件将该标志置 1。

该标志由软件写零清除。

位 7 INIT:初始化模式 (Initialization mode)

0:自由运行模式。

1:初始化模式,用于编程时间和日期寄存器(RTC_TR 和 RTC_DR)以及预分频器寄存器(RTC_PRER)。计数器停止计数,当 INIT 被复位后,计数器从新值开始计数。

位 6 INITF:初始化标志 (Initialization flag)

当此位置 1 时,RTC 处于初始化状态,此时可更新事件、日期和预分频器寄存器。

0:不允许更新日历寄存器。

1:允许更新日历寄存器。

位 5 RSF:寄存器同步标志 (Registers synchronization flag)

每次将日历寄存器的值复制到影子寄存器(RTC_SSRx、RTC_TRx 和 RTC_DRx)时,都会由硬件将此位置 1。在初始化模式下、平移操作挂起时 (SHPF=1) 或在旁路影子寄存器模式 (BYPSHAD=1) 下,该位由硬件清零。该位还可由软件清零。

0:日历影子寄存器尚未同步

1:日历影子寄存器已同步

位 4 INITS:初始化状态标志 (Initialization status flag)

当日历年份字段不为 0 时(上电复位状态),由硬件将该位置 1。

0:日历尚未初始化

1:日历已经初始化

位 3 SHPF:平移操作挂起 (Shift operation pending)

0:没有平移操作挂起

1:某个平移操作挂起

只要通过对 RTC_SHIFTR 寄存器执行写操作来启动平移操作,此标志便由硬件置 1。执行完相应的平移操作后,此标志由硬件清零。对 SHPF 执行写入操作不起作用。

位 2 WUTWF:唤醒定时器写标志 (Wakeup timer write flag)

在 RTC_CR 寄存器中的 WUTE 位置 0 后,当唤醒定时器值可更改时,由硬件将该位置 1。

0:不允许更新唤醒定时器配置

1:允许更新唤醒定时器配置

位 1 ALRBWF:闹钟 B 写标志 (Alarm B write flag)

在 RTC_CR 寄存器中的 ALRBIE 位置 0 之后,当闹钟 B 的值可更改时,由硬件将该位置 1。该位在初始化模式下由硬件清零。

0:不允许更新闹钟 B

1:允许更新闹钟 B

位 0 ALRAWF:闹钟 A 写标志 (Alarm A write flag)

在 RTC_CR 寄存器中的 ALRAE 位置 0 后,当闹钟 A 的值可更改时,由硬件将该位置 1。

该位在初始化模式下由硬件清零。

0:不允许更新闹钟 A

1:允许更新闹钟 A

RTC 预分频器寄存器 (RTC_PRER)

预分频器寄存器复位值刚好使时钟频率为1Hz,满足RTC的需求,所以这里不对其进行配置。


RTC初始化程序如下:


#include 'RTC.h'

#include 'stm32f4xx.h'

void RTC_Init(u8 hour,u8 fen,u8 miao,u8 nian,u8 yue,u8 ri,u8 xingqi)

{

  u32 temp = 0;

  RCC- >APB1ENR  |=  1< < 28;  //开电源管理模块时钟

  PWR- >CR  |=  1< < 8;          //解除保护

  RCC- >BDCR  &=~  (1< < 2);      //调试模式下不旁路LSE

  RCC- >BDCR  |=  1< < 0;        //开启LSE振荡器

  while(!(RCC- >BDCR & 1< < 1));

  RCC- >BDCR  &=~  (0x3< < 8);

  RCC- >BDCR  |=  1< < 8;        //选择LSE为RTC时钟源

  RCC- >BDCR  |=  1< < 15;      //使能RTC模块

  RTC- >WPR  =  0xCA;          //解除写保护

  RTC- >WPR  =  0x53;          //解除写保护

  RTC- >ISR  |=  1< < 7;        //INIT位置1

  while((RTC- >ISR  &  (1< < 6))==0);      //等待进入初始化模式

  temp |= (hour/10)< < 20  |  (hour%10)< < 16  |  (fen/10)< < 12  |  (fen%10)< < 8  |  (miao/10)< < 4  |  (miao%10);

  RTC- >TR  =  temp;

  temp  =  0;

  temp |= (nian/10)< < 20  |  (nian%10)< < 16  |  (xingqi)< < 13  |  (yue/10)< < 12  |  (yue%10)< < 8  |  (ri/10)< < 4  |  (ri%10);

  RTC- >DR  =  temp;

  RTC- >CR  =  0;

  RTC- >ISR  &=~(1< < 7);      //退出初始化模式

  RTC- >WPR = 0;              //写任意值,再次保护

}

编写主函数测试:


#include 'stm32f4xx.h'

#include 'usart.h'

#include 'delay.h'

#include 'stdio.h'

#include 'RTC.h'



typedef struct

{

  u8 shi;

  u8 fen;

  u8 miao;

  u8 nian;

  u8 yue;

  u8 ri;

  u8 xingqi;

}TIME_Typedef;

TIME_Typedef time = {1};



int main()

{

  Usart1_Init(115200);

  RTC_Init(23,59,50,19,7,9,2);


  while(1)

  {

    time.shi    =    ((RTC- >TR  &  0x300000) >>20)*10  +  ((RTC- >TR  &  0xf0000) >>16);

    time.fen    =    ((RTC- >TR  &  0x7000) >>12)*10  +  ((RTC- >TR  &  0xf00) >>8);

    time.miao    =    ((RTC- >TR  &  0x70) >>4)*10  +  (RTC- >TR  &  0xf);



    printf('%d:%d:%drn',time.shi,time.fen,time.miao);


    Delay_ms(1000);

  }

}

运行后可以看到串口输出了设置的时分秒计数,时间不会出错,RTC实时时钟测试成功。


图片


但是在系统复位后,发现又重新从刚开始设的时间计时,这显然不是我们想要的,所以在这里再添加备份域的读写函数。


RTC 备份寄存器 (RTC_BKPxR)


图片


RTC的备份域是在RTC基地址上偏移了0x50开始到偏移0x9c的范围。


//备份域

//注意:备份域寄存器也受DBP位保护,在未初始化RTC模块的条件下,如果要使用备份域,

//需单独开启DBP位

//num:0~19

void RTC_WriteBKPxR(u8 num,u32 dat)

{

  u32 add = 0;


  num %= 20;

  add = RTC_BASE + 0x50 + 4*num;

  *((volatile u32 *)add) = dat;

}



u32 RTC_ReadBKPxR(u8 num)

{

  u32 add = 0;


  num %= 20;

  add = RTC_BASE + 0x50 + 4*num;

  add = *((volatile u32 *)add);


  return add;

}

然后再在初始化函数中调用读取备份域函数,判断是否已经设置过时间,如果第一次设时间就写备份域的标记,再设置时间;不是第一次就直接开启计时。初始化函数更改如下:


void RTC_Init(u8 hour,u8 fen,u8 miao,u8 nian,u8 yue,u8 ri,u8 xingqi)

{

  u32 temp = 0;

  RCC- >APB1ENR  |=  1< < 28;  //开电源管理模块时钟

  PWR- >CR  |=  1< < 8;          //解除保护

  RCC- >BDCR  &=~  (1< < 2);      //调试模式下不旁路LSE

  RCC- >BDCR  |=  1< < 0;        //开启LSE振荡器

  while(!(RCC- >BDCR & 1< < 1));

  RCC- >BDCR  &=~  (0x3< < 8);

  RCC- >BDCR  |=  1< < 8;        //选择LSE为RTC时钟源

  RCC- >BDCR  |=  1< < 15;      //使能RTC模块

  RTC- >WPR  =  0xCA;          //解除写保护

  RTC- >WPR  =  0x53;          //解除写保护

  RTC- >ISR  |=  1< < 7;        //INIT位置1

  while((RTC- >ISR  &  (1< < 6))==0);      //等待进入初始化模式

  temp = RTC_ReadBKPxR(0);

  if(temp != 0xaa)

  {

    RTC_WriteBKPxR(0,0xaa);

    temp  =  0;

    temp |= (hour/10)< < 20  |  (hour%10)< < 16  |  (fen/10)< < 12  |  (fen%10)< < 8  |  (miao/10)< < 4  |  (miao%10);

    RTC- >TR  =  temp;

    temp  =  0;

    temp |= (nian/10)< < 20  |  (nian%10)< < 16  |  (xingqi)< < 13  |  (yue/10)< < 12  |  (yue%10)< < 8  |  (ri/10)< < 4  |  (ri%10);

    RTC- >DR  =  temp;

  }

  RTC- >CR  =  0;

  RTC- >ISR  &=~(1< < 7);      //退出初始化模式

  RTC- >WPR = 0;              //写任意值,再次保护

}

主函数依旧不变,运行程序发现系统复位后,时间是继续复位之前的计时,不会再被重新设置,所以在用RTC时加上备份域就可以防止系统复位造成计时不理想。


图片

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • PIC单片机控制的遥控防盗报警器电路

  • 红外线探测报警器

  • 短波AM发射器电路设计图

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 开关电源的基本组成及工作原理

  • 用NE555制作定时器

    相关电子头条文章