目录
一、引言
二、RTC 概述
三、RTC 的工作原理
1.时钟源
2.计数器
3.闹钟功能
4.备份寄存器
四、RTC 寄存器
1.RTC_TR(Time Register,时间寄存器)
2.RTC_DR(Date Register,日期寄存器)
3.RTC_SSR(Subsecond Register,亚秒寄存器)
4.RTC_PRER(Prescaler Register,预分频器寄存器)
5.RTC_CR(Control Register,控制寄存器)
6.RTC_ISR(Interrupt Status Register,中断状态寄存器)
7.RTC_BKPxR(Backup Register,备份寄存器)
五、实际应用
1.时间记录
2.定时任务
3.低功耗模式下的时间保持
六、代码实现
七、总结
在嵌入式系统中,准确的时间信息往往是至关重要的。STM32 系列微控制器内置的实时时钟(RTC)模块为系统提供了可靠的时间基准。本文将深入探讨 STM32 的 RTC 的功能、工作原理以及在实际应用中的使用方法
RTC 是一种能够独立运行的时钟模块,即使在微控制器进入低功耗模式时也能保持时间的准确性。STM32 的 RTC 具有以下主要特点:
- 功能:用于存储当前的时间信息,包括小时、分钟、秒等。
- 位定义:
pm
:占 1 位,用于表示上午 / 下午(AM/PM)符号,0 表示 AM/24 小时制,1 表示 PM。ht(1:0)
:占 2 位,是小时的十位部分。hu(3:0)
:占 4 位,是小时的个位部分。mnt(2:0)
:占 3 位,为分钟的十位部分。mnu(3:0)
:占 4 位,是分钟的个位部分。st(2:0)
:占 3 位,代表秒的十位部分。su(3:0)
:占 4 位,是秒的个位部分。- 操作注意事项:数据是以 BCD 码格式存储的,读取之后需要进行转换才能得到常规的十进制时间数据。在初始化模式下,对该寄存器进行写操作可以设置时间。
- 功能:保存当前的日期信息,包含年、月、日、星期等。
- 位定义:
yt(1:0)
:占 2 位,是年份的十位部分。yu(3:0)
:占 4 位,为年份的个位部分。wdu(2:0)
:占 3 位,表示星期几的个位,000 表示禁止,001 表示星期一,以此类推,111 表示星期日。mt
:占 1 位,是月份的十位部分。mu
:占 4 位,为月份的个位部分。dt(1:0)
:占 2 位,代表日期的十位部分。du(3:0)
:占 4 位,是日期的个位部分。- 操作要点:同样以 BCD 码格式存储数据,写操作可设置日期。
- 功能:用于记录亚秒值,能够提供更高精度的时间信息,通常用于精确到毫秒或更短时间单位的时间记录。
- 计算亚秒值:亚秒值的计算需要结合同步预分频器的值。公式为亚秒值 =
(prediv_s – ss(15:0)) / (prediv_s + 1)
,其中ss(15:0)
是同步预分频器计数器的值,prediv_s
是同步预分频器的值。
- 组成:由 7 位的异步预分频器
apre
和 15 位的同步预分频器spre
组成。- 功能:对输入的时钟源进行分频,以得到合适的时钟频率用于 RTC 的时间和日期更新。异步预分频器时钟
ck_apre
用于为二进制RTC_SSR
亚秒递减计数器提供时钟,同步预分频器时钟ck_spre
用于更新日历。- 时钟频率计算:异步预分频器时钟
fck_apre = frtc_clk / (prediv_a + 1)
,同步预分频器时钟fck_spre = frtc_clk / (prediv_s + 1)
。为了最大程度降低功耗,一般将异步预分频器配置为较高的值。
- 功能:用于控制 RTC 的各种功能,如开启或关闭 RTC、设置闹钟使能、选择时钟源等。
- 具体位的作用:不同的位具有不同的功能,例如可能有位用于使能闹钟输出、配置闹钟输出的极性、设置时间戳功能等。
VDD
电源关闭时通过 VBAT
保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在器件从待机模式唤醒时复位。它可用于在掉电等情况下保存一些关键数据。在低功耗应用中,当微控制器进入低功耗模式时,RTC 可以继续运行,保持时间的准确性。当系统从低功耗模式唤醒时,可以通过 RTC 获取当前的时间信息,无需重新初始化时间。
以下是一个使用 STM32F429 的 RTC(实时时钟)的代码示例:
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
void RTC_Init(void)
{
// 使能电源时钟和备份区域时钟
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKPSRAM_CLK_ENABLE();
// 允许访问备份寄存器
HAL_PWR_EnableBkUpAccess();
// 检查是否已经配置过 RTC
if ((RCC->BDCR & RCC_BDCR_RTCEN)!= 0)
{
// RTC 已经配置过,无需再次初始化
return;
}
// 选择 LSE 作为 RTC 时钟源
__HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW);
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK)
{
Error_Handler();
}
// 使能 RTC 时钟
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct)!= HAL_OK)
{
Error_Handler();
}
// 初始化 RTC
RTC_HandleTypeDef hrtc;
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 0x7F;
hrtc.Init.SynchPrediv = 0xFF;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
if (HAL_RTC_Init(&hrtc)!= HAL_OK)
{
Error_Handler();
}
}
void RTC_SetTime(uint8_t hour, uint8_t minute, uint8_t second)
{
RTC_TimeTypeDef sTime = {0};
sTime.Hours = hour;
sTime.Minutes = minute;
sTime.Seconds = second;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN)!= HAL_OK)
{
Error_Handler();
}
}
void RTC_SetDate(uint8_t year, uint8_t month, uint8_t day)
{
RTC_DateTypeDef sDate = {0};
sDate.Year = year;
sDate.Month = month;
sDate.Date = day;
sDate.WeekDay = RTC_WEEKDAY_MONDAY;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN)!= HAL_OK)
{
Error_Handler();
}
}
void RTC_GetTime(uint8_t *hour, uint8_t *minute, uint8_t *second)
{
RTC_TimeTypeDef sTime;
if (HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN)!= HAL_OK)
{
Error_Handler();
}
*hour = sTime.Hours;
*minute = sTime.Minutes;
*second = sTime.Seconds;
}
void RTC_GetDate(uint8_t *year, uint8_t *month, uint8_t *day)
{
RTC_DateTypeDef sDate;
if (HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN)!= HAL_OK)
{
Error_Handler();
}
*year = sDate.Year;
*month = sDate.Month;
*day = sDate.Date;
}
void Error_Handler(void)
{
// 处理错误
while (1)
{
}
}
在主函数中,可以这样调用:
int main(void)
{
HAL_Init();
RTC_Init();
// 设置时间为 12:30:00
RTC_SetTime(12, 30, 0);
// 设置日期为 2023 年 9 月 14 日
RTC_SetDate(23, 9, 14);
uint8_t hour, minute, second;
uint8_t year, month, day;
// 获取时间和日期并打印
RTC_GetTime(&hour, &minute, &second);
RTC_GetDate(&year, &month, &day);
while (1)
{
}
}
STM32 的 RTC 模块为嵌入式系统提供了可靠的时间基准。通过了解 RTC 的工作原理、配置方法和应用场景,开发者可以充分利用 RTC 的功能,为系统添加时间记录、定时任务等功能,提高系统的实用性和可靠性。在使用 RTC 时,需要注意时钟源的选择、时间的初始化和校准以及备份寄存器的使用等问题,以确保 RTC 的正常运行和数据的安全性。
希望本文对大家在使用 STM32 的 RTC 模块时有所帮助。