第十三章 RTC 实时时钟
第十三章 RTC 实时时钟
目录
第十三章 RTC 实时时钟
1 RTC简介
1.1 主要特性
2 功能描述
2.1 概述
2.2 复位过程
2.3 读RTC寄存器
2.4 配置RTC寄存器
2.5 RTC标志的设置
3 RTC寄存器描述
3.1 RTC控制寄存器高位(RTC_CRH)
3.2 RTC控制寄存器低位(RTC_CRL)
3.3 RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL)
3.4 RTC预分频器余数寄存器(RTC_DIVH/RTC_DIVL)
3.5 RTC计数器寄存器(RTC_CNTH/RTC_CNTL)
3.6 RTC闹钟寄存器(RTC_ALRH/RTC_ALRL)
3.7 RTC寄存器映像
4 程序设计
4.1 RTC_Calendar例程
4.2 RTC_LSICalib例程
5 下载验证
5.1 RTC_Calendar例程
5.2 RTC_LSICalib例程
本章分为如下几个小节:
1 RTC简介
2 功能描述
3 RTC寄存器描述
4 程序设计
5 下载验证
1 RTC简介
实时时钟是一个独立的定时器。RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC 模块和时钟配置系统(RCC_BDCR 寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC 的设置和时间维持不变。系统复位后,对后备寄存器和 RTC 的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。
执行以下操作将使能对后备寄存器和 RTC 的访问:
- 设置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位,使能电源和后备接口时钟
- 设置寄存器 PWR_CR 的 DBP 位,使能对后备寄存器和 RTC 的访问。
1.1 主要特性
W55MH32的RTC的主要特性如下:
- 可编程的预分频系数:分频系数最高为 220。
- 32 位的可编程计数器,可用于较长时间段的测量。
- 2 个分离的时钟:用于 APB1 接口的 PCLK1 和 RTC 时钟(RTC 时钟的频率必须小于 PCLK1 时钟频率的四分之一以上)。
- 可以选择以下三种 RTC 的时钟源:
- HSE 时钟除以 128;
- LSE 振荡器时钟;
- LSI 振荡器时钟(详见 6.2.8 节 RTC 时钟)。
- 2 个独立的复位类型:
- APB1 接口由系统复位;
- RTC 核心(预分频器、闹钟、计数器和分频器)只能由后备域复位(详见 6.1.3 节)。
- 3 个专门的可屏蔽中断:
- 闹钟中断,用来产生一个软件可编程的闹钟中断。
- 秒中断,用来产生一个可编程的周期性中断信号(最长可达 1 秒)。
- 溢出中断,指示内部可编程计数器溢出并回转为 0 的状态。
2 功能描述
2.1 概述
RTC 由两个主要部分组成(参见下图)。第一部分(APB1 接口)用来和 APB1 总线相连。此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线接口。
另一部分(RTC 核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是 RTC 的预分频模块,它可编程产生最长为 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20 位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个TR_CLK 周期中 RTC 产生一个中断(秒中断)。第二个模块是一个 32 位的可编程计数器,可被初始化为当前的系统时间。系统时间按 TR_CLK 周期累加并与存储在 RTC_ALR 寄存器中的可编程时间相比较,如果 RTC_CR 控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。
简化的 RTC 框图
2.2 复位过程
除了 RTC_PRL、RTC_ALR、RTC_CNT 和 RTC_DIV 寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
2.3 读RTC寄存器
RTC 核完全独立于 RTCAPB1 接口。
软件通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值。但是,相关的可读寄存器只在与RTCAPB1 时钟进行重新同步的 RTC 时钟的上升沿被更新。RTC 标志也是如此的。这意味着,如果 APB1 接口曾经被关闭,而读操作又是在刚刚重新开启 APB1 之后,则在第一次的内部寄存器更新之前,从 APB1 上读出的 RTC 寄存器数值可能被破坏了(通常读到 0)。下述几种情况下能够发生这种情形:
发生系统复位或电源复位
系统刚从待机模式唤醒(参见第 18.3节:低功耗模式)。
系统刚从停机模式唤醒(参见第 18.3节:低功耗模式)。
所有以上情况中,APB1 接口被禁止时(复位、无时钟或断电)RTC 核仍保持运行状态。
因此,若在读取 RTC 寄存器时,RTC 的 APB1 接口曾经处于禁止状态,则软件首先必须等待RTC_CRL 寄存器中的 RSF 位(寄存器同步标志)被硬件置'1')。
注: RTC 的 APB1 接口不受 WFI 和 WFE 等低功耗模式的影响。
2.4 配置RTC寄存器
必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入 RTC_PRL、RTC_CNT、RTC_ALR 寄存器。
另外,对 RTC 任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询 RTC_CR寄存器中的 RTOFF 状态位,判断 RTC 寄存器是否处于更新中。仅当 RTOFF 状态位是'1'时,才可以写入 RTC 寄存器。
配置过程:
1. 查询 RTOFF 位,直到 RTOFF 的值变为'1'
2. 置 CNF 值为 1,进入配置模式
3. 对一个或多个 RTC 寄存器进行写操作
4. 清除 CNF 标志位,退出配置模式
5. 查询 RTOFF,直至 RTOFF 位变为'1'以确认写操作已经完成。仅当 CNF 标志位被清除时,写操作才能进行,这个过程至少需要 3 个 RTCCLK 周期。
2.5 RTC标志的设置
在每一个 RTC 核心的时钟周期中,更改 RTC 计数器之前设置 RTC 秒标志(SECF)。在计数器到达0x0000 之前的最后一个 RTC 时钟周期中,设置 RTC 溢出标志(OWF)。
在计数器的值到达闹钟寄存器的值加 1(RTC_ALR+1)之前的 RTC 时钟周期中,设置 RTC_Alarm 和RTC 闹钟标志(ALRF)。对 RTC 闹钟的写操作必须使用下述过程之一与 RTC 秒标志同步:
使用 RTC 闹钟中断,并在中断处理程序中修改 RTC 闹钟和/或 RTC 计数器。
等待 RTC 控制寄存器中的 SECF 位被设置,再更改 RTC 闹钟和/或 RTC 计数器。
RTC 秒和闹钟波形图示例,PR=0003,ALARM=00004
RTC 溢出波形图示例,PR=0003
3 RTC寄存器描述
3.1 RTC控制寄存器高位(RTC_CRH)
偏移地址:0x00
复位值:0x0000
这些位用来屏蔽中断请求。注意:系统复位后所有的中断被屏蔽,因此可通过写 RTC 寄存器来确保在初始化后没有挂起的中断请求。当外设正在完成前一次写操作时(标志位 RTOFF=0),不能对RTC_CRH 寄存器进行写操作。RTC 功能由这个控制寄存器控制。
3.2 RTC控制寄存器低位(RTC_CRL)
偏移地址:0x00
复位值:0x0000
注:1.任何标志位都将保持挂起状态,直到适当的 RTC_CR 请求位被软件复位,表示所请求的中断已经被接受。
2.在复位时禁止所有中断,无挂起的中断请求,可以对 RTC 寄存器进行写操作。
3.当 APB1 时钟不运行时,OWF、ALRF、SECF 和 RSF 位不被更新。
4.OWF、ALRF、SECF 和 RSF 位只能由硬件置位,由软件来清零。
5.若 ALRF=1 且 ALRIE=1,则允许产生 RTC 全局中断。如果在 EXTI 控制器中允许产生 EXTI 线 17 中断,则允许产生 RTC 全局中断和 RTC 闹钟中断。
6.若 ALRF=1,如果在 EXTI 控制器中设置了 EXTI 线 17 的中断模式,则允许产生 RTC 闹钟中断;如果在 EXTI 控制器中设置了 EXTI 线 17 的事件模式,则这条线上会产生一个脉冲(不会产生 RTC闹钟中断)。
3.3 RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL)
预分频装载寄存器用来保存 RTC 预分频器的周期计数值。它们受 RTC_CR 寄存器的 RTOFF 位保护,仅当 RTOFF 值为'1'时允许进行写操作。
RTC 预分频装载寄存器高位(RTC_PRLH)
偏移地址:0x08
复位值:0x0000
RTC 预分频装载寄存器低位(RTC_PRLL)
偏移地址:0x0C
复位值:0x8000
注: 如果输入时钟频率(fRTCCLK)为 32.768 千赫,则在此寄存器中写入 7FFFh 以获得 1 秒的信号周期
3.4 RTC预分频器余数寄存器(RTC_DIVH/RTC_DIVL)
在 TR_CLK 的每个周期里,RTC 预分频器中计数器的值都会被重新设置为 RTC_PRL 寄存器的值。用户可通过读取 RTC_DIV 寄存器,以获得预分频计数器的当前值,而不停止分频计数器的工作,从而获得精确的时间测量。此寄存器是只读寄存器,其值在 RTC_PRL 或 RTC_CNT 寄存器中的值发生改变后,由硬件重新装载。
RTC 预分频器余数寄存器高位(RTC_DIVH)
偏移地址:0x10
复位值:0x0000
RTC 预分频器余数寄存器低位(RTC_DIVL)
偏移地址:0x14
复位值:0x8000
3.5 RTC计数器寄存器(RTC_CNTH/RTC_CNTL)
RTC 核有一个 32 位可编程的计数器,可通过两个 16 位的寄存器访问。计数器以预分频器产生的TR_CLK时间基准为参考进行计数。RTC_CNT寄存器用来存放计数器的计数值。他们受 RTC_CR的位RTOFF写保护,仅当RTOFF值为'1'时,允许写操作。在高或低寄存器(RTC_CNTH或RTC_CNTL)上的写操作,能够直接装载到相应的可编程计数器,并且重新装载 RTC 预分频器。当进行读操作时,直接返回计数器内的计数值(系统时间)。
RTC 计数器寄存器高位(RTC_CNTH)
偏移地址:0x18
复位值:0x0000
RTC 计数器寄存器低位(RTC_CNTL)
偏移地址:0x1C
复位值:0x0000
3.6 RTC闹钟寄存器(RTC_ALRH/RTC_ALRL)
当可编程计数器的值与 RTC_ALR 中的 32 位值相等时,即触发一个闹钟事件,并且产生 RTC 闹钟中断。此寄存器受 RTC_CR 寄存器里的 RTOFF 位写保护,仅当 RTOFF 值为'1'时,允许写操作。
RTC 闹钟寄存器高位(RTC_ALRH)
偏移地址:0x20
复位值:0xFFFF
RTC 闹钟寄存器低位(RTC_ALRL)
偏移地址:0x24
复位值:0xFFFF
3.7 RTC寄存器映像
RTC-寄存器映像和复位值
4 程序设计
4.1 RTC_Calendar例程
1.初始化部分:延时初始化:调用delay_init()函数,虽未展示其具体实现,但为程序提供时间相关基础功能。
- 时钟配置:RCC_ClkConfiguration()函数对系统时钟进行配置。先复位 RCC,开启 HSE 并等待其就绪,配置 PLL 为 HSE 经 1 分频后输入,9 倍频输出,使能 PLL 并等待其就绪,选择 PLLCLK 作为系统时钟源,同时设置 HCLK、PCLK1 和 PCLK2 的分频因子,还开启 LSI 和 HSI。
- 串口初始化:UART_Configuration()函数配置 USART1 串口。使能 USART1 和 GPIOA 时钟,将 PA9 配置为复用推挽输出(TX),PA10 配置为浮空输入(RX),设置波特率为 115200,数据位 8 位,停止位 1 位,无校验,无硬件流控制,工作模式为收发模式,最后使能串口。
- NVIC 配置:NVIC_Configuration()函数设置 NVIC 优先级分组为 1,使能 RTC 中断,设置其抢占优先级为 1,子优先级为 0。
- RTC 配置:RTC_Configuration()函数配置 RTC。使能 PWR 和 BKP 时钟,允许访问备份区域,对 BKP 进行初始化,开启 LSI 并等待其就绪,选择 LSI 作为 RTC 时钟源,使能 RTC 时钟并等待同步,设置 RTC 秒中断,设置预分频器为 32767,使 RTC 周期为 1 秒。
2.主循环部分:在main()函数中,先进行延时、时钟配置,接着配置串口,输出 “RTC Calendar Test.” 及系统时钟频率信息。检查 BKP 寄存器值判断 RTC 是否已配置,若未配置则进行 RTC 配置并设置时间,若已配置则判断复位类型并根据情况处理。最后调用Time_Show()函数进入一个循环,当TimeDisplay为 1 时显示当前时间。
3.时间调整与显示部分:时间调整:Time_Adjust()函数调用Time_Regulate()函数获取用户通过串口输入的时分秒,然后设置 RTC 计数器。
// 时间调整函数(通过串口输入设置时间)
void Time_Adjust(void)
{RTC_WaitForLastTask();RTC_SetCounter(Time_Regulate()); // 调用输入函数获取时间并设置RTC计数器RTC_WaitForLastTask();
}
// 时间输入函数(串口交互获取时分秒)
uint32_t Time_Regulate(void)
{uint32_t Tmp_HH, Tmp_MM, Tmp_SS;// 输入小时(0-23)printf("\r\n Please Set Hours");while ((Tmp_HH = USART_Scanf(23)) == 0xFF);// 输入分钟(0-59)printf("\r\n Please Set Minutes");while ((Tmp_MM = USART_Scanf(59)) == 0xFF);// 输入秒(0-59)printf("\r\n Please Set Seconds");while ((Tmp_SS = USART_Scanf(59)) == 0xFF);return (Tmp_HH * 3600 + Tmp_MM * 60 + Tmp_SS); // 返回总秒数
}
// 时间显示函数(实时打印时间)
void Time_Show(void)
{printf("\n\r");while (1){if (TimeDisplay == 1) // TimeDisplay由RTC中断触发置1{Time_Display(RTC_GetCounter()); // 转换并显示时间TimeDisplay = 0;}}
}
// 时间格式化显示
void Time_Display(uint32_t TimeVar)
{uint32_t THH = TimeVar / 3600;uint32_t TMM = (TimeVar % 3600) / 60;uint32_t TSS = TimeVar % 60;// 计数器溢出处理(约24小时归零)if (RTC_GetCounter() == 0x0001517F) {RTC_SetCounter(0);RTC_WaitForLastTask();}printf("Time: %02d:%02d:%02d\n", THH, TMM, TSS);
}
时间显示:Time_Show()函数进入一个循环,当TimeDisplay为 1 时,调用Time_Display()函数获取 RTC 计数器的值并转换为时、分、秒格式进行显示。同时,Time_Display()函数还会在 RTC 计数器达到特定值(0x0001517F)时将其重置为 0。
4.串口输入处理部分:USART_Scanf()函数用于从串口接收用户输入的数字,检查输入是否为有效数字,若无效则提示用户重新输入,直到输入有效数字。
4.2 RTC_LSICalib例程
1.初始化部分:延时初始化:调用delay_init()函数,虽未给出其具体实现,但为程序提供时间相关的基础功能。
// 1. 延时初始化(依赖delay.h实现)
delay_init();// 1.a 串口初始化
void UART_Configuration(uint32_t bound)
{// 使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);// 配置TX/RX引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // PA9-TXGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // PA10-RXGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置串口参数USART_InitStructure.USART_BaudRate = bound; // 115200bpsUSART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART1, &USART_InitStructure);USART_Cmd(USART1, ENABLE);
}// 1.b RTC配置
void RTC_Configuration(void)
{// 使能电源和备份域时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);PWR_BackupAccessCmd(ENABLE);BKP_DeInit(); // 复位备份域// 配置RTC时钟源(LSI)RCC_LSICmd(ENABLE);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro(); // 等待RTC寄存器同步RTC_ITConfig(RTC_IT_SEC, ENABLE); // 使能秒中断RTC_SetPrescaler(40000); // 预设预分频值(假设LSI=40kHz)// 配置RTC输出BKP_TamperPinCmd(DISABLE);BKP_RTCOutputConfig(BKP_RTCOutputSource_Second);
}// 1.c 定时器配置(TIM5用于测量LSI频率)
void TIM_Configuration(void)
{// 使能时钟和引脚重映射RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);GPIO_PinRemapConfig(GPIO_Remap_TIM5CH4_LSI, ENABLE);// 配置TIM5时基TIM_TimeBaseStructure.TIM_Prescaler = 0;TIM_TimeBaseStructure.TIM_Period = 0xFFFF;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);// 配置TIM5通道4为输入捕获模式TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;TIM_ICInitStructure.TIM_ICFilter = 0;TIM_ICInit(TIM5, &TIM_ICInitStructure);// 使能TIM5和捕获中断TIM_Cmd(TIM5, ENABLE);TIM5->SR = 0; // 清除中断标志TIM_ITConfig(TIM5, TIM_IT_CC4, ENABLE);
}// 1.d NVIC中断配置
void NVIC_Configuration(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 优先级分组1// 配置RTC中断(最高优先级)NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 配置TIM5中断(次高优先级)NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}
串口初始化:UART_Configuration()函数配置 USART1 串口。使能 USART1 和 GPIOA 时钟,将 PA9 配置为复用推挽输出(用于 TX 发送),PA10 配置为浮空输入(用于 RX 接收)。设置串口波特率为 115200,数据位 8 位,停止位 1 位,无校验位,无硬件流控制,工作模式为同时接收和发送,并最终使能串口。
RTC 配置:RTC_Configuration()函数对 RTC 进行配置。使能 PWR 和 BKP 时钟,允许访问备份区域,对 BKP 进行初始化。开启 LSI,选择 LSI 作为 RTC 时钟源,使能 RTC 时钟并等待同步,设置 RTC 秒中断,设置预分频器为 40000,禁用 BKP 篡改引脚,配置 RTC 输出为秒信号。
定时器配置:TIM_Configuration()函数对 TIM5 进行配置。使能 AFIO 和 TIM5 时钟,重映射 TIM5 通道 4 到 LSI。设置 TIM5 的预分频器为 0,计数模式为向上计数,周期为 0xFFFF,时钟分频为 1。配置 TIM5 通道 4 为输入捕获模式,上升沿触发,直接映射到 TI,不分频,无滤波。使能 TIM5,清除状态寄存器,使能 TIM5 通道 4 捕获中断。
NVIC 配置:NVIC_Configuration()函数设置 NVIC 优先级分组为 1。使能 RTC 中断,设置其抢占优先级为 0,子优先级为 0;使能 TIM5 中断,设置子优先级为 2。
2.主循环部分:在main()函数中,先进行延时初始化,接着配置串口,输出 “RTC LSI Calib Test.” 及系统时钟频率信息。然后进行 RTC、TIM5 配置和 NVIC 配置。等待OperationComplete变为 2,这意味着 TIM5 测量操作完成。当OperationComplete为 2 时,计算 LSI 的实际频率,并根据该频率设置 RTC 的预分频器,最后进入无限循环while(1)。
int main(void)
{// 系统初始化delay_init();UART_Configuration(115200);printf("RTC LSI Calib Test.\n");RCC_GetClocksFreq(&clocks);printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",(float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,(float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000, (float)clocks.ADCCLK_Frequency / 1000000);// 外设初始化RTC_Configuration();TIM_Configuration();NVIC_Configuration();// 2. 等待TIM5测量完成(OperationComplete=2)while (OperationComplete != 2);// 计算LSI实际频率并校准RTCif (PeriodValue != 0){// LSI频率 = (2*PCLK1)/PeriodValueLsiFreq = (uint32_t)((uint32_t)(clocks.PCLK1_Frequency * 2) / (uint32_t)PeriodValue);}printf("LsiFreq: %d Hz\n", LsiFreq);// 根据测量结果校准RTC预分频器RTC_SetPrescaler(LsiFreq - 1);RTC_WaitForLastTask();// 无限循环while (1);
}
3.辅助函数部分:IncrementVar_OperationComplete()函数使OperationComplete变量自增,并返回自增前的值。
// 3.a 递增OperationComplete并返回旧值
uint32_t IncrementVar_OperationComplete(void)
{OperationComplete++;return (uint32_t)(OperationComplete - 1);
}// 3.b 获取OperationComplete值
uint32_t GetVar_OperationComplete(void)
{return (uint32_t)OperationComplete;
}// 3.c 设置PeriodValue值
void SetVar_PeriodValue(uint32_t Value)
{PeriodValue = (uint32_t)(Value);
}
GetVar_OperationComplete()函数返回OperationComplete变量的值。
SetVar_PeriodValue()函数设置PeriodValue变量的值。