基于STM32的RTC(实时时钟)程序设计与实现

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程序实现了以下功能:

核心功能:

  1. RTC初始化和配置:支持LSE和LSI时钟源
  2. 时间日期管理:设置、读取、格式转换
  3. 闹钟功能:单次/重复闹钟,中断处理
  4. 周期性唤醒:低功耗定时唤醒
  5. 备份寄存器:数据保持,校准值存储

高级功能:

  1. 日历计算:闰年判断、星期计算、日期差
  2. 时间同步:GPS、NTP时间同步
  3. 时间戳记录:事件时间记录系统
  4. 校准功能:自动/手动RTC时钟校准
  5. 低功耗优化:RTC唤醒的低功耗模式
  6. 诊断功能:RTC状态检查与诊断

应用场景:

  • 电子时钟/手表
  • 数据记录仪
  • 定时控制器
  • 事件时间戳记录
  • 低功耗定时唤醒系统
  • 时间同步系统

这个程序可以直接用于STM32项目,特别是需要精确时间管理的应用。

相关推荐
九鼎创展科技2 小时前
联发科 MT8883 核心优势深度解析:对比 MT8385/MT8788/MT8183
人工智能·科技·嵌入式硬件·边缘计算
zmj32032414 小时前
单片机串口收发数据不可靠--用做指令会执行错误动作
单片机·嵌入式硬件·串口
yuan1999715 小时前
STM32 驱动 RC522(MFRC522)实现方案
单片机·嵌入式硬件
踏着七彩祥云的小丑16 小时前
嵌入式——认识电子元器件——电容系列
单片机·嵌入式硬件
Sean_VIP17 小时前
SeanLib系列函数库-MyList
stm32
NQBJT17 小时前
DMA —— 让 CPU “偷懒”的数据搬运工
stm32·单片机·dma·嵌入式
xiangw@GZ18 小时前
EMC原理:CS传导抗扰度测试总结
单片机·嵌入式硬件
椰羊~王小美18 小时前
STM32加密步骤简述
stm32
三佛科技-1341638421218 小时前
PL3325CS/CD/CH/CE 与PL3325BE 之间的对比与联系(应用功率与典型应用电路)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺