STM32 RTC程序,包含初始化、时间设置、闹钟、日历、时间戳转换和低功耗唤醒等功能。
一、RTC基础与配置
1.1 RTC核心概念
c
/* RTC 核心数据结构定义 */
#include "stm32f1xx_hal.h"
#include <time.h>
#include <stdio.h>
// RTC时间结构体
typedef struct {
uint8_t hours; // 时 (0-23)
uint8_t minutes; // 分 (0-59)
uint8_t seconds; // 秒 (0-59)
uint8_t weekday; // 星期 (1-7, 1=Monday)
} RTC_TimeTypeDef;
// RTC日期结构体
typedef struct {
uint8_t day; // 日 (1-31)
uint8_t month; // 月 (1-12)
uint16_t year; // 年 (2000-2099)
} RTC_DateTypeDef;
// 闹钟结构体
typedef struct {
uint8_t alarm_hours; // 闹钟时
uint8_t alarm_minutes; // 闹钟分
uint8_t alarm_seconds; // 闹钟秒
uint8_t alarm_enabled; // 闹钟使能
uint8_t alarm_weekday; // 闹钟星期
} RTC_AlarmTypeDef;
// 全局RTC句柄
RTC_HandleTypeDef hrtc;
1.2 RTC初始化函数
c
/* RTC初始化 */
void RTC_Init(void) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
// 使能PWR时钟
__HAL_RCC_PWR_CLK_ENABLE();
// 使能备份域写访问
HAL_PWR_EnableBkUpAccess();
// 使能LSE(外部32.768kHz晶振)
__HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
// 等待LSE就绪
uint32_t timeout = 0;
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) {
timeout++;
if (timeout > 0xFFFFF) {
// LSE启动失败,使用LSI
__HAL_RCC_LSE_CONFIG(RCC_LSE_OFF);
__HAL_RCC_LSI_ENABLE();
// 等待LSI就绪
while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET);
// 配置RTC时钟源为LSI
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI);
break;
}
}
if (timeout <= 0xFFFFF) {
// 配置RTC时钟源为LSE
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
}
// 使能RTC时钟
__HAL_RCC_RTC_ENABLE();
// 初始化RTC
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = 0x7F; // 异步分频
hrtc.Init.SynchPrediv = 0xFF; // 同步分频
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_SECOND; // 输出秒脉冲
if (HAL_RTC_Init(&hrtc) != HAL_OK) {
Error_Handler();
}
// 设置初始时间(2024年1月1日 00:00:00 星期一)
sTime.Hours = 0;
sTime.Minutes = 0;
sTime.Seconds = 0;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
Error_Handler();
}
sDate.WeekDay = RTC_WEEKDAY_MONDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 1;
sDate.Year = 24; // 2024年
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) {
Error_Handler();
}
printf("RTC Initialized Successfully\n");
}
/* 配置RTC时钟源 */
void RTC_ClockConfig(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
// 配置LSE
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
// 配置RTC时钟
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
Error_Handler();
}
}
二、时间与日期管理
2.1 时间设置与读取
c
/* 设置RTC时间 */
void RTC_SetTime(uint8_t hour, uint8_t min, uint8_t sec) {
RTC_TimeTypeDef sTime = {0};
// 参数检查
if (hour >= 24 || min >= 60 || sec >= 60) {
printf("Error: Invalid time parameters\n");
return;
}
sTime.Hours = hour;
sTime.Minutes = min;
sTime.Seconds = sec;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
printf("Error: Failed to set time\n");
} else {
printf("Time set to: %02d:%02d:%02d\n", hour, min, sec);
}
}
/* 读取RTC时间 */
void RTC_GetTime(uint8_t *hour, uint8_t *min, uint8_t *sec) {
RTC_TimeTypeDef sTime = {0};
if (HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
printf("Error: Failed to get time\n");
return;
}
*hour = sTime.Hours;
*min = sTime.Minutes;
*sec = sTime.Seconds;
// 12小时制转换
uint8_t am_pm = 0; // 0=AM, 1=PM
if (*hour > 12) {
*hour -= 12;
am_pm = 1;
} else if (*hour == 0) {
*hour = 12;
}
printf("Current time: %02d:%02d:%02d %s\n",
*hour, *min, *sec, am_pm ? "PM" : "AM");
}
/* 设置RTC日期 */
void RTC_SetDate(uint16_t year, uint8_t month, uint8_t day, uint8_t weekday) {
RTC_DateTypeDef sDate = {0};
// 参数检查
if (year < 2000 || year > 2099) {
printf("Error: Year must be 2000-2099\n");
return;
}
if (month < 1 || month > 12) {
printf("Error: Month must be 1-12\n");
return;
}
if (day < 1 || day > 31) {
printf("Error: Day must be 1-31\n");
return;
}
if (weekday < 1 || weekday > 7) {
printf("Error: Weekday must be 1-7\n");
return;
}
sDate.Year = year - 2000; // 转换为RTC格式
sDate.Month = month;
sDate.Date = day;
sDate.WeekDay = weekday;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) {
printf("Error: Failed to set date\n");
} else {
const char *weekdays[] = {"", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
printf("Date set to: %s %04d-%02d-%02d\n",
weekdays[weekday], year, month, day);
}
}
/* 读取RTC日期 */
void RTC_GetDate(uint16_t *year, uint8_t *month, uint8_t *day, uint8_t *weekday) {
RTC_DateTypeDef sDate = {0};
if (HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) {
printf("Error: Failed to get date\n");
return;
}
*year = 2000 + sDate.Year; // 转换回完整年份
*month = sDate.Month;
*day = sDate.Date;
*weekday = sDate.WeekDay;
const char *weekdays[] = {"", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"};
printf("Current date: %s, %04d-%02d-%02d\n",
weekdays[*weekday], *year, *month, *day);
}
2.2 时间格式转换
c
/* 将RTC时间转换为字符串 */
void RTC_TimeToString(char *buffer, uint8_t format_24h) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
if (format_24h) {
// 24小时制
sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d",
2000 + sDate.Year, sDate.Month, sDate.Date,
sTime.Hours, sTime.Minutes, sTime.Seconds);
} else {
// 12小时制
uint8_t hour = sTime.Hours;
const char *ampm = "AM";
if (hour >= 12) {
ampm = "PM";
if (hour > 12) hour -= 12;
} else if (hour == 0) {
hour = 12;
}
sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d %s",
2000 + sDate.Year, sDate.Month, sDate.Date,
hour, sTime.Minutes, sTime.Seconds, ampm);
}
}
/* 将UNIX时间戳转换为RTC时间 */
void UnixTime_To_RTC(uint32_t unix_timestamp) {
struct tm *timeinfo;
time_t rawtime = (time_t)unix_timestamp;
timeinfo = localtime(&rawtime);
RTC_SetTime(timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
RTC_SetDate(1900 + timeinfo->tm_year,
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
(timeinfo->tm_wday == 0) ? 7 : timeinfo->tm_wday);
}
/* 从RTC读取UNIX时间戳 */
uint32_t RTC_To_UnixTime(void) {
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
struct tm timeinfo = {0};
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
timeinfo.tm_year = sDate.Year + 100; // 从2000年开始
timeinfo.tm_mon = sDate.Month - 1; // 月份从0开始
timeinfo.tm_mday = sDate.Date;
timeinfo.tm_hour = sTime.Hours;
timeinfo.tm_min = sTime.Minutes;
timeinfo.tm_sec = sTime.Seconds;
timeinfo.tm_wday = sDate.WeekDay - 1; // 星期从0开始
return mktime(&timeinfo);
}
三、闹钟功能实现
3.1 闹钟设置与管理
c
/* 设置RTC闹钟 */
void RTC_SetAlarm(uint8_t hour, uint8_t min, uint8_t sec, uint8_t weekday_mask) {
RTC_AlarmTypeDef sAlarm = {0};
// 参数检查
if (hour >= 24 || min >= 60 || sec >= 60) {
printf("Error: Invalid alarm time\n");
return;
}
// 配置闹钟A
sAlarm.AlarmTime.Hours = hour;
sAlarm.AlarmTime.Minutes = min;
sAlarm.AlarmTime.Seconds = sec;
sAlarm.AlarmTime.SubSeconds = 0;
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (weekday_mask == 0) {
// 每天闹钟
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_WEEKDAY;
sAlarm.AlarmDateWeekDay = RTC_WEEKDAY_MONDAY; // 任意值
} else {
// 指定星期闹钟
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_WEEKDAY;
sAlarm.AlarmDateWeekDay = weekday_mask;
}
sAlarm.Alarm = RTC_ALARM_A;
sAlarm.AlarmOutput = RTC_ALARMOUTPUTMODE_INTERRUPT;
// 清除闹钟标志
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
// 设置闹钟
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) {
printf("Error: Failed to set alarm\n");
} else {
printf("Alarm set for: %02d:%02d:%02d\n", hour, min, sec);
}
}
/* 使能闹钟中断 */
void RTC_EnableAlarm(void) {
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
printf("Alarm interrupt enabled\n");
}
/* 禁用闹钟 */
void RTC_DisableAlarm(void) {
HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
HAL_NVIC_DisableIRQ(RTC_Alarm_IRQn);
printf("Alarm disabled\n");
}
/* 闹钟中断服务函数 */
void RTC_Alarm_IRQHandler(void) {
// 检查闹钟A标志
if (__HAL_RTC_ALARM_GET_FLAG(&hrtc, RTC_FLAG_ALRAF) != RESET) {
// 清除标志
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
// 闹钟触发处理
alarm_callback();
printf("Alarm triggered!\n");
}
}
/* 闹钟回调函数 */
void alarm_callback(void) {
// 这里实现闹钟触发后的操作
// 例如:点亮LED、播放声音、发送通知等
// 示例:闪烁LED
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
// 可以添加蜂鸣器控制
// beep_on();
// HAL_Delay(1000);
// beep_off();
}
3.2 周期性唤醒
c
/* 配置RTC周期性唤醒 */
void RTC_ConfigurePeriodicWakeup(uint16_t period_seconds) {
// 计算唤醒周期
// 唤醒时钟 = RTCCLK / (PREDIV_S + 1) = 32768 / (127 + 1) = 256Hz
// 唤醒周期 = 唤醒时钟 * (唤醒计数值 + 1)
uint16_t wakeup_counter = 0;
if (period_seconds <= 0) {
printf("Error: Invalid period\n");
return;
}
// 计算唤醒计数值
// 默认时钟为256Hz,每秒256个计数
wakeup_counter = period_seconds * 256 - 1;
// 设置唤醒定时器
if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, wakeup_counter, RTC_WAKEUPCLOCK_RTCCLK_DIV16) != HAL_OK) {
printf("Error: Failed to set wakeup timer\n");
return;
}
// 使能唤醒中断
HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
printf("Periodic wakeup configured: %d seconds\n", period_seconds);
}
/* 唤醒中断服务函数 */
void RTC_WKUP_IRQHandler(void) {
if (__HAL_RTC_WAKEUPTIMER_GET_FLAG(&hrtc, RTC_FLAG_WUTF) != RESET) {
// 清除标志
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
// 唤醒处理
wakeup_callback();
printf("Wakeup triggered\n");
}
}
/* 进入停止模式,等待RTC唤醒 */
void Enter_StopMode_RTCWakeup(void) {
printf("Entering Stop mode...\n");
// 配置唤醒引脚
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
// 进入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后系统时钟需要重新配置
SystemClock_Config();
printf("Woke up from Stop mode\n");
}
四、日历与时间计算
4.1 日期计算功能
c
/* 判断是否为闰年 */
uint8_t is_leap_year(uint16_t year) {
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
return 1;
}
return 0;
}
/* 获取月份天数 */
uint8_t get_days_in_month(uint16_t year, uint8_t month) {
uint8_t days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && is_leap_year(year)) {
return 29;
}
if (month >= 1 && month <= 12) {
return days_in_month[month - 1];
}
return 0;
}
/* 计算星期(Zeller公式) */
uint8_t calculate_weekday(uint16_t year, uint8_t month, uint8_t day) {
uint16_t y = year;
uint8_t m = month;
if (month < 3) {
y--;
m += 12;
}
uint16_t c = y / 100;
y = y % 100;
uint16_t w = (y + y/4 + c/4 - 2*c + 26*(m+1)/10 + day - 1) % 7;
// 转换为1-7(周一至周日)
return (w + 6) % 7 + 1;
}
/* 计算两个日期之间的天数差 */
int32_t days_between_dates(uint16_t y1, uint8_t m1, uint8_t d1,
uint16_t y2, uint8_t m2, uint8_t d2) {
// 将日期转换为儒略日
uint32_t jd1 = date_to_julian(y1, m1, d1);
uint32_t jd2 = date_to_julian(y2, m2, d2);
return (int32_t)(jd2 - jd1);
}
/* 日期转儒略日 */
uint32_t date_to_julian(uint16_t year, uint8_t month, uint8_t day) {
uint8_t a = (14 - month) / 12;
uint16_t y = year + 4800 - a;
uint8_t m = month + 12 * a - 3;
uint32_t jd = day + (153 * m + 2) / 5 + 365 * y + y/4 - y/100 + y/400 - 32045;
return jd;
}
4.2 时间同步功能
c
/* 从GPS NMEA数据解析时间 */
uint8_t RTC_SyncFromGPS(char *nmea_data) {
// GPS NMEA格式: $GPRMC,hhmmss.sss,A,ddmm.mmmm,N,dddmm.mmmm,E,...
char *token = strtok(nmea_data, ",");
uint8_t field = 0;
char time_str[10];
char date_str[7];
uint8_t time_valid = 0;
uint8_t date_valid = 0;
while (token != NULL) {
field++;
if (field == 1) { // 消息类型
if (strcmp(token, "$GPRMC") != 0) {
return 0; // 不是GPRMC消息
}
} else if (field == 2) { // UTC时间
if (strlen(token) >= 6) {
strncpy(time_str, token, 6);
time_valid = 1;
}
} else if (field == 3) { // 定位状态
if (token[0] != 'A') { // A=有效定位
return 0;
}
} else if (field == 10) { // UTC日期
if (strlen(token) == 6) {
strncpy(date_str, token, 6);
date_valid = 1;
}
}
token = strtok(NULL, ",");
}
if (time_valid && date_valid) {
// 解析时间
char hour_str[3] = {time_str[0], time_str[1], '\0'};
char min_str[3] = {time_str[2], time_str[3], '\0'};
char sec_str[3] = {time_str[4], time_str[5], '\0'};
uint8_t hour = atoi(hour_str);
uint8_t min = atoi(min_str);
uint8_t sec = atoi(sec_str);
// 解析日期
char day_str[3] = {date_str[0], date_str[1], '\0'};
char month_str[3] = {date_str[2], date_str[3], '\0'};
char year_str[3] = {date_str[4], date_str[5], '\0'};
uint8_t day = atoi(day_str);
uint8_t month = atoi(month_str);
uint16_t year = 2000 + atoi(year_str);
// 计算星期
uint8_t weekday = calculate_weekday(year, month, day);
// 设置RTC
RTC_SetTime(hour, min, sec);
RTC_SetDate(year, month, day, weekday);
printf("Time synced from GPS: %04d-%02d-%02d %02d:%02d:%02d\n",
year, month, day, hour, min, sec);
return 1;
}
return 0;
}
/* 从网络时间协议同步 */
void RTC_SyncFromNTP(void) {
// 简化示例,实际需要网络连接
// 这里模拟从NTP服务器获取时间
// 假设从网络获取到的时间戳
uint32_t ntp_timestamp = 1704067200; // 2024-01-01 00:00:00
// 转换为RTC时间
UnixTime_To_RTC(ntp_timestamp);
printf("Time synced from NTP\n");
}
五、备份寄存器与数据保持
5.1 备份寄存器操作
c
/* 向备份寄存器写入数据 */
void BKP_WriteData(uint8_t register_number, uint16_t data) {
// 使能PWR时钟和备份接口
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
// 允许访问备份域
HAL_PWR_EnableBkUpAccess();
// 选择备份寄存器
switch (register_number) {
case 1:
BKP->DR1 = data;
break;
case 2:
BKP->DR2 = data;
break;
case 3:
BKP->DR3 = data;
break;
case 4:
BKP->DR4 = data;
break;
case 5:
BKP->DR5 = data;
break;
case 6:
BKP->DR6 = data;
break;
case 7:
BKP->DR7 = data;
break;
case 8:
BKP->DR8 = data;
break;
case 9:
BKP->DR9 = data;
break;
case 10:
BKP->DR10 = data;
break;
default:
printf("Error: Invalid BKP register\n");
return;
}
printf("BKP_DR%d written: 0x%04X\n", register_number, data);
}
/* 从备份寄存器读取数据 */
uint16_t BKP_ReadData(uint8_t register_number) {
uint16_t data = 0;
// 使能备份接口
__HAL_RCC_BKP_CLK_ENABLE();
// 读取备份寄存器
switch (register_number) {
case 1:
data = BKP->DR1;
break;
case 2:
data = BKP->DR2;
break;
case 3:
data = BKP->DR3;
break;
case 4:
data = BKP->DR4;
break;
case 5:
data = BKP->DR5;
break;
case 6:
data = BKP->DR6;
break;
case 7:
data = BKP->DR7;
break;
case 8:
data = BKP->DR8;
break;
case 9:
data = BKP->DR9;
break;
case 10:
data = BKP->DR10;
break;
default:
printf("Error: Invalid BKP register\n");
return 0;
}
printf("BKP_DR%d read: 0x%04X\n", register_number, data);
return data;
}
/* 保存校准值到备份寄存器 */
void RTC_SaveCalibration(int8_t calibration_value) {
// 将校准值转换为16位
uint16_t cal_data = (uint16_t)((int16_t)calibration_value);
// 保存到备份寄存器
BKP_WriteData(1, cal_data);
// 保存标志
BKP_WriteData(2, 0x5A5A); // 标志表示已校准
printf("Calibration saved: %d\n", calibration_value);
}
/* 从备份寄存器加载校准值 */
int8_t RTC_LoadCalibration(void) {
// 检查校准标志
uint16_t flag = BKP_ReadData(2);
if (flag == 0x5A5A) {
// 读取校准值
uint16_t cal_data = BKP_ReadData(1);
int8_t calibration = (int8_t)cal_data;
printf("Calibration loaded: %d\n", calibration);
return calibration;
} else {
printf("No calibration data found\n");
return 0;
}
}
参考代码 STM32 RTC程序 www.youwenfan.com/contentcst/133709.html
六、完整示例应用
6.1 主程序示例
c
/* 主程序 - 完整的RTC应用示例 */
#include "main.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"
// 全局变量
volatile uint8_t alarm_triggered = 0;
volatile uint8_t wakeup_triggered = 0;
int main(void) {
// HAL库初始化
HAL_Init();
// 系统时钟配置
SystemClock_Config();
// 外设初始化
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_RTC_Init();
printf("\r\n===== STM32 RTC Demo =====\r\n");
// 加载保存的校准值
int8_t calibration = RTC_LoadCalibration();
if (calibration != 0) {
RTC_CalibrationAdjust(calibration);
}
// 设置时间日期示例
RTC_SetTime(12, 30, 0); // 12:30:00
RTC_SetDate(2024, 1, 1, 1); // 2024-01-01 Monday
// 设置闹钟
RTC_SetAlarm(12, 30, 30, 0); // 每天12:30:30闹钟
RTC_EnableAlarm();
// 设置周期性唤醒(每5秒)
RTC_ConfigurePeriodicWakeup(5);
// 主循环
while (1) {
char time_str[30];
uint8_t hour, min, sec;
uint16_t year;
uint8_t month, day, weekday;
// 读取并显示时间
RTC_GetTime(&hour, &min, &sec);
RTC_GetDate(&year, &month, &day, &weekday);
RTC_TimeToString(time_str, 1);
printf("Current: %s\r\n", time_str);
// 检查闹钟触发
if (alarm_triggered) {
printf("*** ALARM! ***\r\n");
alarm_triggered = 0;
// 处理闹钟事件
// 例如:发送通知、控制外设等
}
// 检查唤醒触发
if (wakeup_triggered) {
printf("Wakeup from RTC\r\n");
wakeup_triggered = 0;
}
// 检查串口命令
if (serial_data_ready()) {
process_serial_command();
}
// 低功耗处理
if (is_system_idle()) {
Enter_LowPower_Mode();
}
HAL_Delay(1000);
}
}
/* 处理串口命令 */
void process_serial_command(void) {
char cmd[50];
serial_read_line(cmd, sizeof(cmd));
if (strncmp(cmd, "SETTIME ", 8) == 0) {
// 设置时间: SETTIME HH:MM:SS
uint8_t hour, min, sec;
sscanf(cmd + 8, "%hhu:%hhu:%hhu", &hour, &min, &sec);
RTC_SetTime(hour, min, sec);
} else if (strncmp(cmd, "SETDATE ", 8) == 0) {
// 设置日期: SETDATE YYYY-MM-DD
uint16_t year;
uint8_t month, day;
sscanf(cmd + 8, "%hu-%hhu-%hhu", &year, &month, &day);
// 计算星期
uint8_t weekday = calculate_weekday(year, month, day);
RTC_SetDate(year, month, day, weekday);
} else if (strncmp(cmd, "SETALARM ", 9) == 0) {
// 设置闹钟: SETALARM HH:MM:SS
uint8_t hour, min, sec;
sscanf(cmd + 9, "%hhu:%hhu:%hhu", &hour, &min, &sec);
RTC_SetAlarm(hour, min, sec, 0);
} else if (strcmp(cmd, "GETTIME") == 0) {
// 获取时间
char time_str[30];
RTC_TimeToString(time_str, 1);
printf("Time: %s\r\n", time_str);
} else if (strcmp(cmd, "CALIBRATE") == 0) {
// 进入校准模式
RTC_CalibrationMode();
} else if (strncmp(cmd, "SAVECAL ", 8) == 0) {
// 保存校准值: SAVECAL value
int8_t cal_value = atoi(cmd + 8);
RTC_SaveCalibration(cal_value);
} else if (strcmp(cmd, "HELP") == 0) {
// 显示帮助
printf("Available commands:\r\n");
printf(" SETTIME HH:MM:SS\r\n");
printf(" SETDATE YYYY-MM-DD\r\n");
printf(" SETALARM HH:MM:SS\r\n");
printf(" GETTIME\r\n");
printf(" CALIBRATE\r\n");
printf(" SAVECAL value\r\n");
printf(" HELP\r\n");
} else {
printf("Unknown command\r\n");
}
}
6.2 校准功能
c
/* RTC时钟校准 */
void RTC_CalibrationAdjust(int8_t calibration_value) {
// 校准范围: -511 到 +512
// 校准步长: 0.9537 ppm
// 正值: 增加频率
// 负值: 降低频率
uint16_t calib_value = 0;
if (calibration_value >= 0) {
// 正值校准
calib_value = (uint16_t)calibration_value;
calib_value |= 0x8000; // 设置符号位
} else {
// 负值校准
calib_value = (uint16_t)(-calibration_value);
}
// 写入校准寄存器
RTC->CALIBR = calib_value;
printf("RTC calibrated: %d (0x%04X)\r\n", calibration_value, calib_value);
}
/* 自动校准模式 */
void RTC_CalibrationMode(void) {
printf("Entering calibration mode...\r\n");
printf("Connect 1Hz reference signal to PA0\r\n");
printf("Calibration in progress...\r\n");
uint32_t rtc_count = 0;
uint32_t ref_count = 0;
uint32_t error_ppm = 0;
// 使用TIM2测量RTC输出和参考时钟
// 假设RTC输出1Hz信号到PC13
// 参考1Hz信号连接到PA0
// 测量10秒
for (int i = 0; i < 10; i++) {
// 测量RTC脉冲
// 测量参考脉冲
HAL_Delay(1000);
}
// 计算误差
if (rtc_count > 0 && ref_count > 0) {
error_ppm = (rtc_count - ref_count) * 1000000 / ref_count;
// 计算校准值
int8_t cal_value = (int8_t)(error_ppm / 0.9537);
printf("Error: %ld ppm\r\n", error_ppm);
printf("Calibration value: %d\r\n", cal_value);
// 应用校准
RTC_CalibrationAdjust(cal_value);
// 保存校准值
RTC_SaveCalibration(cal_value);
}
}
七、高级功能
7.1 时间戳记录
c
/* 时间戳记录系统 */
#define MAX_TIMESTAMPS 100
typedef struct {
uint32_t unix_time; // UNIX时间戳
uint16_t millis; // 毫秒部分
uint8_t event_type; // 事件类型
uint8_t event_data; // 事件数据
} TimestampRecord;
TimestampRecord timestamp_log[MAX_TIMESTAMPS];
uint16_t timestamp_index = 0;
/* 记录时间戳 */
void RecordTimestamp(uint8_t event_type, uint8_t event_data) {
if (timestamp_index >= MAX_TIMESTAMPS) {
timestamp_index = 0; // 循环缓冲
}
timestamp_log[timestamp_index].unix_time = RTC_To_UnixTime();
timestamp_log[timestamp_index].millis = HAL_GetTick() % 1000;
timestamp_log[timestamp_index].event_type = event_type;
timestamp_log[timestamp_index].event_data = event_data;
timestamp_index++;
printf("Timestamp recorded: Event %d, Data %d\r\n", event_type, event_data);
}
/* 获取时间戳记录 */
void PrintTimestampLog(void) {
printf("Timestamp Log:\r\n");
printf("Index Time Event Data\r\n");
printf("---------------------------------------\r\n");
for (uint16_t i = 0; i < MAX_TIMESTAMPS; i++) {
if (timestamp_log[i].unix_time > 0) {
time_t t = timestamp_log[i].unix_time;
struct tm *tm_info = localtime(&t);
printf("%5d %04d-%02d-%02d %02d:%02d:%02d.%03d %2d %3d\r\n",
i,
tm_info->tm_year + 1900,
tm_info->tm_mon + 1,
tm_info->tm_mday,
tm_info->tm_hour,
tm_info->tm_min,
tm_info->tm_sec,
timestamp_log[i].millis,
timestamp_log[i].event_type,
timestamp_log[i].event_data);
}
}
}
7.2 看门狗与RTC结合
c
/* 独立看门狗与RTC结合 */
void IWDG_InitWithRTC(void) {
// 初始化独立看门狗
IWDG_HandleTypeDef hiwdg = {0};
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_64; // 40kHz/64 = 625Hz
hiwdg.Init.Reload = 625; // 1秒超时
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
Error_Handler();
}
// 使用RTC定时喂狗
printf("IWDG initialized with 1s timeout\r\n");
}
/* RTC喂狗任务 */
void RTC_FeedWatchdogTask(void) {
static uint32_t last_feed_time = 0;
uint32_t current_time = RTC_To_UnixTime();
// 每30秒喂一次狗
if (current_time - last_feed_time >= 30) {
HAL_IWDG_Refresh(&hiwdg);
last_feed_time = current_time;
printf("Watchdog fed at %lu\r\n", current_time);
}
}
八、错误处理与诊断
8.1 RTC错误检测
c
/* RTC状态检查 */
uint8_t RTC_CheckStatus(void) {
uint8_t status = 0;
// 检查RTC寄存器同步状态
if (__HAL_RTC_IS_CALENDAR_INITIALIZED(&hrtc)) {
status |= 0x01; // 日历已初始化
}
// 检查RTC运行状态
if (__HAL_RTC_IS_CALENDAR_RUNNING(&hrtc)) {
status |= 0x02; // 日历正在运行
}
// 检查备份域复位标志
if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)) {
status |= 0x04; // 引脚复位
}
if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)) {
status |= 0x08; // 上电/掉电复位
}
if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)) {
status |= 0x10; // 软件复位
}
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
status |= 0x20; // 独立看门狗复位
}
if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) {
status |= 0x40; // 窗口看门狗复位
}
if (__HAL_RCC_GET_FLAG(RCC_FLAG_LPWRRST)) {
status |= 0x80; // 低功耗复位
}
return status;
}
/* 打印RTC诊断信息 */
void RTC_DiagnosticInfo(void) {
uint8_t status = RTC_CheckStatus();
printf("=== RTC Diagnostic Information ===\r\n");
// 基本信息
printf("RTC Clock Source: ");
if (__HAL_RCC_GET_RTC_SOURCE() == RCC_RTCCLKSOURCE_LSE) {
printf("LSE (32.768kHz)\r\n");
} else if (__HAL_RCC_GET_RTC_SOURCE() == RCC_RTCCLKSOURCE_LSI) {
printf("LSI (~40kHz)\r\n");
} else if (__HAL_RCC_GET_RTC_SOURCE() == RCC_RTCCLKSOURCE_HSE_DIV32) {
printf("HSE/32\r\n");
} else {
printf("Unknown\r\n");
}
// 状态标志
printf("Calendar Initialized: %s\r\n", (status & 0x01) ? "Yes" : "No");
printf("Calendar Running: %s\r\n", (status & 0x02) ? "Yes" : "No");
// 复位标志
printf("Reset Flags:\r\n");
printf(" Pin Reset: %s\r\n", (status & 0x04) ? "Yes" : "No");
printf(" Power Reset: %s\r\n", (status & 0x08) ? "Yes" : "No");
printf(" Software Reset: %s\r\n", (status & 0x10) ? "Yes" : "No");
printf(" IWDG Reset: %s\r\n", (status & 0x20) ? "Yes" : "No");
printf(" WWDG Reset: %s\r\n", (status & 0x40) ? "Yes" : "No");
printf(" Low Power Reset: %s\r\n", (status & 0x80) ? "Yes" : "No");
// 备份寄存器状态
printf("Backup Register Status:\r\n");
for (uint8_t i = 1; i <= 10; i++) {
uint16_t data = BKP_ReadData(i);
printf(" BKP_DR%d: 0x%04X\r\n", i, data);
}
// 当前时间
char time_str[30];
RTC_TimeToString(time_str, 1);
printf("Current RTC Time: %s\r\n", time_str);
printf("=================================\r\n");
}
九、优化与最佳实践
9.1 低功耗优化
c
/* 低功耗RTC配置 */
void RTC_LowPowerConfig(void) {
// 配置RTC为低功耗模式
RTC_HandleTypeDef hlprtc = {0};
hlprtc.Instance = RTC;
hlprtc.Init.HourFormat = RTC_HOURFORMAT_24;
hlprtc.Init.AsynchPrediv = 127;
hlprtc.Init.SynchPrediv = 255;
hlprtc.Init.OutPut = RTC_OUTPUT_DISABLE; // 禁用输出以省电
hlprtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hlprtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
if (HAL_RTC_Init(&hlprtc) != HAL_OK) {
Error_Handler();
}
// 禁用RTC输出引脚
__HAL_RCC_RTC_DISABLE();
printf("RTC configured for low power mode\r\n");
}
/* 进入低功耗模式 */
void Enter_LowPower_Mode(void) {
// 停止非必要外设
HAL_UART_MspDeInit(&huart1);
// 配置唤醒源
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN2);
// 进入停止模式
printf("Entering STOP mode...\r\n");
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后
SystemClock_Config();
HAL_UART_MspInit(&huart1);
printf("Woke up from STOP mode\r\n");
}
9.2 时间同步优化
c
/* 精确时间同步 */
typedef struct {
uint32_t last_sync_time; // 最后同步时间
int32_t drift_accumulated; // 累计漂移
int32_t drift_rate; // 漂移率 (ppm)
uint8_t sync_count; // 同步次数
} TimeSyncData;
TimeSyncData time_sync = {0};
/* 时间同步优化 */
void OptimizedTimeSync(uint32_t reference_time) {
uint32_t current_rtc_time = RTC_To_UnixTime();
int32_t time_error = (int32_t)(reference_time - current_rtc_time);
// 记录同步
time_sync.last_sync_time = current_rtc_time;
time_sync.sync_count++;
// 计算漂移
if (time_sync.sync_count > 1) {
uint32_t interval = current_rtc_time - time_sync.last_sync_time;
if (interval > 0) {
time_sync.drift_rate = (time_error * 1000000) / interval; // ppm
}
}
// 平滑调整
if (abs(time_error) < 10) {
// 小误差,平滑调整
RTC_CalibrationAdjust(time_sync.drift_rate / 1000);
} else {
// 大误差,直接设置
UnixTime_To_RTC(reference_time);
}
printf("Time sync: error=%lds, drift=%ldppm\r\n",
time_error, time_sync.drift_rate);
}
十、完整工程结构
STM32_RTC_Project/
├── Core/
│ ├── Src/
│ │ ├── main.c # 主程序
│ │ ├── stm32f1xx_it.c # 中断服务程序
│ │ ├── rtc.c # RTC核心功能
│ │ └── system_stm32f1xx.c
│ └── Inc/
│ ├── main.h
│ ├── rtc.h
│ └── config.h
├── Drivers/
│ ├── CMSIS/
│ └── STM32F1xx_HAL_Driver/
├── Middlewares/
│ ├── Calendar/
│ │ ├── calendar.c
│ │ └── calendar.h
│ └── TimeSync/
│ ├── timesync.c
│ └── timesync.h
└── README.md
总结
这个完整的STM32 RTC程序实现了以下功能:
核心功能:
- RTC初始化和配置:支持LSE和LSI时钟源
- 时间日期管理:设置、读取、格式转换
- 闹钟功能:单次/重复闹钟,中断处理
- 周期性唤醒:低功耗定时唤醒
- 备份寄存器:数据保持,校准值存储
高级功能:
- 日历计算:闰年判断、星期计算、日期差
- 时间同步:GPS、NTP时间同步
- 时间戳记录:事件时间记录系统
- 校准功能:自动/手动RTC时钟校准
- 低功耗优化:RTC唤醒的低功耗模式
- 诊断功能:RTC状态检查与诊断
应用场景:
- 电子时钟/手表
- 数据记录仪
- 定时控制器
- 事件时间戳记录
- 低功耗定时唤醒系统
- 时间同步系统
这个程序可以直接用于STM32项目,特别是需要精确时间管理的应用。