目录
一、什么是RTC
二、如何配置RTC
1、标准实时时钟部分(万年历部分)
1.1 时钟源分类
1.2 RTC时钟源的选择
1.3 精密校正
1.4 异步7位预分频器
1.5 粗略校正
1.6 同步15位分频
1.7 日历寄存器
1.8 RTC的初始化与配置
1.9 程序设计
2、闹钟部分
2.1 闹钟的初始化与配置
2.2 程序设计
3、周期性自动唤醒部分
3.1 唤醒的初始化与配置
3.2 程序设计
三、具体使用RTC
需求1:利用RTC设计日历
需求2:利用RTC设计闹钟
需求3:周期性自动唤醒获取数据
一、什么是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中断使能
}
闹钟中断服务函数
闹钟中断服务函数
{
//判断EXTI触发的中断信号
{
//清除EXTI中断标志位--->触发条件:闹钟标志位出现上升沿
//清除闹钟中断标志位(此位是中断标志位)--->触发条件:到达预设的数值时便会触发
中断
//执行紧急事件
}
}
不清除EXTI中断标志位则会一直执行闹钟中断服务函数;而不清除闹钟中断标志位,则不会再次进入执行闹钟中断服务函数。为什么?首先时间/日期寄存器( RTC_TR 和 RTC_DR)与闹钟寄存器 (RTC_ALRMAR) 匹配 时,RTC的闹钟标志位由0到1(上升沿跳变),而EXTI会实时监控ISR的变化,当检测到ISR由0到1(上升沿跳变)时就会触发NVIC。进入中断后清除EXTI中断标志位(以便下次再次进入),不清除闹钟标志位还是1,这时候当下一次时间匹配时,EXTI检测不到ISR由0到1(上升沿跳变)就无法再次触发NVIC。
3、周期性自动唤醒部分
3.1 唤醒的初始化与配置
总结:
- 禁止唤醒
- 轮询RTC_ISR的WUTW是否置1
- 配置时钟
- 配置重装载值
- 使能唤醒
- 配置EXTI控制器
- 使能唤醒中断
- 清除唤醒中断标志位
- 配置NVIC控制器
3.2 程序设计
唤醒初始化函数
{
/*解除RTC寄存器写保护*/
//禁止唤醒定时器
//等待允许更新唤醒定时器
/*时钟源配置*/
//配置唤醒自动重载值
//使能唤醒定时器
/*激活RTC寄存器写保护*/
/*EXTI控制器配置*/
/*NVIC控制器配置*/
//唤醒定时器中断使能
//清除中断标志位
}
唤醒中断服务函数
{
//判断EXTI触发的中断信号
{
//清除EXTI中断标志位-->触发条件:唤醒标志位出现上升沿
//清除唤醒标志位-->触发条件:计完数后便会触发
//紧急事件
}
}
总结:
不清除EXTI中断标志位则会一直执行唤醒中断服务函数;而不清除唤醒中断标志位,则不会再次进入执行唤醒中断服务函数。为什么?首先唤醒重载值递减到0时,RTC的唤醒标志位由0到1(上升沿跳变),而EXTI会实时监控ISR的变化,当检测到ISR由0到1(上升沿跳变)时就会触发NVIC。进入中断后清除EXTI中断标志位(以便下次再次进入),如果不清除闹钟标志位还是1,这时候下一次计完数时,EXTI检测不到ISR由0到1(上升沿跳变)就无法再次触发NVIC。
注意:使能唤醒中断后,需要清除一下中断标志位,因为在唤醒初始化中当使能唤醒定时器后,计数完成,中断标志位置1。若不清除一下中断标志位,则无法触发EXTI中断。
三、具体使用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");
}
}
需求3:周期性自动唤醒获取数据
唤醒初始化函数:
/*
函数名: wuck_init
函数功能:唤醒初始化
返回值:void
形参:void
函数说明:
*/
void wuck_init(u16 ms)
{
//解除RTC寄存器写保护
RTC->WPR = 0xca;
RTC->WPR = 0x53;
//禁止唤醒定时器
RTC->CR &= ~(1 << 10);
while(!(RTC->ISR & (1 << 2)));//允许更新唤醒定时器
/*时钟源配置*/
RCC->BDCR |= (1 << 0);//使能外部低速振荡器 0位
RCC->BDCR |= (1 << 8);//选择外部低速时钟 8:9位
RCC->BDCR |= (1 << 15);//使能RTC时钟 15位
RTC->WUTR = ms;//唤醒自动重载值
//使能唤醒定时器
RTC->CR |= (1 << 10);
//激活RTC寄存器写保护
RTC->WPR = 0xff;
/*EXTI控制器配置*/
//系统配置控制器使能(APB2)
RCC->APB2ENR |= 1 << 14;
//检测边沿
EXTI->RTSR |= 1 << 22;
//使能外部中断
EXTI->IMR |= 1 << 22;
/*NVIC控制器配置*/
RTC->CR |= (1 << 14);//唤醒定时器中断使能
RTC->ISR &= ~(1 << 10);//清除中断标志位
u32 pri = NVIC_EncodePriority (5, 2, 1);//计算优先级编码值,设置抢占和响应的级别值
NVIC_SetPriority(RTC_WKUP_IRQn, pri);//设置具体某个中断源的优先级
NVIC_EnableIRQ(RTC_WKUP_IRQn);//中断信号响应通道使能
}
唤醒中断服务函数:
/*
函数名:RTC_WKUP_IRQHandler
函数功能:RTC唤醒中断服务函数
返回值:void
形参:void
函数说明:
*/
void RTC_WKUP_IRQHandler(void)
{
//判断EXTI22触发的中断信号
if(EXTI->PR & (1 << 22))
{
//清除标志位
EXTI->PR |= 1 << 22;
//清除闹钟标志位
RTC->ISR &= ~(1 << 10);
//紧急事件
printf("启动!\r\n");
}
}