1. RTC模块概述
RTC(Real-Time Clock)是STM32内部独立的实时时钟模块,用于在系统主电源关闭或低功耗模式下维持精确的时间/日期计数。其核心特性包括:
- 独立供电:通过备份域(VBAT引脚)供电,主电源(VDD)掉电后仍可运行。
- 高精度计时:支持外部低速晶振(LSE,32.768 kHz)或内部低速RC振荡器(LSI,≈40 kHz)。
- 多功能计数器:提供秒、分、时、日、月、年等日历功能,以及可编程闹钟和周期性唤醒。
- 低功耗集成:在停止/待机模式下保持活动,支持超低功耗时间跟踪。
2. RTC核心功能
2.1 时钟源选择
- LSE(外部低速晶振)
- 频率:32.768 kHz(精确匹配时间基准,误差±5 ppm)。
- 需外接晶振及负载电容(典型6 pF),适合高精度计时。
- LSI(内部低速RC振荡器)
- 频率:≈40 kHz(精度较低,受温度/电压影响)。
- 无需外部元件,适合成本敏感场景。
- HSE分频(外部高速晶振分频)
- 通过HSE分频(通常128分频)提供时钟,依赖主电源。
2.2 日历与计时
- 32位二进制计数器(RTC_CNT)
以秒为单位递增,通过软件算法转换为日历格式(年/月/日/时/分/秒)。 - 可编程预分频器
- 异步预分频器(RTC_PRER_ASYNC):7位,分频范围1-128。
- 同步预分频器(RTC_PRER_SYNC):15位,分频范围1-32768。
总频率分频公式:
2.3 闹钟与唤醒
- 闹钟寄存器(RTC_ALR)
可设置特定时间或日期触发中断,支持单次/周期性报警。 - 自动唤醒单元(RTC_WUTR)
通过可编程递减计数器(16位)生成周期性中断/事件,唤醒低功耗模式下的系统。
3. 备份域与数据保护
- 备份域(Backup Domain)
- 供电电源:VBAT引脚(1.8-3.6 V)或VDD(主电源有效时自动切换)。
- 包含模块:RTC、42字节备份寄存器(BKP_DR0-BKP_DR41)、侵入检测电路。
- 数据保护机制
- 写保护:修改RTC或备份寄存器前需解除保护(
PWR->CR.DBP = 1
)。 - 侵入检测:TAMPER引脚电平跳变可触发中断并清除备份寄存器数据(防篡改)。
- 写保护:修改RTC或备份寄存器前需解除保护(
4. 中断与事件
- 中断类型
- 闹钟中断(RTC_ALARM):匹配预设时间时触发。
- 唤醒中断(RTC_WAKEUP):周期性计数器归零时触发。
- 秒中断(RTC_SECOND):每秒触发一次(用于时间同步)。
- 侵入检测中断:TAMPER引脚信号触发。
- 事件输出
RTC信号(如闹钟、唤醒)可映射至外部引脚,触发其他外设或唤醒CPU。
5. 校准与精度提升
- 同步校准
通过调整同步预分频器的值补偿时钟误差(如LSI的±12%偏差)。 - 粗调与微调
- 粗调:修改预分频系数(适用于大范围偏差)。
- 微调:通过RTC校准寄存器(RTC_CALR)动态增减时钟周期(精度±1 ppm)。
6. 低功耗模式下的行为
- 停止模式(Stop Mode)
RTC保持运行,计数器继续递增,可配置闹钟或唤醒事件退出低功耗。 - 待机模式(Standby Mode)
RTC保持运行(需VBAT供电),唤醒后系统复位,但RTC计数器值保留。
7.上完整程序模版,复制可用,已详细注释
/* 包含STM32标准外设库头文件 */
#include "stm32f10x.h"
/* 宏定义硬件配置(示例:LSE作为时钟源,备份寄存器DR0存储初始化标志) */
#define RTC_CLOCK_SOURCE RCC_RTCCLKSource_LSE // RTC时钟源:LSE或LSI
#define RTC_ASYNC_PREDIV 0x7F // 异步预分频器(127,分频值=127+1)
#define RTC_SYNC_PREDIV 0x00FF // 同步预分频器(255,分频值=255+1)
#define BACKUP_REG_FLAG BKP_DR0 // 备份寄存器选择(DR0-DR41)
#define INIT_FLAG_VALUE 0xA5A5 // 初始化完成标志
/* 时间结构体类型定义 */
typedef struct {
uint8_t hours; // 小时(0-23)
uint8_t minutes; // 分钟(0-59)
uint8_t seconds; // 秒(0-59)
uint8_t weekday; // 周几(1-7,1=周一)
uint8_t date; // 日(1-31)
uint8_t month; // 月(1-12)
uint16_t year; // 年(0-4095)
} RTC_TimeTypeDef;
/**
* @brief RTC初始化函数(配置时钟源、预分频器、备份域)
* @param 无
* @retval 状态:0-成功,1-LSE启动失败
*/
uint8_t RTC_Init(void) {
/* 1. 使能PWR和BKP时钟(访问备份域前必须开启) */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 解除备份域写保护
/* 2. 检查是否首次初始化(避免重复配置丢失时间) */
if (BKP_ReadBackupRegister(BACKUP_REG_FLAG) != INIT_FLAG_VALUE) {
/* 2.1 配置LSE时钟(若时钟源选择LSE) */
RCC_LSEConfig(RCC_LSE_ON); // 开启LSE晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待LSE就绪
/* 2.2 配置RTC时钟源 */
RCC_RTCCLKConfig(RTC_CLOCK_SOURCE); // 选择时钟源
RCC_RTCCLKCmd(ENABLE); // 使能RTC时钟
/* 2.3 配置预分频器(LSE=32.768kHz,目标1Hz) */
RTC_SetPrescaler(RTC_ASYNC_PREDIV, RTC_SYNC_PREDIV); // 分频系数=(127+1)*(255+1)=32768
/* 2.4 等待RTC寄存器同步 */
RTC_WaitForSynchro(); // 确保寄存器可写入
/* 2.5 设置初始化完成标志 */
BKP_WriteBackupRegister(BACKUP_REG_FLAG, INIT_FLAG_VALUE);
}
/* 3. 使能RTC秒中断(可选) */
RTC_ITConfig(RTC_IT_SEC, ENABLE); // 开启秒中断
NVIC_EnableIRQ(RTC_IRQn); // 使能RTC全局中断
return 0; // 初始化成功
}
/**
* @brief 设置RTC时间
* @param time: 时间结构体指针
* @retval 无
*/
void RTC_SetTime(RTC_TimeTypeDef *time) {
uint32_t counter = 0;
/* 将日历时间转换为秒计数器(简化版,忽略闰年) */
counter += (time->year - 1970) * 31536000; // 年转换为秒(非精确)
counter += (time->month - 1) * 2592000; // 月转换为秒
counter += (time->date - 1) * 86400; // 日转换为秒
counter += time->hours * 3600; // 小时转秒
counter += time->minutes * 60; // 分钟转秒
counter += time->seconds; // 秒
/* 写入计数器值 */
RTC_SetCounter(counter); // 设置RTC计数器
}
/**
* @brief 读取RTC时间
* @param time: 时间结构体指针
* @retval 无
*/
void RTC_GetTime(RTC_TimeTypeDef *time) {
uint32_t counter = RTC_GetCounter(); // 获取当前计数器值
/* 将秒计数器转换为日历时间(简化版,忽略闰年) */
time->year = 1970 + (counter / 31536000);
counter %= 31536000;
time->month = 1 + (counter / 2592000);
counter %= 2592000;
time->date = 1 + (counter / 86400);
counter %= 86400;
time->hours = counter / 3600;
counter %= 3600;
time->minutes = counter / 60;
time->seconds = counter % 60;
}
/********************* 模板使用示例 *********************
// RTC中断服务函数(需在stm32f10x_it.c中实现)
void RTC_IRQHandler(void) {
if (RTC_GetITStatus(RTC_IT_SEC) != RESET) {
RTC_ClearITPendingBit(RTC_IT_SEC); // 清除秒中断标志
// 处理每秒触发的事件(如刷新显示)
}
}
int main(void) {
RTC_TimeTypeDef time = {
.year = 2023, .month = 10, .date = 1,
.hours = 12, .minutes = 0, .seconds = 0
};
SystemInit();
RTC_Init(); // 初始化RTC
if (BKP_ReadBackupRegister(BACKUP_REG_FLAG) != INIT_FLAG_VALUE) {
RTC_SetTime(&time); // 首次上电设置时间
}
while(1) {
RTC_GetTime(&time); // 读取当前时间
// 执行其他任务...
}
}
********************************************************/
8.模板程序功能说明
-
模块化设计
RTC_Init()
:处理时钟源选择、预分频器配置及备份域初始化。RTC_SetTime()
/RTC_GetTime()
:实现日历时间与RTC计数器的双向转换。- 中断支持:集成秒中断配置框架,需用户实现
RTC_IRQHandler
。
-
首次初始化检测
通过备份寄存器保存初始化标志,防止复位后重复配置导致时间重置。
9.关键参数详解
-
预分频器配置
- 异步预分频(RTC_ASYNC_PREDIV):7位值,范围0-127,分频系数=值+1。
- 同步预分频(RTC_SYNC_PREDIV):15位值,范围0-32767,分频系数=值+1。
- 总频率计算:fRTC=(ASYNC+1)×(SYNC+1)fsource示例:LSE=32768Hz → (127+1)*(255+1)=32768 → 1Hz。
-
备份域操作
- 写保护解除:必须调用
PWR_BackupAccessCmd(ENABLE)
才能修改备份寄存器。 - 数据持久化:VBAT供电时,备份寄存器内容在掉电后仍保留。
- 写保护解除:必须调用
-
时间结构体成员
- weekday:1(周一)至7(周日),需用户根据日期计算。
- 闰年处理:模板简化了日期计算,实际应用需添加闰年判断逻辑。
10.使用注意事项
-
硬件连接
- 使用LSE时,需在OSC32_IN/OSC32_OUT引脚接32.768kHz晶振及负载电容(通常6-12pF)。
- VBAT引脚需连接备用电源(如CR2032纽扣电池)。
-
中断配置
- 需在
stm32f10x_it.c
中实现RTC_IRQHandler
,处理秒/闹钟中断事件。 - 唤醒中断需额外配置
RTC_WakeUpCmd()
和RTC_ITConfig(RTC_IT_WUT)
。
- 需在
-
日历精度优化
- 添加闰年补偿:在
RTC_SetTime()
和RTC_GetTime()
中增加闰年判断逻辑。 - 使用NTP同步:通过网络或GPS定期校准RTC时间。
- 添加闰年补偿:在
-
低功耗模式兼容性
- 进入停止模式前,确保RTC时钟源(LSE/LSI)保持活动状态。
- 待机模式唤醒后,需重新初始化部分外设,但RTC计数器保持有效。
11. 应用场景
- 实时时钟系统:电子表、智能仪表的时间显示与记录。
- 数据日志记录:结合备份寄存器存储带时间戳的传感器数据。
- 定时任务调度:工业控制器中的周期性操作(如每日启停设备)。
- 低功耗设备:电池供电设备(如IoT节点)的间歇性唤醒与数据上传。
- 安全监控:通过侵入检测记录非法物理访问事件。