春娇霹雳娃 发表于 2023-7-28 16:48

看过来,芯片也能当时钟?基于MM32L0130实现RTC实时时钟工程

本帖最后由 春娇霹雳娃 于 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

软件环境:

[*]KEIL V5.37
[*]终端软件Tera Term



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->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.附件
电路原理图:
工程文件

春娇霹雳娃 发表于 2023-8-2 11:36

yszong 发表于 2023-8-15 21:29

2个预分频器呢

春娇霹雳娃 发表于 2023-8-16 09:14

yszong 发表于 2023-8-15 21:29
2个预分频器呢

单片小菜 发表于 2023-8-16 16:44

这个时钟的精度如何?

lajfda002 发表于 2023-8-16 16:58

感觉你的帖子都很厉害,你是大佬,高手。

春娇霹雳娃 发表于 2023-8-16 17:33

单片小菜 发表于 2023-8-16 16:44
这个时钟的精度如何?

春娇霹雳娃 发表于 2023-8-16 17:33

lajfda002 发表于 2023-8-16 16:58
感觉你的帖子都很厉害,你是大佬,高手。

页: [1]
查看完整版本: 看过来,芯片也能当时钟?基于MM32L0130实现RTC实时时钟工程