本帖最后由 春娇霹雳娃 于 2023-7-31 10:52 编辑
#申请原创# @21小跑堂
1.简介
RTC 模块是用于提供时间(时、分、秒、亚秒)和日期(年、月、日)功能的定时计数器,日历以 BCD 码的格式显示。RTC内部存在实时日历计数器,默认情况下,每隔2个RTC时钟周期,会将实时的日历计数复制到影子寄存器。其设计框图如下图所示:
MM32L0130使用高性能的 Arm® Cortex-M0+ 的 32 位微控制器,最高工作频率可达 48MHz,内置高速存储器,丰富的增强型 I/O 端口和多种外设。其开发板硬件外观如下图所示:
本实验实现以下功能:
- 可编程的日历功能,包括小时(12/24 小时制) 、分钟、秒钟、星期、日期、月份、年份
- 可编程闹钟,任意日历字段的组合(日/星期、时、分、秒、亚秒)触发闹钟,支持闹钟中断,闹钟响应时间可配置
- 支持周期性循环唤醒中断,周期性唤醒时间可配置
- 支持入侵检测,当产生入侵事件,可通过时间戳获取入侵时间
2.实验环境
硬件环境:
- EV Board (MM32L0136C7P)
- Jlink v11.0
软件环境:
3.软件配置
时钟配置
RTC可选择时钟源LSE、LSI、HSE128分频。RTC内部包含2个预分频器:7 位的异步预分频器与15位的同步预分频器,用于提供日历或其它功能的时钟。
void CLOCK_RTCToLSE(void){
RCC->BDCR |= RCC_BDCR_DBP_MASK;
RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_RTC, true);
/* Reset RTC BKP. */
RCC_ResetRTCClock();
RCC->BDCR &= ~RCC_BDCR_RTCRST_MASK;
RCC_EnableBKPWriteProtect(false);
/* Enable LSE clock source. */
RCC->BDCR |= (RCC_BDCR_LSEON_MASK | RCC_BDCR_RTCSEL(1u));
while (0u == (RCC->BDCR & RCC_BDCR_LSERDY_MASK) ){}
RCC_EnableRTCClock(true);}
配置影子寄存器
/* Get current time. */
void RTC_GetTime(RTC_Type * RTCx, RTC_Datetime_Type * time){
uint32_t ss = RTCx->SSR;
uint32_t temp1 = RTCx->TR;
uint32_t temp2 = RTCx->DR;
time->SubSecs = ( ss & RTC_SSR_SS_MASK);
time->Secs = ( (temp1 & ( RTC_TR_ST_MASK | RTC_TR_SU_MASK) ) >> RTC_TR_SU_SHIFT );
time->Mins = ( (temp1 & ( RTC_TR_MNU_MASK | RTC_TR_MNT_MASK) ) >> RTC_TR_MNU_SHIFT );
time->Hours = ( (temp1 & ( RTC_TR_HU_MASK | RTC_TR_HT_MASK) ) >> RTC_TR_HU_SHIFT );
time->Week = ( (temp2 & RTC_DR_WDU_MASK ) >> RTC_DR_WDU_SHIFT );
time->Days = ( (temp2 & ( RTC_DR_DU_MASK | RTC_DR_DT_MASK) ) >> RTC_DR_DU_SHIFT );
time->Months = ( (temp2 & ( RTC_DR_MU_MASK | RTC_DR_MT_MASK) ) >> RTC_DR_MU_SHIFT );
time->Years = ( (temp2 & ( RTC_DR_YU_MASK | RTC_DR_YT_MASK) ) >> RTC_DR_YU_SHIFT );}
RTC初始化
- 将RTC的同步预分频、异步预分频及时间为12/24小时制的配置放入RTC_Init()中。
- 初始化前需要配置RTC_ISR寄存器的INIT位为1,等待INITF位硬件置1后才能进行数据写入。
- 在进行完初始化及初始时间配置后,清除INIT位,退出初始化模式。
/* Start initialization. */
RTCx->ISR |= RTC_ISR_INIT_MASK;
while ( ( 0u == (RTCx->ISR & RTC_ISR_INITF_MASK) ) && (init->TimeOut > 0u) ) /* Prevent the infinite loop of driver. */{
init->TimeOut--;}
时间配置
- RTC_TR时间寄存器配置RTC的时分秒,按个位与十位分别配置
- RTC_DR日期寄存器,年月日以个位与十位分开的形式配置
- 数据以BCD码格式保存,即用4位二进制数来表示1位十进制数中的0~9这10个数
- 时间计数分12小时制与24小时制,由RTC_CR寄存器的FMT位控制
- RTC存在内部计数器,每2个RTC周期,将数值存放入时间寄存器与日期寄存器
/* Get current time. */
void RTC_GetTime(RTC_Type * RTCx, RTC_Datetime_Type * time){
uint32_t ss = RTCx->SSR;
uint32_t temp1 = RTCx->TR;
uint32_t temp2 = RTCx->DR;
time->SubSecs = ( ss & RTC_SSR_SS_MASK);
time->Secs = ( (temp1 & ( RTC_TR_ST_MASK | RTC_TR_SU_MASK) ) >> RTC_TR_SU_SHIFT );
time->Mins = ( (temp1 & ( RTC_TR_MNU_MASK | RTC_TR_MNT_MASK) ) >> RTC_TR_MNU_SHIFT );
time->Hours = ( (temp1 & ( RTC_TR_HU_MASK | RTC_TR_HT_MASK) ) >> RTC_TR_HU_SHIFT );
time->Week = ( (temp2 & RTC_DR_WDU_MASK ) >> RTC_DR_WDU_SHIFT );
time->Days = ( (temp2 & ( RTC_DR_DU_MASK | RTC_DR_DT_MASK) ) >> RTC_DR_DU_SHIFT );
time->Months = ( (temp2 & ( RTC_DR_MU_MASK | RTC_DR_MT_MASK) ) >> RTC_DR_MU_SHIFT );
time->Years = ( (temp2 & ( RTC_DR_YU_MASK | RTC_DR_YT_MASK) ) >> RTC_DR_YU_SHIFT );}
读取时间
若要读取RTC_TR、RTC_DR、RTC_SSR寄存器,需等待RTC_ISR寄存器的RSF位置1后,再读取寄存器。(等待寄存器同步)
/* Get current time. */
void RTC_GetTime(RTC_Type * RTCx, RTC_Datetime_Type * time){
uint32_t ss = RTCx->SSR;
uint32_t temp1 = RTCx->TR;
uint32_t temp2 = RTCx->DR;
time->SubSecs = ( ss & RTC_SSR_SS_MASK);
time->Secs = ( (temp1 & ( RTC_TR_ST_MASK | RTC_TR_SU_MASK) ) >> RTC_TR_SU_SHIFT );
time->Mins = ( (temp1 & ( RTC_TR_MNU_MASK | RTC_TR_MNT_MASK) ) >> RTC_TR_MNU_SHIFT );
time->Hours = ( (temp1 & ( RTC_TR_HU_MASK | RTC_TR_HT_MASK) ) >> RTC_TR_HU_SHIFT );
time->Week = ( (temp2 & RTC_DR_WDU_MASK ) >> RTC_DR_WDU_SHIFT );
time->Days = ( (temp2 & ( RTC_DR_DU_MASK | RTC_DR_DT_MASK) ) >> RTC_DR_DU_SHIFT );
time->Months = ( (temp2 & ( RTC_DR_MU_MASK | RTC_DR_MT_MASK) ) >> RTC_DR_MU_SHIFT );
time->Years = ( (temp2 & ( RTC_DR_YU_MASK | RTC_DR_YT_MASK) ) >> RTC_DR_YU_SHIFT );}
闹钟功能:
- RTC支持闹钟响应功能,当闹钟寄存器中的值与计数器的值相同时,闹钟标志会置位,闹钟可匹配:日期/星期、小时、分钟、秒钟、亚秒
- 闹钟在配置时需选择是匹配日期还是匹配星期
- 闹钟可屏蔽不需要匹配的部分时间,例如:若屏蔽日期/星期、小时、分钟,则每分钟的当前闹钟秒数都会产生闹钟响应标志
- 支持闹钟中断响应。
/* Set alarm time. */
RTCx->ALARMAR = 0u;
RTCx->ALARMASSR = 0u;
uint32_t Alarm = ( RTC_ALARMAR_SU(time->Secs % 10u) | RTC_ALARMAR_ST(time->Secs / 10u) | RTC_ALARMAR_MNU(time->Mins % 10u)
| RTC_ALARMAR_MNT(time->Mins / 10u) | RTC_ALARMAR_HU(time->Hours % 10u) | RTC_ALARMAR_HT(time->Hours / 10u)
| RTC_ALARMAR_DU(time->Days % 10u) | RTC_ALARMAR_DT(time->Days / 10u) | RTC_ALARMAR_WDSEL(alarm->WeekDaysel) );
闹钟中断
闹钟中断使能&闹钟标志产生 ->闹钟中断
/* RTC enable interrupt. */
void RTC_EnableInterrupt(RTC_Type * RTCx, uint32_t interrupts, bool enable)
周期性唤醒
对RTC_WUTR(唤醒定时器寄存器)中的数据递减,直到递减为0,唤醒标志位置位,寄存器重装载数据,再次递减到0,不断循环。
入侵检测
RTC支持入侵检测,通过给入侵引脚一个触发信号,使入侵标志置起,备份域寄存器会在发生入侵事件时复位。
/* Configurate tamp and enable tamp. */
void RTC_EnableTampConfig(RTC_Type * RTCx, RTC_Tamp_Type * tamp){
RTCx->TAMPCR &= ~(RTC_TAMP_TAMP1 | RTC_TAMP_TAMP2);
RTCx->BKPXR[tamp->BKP] = tamp->BKPData;
RTCx->TAMPCR = RTC_TAMPCR_TAMPTS(tamp->SaveTime);
if (tamp->Tamp == RTC_TAMP_TAMP1){
RTCx->TAMPCR |= RTC_TAMPCR_TAMP1TRG(tamp->Trigger);
RTC_EnableTamp(RTCx, RTC_TAMPCR_TAMP1E_MASK, true);
}if (tamp->Tamp == RTC_TAMP_TAMP2){
RTCx->TAMPCR |= RTC_TAMPCR_TAMP2TRG(tamp->Trigger);
RTC_EnableTamp(RTCx, RTC_TAMPCR_TAMP2E_MASK, true);}}
4.实验样例
rtc_basic
按下”a”初始化RTC并配置初始时间,按下”b”获取当前时间,串口输出年、月、日、时、分、秒、亚秒,按下”c”配置闹钟响应时间,并在闹钟响应后输出当前时间。
int main(void){
uint8_t ch;
BOARD_Init();
printf("rtc_basic example.\r\n");
printf("Press key 'a' to initialize rtc, 'b' to get current time, 'c' to alarm time.\r\n");
while (1){
ch = getchar();
switch(ch){
case 'a':
app_rtc_init();
printf("a: app_rtc_init().\r\n");
break;
case 'b':
app_rtc_get_current_time();
printf("b: app_rtc_get_current_time().\r\n");
break;
case 'c':
app_rtc_setalalrm();
printf("c: app_rtc_setalalrm().\r\n");
break;
default:
printf("a: app_rtc_init().\r\n");
printf("b: app_rtc_get_current_time().\r\n");
printf("c: app_rtc_setalalrm().\r\n");
break;}
printf("%02x-", (unsigned int)(long)rtc_time.Years);
printf("%02x-", (unsigned int)(long)rtc_time.Months);
printf("%02x ", (unsigned int)(long)rtc_time.Days);
printf("%02x ", (unsigned int)(long)rtc_time.Week);
printf("%02x-", (unsigned int)(long)rtc_time.Hours);
printf("%02x", (unsigned int)(long)rtc_time.Mins);
printf("-%02x", (unsigned int)(long)rtc_time.Secs);
printf(" %02x", (unsigned int)(long)rtc_time.SubSecs);
printf("\r\n");}}
rtc_interrupt
按下”a”初始化RTC并配置初始时间,按下”b”获取当前时间,串口输出年、月、日、时、分、秒、亚秒,按下”c”配置闹钟响应时间及闹钟中断,并在闹钟中断响应后输出当前时间。
int main(void){
uint8_t ch;
BOARD_Init();
printf("rtc_interrupt example.\r\n");
printf("Press key 'a' to initialize rtc, 'b' to get current time, 'c' to alarm interrupt time.\r\n");
while (1){
ch = getchar();
switch(ch){
case 'a':
app_rtc_init();
printf("a: app_rtc_init().\r\n");
break;
case 'b':
app_rtc_get_current_time();
printf("b: app_rtc_get_current_time().\r\n");
break;
case 'c':
app_rtc_setalalrm();
printf("c: app_rtc_setalalrm().\r\n");
break;
default:
printf("a: app_rtc_init().\r\n");
printf("b: app_rtc_get_current_time().\r\n");
printf("c: app_rtc_setalalrm().\r\n");
break;}
printf("%02x-", (unsigned int)(long)rtc_time.Years);
printf("%02x-", (unsigned int)(long)rtc_time.Months);
printf("%02x ", (unsigned int)(long)rtc_time.Days);
printf("%02x ", (unsigned int)(long)rtc_time.Week);
printf("%02x-", (unsigned int)(long)rtc_time.Hours);
printf("%02x", (unsigned int)(long)rtc_time.Mins);
printf("-%02x", (unsigned int)(long)rtc_time.Secs);
printf(" %02x", (unsigned int)(long)rtc_time.SubSecs);
printf("\r\n");}}
rtc_wakeup
设置RTC周期性唤醒的时间为10s,当检测到唤醒标志,串口打印”wakeup”。
int main(void){
BOARD_Init();
app_rtc_Init();
app_rtc_setwakeup();
while (1){
if (true == app_rtc_wakeup_done_flag){
app_rtc_wakeup_done_flag = false;
printf("wakeup\r\n");}}}
rtc_tamper
向备份域寄存器中写入非0的数值,配置入侵触发为上升沿触发;当入侵引脚PC13获得一个上升沿,入侵标志置位,备份域寄存器复位。
int main(void){
BOARD_Init();
app_rtc_init();
printf("rtc_tamper.\r\n");
while (1){
app_rtc_tamper();}}
5.附件
电路原理图:
EVB-L0136_SCH.pdf
(486.94 KB)
工程文件
_workspace.zip
(2.09 MB)
|
单片机典型外设RTC应用实例,基于MM32L0实现RTC实时时钟