Nuoeriris · 2021年01月21日

MM32F013x——万年历

MM32F013X内部的RTC是一个独立的定时器单元,它拥有一组连续计数的计数器,配置相应的寄存器参数,可以实现闹钟、秒中断、毫秒中断、MCU定时唤醒、万年历等功能。

主要特征:
  • 可编程的预分频系数:分频系数最高为 220
  • 32 位的可编程计数器,用于较长时间段的测量
  • 2 个分离的时钟:用于 APB1 接口的 PCLK1 和 RTC 时钟 (RTC 时钟的频率必须小于PCLK1 时钟频率的四分之一以上)
  • 可以选择以下三种 RTC 的时钟源
– HSE 时钟除以 128
– LSE 振荡器时钟
– LSI 振荡器时钟
  • 2 个独立的复位类型:
– APB1 接口由系统复位
– RTC 核心 (预分频器、闹钟、计数器和分频器) 只能由后备域复位
  • 3 个专门的屏蔽中断:
– 闹钟中断,用来产生一个软件可编程的闹钟中断
– 秒 / 毫秒中断,用来产生一个可编程的周期性中断信号 (最长可达 1 秒)
– 溢出中断,指示内部可编程计数器溢出并返回为 0 的状态

本文将重点介绍如何在MM32F013X上通过内部RTC模块实现万年历的功能。

1. 实现功能

通过修改RTC计数器的初始值来设置系统当前的时间和日期,使能RTC秒中断功能;在RTC产生秒中断后,通过获取当前RTC的计数值,将其转换为对应的年月日信息,再通过蔡勒公式计算出星期,将最终的结果通过串口的形式输出显示。

RTC模块的电源域处在VDD数字电源域,只要MCU供电就可以使用RTC,没有独立的VBAT供电引脚,所以无法使用纽扣电池类的应用。

2. 参考代码

2.1 结构体定义及全局变量
typedef struct
{
    uint16_t year;
    uint8_t  month;
    uint8_t  day;
    uint8_t  week;
    uint8_t  hour;
    uint8_t  minute;
    uint8_t  second;
} CALENDAR_t;
const uint8_t RTC_DayOfMonth[12] =
{
    31,28,31,30,31,30,31,31,30,31,30,31
};
CALENDAR_t    RTC_Calendar;
2.2 RTC初始化配置:使用外部32.768kHz的晶振源
void RTC_Configure(void)
{
    uint16_t BKP_Value = 0x5A5A;
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Enable PWR Clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    /* Enable WKUP pin */
    PWR_WakeUpPinCmd(ENABLE);
    /* Enable Access To The RTC & Backup Registers */
    PWR_BackupAccessCmd(ENABLE);
    if(BKP_ReadBackupRegister(BKP_DR1) != BKP_Value)
    {
        BKP_DeInit();
        /* Enable LSE Clock Source */
        RCC_LSEConfig(RCC_LSE_ON);

        /* Wait LSI Clock Source Ready */
        while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);

        /* Config RTC Clock Source : LSE */
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

        /* Enable RTC Clock */
        RCC_RTCCLKCmd(ENABLE);

        /* Wait For Synchronization */
        RTC_WaitForSynchro();
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Set The RTC Prescaler Value */
        RTC_SetPrescaler(32767);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Enable RTC Second Interrupt */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Exit From The RTC Configuration Mode */
        RTC_ExitConfigMode();

        BKP_WriteBackupRegister(BKP_DR1, BKP_Value);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        /* Set initial time */
        RTC_SetDateTime(2021, 1, 12, 14, 48, 0);
    }
    else
    {
        /* Wait For Synchronization */
        RTC_WaitForSynchro();

        /* Enable RTC Second Interrupt */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }

    /* Config RTC NVIC */
    NVIC_InitStructure.NVIC_IRQChannel  = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
2.3 RTC秒中断函数
void RTC_BKP_IRQHandler(void)
{
    if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
    {
        /* Update Date and Time */
        RTC_UpdateCalendar();

        /* Print current Date and Time */
        RTC_PrintDateTime();

        /* Clear Alarm Flag */
        RTC_ClearITPendingBit(RTC_IT_SEC);
        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();
    }
}

2.4 将RTC计数值转换为日期信息

void RTC_UpdateCalendar(void)
{
    static uint32_t PreTotalDay = 0;

    uint32_t TotalSecond = 0;
    uint32_t TotalDay    = 0;

    uint16_t Year  = 1970;
    uint8_t  Month = 0;

    /* Get The RTC Counter Value */
    TotalSecond = RTC_GetCounter();
    TotalDay    = TotalSecond / 86400;

    if(PreTotalDay != TotalDay)
    {
        PreTotalDay = TotalDay;

        while(TotalDay >= 365)
        {
            if(RTC_LeapYear(Year) == 1)
            {
                if(TotalDay >= 366)
                {
                    TotalDay -= 366;
                }
                else
                {
                    break;
                }
            }
            else
            {
                TotalDay -= 365;
            }

            Year++;
        }
        RTC_Calendar.year = Year;
        while(TotalDay >= 28)
        {
            if((Month == 1) && (RTC_LeapYear(RTC_Calendar.year) == 1))
            {
                if(TotalDay >= 29)
                {
                    TotalDay -= 29;
                }
                else
                {
                    break;
                }
            }
            else
            {
                if(TotalDay >= RTC_DayOfMonth[Month])
                {
                    TotalDay -= RTC_DayOfMonth[Month];
                }
                else
                {
                    break;
                }
            }

            Month++;
        }

        RTC_Calendar.month = Month    + 1;
        RTC_Calendar.day   = TotalDay + 1;

        RTC_Calendar.week  = RTC_GetWeek(RTC_Calendar.year, RTC_Calendar.month, RTC_Calendar.day);
    }

    RTC_Calendar.hour   =  (TotalSecond % 86400) / 3600;
    RTC_Calendar.minute = ((TotalSecond % 86400) % 3600) / 60;
    RTC_Calendar.second = ((TotalSecond % 86400) % 3600) % 60;
}
2.5 将日期信息转换为RTC计数值
void RTC_SetDateTime(uint16_t Year, uint8_t Month, uint8_t Day,
                     uint8_t  Hour, uint8_t Min,   uint8_t Sec)
{
    uint32_t TotalSecond = 0;
    uint16_t y = 0;
    uint8_t  m = 0;

    if((Year >= 1970) && (Year <= 2099))
    {
        for(y = 1970;  y < Year; y++)
        {
            if(RTC_LeapYear(y) == 1)
            {
                TotalSecond += 31622400;    /* Total Seconds Of Leap   Year */
            }
            else
            {
                TotalSecond += 31536000;    /* Total Seconds Of Normal Year */
            }
        }

        for(m = 0; m < (Month - 1); m++)
        {
            TotalSecond += RTC_DayOfMonth[m] * 86400; /*Total Seconds Of Month */
            if((RTC_LeapYear(Year) == 1) && (m == 1))
            {
                TotalSecond += 86400;
            }
        }
        TotalSecond += (uint32_t)(Day - 1) * 86400; /* Total Seconds Of Day    */
        TotalSecond += (uint32_t)Hour      * 3600;  /* Total Seconds Of Hour   */
        TotalSecond += (uint32_t)Min       * 60;    /* Total Seconds Of Minute */
        TotalSecond += Sec;

        /* Enable PWR Clock */
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

        /* Enable Access To The RTC & Backup Registers */
        PWR_BackupAccessCmd(ENABLE);

        /* Set The RTC Counter Value */
        RTC_SetCounter(TotalSecond);

        /* Wait Until Last Write Operation On RTC REG Has Finished */
        RTC_WaitForLastTask();

        RTC_UpdateCalendar();
    }
    else
    {
        printf("\r\nError Date & Time!!!\r\n");
    }
}
2.6 RTC信息打印
void RTC_PrintDateTime(void)
{
    printf("\r\n%04d-%02d-%02d", RTC_Calendar.year, RTC_Calendar.month,  RTC_Calendar.day);

    switch(RTC_Calendar.week)
    {
     case 0 :
        printf(" SUN ");
        break;

     case 1 :
        printf(" MON ");
        break;

     case 2 :
        printf(" TUE ");
        break;

     case 3 :
        printf(" WED ");
        break;

     case 4 :
        printf(" THU ");
        break;

     case 5 :
        printf(" FRI ");
        break;

     case 6 :
        printf(" SAT ");
        break;

     default:
        break;
    }

    printf("%02d:%02d:%02d\r\n", RTC_Calendar.hour, RTC_Calendar.minute, RTC_Calendar.second);
}
2.7 RTC功能函数:判断闰年、蔡勒公式计算星期
uint8_t RTC_LeapYear(uint16_t Year)
{
    if(
        (((Year % 400) == 0)                     ) ||   /* Century Leap Year */
        (((Year % 100) != 0) && ((Year % 4) == 0))      /* Normal  Leay Year */
    )
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

uint8_t RTC_GetWeek(uint16_t Year, uint8_t Month, uint8_t Day)
{
    int w, c, y;
    /* Month 1 Or 2 of This Year Must Be As Last Month 13 Or 14 */
    if((Month == 1) || (Month == 2))
    {
        Month += 12;
        Year  -= 1;
    }

    w = 0;          /* Weekday */
    c = Year / 100; /* Century */
    y = Year % 100; /* Year    */

    w = y + (y / 4) + (c / 4) - (2 * c) + (26 * (Month + 1) / 10) + Day - 1;

    while(w < 0) w += 7;

    w %= 7;

    return w;
}

3. 运行结果

编译软件工程无误后下载代码,在串口终端工具中我们可以看到每间隔1秒钟,RTC产生一次中断,在中断中我们将当前的日期信息通过串口打印在显示终端软件上:
1.png

推荐阅读
关注数
6143
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息