发新帖本帖赏金 40.00元(功能说明)我要提问
返回列表
打印
[MM32生态]

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

[复制链接]
1224|8
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 春娇霹雳娃 于 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->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)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 40.00 元 2023-08-15
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2023-8-15 10:47 回复TA
单片机典型外设RTC应用实例,基于MM32L0实现RTC实时时钟 
沙发
春娇霹雳娃|  楼主 | 2023-8-2 11:36 | 只看该作者

使用特权

评论回复
板凳
yszong| | 2023-8-15 21:29 | 只看该作者
2个预分频器呢

使用特权

评论回复
地板
春娇霹雳娃|  楼主 | 2023-8-16 09:14 | 只看该作者

使用特权

评论回复
5
单片小菜| | 2023-8-16 16:44 | 只看该作者
这个时钟的精度如何?

使用特权

评论回复
6
lajfda002| | 2023-8-16 16:58 | 只看该作者
感觉你的帖子都很厉害,你是大佬,高手。

使用特权

评论回复
7
春娇霹雳娃|  楼主 | 2023-8-16 17:33 | 只看该作者
单片小菜 发表于 2023-8-16 16:44
这个时钟的精度如何?

使用特权

评论回复
8
春娇霹雳娃|  楼主 | 2023-8-16 17:33 | 只看该作者
lajfda002 发表于 2023-8-16 16:58
感觉你的帖子都很厉害,你是大佬,高手。

使用特权

评论回复
发新帖 本帖赏金 40.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:灵动系统开发工程师
简介:none........

19

主题

154

帖子

3

粉丝