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产生一次中断,在中断中我们将当前的日期信息通过串口打印在显示终端软件上: