本章所要实现的功能是:设置 RTC 时间日期初值,在 RTC 秒中断内使用串口 打印出 RTC 日期和时间,RTC闹钟时间通过串口打印,并通过蜂鸣器响应,
DS0 指示灯闪烁提示系统运行。程序框架如下: (1)初始化 RTC,设置 RTC 时间日期初值 (2)开启 RTC 的秒中断,编写 RTC 中断函数 (3)在 RTC 中断内更新时间并打印输出 (4)编写主函数
(1)使能电源时钟和后备域时钟,开启 RTC 后备寄存器写访问 要访问 RTC 和 RTC 备份区域就必须先使能电源及后备域时钟,然后使能 RTC 后备区域访问。电源时钟使能,通过 RCC_APB1ENR 寄存器来设置;RTC 及 RTC 备份寄存器的写访问,通过 PWR_CR 寄存器的 DBP 位设置。调用库函数为: RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//打开电源时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//打开 RTC 后备 域时钟 PWR_BackupAccessCmd(ENABLE);//打开后备寄存器访问 (2)复位备份区域,开启外部低速振荡器 在取消备份区域写保护之后,我们可以先对这个区域复位,以清除前面的设 置,当然这个操作不要每次都执行,因为备份区域的复位将导致之前存在的数据 丢失,所以要不要复位,要视情况而定。然后我们使能外部低速振荡器,注意这 里一般要先判断 RCC_BDCR 的 LSERDY 位来确定低速振荡器已经就绪了才开始 下面的操作。备份区域复位的库函数为: BKP_DeInit(); //复位备份区域 开启外部低速振荡器的函数是: RCC_LSEConfig(RCC_LSE_ON);//开启外部 32.768K RTC 时钟 (3)选择 RTC 时钟,并使能 选择 LSE 为 RTC 时钟源库函数是: RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟 使能 RTC 时钟库函数是: RCC_RTCCLKCmd(ENABLE);//使能 RTC 时钟 (4)设置 RTC 的分频以及配置 RTC 时钟 在开启了 RTC 时钟之后,我们要做的就是设置 RTC 时钟的分频数,通过 RTC_PRLH 和 RTC_PRLL 来设置,然后等待 RTC 寄存器操作完成,并同步之后, 设置秒钟中断。然后设置 RTC 的允许配置位( RTC_CRH 的 CNF 位),设置时 间(其实就是设置 RTC_CNTH 和 RTC_CNTL 两个寄存器)。 在进行 RTC 配置之前首先要打开允许配置位(CNF),调用的库函数是: RTC_EnterConfigMode();// 允许配置 在配置完成之后,千万不要忘记更新配置同时退出配置模式,调用的库函数 是: RTC_ExitConfigMode();//退出配置模式,更新配置 设置 RTC 时钟分频数,调用的库函数是: void RTC_SetPrescaler(uint32_t PrescalerValue); 这个函数只有一个参数,就是 RTC 时钟的分频数,很好理解。 然后是设置秒中断允许,RTC 使能中断的函数是: void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState); 函数的第一个参数用来选择 RTC 的中断类型,可通过库文件的头文件查看, 第二个参数用于使能还是失能。比如要使能 RTC 秒中断,如下: RTC_ITConfig(RTC_IT_SEC, ENABLE); 接下来便是设置时间了,设置时间实际上就是设置 RTC 的计数值,时间与 计数值之间是需要换算的。库函数中设置 RTC 计数值的方法是: void RTC_SetCounter(uint32_t CounterValue); (5)更新配置,设置 RTC 中断分组 在设置完时钟之后,我们将配置更新同时退出配置模式,这里还是通过 RTC_CRH 的 CNF 来实现。 调用库函数的方法是: RTC_ExitConfigMode();//退出配置模式,更新配置 在退出配置模式更新配置之后我们在备份区域 BKP_DR1 中写入 0XA0A0 代 表我们已经初始化过时钟了,下次开机(或复位)的时候,先读取 BKP_DR1 的 值,然后判断是否是 0XA0A0 来决定是不是要配置。接着我们配置 RTC 的秒钟 中断,并进行分组。 往备份区域写用户数据的函数是: void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data); 函数的第一个参数用来设置备份寄存器的标号,这个在 rtc 库文件头文件内 有定义,第二个参数是我们往备份寄存器写入的数据。比如我们向 BKP_DR1 中 写入 0XA0A0。函数如下: BKP_WriteBackupRegister(BKP_DR1, 0XA0A0); 同样库函数还提供一个读取备份寄存器内容的函数,如下: uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR); 函数参数作用和写备份寄存器是一样的功能,这个很好理解。 使能中断后,就需要设置 RTC 的中断优先级,即调用 NVIC_Init 函数初始化, 这个在前面很多章节中都介绍过,这里不多说 (6)编写 RTC 中断服务函数 前面步骤中我们配置好了 RTC 的秒中断,所以我们还需要编写对应的中断服 务函数。RTC 中断服务函数名在 STM32F1 启动文件内可以查找到,RTC 中断函数 名如下: RTC_IRQHandler 因为 RTC 的中断类型有很多,所以进入中断后,我们需要在中断服务函数开 头处通过读取 RTC 状态寄存器的值判断此次中断是哪种类型,然后做出相应的控 制。库函数中用来读取 RTC 状态标志位的函数如下: FlagStatus RTC_GetFlagStatus(uint32_t RTC_FLAG); 参数 RTC_FLAG 用来选择 RTC 状态标志,参数选择如下: 秒中断标志参数为 RTC_IT_SEC。在秒钟中断产生的时候,读取当前的时间 值。在中断函数结束之前我们会清除下对应的中断标志。 清除 RTC 秒中断标志函数如下: RTC_ClearITPendingBit(RTC_IT_SEC); 将以上几步全部配置好后,我们就可以正常使用 RTC 中断来更新时间了
#ifndef _rtc_H
#define _rtc_H
#include "system.h"
//时间结构体
typedef struct
{
u8 hour;
u8 min;
u8 sec;
//公历日月年周
u16 w_year;
u8 w_month;
u8 w_date;
u8 week;
}_calendar;
extern _calendar calendar; //日历结构体
u8 RTC_Init(void); //初始化RTC,返回0,失败;1,成功;
u8 Is_Leap_Year(u16 year);//平年,闰年判断
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
u8 RTC_Get(void); //更新时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间
#endif
#include "rtc.h"
#include "SysTick.h"
#include "usart.h"
#include "stdio.h"
#include "beep.h"
_calendar calendar;//时钟结构体
static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
/*******************************************************************************
* 函 数 名 : RTC_Init
* 函数功能 : RTC初始化
* 输 入 : 无
* 输 出 : 0,初始化成功
1,LSE开启失败
*******************************************************************************/
u8 RTC_Init(void)
{
//检查是不是第一次配置时钟
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR , ENABLE);//使能PWR和BKP外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP , ENABLE);//使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if(calendar.w_year<2023)BKP_WriteBackupRegister(BKP_DR1, 0);
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA0A0) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1;//初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode();// 允许配置
RTC_SetPrescaler(32767); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_Set(2023,1,14,17,20,55); //设置时间
RTC_Alarm_Set(2023,1,14,17,21,55);//设置闹钟时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0XA0A0); //向指定的后备寄存器中写入用户程序数据
}
else//系统继续计时
{
RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
RTC_NVIC_Config();//RCT中断分组设置
RTC_Get();//更新时间
return 0; //ok
}
//RTC时钟中断
//每秒触发一次
//extern u16 tcnt;
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
{
RTC_Get();//更新时间
printf("RTC Time:%d-%d-%d %d:%d:%d\r\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
RTC_Get(); //更新时间
printf("Alarm Time:%d-%d-%d %d:%d:%d\r\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间
BEEP=1;
}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断
RTC_WaitForLastTask();
}
//判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{
if(year%4==0) //必须能被4整除
{
if(year%100==0)
{
if(year%400==0)return 1;//如果以00结尾,还要能被400整除
else return 0;
}else return 1;
}else return 0;
}
//月份数据表
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
/*******************************************************************************
* 函 数 名 : RTC_Set
* 函数功能 : RTC设置日期时间函数(以1970年1月1日为基准,把输入的时钟转换为秒钟)
1970~2099年为合法年份
* 输 入 : syear:年 smon:月 sday:日
hour:时 min:分 sec:秒
* 输 出 : 0,成功
1,失败
*******************************************************************************/
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t2099)return 1;
for(t=1970;t=365)
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=366)temp-=366;//闰年的秒钟数
else {temp1++;break;}
}
else temp-=365; //平年
temp1++;
}
calendar.w_year=temp1;//得到年份
temp1=0;
while(temp>=28)//超过了一个月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
{
if(temp>=29)temp-=29;//闰年的秒钟数
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
else break;
}
temp1++;
}
calendar.w_month=temp1+1; //得到月份
calendar.w_date=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
calendar.hour=temp/3600; //小时
calendar.min=(temp%3600)/60; //分钟
calendar.sec=(temp%3600)%60; //秒钟
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期
return 0;
}
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 yearH,yearL;
yearH=year/100; yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7);
}
#include "stm32f10x.h"
#include "rtc.h"
#include "usart.h"
#include "led.h"
#include "beep.h"
#include "SysTick.h"
int main()
{
u8 i=0;
u8 key=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置优先级分组
SysTick_Init(72);
LED_Init();
BEEP_Init();
USART1_Init(115200);//波特率115200
if(RTC_Init())
{
printf("RTC初始化失败");
}
else
printf("RTC初始化成功");
while(1)
{
i++;
if(i%20==0)LED1=!LED1;
delay_ms(10);
}
}