RTC框图
实时时钟(Real-time clock: RTC)是一个独立的计时器。RTC提供一组连续运行的计数器,可以与合适的软件一起使用,以提供时钟日历功能。可以写入计数器值以设置系统的当前时间/日期。
可以选择以下三种作为RTC时钟源:
- HSE时钟进行128分频
- LSE振荡器时钟
- LSI振荡器时钟
有关时钟的更多信息,参考STM32时钟树
RTC核心部分由2个部分构成:
- RTC分频器由
RTC_PRL
、RTC_DIV
构成:RTC_PRL
是预分频装载寄存器,用来保存RTC预分频器的周期计数值。RTC_DIV
是预分频器计数寄存器(只读)。在TR_CLK的每个周期里,RTC预分频器中计数器的值都会被重新设置为RTC_PRL寄存器的值。用户可通过读取RTC_DIV寄存器,以获得预分频计数器的当前值,而不停止分频计数器的工作,从而获得精确的时间测量。当RTC_DIV的值等于RTC_PRL的值,可生成最长为1s的时间基准TR_CLK
- RTC32位可编程计数器由
RTC_CNT
、RTC_ALR
构成RTC_CNT
是32位计数寄存器,存放RTC当前计数值,计数的速率取决于TR_CLK
。分为两个16位寄存器RTC_CNTH
和RTC_CNTL
RTC_ALR
是闹钟(alarm)寄存器,当可编程计数器(RTC_CNT)的值与RTC_ALR中的32位值相等时,触发一个闹钟事件,并且产生RTC闹钟中断。
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作使能对(Backup)后备寄存器和RTC的访问:
- 通过设置RCC_APB1ENR寄存器的PWREN位和BKPEN位,来开启POWER和BACKUP的时钟
- 通过设置电源控制寄存器(PWR_CR)的DBP位,允许对RTC和Backup寄存器的访问
PWR
电源控制(Power control:PWR),电源框图如下。STM32的工作电压(VDD)为2.0~3.6V。通过内置的电压调节器提供所需的1.8V电源。 当主电源VDD掉电后,可通过VBAT脚为实时时钟(RTC)和备份寄存器提供电源。
备份域(Backup domain)有以下几个部分构成:
- 频率为32K的LSE振荡器
- BKP(备份)寄存器
- RCC_BDCR(备份域控制)寄存器
- RTC
BKP
备份寄存器(Backup Register)是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电,数据不会丢失。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。BKP可用于RTC校准功能。
RTC显示实时时间
通过串口配置修改将RTC计数寄存器修改为当前时间,开启RTC秒中断(Second Interrupt),1S产生一次RTC中断,并通过串口助手实时显示时间。备份寄存器在这个例程中的作用就是检查RTC是否被配置了。由于使用的最小系统板VBAT引脚没有接电池,所以需要单独供电,断电后BKP的内容会被清空。
整体流程图
通过串口修改RTC外设寄存器的值设为当前时间,配置完成后单片机向串口助手发送数据,使用串口助手显示当前时间。BKP在本例程中的作用就是用于检测RTC是否被配置,通过向BKP_DR1寄存器写入一个任意的值,来判断RTC是否被配置。整体流程图如下:
void MyRTC_Init(void)
{NVIC_Configuration();if(BKP_ReadBackupRegister(BKP_DR1) != 0x1234){printf("\r\n\n RTC not yet configured....");RTC_Configuration(); //配置RTCprintf("\r\n RTC configured....");Time_Adjust();BKP_WriteBackupRegister(BKP_DR1, 0x1234);}else{/* 检查电源复位标志位是否被设置 */if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){printf("\r\n\n Power On Reset occurred....");}/* 检查引脚复位标志位是否被设置 */else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){printf("\r\n\n External Reset occurred....");}printf("\r\n No need to configure RTC....");/* 等待RTC同步 */RTC_WaitForSynchro();/* 使能RTC秒中断 */RTC_ITConfig(RTC_IT_SEC, ENABLE);/* 等待RTC寄存器上的最后一次写入操作完成 */RTC_WaitForLastTask();}/* 清除复位标志位 */RCC_ClearFlag();
}
配置RTC
系统复位后,执行以下操作才能实现对(Backup)备份寄存器和RTC的访问:
- 开启PWR、BKP时钟
- 允许访问BACKUP寄存器、RTC外设
{ signal: [{ name: "RTCCLK", wave: "p..........", period: 2 },{ name: "RTC_DIV", wave: "=.=.=.=.=.=.=.=.=.=.=.", data: ["0x00", "0x03", "0x02", "0x01", "0x00","0x03", "0x02", "0x01", "0x00", "0x03", "0x02"] },{ name: "TR_CLK", wave: "0.1.0.....1.0.....1.0.."},{ name: "RTC_Second", wave: "0.1.0.....1.0.....1.0.."},{ name: "RTC_CNT", wave: "=...=.......=.......=...", data: ["FFFFFFFD", "FFFFFFFFE", "FFFFFFFFF", "00000000"] },{ name: "RTC_Overflow", wave:"0.................1.0.."}],head:{text:'RTC_PRL=0x03,计数溢出的波形图'}
}
- RTCCLK的时钟是来自LSE振荡器的时钟,\(f_{LSE}\)=32.728KHz。由参考手册可得\(f_{TR\_CLK}=\frac{f_{RTCCLK}}{RTC\_PRL[19:0]+1}\),时间基准TR_CLK最大可计时1S,即RTC_Second的时钟周期最长可为1S。要获得1S的时间基准,RTC_PRL寄存器应该写入32767。
- RTC_DIV存放分频计数器当前值,当减为0时,在TR_CLK的上升沿时发生重装载(reload),RTC_DIV的值被重装载为RTC_PRL的值
- RTC_CNT寄存器的值在每个TR_CLK的时钟周期下,值都会递增。RTC_CNT寄存器用于存放系统的当前时间
- RTC_Overflow:当32位的RTC_CNT寄存器计数满了之后,在下个TR_CLK上升沿时,产生一次溢出信号
RTC配置程序代码:
void RTC_Configuration(void)
{/* 开启PWR、BKP时钟 */RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);/* 允许访问Backup和RTC寄存器 */PWR_BackupAccessCmd(ENABLE);/* 复位BKP */BKP_DeInit();/* 使能LSE */RCC_LSEConfig(RCC_LSE_ON);/* 等待LSE准备好 */while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET){}/* 选择LSE做为RTC时钟源 */RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);/* 使能RTC时钟 */RCC_RTCCLKCmd(ENABLE);/* 等待RTC寄存器同步 */RTC_WaitForSynchro();/* RTC写寄存器操作必须要这段语句,等待上次对RTC寄存器的写操作完成 */RTC_WaitForLastTask();/* 每秒产生一次中断 */RTC_ITConfig(RTC_IT_SEC, ENABLE);/* 等待上次对RTC寄存器的写操作完成 */RTC_WaitForLastTask();/* 设置预分频 */RTC_SetPrescaler(32767); //RTC时钟周期 = (32.768KHz) / (32767 + 1) = 1Hz = 1s/* 等待上次对RTC寄存器的写操作完成 */RTC_WaitForLastTask();
}
串口修改RTC
通过串口设置RTC计数寄存器的值,printf()
向串口助手发送信息,USART_Scanf()
函数接收来自串口助手的消息。USART的数据帧使用8位有效数据。
串口修改RTC程序代码:
/*** @brief 调整时间* @param none* @retval none*/
void Time_Adjust(void)
{/* 等待上次对RTC寄存器的写操作完成 */RTC_WaitForLastTask();/* 设置RTC计数器的值 */RTC_SetCounter(Time_Regulate());/* 等待上次对RTC寄存器的写操作完成 */RTC_WaitForLastTask();
}/*** @brief 通过串口设置时间* @param none* @retval 用秒表示的时间*/
uint32_t Time_Regulate(void)
{uint32_t Tmp_HH = 0xFF, Tmp_MM = 0xFF, Tmp_SS = 0xFF;printf("\r\n==============Time Settings=====================================");printf("\r\n Please Set Hours");while(Tmp_HH == 0xFF){Tmp_HH = USART_Scanf(23);}printf(": %d", Tmp_HH);printf("\r\n Please Set Minutes");while(Tmp_MM == 0xFF){Tmp_MM = USART_Scanf(59);}printf(": %d", Tmp_MM);printf("\r\n Please Set Seconds");while(Tmp_SS == 0xFF){Tmp_SS = USART_Scanf(59);}printf(": %d", Tmp_SS);/* 将输入的值转换为Counter CNT */return (Tmp_HH * 60 * 60 + Tmp_MM * 60 + Tmp_SS);
}/*** @brief 获取来自串口的数据* @param value 允许串口输入的最大值* @retval 转换后的串口的数据*/
uint8_t USART_Scanf(uint32_t value)
{uint32_t index = 0;uint8_t rev[2] = {0, 0};while(index < 2){/* 等待RXNE = 1 */while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET){}rev[index++] = USART_ReceiveData(USART1);if(rev[index - 1] < 0x30 || rev[index - 1] > 0x39){printf("\n\rPlease enter valid number between 0 and 9");index--;}}index = ((rev[0] - 0x30) * 10) + (rev[1] - 0x30); //获取输入的值/* 检验 */if(index > value){printf("\n\rPlease enter valid number between 0 and %d", value);return 0xFF;}return index;
}
显示实时时间
/*** @brief 将时间按照指定格式显示* @param value RTC计数寄存器的值* @retval none*/
void Time_Display(uint32_t value)
{/* 当前时间为23:59:59时,复位RTC */if(RTC_GetCounter() == 0x0001517F){RTC_SetCounter(0x00);/* Waits until last write operation on RTC registers has finished. */RTC_WaitForLastTask();}uint32_t Tmp_HH = 0x00, Tmp_MM = 0x00, Tmp_SS = 0x00;Tmp_HH = value / 3600; //小时Tmp_MM = (value % 3600) / 60; //分钟Tmp_SS = (value % 3600) % 60; //秒printf("Time: %0.2d:%0.2d:%0.2d\r", Tmp_HH, Tmp_MM, Tmp_SS);
}/*** @brief 显示实时时间* @param none* @retval none*/
void Time_Show(void)
{while(1){if(time_display == 1){Time_Display(RTC_GetCounter());time_display = 0; //通过RTC中断来置1}}
}
RTC中断处理函数
void RTC_IRQHandler(void)
{if(RTC_GetITStatus(RTC_IT_SEC) != RESET){time_display = 1;RTC_ClearITPendingBit(RTC_IT_SEC);/* Waits until last write operation on RTC registers has finished. */RTC_WaitForLastTask();}
}
主函数
int main(void)
{COM_Init(); //串口初始化,采样数据MyRTC_Init(); //初始化RTCTime_Show(); //在无限循环中显示时间
}
演示结果
使用串口助手手动调节RTC来显示当前时间
完整例程
9-1 RTC