1.STM32F1 RTC介绍
STM32 的实时时钟( RTC)是一个独立的定时器。
STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置是在后备区域,无论器件状态如何(运行模式、低功耗模式或处于复位状态),只要保证后备区域供电正常,RTC便不会停止工作,所以通常会在后备区域供电端加一个纽扣电池,即使主电源停止供电,后备电源也会启动供电,从而保证RTC时钟不停的运行,只有当主电源和后备纽扣电池都没有电的时,RTC才停止工作。
从 RTC 的定时器特性来说,它是一个 32 位的计数器,只能向上计数。它的时钟来源有三种,分别为高速外部时钟的 128 分频( HSE/128)、 低速内部时钟 LSI 以及低速外部时钟 LSE。
但一般都是使用低速外部时钟 LSE作为时钟来源。因为只有这样主电源掉电RTC才不会停止。
LSE 通常都是32.768KHZ ,因为2的15次方刚好是32768,这样方便对这个频率进行分频。例如,2的15次方,二进制向左移一位,变成2的14次方,就是一次分频,即32768/2=16384.
2. RTC结构框图
STM32F1 RTC拥有这么多功能,是由RTC内部结构决定。要更好的理解STM32F1的RTC,就需要了解它内部的结构。如图32.1.1所示:(大家也可以查看《STM32F10x中文参考手册》-16实时时钟(RTC)章节内容
系统复位后, 默认禁止访问后备寄存器和 RTC,防止对后备区域(BKP)的意外写操作。执行以下操作使能对后备寄存器和 RTC 的访问:
(1) 设置 RCC_APB1ENR 寄存器的 PWREN 和 BKPEN 位来使能电源和后备接口时钟。
(2) 设置电源控制寄存器(PWR_CR)的 DBP 位使能对后备寄存器和 RTC 的访问。
设置后备寄存器为可访问后,在第一次通过 APB1 接口访问 RTC 时, 因为时钟频率的差异,所以必须等待 APB1 与 RTC 外设同步,确保被读取出来的 RTC 寄存器值是正确的。若在同步之后,一直没有关闭 APB1 的 RTC 外设接口,就不需要再次同步了。
如果内核要对 RTC寄存器进行任何的写操作,在内核发出写指令后, RTC模块在 3个RTC CLK 时钟之后,才开始正式的写 RTC 寄存器操作。
由于 RTC CLK 的频率比内核主频低得多,所以每次操作后必须要检查 RTC关闭操作标志位 RTOFF,当这个标志被置 1 时,写操作才正式完成。
3.STM32F1 RTC配置步骤
(RTC相关库函数在stm32f10x_rtc.c和stm32f10x_rtc.h文件中)具体配置步骤如下:
(1)使能电源时钟和后备域时钟,开启RTC后备寄存器写访问
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
PWR_BackupAccessCmd(ENABLE);//打开后备寄存器访问
(2)复位备份区域,开启外部低速振荡器
BKP_DeInit();
RCC_LSEConfig(RCC_LSE_ON);
(3)选择 RTC 时钟,并使能
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
(4)设置 RTC 的分频以及配置 RTC 时钟
RTC_EnterConfigMode();// 允许配置
RTC_ExitConfigMode();
void RTC_SetPrescaler(uint32_t PrescalerValue);
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
RTC_ITConfig(RTC_IT_SEC, ENABLE);
void RTC_SetCounter(uint32_t CounterValue);
(5)更新配置,设置 RTC 中断分组
RTC_ExitConfigMode();//退出配置模式,更新配置
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
BKP_WriteBackupRegister(BKP_DR1, 0XA0A0);
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
(6)编写RTC中断服务函数
RTC_IRQHandler
FlagStatus RTC_GetFlagStatus(uint32_t RTC_FLAG);
RTC_ClearITPendingBit(RTC_IT_SEC);
3.硬件电路
本实验使用到硬件资源如下:
(1)D1指示灯
(2)串口1
(3)RTC
RTC属于STM32F1芯片内部的资源,只要通过软件配置好即可使用。
D1指示灯用来提示系统运行状态。串口1将读取的RTC时间日期信息打印出来。
这里需要注意RTC 不能断电,否则时间数据将会丢失,如果想让时间在断电后还可以继续走,那么必须确保开发板上的纽扣电池有电。
4.编写RTC控制程序
本实验所要实现的功能是:设置RTC时间日期初值,在RTC秒中断内使用串口打印出RTC日期和时间,D1指示灯闪烁提示系统运行。
程序框架如下:
(1)初始化RTC,设置RTC时间日期初值
(2)开启RTC的秒中断,编写RTC中断函数,
(3)在RTC中断内更新时间并打印输出
(4)编写主函数
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "rtc.h"
int main()
{
u8 i=0;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级分组
LED_Init();
USART1_Init(9600);
RTC_Init();
while(1)
{
i++;
if(i%20 ==0)
{
led1=!led1;//LED1闪,用来指示主程序循环是否运行
delay_ms(300);
}
}
}
rtc.c
#include "rtc.h"
#include "SysTick.h"
#include "system.h"
#include "usart.h"
_calendar calendar;
void RTC_NVIC_Confing()//RTC中断优先级配置函数
{
NVIC_InitTypeDef NVIC_InitStructure;
//设置中断优先级,使能中断通道
NVIC_InitStructure.NVIC_IRQChannel= RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void RTC_Get()
{
u32 timedata =0;
timedata=RTC_GetCounter();
calendar.hour=timedata/3600;
calendar.min=(timedata%3600)/60;
calendar.sec=timedata%60;
}
//初回1:初始化失败
//初回0:初始化成功
u8 RTC_Init() //有返回值是因为需要判断初始化是否成功
{
u8 temp =0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能电源时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//使能后备域时钟
PWR_BackupAccessCmd(ENABLE);//打开后备寄存器访问
if(BKP_ReadBackupRegister(BKP_DR1)!=0xA0A0)//后备寄存器有42个,都可以用来存放后备数据,这里选用第1个
{
BKP_DeInit();//复位备份区域,
RCC_LSEConfig(RCC_LSE_ON);//开启外部低速振荡器
while((RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)&&(temp<250)) //等待外部低速振荡ready
{
temp++;
delay_ms(10);
}
if(temp>=250)
{
return 1;
}
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//配置RTCC的时钟源为LSE
RCC_RTCCLKCmd(ENABLE);
RTC_WaitForLastTask();//等待写操作完成
RTC_WaitForSynchro();//等待时钟同步
RTC_ITConfig(RTC_IT_SEC,ENABLE);//配置中断类型为秒中断并开启
RTC_WaitForLastTask();//等待写操作完成
RTC_EnterConfigMode();// 允许配置
RTC_SetPrescaler(32767);
RTC_WaitForLastTask();//等待写操作完成
RTC_SetCounter(0xf73f);//初始化时间初值为17:34:55,计算方法是全部计算成秒,即17*3600+34*60+55=0xf73f
RTC_ExitConfigMode();
BKP_WriteBackupRegister(BKP_DR1, 0xA0A0);//这样第二次开机时就不会进入上面的初始化了
}
else //第二次开机时走这里
{
RTC_WaitForSynchro();//等待时钟同步
RTC_ITConfig(RTC_IT_SEC,ENABLE);//配置中断类型为秒中断并开启
RTC_WaitForLastTask();//等待写操作完成
}
RTC_NVIC_Confing();
RTC_Get();
return 0;
}
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC)!=RESET)//判断秒中断是否产生,如产生执行下面的语句
{
RTC_Get();
printf("RTC Time:%d:%d:%d\r\n",calendar.hour,calendar.min,calendar.sec);
}
RTC_ClearITPendingBit(RTC_IT_SEC);//清除中断状态标志
}
程序烧写到开发板,实验结果如下,实验是成功的。