目录
一、什么是RTC
RTC:
RTC能够持续记录当前的时间,包括年、月、日、时、分、秒等,自动计算闰年,能区分每个月的天数甚至可能包括星期几和时区信息
RTC框图:
二、如何配置RTC
1、标准实时时钟部分(万年历部分)
1.1 时钟源分类
1.2 RTC时钟源的选择
①LSE(32.768KHZ):外部低速时钟 分频=同步*异步=256*128
//通常使用 外部低速时钟有独立备份电源可以让RTC独立运行
如何配置外部低速时钟源提供给RTC?
选择外部低速时钟(备份时钟)就找到RCC备份域控制寄存器相应的位
RCC备份域控制寄存器:
使能外部低速振荡器 0位
等待外部低速振荡器就绪 1位
选择外部低速时钟 8:9位
②LSI(32KHZ):内部低速时钟 分频=同步*异步=250*128
选择内部低速时钟(备份时钟)就找到RCC备份域控制寄存器、RCC控制和状态寄存器相应的位
RCC控制和状态寄存器:
使能内低速振荡器 0位
等待内部低速振荡器就绪 1位
RCC备份域控制寄存器:
选择内部低速时钟 8:9位
1.3 精密校正
不做高精度需求是不需要此寄存器
1.4 异步7位预分频器
128分频
1.5 粗略校正
不需要
1.6 同步15位分频
默认256分频 //1HZ 1次/s
1.7 日历寄存器
设置日期时间
获取日期时间
1.8 RTC的初始化与配置
总结:
①将电源控制寄存器(PWR->CR)的DBP位写1 -->解除RTC控制器和RTC备份寄存器的保护
配置这个寄存器之前,要使能电源控制器时钟 RCC->APB1
②往RTC_WPR 中写 0xca、再写 0x53--->取消所有RTC寄存器的写保护
往RTC_WPR寄存器中随便写一个数据就会再次激活写保护
总结:
1、使能电源控制器(PWR)
2、使能备份域的访问
3、选择RTC时钟源
4、使能RTC时钟
设置日历寄存器的要求
总结:
要想改变日历寄存器的时间的值或者分频值,需要让日历进入初始化模式
(日历停止工作)
更改完以后,要想日历继续工作,需要退出初始化模式(自由模式)
0、取消写保护
1、进入初始化模式---> RTC->ISR 寄存器的INIT位写1
2、检测是否允许更新(改变)日历值位--->轮询RTC->ISR 寄存器的INITF位是否置1
3、分频--->同步、异步
4、设置日期和时间寄存器
5、退出初始化模式---> RTC->ISR 寄存器的INIT位写0
6、激活写保护
读取日历时间和日期
要想读时间和日期,要等待时间和日期同步(加载到影子寄存器)
如何等待时间和日期同步到影子寄存器
ISR寄存器中RSF位要先清零,然后等待置1,读时间或者日期值.
注意:在使用RTC寄存器之前需要进行取消写保护
1.9 程序设计
初始配置RTC:
初始化配置函数
{
//使能电源控制器的时钟
/*解除RTC控制器和备份域控制器寄存器的写保护*/
/*配置时钟源*/
//使能外部低速时钟
//等待外部低速时钟使能
//选择外部低速时钟作为RTC时钟
//使能RTC
/*解除RTC寄存器的写保护*/
//配置RTC相关寄存器
//CR
/*激活RTC寄存器的写保护*/
//设置初始时间
}
设置时间
设置时间和日期函数
{
//解除RTC寄存器的写保护
//进入日历初始化模式
//等待进入初始化模式完成
/*把十进制日期值转换成BCD码形式*/
//设置日期到DR寄存器
/*把十进制时间值转换成BCD码形式*/
//设置时间到TR寄存器
//退出日历初始化模式
//激活RTC寄存器的写保护
}
十进制Dec转BCD码:
BCD=(Dec/10)<<4 | (Dec%10)
获取时间
获取日期和时间函数
{
//解除RTC寄存器的写保护
将同步标志位清零
等待同步标志位置1
读日期寄存器的值
/*把BCD码形式转换成十进制日期值*/
将同步标志位清零
等待同步标志位置1
读时间寄存器的值
/*把BCD码形式转换成十进制时间值*/
//激活RTC寄存器的写保护
}
BCD码转十进制Dec:
Dec=(BCD>>4)*10+(BCD&0x0f)
2、闹钟部分
2.1 闹钟的初始化与配置
总结:
1、禁止闹钟
2、轮询RTC_ISR的ALRAWF(闹钟A)\ALRBWF(闹钟B)位是否置1
3、配置闹钟寄存器
4、使能闹钟
5、配置EXT控制器
6、使能闹钟中断
7、配置NVIC控制器
2.2 程序设计
设置RTC闹钟初始化函数
{
/*解除RTC寄存器写保护*/
/*闹钟初始化*/
//禁止闹钟运行
//等待更新闹钟
/*配置闹钟*/
//设置掩码
//获取日期BCD码
//使能闹钟
//激活RTC寄存器写保护
/*EXTI控制器配置*/
//系统配置控制器使能(APB2)
//检测边沿
//使能外部中断
/*NVIC控制器配置*/
//闹钟A中断使能
}
闹钟中断服务函数
{
//判断EXTI17触发的中断信号
{
//清除EXTI中断标志位
//清除闹钟标志位(此位不是中断标志位)
//执行紧急事件
}
}
不清除EXTI中断标志位则会一直执行闹钟中断服务函数;而不清除闹钟标志位,则不会进入执行闹钟中断服务函数。为什么?首先时间/日期寄存器( RTC_TR 和 RTC_DR)与闹钟寄存器 (RTC_ALRMAR) 匹配 时,RTC的闹钟标志位由0到1(上升沿跳变),而EXTI会实时监控ISR的变化,当检测到ISR由0到1(上升沿跳变)时就会触发NVIC。进入中断后清除EXTI中断标志位(以便下次再次进入),不清除闹钟标志位还是1,这时候当下一次时间匹配时,EXTI检测不到ISR由0到1(上升沿跳变)就无法再次触发NVIC。
三、具体使用RTC
需求1:利用RTC设计日历
RTC初始化函数:
/*
函数名: rtc_init
函数功能:RTC初始化
返回值:void
形参:void
函数说明:
*/
void rtc_init(RTC_t date_time)
{
/*解除RTC和相关寄存器保护*/
RCC->APB1ENR |= (1 << 28);//使能电源控制器时钟
PWR->CR |= (1 << 8);//解除RTC控制器控制
/*时钟源配置*/
RCC->BDCR |= (1 << 0);//使能外部低速振荡器 0位
while(!(RCC->BDCR & (1 << 1)));//等待外部低速振荡器就绪 1位
RCC->BDCR |= (1 << 8);//选择外部低速时钟 8:9位
RCC->BDCR |= (1 << 15);//使能RTC时钟 15位
/*解除RTC寄存器的写保护*/
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
/*配置相关寄存器*/
//CR
RTC->CR &= ~(1 << 6);//24小时制
RTC->CR &= ~(1 << 5);//影子寄存器
/*激活RTC寄存器的写保护*/
RTC->WPR = 0xff;
/*设置初始时间*/
if(RTC->BKP0R != 0xff)
{
set_date_time(date_time);
RTC->BKP0R = 0xff;
}
}
设置初始时间的相关内容:
RTC备份寄存器和备份电源
没有备份电源,有RTC备份寄存器:只能做到复位后,时间继续走;断电后重新上电后时间会重置
如何做到复位后时间继续走?
正常情况下,在不使用RTC备份寄存器时,复位后时间将会被重置;而在使用备份寄存器后,
在RTC初始化函数里首次执行设置初始时间的地方做个标志(相当于”到此一游”),如果下次复位后,运行到此标记时,则不会重新设置初始时间。说白了,就是利用这个标志绕过初始化重新设置时间。
如何做到断电后时间不会被重置?
需要备份电源(纽扣电池)向RTC供电
总结:
备份电源作用是:
芯片断电可以让RTC继续工作,同时备份寄存器不复位
RTC备份寄存器的作用:
防止芯片复位后,CUP重置初始时间(执行RTC初始化函数-->重新设定原来的时间)
设置时间和日期:
/*
函数名: set_date_time
函数功能:设置日期和时间
返回值:void
形参:void
函数说明:
*/
void set_date_time(RTC_t date_time)
{
//解除RTC寄存器的写保护
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
//进入日历初始化模式
RTC->ISR |= (1 << 7);
//等待进入初始化模式完成
while(!(RTC->ISR & (1 << 6)));
//把十进制日期值转换成BCD码形式
//设置日期到DR寄存器
RTC->DR = in_dec_out_bcd(date_time.year-2000)<<16 |
in_dec_out_bcd(date_time.week)<<13 |
in_dec_out_bcd(date_time.mon)<<8 |
in_dec_out_bcd(date_time.day);
//把十进制时间值转换成BCD码形式
//设置时间到TR寄存器
RTC->TR = in_dec_out_bcd(date_time.hour)<<16 |
in_dec_out_bcd(date_time.min )<<8 |
in_dec_out_bcd(date_time.sec );
//退出日历初始化模式
RTC->ISR &= ~(1 << 7);
//激活RTC寄存器的写保护
RTC->WPR = 0xff;
}
十进制日期值转换成BCD码形式函数:
/*
函数名: in_dec_out_bcd
函数功能:十进制日期值转换成BCD码形式
返回值:void
形参:u8 dec
函数说明:
*/
u8 in_dec_out_bcd(u8 dec)
{
return (dec/10)<<4 | (dec%10);
}
获取日期和时间函数:
/*
函数名: get_date_time
函数功能:获取日期和时间
返回值:void
形参:void
函数说明:
*/
RTC_t get_date_or_time(void)
{
u32 d_temp;
u32 t_temp;
RTC_t get_date_time;
//解除RTC寄存器的写保护
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
//将同步标志位清零
RTC->ISR &= ~(1 << 5);
//等待同步标志位置1
while(!(RTC->ISR & (1 << 5)));
//读日期寄存器的值
d_temp = RTC->DR;
//把BCD码形式转换成十进制日期值
get_date_time.year = in_bcd_out_dec(d_temp>>16)+2000;
get_date_time.week = in_bcd_out_dec((d_temp>>13) & 0x07);
get_date_time.mon = in_bcd_out_dec(d_temp>>8& 0x1f);
get_date_time.day = in_bcd_out_dec(d_temp);
//将同步标志位清零
RTC->ISR &= ~(1 << 5);
//等待同步标志位置1
while(!(RTC->ISR & (1 << 5)));
//读时间寄存器的值
t_temp = RTC->TR;
//把BCD码形式转换成十进制时间值
get_date_time.hour = in_bcd_out_dec(t_temp>>16);
get_date_time.min = in_bcd_out_dec(t_temp>>8);
get_date_time.sec = in_bcd_out_dec(t_temp);
//激活RTC寄存器的写保护
RTC->WPR = 0xff;
return get_date_time;
}
需求2:利用RTC设计闹钟
设置RTC闹钟初始化函数:
/*
函数名: alr_init
函数功能:设置RTC闹钟初始化
返回值:void
形参:void
函数说明:
*/
RTC_t alr = {2024,9,26,4,19,41,5};
void alr_init(RTC_t alr)
{
//解除RTC寄存器写保护
RTC->WPR = 0xca;
RTC->WPR = 0x53;
/*设置闹钟*/
//禁止闹钟运行
RTC->CR &= ~(1<<8);
while(!(RTC->ISR & (1 << 0)));//等待更新闹钟A
//日期、时分秒比较
//RTC->ALRMAR |= ;//日期掩码
//RTC->ALRMAR |= ;//小时掩码
//RTC->ALRMAR |= ;//分钟掩码
RTC->ALRMAR &= ~(1 << 7);//秒掩码
RTC->ALRMAR &= ~(1 << 30);//日期个位
RTC->ALRMAR &= ~(1 << 22);//24小时
//获取日期BCD码
RTC->ALRMAR = in_dec_out_bcd(alr.day)<<24 |
in_dec_out_bcd(alr.hour)<<16|
in_dec_out_bcd(alr.min)<<8 |
in_dec_out_bcd(alr.sec);
//设置掩码
RTC->ALRMAR |= (1 << 31) | (1 << 23) | (1 << 15);
//设置闹钟
RTC->CR |= (1 << 8);//使能闹钟A
//激活RTC寄存器写保护
RTC->WPR = 0xff;
/*EXTI控制器配置*/
//系统配置控制器使能(APB2)
RCC->APB2ENR |= 1 << 14;
//检测边沿 上升沿:EXTI->RTSR |= 1<<x 下降沿:EXTI->FTSR |= 1<<x
EXTI->RTSR |= 1 << 17;
//使能外部中断 EXTI->IMR |= 1<<x
EXTI->IMR |= 1 << 17;
/*NVIC控制器配置*/
RTC->CR |= (1 << 12);//闹钟A中断使能
u32 pri = NVIC_EncodePriority (5, 1, 1);//计算优先级编码值,设置抢占和响应的级别值
NVIC_SetPriority(RTC_Alarm_IRQn, pri);//设置具体某个中断源的优先级
NVIC_EnableIRQ(RTC_Alarm_IRQn);//中断信号响应通道使能
}
RTC闹钟中断服务函数:
/*
函数名:RTC_Alarm_IRQHandler
函数功能:RTC中断服务函数
返回值:void
形参:void
函数说明:
*/
void RTC_Alarm_IRQHandler(void)
{
//判断EXTI17触发的中断信号
if(EXTI->PR & (1 << 17))
{
//清除标志位
EXTI->PR |= 1 << 17;
//清除闹钟标志位
RTC->ISR &= ~(1 << 8);
//紧急事件
printf("响铃l!\r\n");
}
}