本文介绍STM8L051F3的RTC相关知识。内容分为以下几部分:
1、RTC简介RTC(Real-time clock):实时时钟。RTC是一个独立的定时器/计数器,它提供了一个实时时间和日历与一个相关的可编程闹钟,同时它还包括一个可用于管理低功耗模式的自动唤醒单元。采用二进制编码格式的8位寄存器,包括秒、分、时(12或24小时格式)、星期x、日、月和年,二进制编码格式还可以获取微秒的时间值。RTC能自动调节闰年和每个月的天数。
另外还有可编程闹钟的8位寄存器,包括微秒、秒、分、时、星期x和日,可以把RTC校准到0.954ppm的精度,在复位后,RTC是处于写保护状态的,只要供电电压维持在系统运行电压内,不管MCU处于睡眠状态,RTC是不会停止运行的。RTC主要特点如下:
- 一个带有微秒、秒、分、时(12或24小时格式)、星期x、日、月和年的日历
- 可软件调节夏令时
- 一个带中断的可编程闹钟,闹钟可由任何一段日历组合触发
- 一个自动唤醒单元提供周期性的触发标志,触发自动唤醒中断
- 5个可屏蔽中断/事件
- 使用微秒转移特点与外部时钟进行精准同步
- 数字校准在954ppm
- 3个内部上拉和可配置滤波器的篡改输入来唤醒CPU
- 可替换功能输出
- RTC_CALIB(RTC标准输出):可配置512Hz或1Hz时钟输出
- RTC_ALARM(RTC警报输出):可选择警报器A或唤醒标志输出
RTC的时钟与预分频器。RTC的时钟源可以选择HSE、HSI、LSE或LSI中的一个。如果使用LSE作为时钟源,那么时钟安全系统会在LSE上应用。为了可以正确地访问到RTC,系统时钟必须等于或者大于4倍的RTC时钟,这样能确保同步运行。RTC有一个13位的同步的预分频器(RTC_SPRERx,x = 1,2)和一个8位的异步预分频器(RTC_APRER),这两个预分频器都是可编程的,通常使用这两个预分频器产生一个1Hz(1s)的时钟节拍来更新RTC。RTC的时钟节拍可由一下公式计算:
fck_SPRE = fRTCCL/((RTC_SPRER+1)(RTC_APRER+1))
RTC模块框图如下(以STM8L051F3为例):
RTC和日历。RTC日历时间和日期寄存器通过与SYSCLK(系统时钟)同步的影子寄存器来访问,他们也可以直接访问,避免同步等待的时间。
- RTC_SSRx(微秒)
- RTC_TR1(秒)
- RTC_TR2(分)
- RTC_TR3(时)
- RTC_DR1(星期x)
- RTC_DR2(日和月)
- RTC_DR3(年)
在系统复位重置后,当前的日历值会定期地被复制到影子寄存器,复制周期是RTCCLK周期,在每次复制完成后,RTC_ISR寄存器的RSF位还会被置位。
可编程闹钟。RTC提供一个可编程闹钟(闹钟 A),该功能通过RTC_CR2寄存器的ALRAE为使能。如果日历的微秒、秒、分、时/或星期x时间与编程在RTC_ALRMASSRx 和RTC_ALRMARx 报警寄存器上的值相匹配,那么ALRAF位会被置位。闹钟中断可以通过RTC_CR2寄存器的ALEAIE位来使能,一旦使能,报警中断可以使系统推出低功耗模式。
周期自动唤醒。周期唤醒标志产生是由一个16位可编程二进制自动重装载递减计数器构成,它的唤醒时间范围能扩展到17位。唤醒功能由RTC_CR2寄存器的WUTE位使能。自动唤醒功能的时钟可以选择以下:
- RTCCLK经过2、4、8或16分频。如RTCCLK是LSE(32768Hz),那么唤醒中断的周期可以配置在122μs到32s
- ck_spre(通常是1Hz),当ck_spre为1Hz,唤醒时间可设置范围在1s~36h,精度1/2秒,可编程时间范围分为两步:
- WUCKSEL[2:1] = 10,时间范围1s~18h
- WUCKSEL[2:1] = 11,时间范围18h~36h
RTC寄存器的写保护。默认情况下所有的RTC寄存器是写保护的(除了RTC_ISR2寄存器,RTC_WPR寄存器),可以通过以下步骤来解除RTC寄存器的写保护:
- 写‘0xCA’到RTC_WPR寄存器
- 写‘0x53到’RTC_WPR寄存器
日历初始化和配置。对日历的时间和日期极性编程,包括时间格式和预分频设置,需要按以下顺序:
1)设置RTC_ISR寄存器的INIT位为1,使能初始化模式,在这个模式下,日历计数器是停止的,它的值可以被更新
2)轮询RTC_ISR寄存器的INITF位,INIT位被置1时进入初始化模式,这需要大约2个RTCCLK时钟周期来实现同步
3)配置预分频寄存器来为日历的计数器产生1Hz的时钟
4)加载初始化的时间和日期值到影子寄存器和通过RTC_CR寄存器的FMT位来配置时间(12小时或24小时)的格式
5)清除INIT位来退出初始化模式,实际的日历计数器的值会在4个RTCCLK时钟周期会自动加载
当初始化完成后,微秒值也会被重新初始化,所以在一秒的时间到后秒的数值会加一。
夏令时。夏令时可以通过RTC_ISR寄存器的SUB1H、ADD1H和BCK位来进行控制。使用SUB1H或ADD1H位,软件可以单独操作往日历中添加或减去一个小时而无需经过初始化过程。另外,软件可以使用BCK位来记录这个操作。
可编程闹钟。根据以下步骤来编程或更新可编程闹钟(alarm A):
1)清除RTC_CR2寄存器的ALRAE为来关闭闹钟
2)轮询RTC_ISR1寄存器ALRAWF位,直到该位被置位才能允许访问闹钟的寄存器
3)编程闹钟寄存器:RTC_ALRMASSRx、RTC_ALRMASSMSKR和RTC_ALRMARx
4)设置RTC_CR2的ALRAE位使能闹钟
5)在ALRAE位置1后,闹钟保留了一个有效的ck_apre时钟
可编程的自动唤醒定时器。应该按以下顺序来配置或更新唤醒定时器的重装载值:
1)清除RTC_CR2寄存器的WUTE位来关闭唤醒定时器
2)轮询RTC_ISR1寄存器的WUTWF位,直到它被置位,确保能够访问自动唤醒重装载计数器和 WUCKSEL[2:1],由于时钟同步原因,这大约需要两个RTCCLK时钟
3)编程唤醒定时器的值(RTC_WUTRL和RTC_WUTRH)和选择期望的时钟(WUCKSEL[2:1])
4)设置RTC_CR2寄存器的WUTE位来重新使能定时器,唤醒定时器开始向下计数
读日历数据。当BYPSHAD位被清除时。为了正确的读取RTC日历寄存器,系统的时钟频率必须等于或者大于4倍的RTC时钟频率,这是为了确保同步机制的安全行为。每次日历寄存器被复制到RTX_SSRx,、RTC_TRx 和RTC_DRx 影子寄存器后,RCT_ISR寄存器的RSF位被置位,每个人RTCCLK都会执行一次。为了确保数据的一致性,在软件读取日历数据时,所有影子寄存器的更新时被暂停的,知道RTC_DR3寄存器被读取。如果软件不需要读取微秒值,它可以先读取RTC_CTR1,直到RTC_DR3被读取之前,其他值都是被锁定的(不允许更新)。如果软件读取日历时间的间隔小于1RTCCLK周期:RSF位必须在第一次读取都软件清零,然后必须等到RSF为被置位之后才能再次读取日历影子寄存器。在低功耗模式被唤醒后,RSF位必须软件清零。在读取RTC_TR和RTC_DR寄存器之前,必须要等到RSF位被置位。在唤醒低功耗模式后,RSF位必须被清零,而不是在进入低功耗之前。
当BYPSHAD位被置1时(避开银子寄存器)。从日历计数器中直接读取数值,这样不需要等待RSF位被置位。这这方法对于退出有效停机模式后非常有用,因为在有效停机模式下,影子寄存器没有更新。在BYPSHAD位被置1时,如果1个RTCCLK发生两个对寄存器的读访问,则由于不连贯可能会导致两次读出的结果不相等。此外,在读期间产生RTCCLK时钟沿,那么其中读取的寄存器值可能不准确,软件必须读取所有寄存器两次,然后根据结果比较,确认数据是一致的和正确的。
复位RTC。日历的影子寄存器和RTC状态寄存器可以被所有有效的系统复位源重置。相反,RTC当前的日历寄存器、控制寄存器、预分频寄存器、唤醒定时器寄存器和闹钟寄存器只能被系统上电复位来重置到默认值,其他系统复位是无效的。当发生系统上电复位,RTC是停止运行的,它的所有寄存器恢复到默认值。
RTC还有许多功能,如RTC同步、数字时钟平滑校准、篡改检测、校准时钟输出、闹钟输出、低功耗模式、RTC中断等等,详细可参考官方手册RM0031第24章内容。
2、电子日历2.1 电子日历配置本小节介绍如何使用RTC的电子日历功能,使用LSE作为RTC时钟源,读取的日期与时间数据使用OLED显示出来(OLED采用的是技新0.96寸OLED(4PIN))。使用的例程:STM8L051F3_14_RTC。系统的工作流程:初始化LSE时钟并等待其稳定-->初始化RTC-->初始化IIC-->初始化OLED-->读取数据并显示(循环)。这里主要介绍RTC的初始化,步骤如下:
1)选择LSE作为RTC的时钟源
2)打开RTC外设时钟
3)配置RTC时钟:24小时制、计时时间 = 1S
4)初始化日期数据
5)初始化时间数据
注:读取出来的数据位BCD码
2.2 例程介绍例程涉及多方面知识,IIC与OLED的通讯(可参考第九章内容)、TIM4定时器(可参考第七章内容)。除了IIC与OLED通讯的实现外,RTC相关的内容都在main.c文件中实现。使用TIM4精准延迟1S的函数如下:
void LSE_StabTime(void)
{
/* 使能TIM4时钟 */ CLK_PeripheralClockConfig(CLK_Peripheral_TIM4, ENABLE); /* 配置TIM4大约1S产生一次更新事件 */ TIM4_TimeBaseInit(TIM4_Prescaler_16384, 123); /* 清除更新标志位 */ TIM4_ClearFlag(TIM4_FLAG_Update); /* 使能 TIM4 */ TIM4_Cmd(ENABLE); /* 等待更新事件产生 */ while( TIM4_GetFlagStatus(TIM4_FLAG_Update)== RESET ); /* 清除更新标志位 */ TIM4_ClearFlag(TIM4_FLAG_Update); /* 关闭 TIM4 */ TIM4_Cmd(DISABLE); /* 关闭TIM4时钟 */ CLK_PeripheralClockConfig(CLK_Peripheral_TIM4, DISABLE); }
RTC初始化函数如下:
void Calendar_Init(void)
{
//选择LSE(32.768KHz)作为时钟源 CLK_RTCClockConfig(CLK_RTCCLKSource_LSE, CLK_RTCCLKDiv_1); //打开RTC时钟 CLK_PeripheralClockConfig(CLK_Peripheral_RTC, ENABLE); /* RTC时钟源:LSE,计时时间:32768/128/256 = 1S */ RTC_InitStr.RTC_HourFormat = RTC_HourFormat_24;//24小时制 RTC_InitStr.RTC_AsynchPrediv = 0x7F; //异步分频器 128分频 RTC_InitStr.RTC_SynchPrediv = 0x00FF; //同步分频器 256分频 RTC_Init(&RTC_InitStr); //初始化RTC参数 /* 初始化RTC_DateStr结构体,设置日期数据 */ RTC_DateStructInit(&RTC_DateStr); //初始化RTC_DateStr结构体 RTC_DateStr.RTC_WeekDay = RTC_Weekday_Thursday;//星期四 RTC_DateStr.RTC_Date = 03; //3日 RTC_DateStr.RTC_Month = RTC_Month_January; //1月 RTC_DateStr.RTC_Year = 18; //2018年 RTC_SetDate(RTC_Format_BIN,&RTC_DateStr); //设置日期数据 /* 初始化RTC_TimeStr结构体,设置时间数据 */ RTC_TimeStructInit(&RTC_TimeStr);//初始化RTC_TimeStr结构体 RTC_TimeStr.RTC_Hours = 23; //23H RTC_TimeStr.RTC_Minutes = 52; //52分 RTC_TimeStr.RTC_Seconds = 00; //0秒 RTC_SetTime(RTC_Format_BIN,&RTC_TimeStr); //设置时间数据 }
读取并显示时间函数如下:
void Time_Show(void)
{
uint8_t Hours_D,Hours_U; uint8_t Minutes_D,Minutes_U; uint8_t Seconds_D,Seconds_U; /* 等待RTC同步 */ while(RTC_WaitForSynchro()!= SUCCESS); /* 获取当前时间数据 */ RTC_GetTime(RTC_Format_BCD,&RTC_TimeStr); /* 把读取‘时’的数据转码 */ Hours_D =(uint8_t)(((uint8_t)(RTC_TimeStr.RTC_Hours & 0xF0)>> 4)+ 48); Hours_U =(uint8_t)(((uint8_t)(RTC_TimeStr.RTC_Hours & 0x0F))+ 48); /* 把读取‘分’的数据转码 */ Minutes_D =(uint8_t)(((uint8_t)(RTC_TimeStr.RTC_Minutes & 0xF0)>> 4)+ 48); Minutes_U =(uint8_t)(((uint8_t)(RTC_TimeStr.RTC_Minutes & 0x0F))+ 48); /* 把读取‘秒’的数据转码 */ Seconds_D =(uint8_t)(((uint8_t)(RTC_TimeStr.RTC_Seconds & 0xF0)>> 4)+ 48); Seconds_U =(uint8_t)(((uint8_t)(RTC_TimeStr.RTC_Seconds & 0x0F))+ 48); /* OLED上显示时、分、秒数据 */ OLED_ShowChar(48,2,Hours_D); OLED_ShowChar(56,2,Hours_U); OLED_ShowChar(72,2,Minutes_D); OLED_ShowChar(80,2,Minutes_U); OLED_ShowChar(96,2,Seconds_D); OLED_ShowChar(104,2,Seconds_U); }
读取并显示日期函数如下:
void Date_Show(void)
{
uint8_t Years_D,Years_U; uint8_t Months_D,Months_U; uint8_t Dates_D,Dates_U; /* 等待RTC同步 */ while(RTC_WaitForSynchro()!= SUCCESS); /* 获取当前日期数据 */ RTC_GetDate(RTC_Format_BCD,&RTC_DateStr); /* 把读取‘年’的数据转码 */ Years_D =(uint8_t)(((uint8_t)(RTC_DateStr.RTC_Year & 0xF0)>> 4)+ 48); Years_U =(uint8_t)(((uint8_t)(RTC_DateStr.RTC_Year & 0x0F))+ 48); /* 把读取‘月’的数据转码 */ Months_D =(uint8_t)(((uint8_t)(RTC_DateStr.RTC_Month & 0xF0)>> 4)+ 48); Months_U =(uint8_t)(((uint8_t)(RTC_DateStr.RTC_Month & 0x0F))+ 48); /* 把读取‘日’的数据转码 */ Dates_D =(uint8_t)(((uint8_t)(RTC_DateStr.RTC_Date & 0xF0)>> 4)+ 48); Dates_U =(uint8_t)(((uint8_t)(RTC_DateStr.RTC_Date & 0x0F))+ 48); /* OLED上显示年、月、日数据 */ OLED_ShowChar(64,0,Years_D); OLED_ShowChar(72,0,Years_U); OLED_ShowChar(88,0,Months_D); OLED_ShowChar(96,0,Months_U); OLED_ShowChar(112,0,Dates_D); OLED_ShowChar(120,0,Dates_U); }
主函数如下:
void main(void)
{
/* 使能LSE时钟 */ CLK_LSEConfig(CLK_LSE_ON); /* 等待LSE时钟准备 */ while(CLK_GetFlagStatus(CLK_FLAG_LSERDY)== RESET); /* 等待1S,LSE稳定 */ LSE_StabTime(); /* RTC初始化 */ Calendar_Init(); /* LED初始化 */ LED_Init(); /* IIC初始化 */ IIC_Init(); /* OLED初始化、清屏 */ OLED_Init(); OLED_Clear(); /* OLED显示字符 */ OLED_ShowString(0,0,"DATA: 20"); OLED_ShowString(80,0,"-"); OLED_ShowString(104,0,"-"); /* OLED显示字符 */ OLED_ShowString(0,2,"TIME: "); OLED_ShowString(64,2,":"); OLED_ShowString(88,2,":"); GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PINS); while(1) { Time_Show(); //读取并显示时间 Date_Show(); //读取并显示日期 } }
使用ST-LINK把程序下载到开发板,LED1亮,OLED显示:日期&时间。效果如下:
|