STM32嵌入式开发:基于RTC的电子万年历与闹钟系统

一、前言

1.1 项目背景

实时时钟(RTC)是嵌入式系统中非常重要的外设,它能够在主电源断电后依靠备用电池继续运行,为系统提供精确的时间和日期信息。基于RTC的电子万年历与闹钟系统是学习STM32 RTC功能的经典项目,涵盖了日期时间计算、闹钟设置、低功耗管理等核心技术。

1.2 RTC功能特点

  • 独立运行:主电源断电后仍可工作
  • 低功耗:使用备用电池可运行数年
  • 高精度:配合外部晶振精度可达±20ppm
  • 丰富中断:支持闹钟、秒中断、溢出中断
  • 日历功能:自动计算年月日星期

1.3 应用场景

  • 智能手表:时间显示、闹钟提醒
  • 智能家居:定时开关控制
  • 数据记录仪:时间戳记录
  • 工业控制:定时任务调度

1.4 技术栈

类别技术/器件说明
主控芯片STM32F103C8T6ARM Cortex-M3
RTC晶振32.768kHz外部低速晶振
备用电池CR20323V纽扣电池
显示屏LCD1602/OLED时间显示
存储内部Flash闹钟数据存储
开发环境Keil MDK-ARM集成开发环境

1.5 学习目标

通过本教程,你将学会:

  • ✅ STM32 RTC外设配置
  • ✅ BKP备份寄存器使用
  • ✅ 闰年计算与日期推算
  • ✅ 闹钟中断处理
  • ✅ 低功耗模式应用

二、系统架构设计

2.1 整体架构

APB1

APB1

I2C

GPIO

GPIO

32.768kHz

Vbat

数据保持

数据保持

STM32F103

RTC模块

BKP备份寄存器

LCD显示屏

按键输入

蜂鸣器

外部晶振

备用电池

闹钟设置

系统状态

2.2 功能模块

电子万年历系统

时间显示

日期计算

闹钟功能

设置功能

时:分:秒

12/24小时制

年月日

星期计算

闰年判断

多组闹钟

重复设置

贪睡功能

时间校准

闹钟设置

显示设置

三、RTC原理详解

3.1 RTC时钟源

STM32 RTC支持三种时钟源:

  • HSE/128:高速外部晶振分频
  • LSE:低速外部晶振(32.768kHz,推荐)
  • LSI:低速内部RC振荡器(约40kHz)

3.2 RTC预分频器

RTC时钟需要分频到1Hz:

  • 32.768kHz / 128 / 256 = 1Hz
  • 预分频器1:128
  • 预分频器2:256

3.3 RTC寄存器

  • RTC_CRH/CRL:控制寄存器
  • RTC_PRLH/PRLL:预分频寄存器
  • RTC_DIVH/DIVL:分频计数器
  • RTC_CNTH/CNTL:32位计数器
  • RTC_ALRH/ALRL:闹钟寄存器

四、硬件准备

4.1 硬件清单

序号名称数量参考价格说明
1STM32F103C8T6核心板1¥12主控芯片
232.768kHz晶振1¥1RTC时钟源
3CR2032电池座1¥2备用电源
4CR2032电池1¥33V备用电池
5LCD1602显示屏1¥8时间显示
6按键4¥2功能按键
7有源蜂鸣器1¥2闹钟提示
8电阻电容若干¥2外围元件

4.2 引脚分配

功能STM32引脚连接对象说明
OSC32_INPC14晶振32.768kHz输入
OSC32_OUTPC15晶振32.768kHz输出
VbatVbat电池备用电源
LCD_RSPB0LCD RS寄存器选择
LCD_RWPB1LCD RW读写选择
LCD_ENPB2LCD EN使能信号
LCD_D0-D7PA0-PA7LCD D0-D7数据总线
KEY_SETPB3设置键功能设置
KEY_UPPB4上键数值增加
KEY_DOWNPB5下键数值减少
KEY_OKPB6确认键确认操作
BEEPPA8蜂鸣器闹钟提示

五、核心代码实现

5.1 RTC驱动模块

📄 创建文件:Hardware/rtc.h

#ifndef __RTC_H
#define __RTC_H

#include "sys.h"

// 时间结构体
typedef struct {
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
    uint8_t weekday;
} RTC_Time;

// 闹钟结构体
typedef struct {
    uint8_t hour;
    uint8_t minute;
    uint8_t enable;
    uint8_t repeat;     // 重复模式
    uint8_t snooze;     // 贪睡时间(分钟)
} RTC_Alarm;

// 函数声明
void RTC_Init(void);
void RTC_SetTime(RTC_Time *time);
void RTC_GetTime(RTC_Time *time);
void RTC_SetAlarm(RTC_Alarm *alarm);
void RTC_AlarmEnable(uint8_t enable);
uint8_t RTC_GetAlarmFlag(void);
void RTC_ClearAlarmFlag(void);
uint8_t IsLeapYear(uint16_t year);
uint8_t GetWeekday(uint16_t year, uint8_t month, uint8_t day);

#endif

📄 创建文件:Hardware/rtc.c

#include "rtc.h"
#include "stm32f10x_bkp.h"
#include "stm32f10x_pwr.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_rtc.h"

// 月份天数表(非闰年)
const uint8_t month_days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

// 基准时间:2000年1月1日 00:00:00
#define BASE_YEAR 2000

/**
 * 判断闰年
 */
uint8_t IsLeapYear(uint16_t year)
{
    return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
}

/**
 * 获取月份天数
 */
uint8_t GetMonthDays(uint16_t year, uint8_t month)
{
    if (month == 2 && IsLeapYear(year))
    {
        return 29;
    }
    return month_days[month - 1];
}

/**
 * 计算星期(蔡勒公式)
 */
uint8_t GetWeekday(uint16_t year, uint8_t month, uint8_t day)
{
    if (month < 3)
    {
        month += 12;
        year--;
    }
    
    uint16_t y = year % 100;
    uint16_t c = year / 100;
    
    uint8_t w = (y + y / 4 + c / 4 - 2 * c + 26 * (month + 1) / 10 + day - 1) % 7;
    
    // 转换为1-7(1=星期日)
    return (w + 7) % 7 + 1;
}

/**
 * 将日期时间转换为秒数(从2000年1月1日算起)
 */
uint32_t DateTimeToSeconds(RTC_Time *time)
{
    uint32_t seconds = 0;
    uint16_t year;
    uint8_t month;
    
    // 计算年份累积秒数
    for (year = BASE_YEAR; year < time->year; year++)
    {
        seconds += (IsLeapYear(year) ? 366 : 365) * 24 * 3600;
    }
    
    // 计算月份累积秒数
    for (month = 1; month < time->month; month++)
    {
        seconds += GetMonthDays(time->year, month) * 24 * 3600;
    }
    
    // 计算日期和时间的秒数
    seconds += (time->day - 1) * 24 * 3600;
    seconds += time->hour * 3600;
    seconds += time->minute * 60;
    seconds += time->second;
    
    return seconds;
}

/**
 * 将秒数转换为日期时间
 */
void SecondsToDateTime(uint32_t seconds, RTC_Time *time)
{
    uint32_t days = seconds / (24 * 3600);
    uint32_t remaining = seconds % (24 * 3600);
    
    // 计算时间
    time->hour = remaining / 3600;
    remaining %= 3600;
    time->minute = remaining / 60;
    time->second = remaining % 60;
    
    // 计算年月日
    time->year = BASE_YEAR;
    while (1)
    {
        uint16_t year_days = IsLeapYear(time->year) ? 366 : 365;
        if (days < year_days) break;
        days -= year_days;
        time->year++;
    }
    
    time->month = 1;
    while (1)
    {
        uint8_t month_days = GetMonthDays(time->year, time->month);
        if (days < month_days) break;
        days -= month_days;
        time->month++;
    }
    
    time->day = days + 1;
    
    // 计算星期
    time->weekday = GetWeekday(time->year, time->month, time->day);
}

/**
 * RTC初始化
 */
void RTC_Init(void)
{
    // 使能PWR和BKP时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    
    // 允许访问BKP和RTC
    PWR_BackupAccessCmd(ENABLE);
    
    // 检查是否首次配置
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
    {
        // 复位备份域
        BKP_DeInit();
        
        // 使能LSE
        RCC_LSEConfig(RCC_LSE_ON);
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
        
        // 选择LSE作为RTC时钟源
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
        
        // 使能RTC时钟
        RCC_RTCCLKCmd(ENABLE);
        
        // 等待RTC寄存器同步
        RTC_WaitForSynchro();
        
        // 等待上次写操作完成
        RTC_WaitForLastTask();
        
        // 设置预分频器(32.768kHz -> 1Hz)
        RTC_SetPrescaler(32767);
        RTC_WaitForLastTask();
        
        // 设置初始时间(2000-01-01 00:00:00)
        RTC_SetCounter(0);
        RTC_WaitForLastTask();
        
        // 标记已配置
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
    }
    else
    {
        // 等待RTC寄存器同步
        RTC_WaitForSynchro();
        
        // 等待上次写操作完成
        RTC_WaitForLastTask();
    }
    
    // 使能秒中断
    RTC_ITConfig(RTC_IT_SEC, ENABLE);
    RTC_WaitForLastTask();
}

/**
 * 设置时间
 */
void RTC_SetTime(RTC_Time *time)
{
    uint32_t seconds = DateTimeToSeconds(time);
    
    // 等待上次写操作完成
    RTC_WaitForLastTask();
    
    // 设置计数器
    RTC_SetCounter(seconds);
    
    // 等待写操作完成
    RTC_WaitForLastTask();
}

/**
 * 获取时间
 */
void RTC_GetTime(RTC_Time *time)
{
    uint32_t seconds = RTC_GetCounter();
    SecondsToDateTime(seconds, time);
}

/**
 * 设置闹钟
 */
void RTC_SetAlarm(RTC_Alarm *alarm)
{
    RTC_Time current_time;
    RTC_GetTime(&current_time);
    
    // 构建闹钟时间
    RTC_Time alarm_time;
    alarm_time.year = current_time.year;
    alarm_time.month = current_time.month;
    alarm_time.day = current_time.day;
    alarm_time.hour = alarm->hour;
    alarm_time.minute = alarm->minute;
    alarm_time.second = 0;
    
    // 如果闹钟时间已过,设置为明天
    uint32_t alarm_seconds = DateTimeToSeconds(&alarm_time);
    uint32_t current_seconds = RTC_GetCounter();
    
    if (alarm_seconds <= current_seconds)
    {
        alarm_seconds += 24 * 3600;  // 加一天
    }
    
    // 等待上次写操作完成
    RTC_WaitForLastTask();
    
    // 设置闹钟
    RTC_SetAlarm(alarm_seconds);
    
    // 等待写操作完成
    RTC_WaitForLastTask();
}

/**
 * 使能/禁用闹钟
 */
void RTC_AlarmEnable(uint8_t enable)
{
    if (enable)
    {
        RTC_ITConfig(RTC_IT_ALR, ENABLE);
    }
    else
    {
        RTC_ITConfig(RTC_IT_ALR, DISABLE);
    }
    RTC_WaitForLastTask();
}

/**
 * 获取闹钟标志
 */
uint8_t RTC_GetAlarmFlag(void)
{
    return RTC_GetFlagStatus(RTC_FLAG_ALR);
}

/**
 * 清除闹钟标志
 */
void RTC_ClearAlarmFlag(void)
{
    RTC_ClearFlag(RTC_FLAG_ALR);
}

5.2 显示模块

📄 创建文件:Hardware/lcd1602.h

#ifndef __LCD1602_H
#define __LCD1602_H

#include "sys.h"

// 引脚定义
#define LCD_RS_PIN      GPIO_Pin_0
#define LCD_RW_PIN      GPIO_Pin_1
#define LCD_EN_PIN      GPIO_Pin_2
#define LCD_DATA_PORT   GPIOA
#define LCD_CTRL_PORT   GPIOB

// 控制宏
#define LCD_RS_HIGH()   GPIO_SetBits(LCD_CTRL_PORT, LCD_RS_PIN)
#define LCD_RS_LOW()    GPIO_ResetBits(LCD_CTRL_PORT, LCD_RS_PIN)
#define LCD_RW_HIGH()   GPIO_SetBits(LCD_CTRL_PORT, LCD_RW_PIN)
#define LCD_RW_LOW()    GPIO_ResetBits(LCD_CTRL_PORT, LCD_RW_PIN)
#define LCD_EN_HIGH()   GPIO_SetBits(LCD_CTRL_PORT, LCD_EN_PIN)
#define LCD_EN_LOW()    GPIO_ResetBits(LCD_CTRL_PORT, LCD_EN_PIN)

// 函数声明
void LCD1602_Init(void);
void LCD1602_Clear(void);
void LCD1602_SetCursor(uint8_t row, uint8_t col);
void LCD1602_ShowChar(uint8_t row, uint8_t col, char ch);
void LCD1602_ShowString(uint8_t row, uint8_t col, char *str);
void LCD1602_ShowNum(uint8_t row, uint8_t col, uint16_t num, uint8_t len);

#endif

📄 创建文件:Hardware/lcd1602.c

#include "lcd1602.h"
#include "delay.h"

/**
 * 写命令
 */
static void LCD_WriteCmd(uint8_t cmd)
{
    LCD_RS_LOW();
    LCD_RW_LOW();
    
    GPIO_Write(LCD_DATA_PORT, cmd);
    
    LCD_EN_HIGH();
    delay_us(5);
    LCD_EN_LOW();
    delay_us(5);
}

/**
 * 写数据
 */
static void LCD_WriteData(uint8_t data)
{
    LCD_RS_HIGH();
    LCD_RW_LOW();
    
    GPIO_Write(LCD_DATA_PORT, data);
    
    LCD_EN_HIGH();
    delay_us(5);
    LCD_EN_LOW();
    delay_us(5);
}

/**
 * LCD初始化
 */
void LCD1602_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置数据口
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(LCD_DATA_PORT, &GPIO_InitStructure);
    
    // 配置控制口
    GPIO_InitStructure.GPIO_Pin = LCD_RS_PIN | LCD_RW_PIN | LCD_EN_PIN;
    GPIO_Init(LCD_CTRL_PORT, &GPIO_InitStructure);
    
    // 初始化序列
    delay_ms(15);
    LCD_WriteCmd(0x38);  // 8位数据,双行,5×7点阵
    delay_ms(5);
    LCD_WriteCmd(0x38);
    delay_us(100);
    LCD_WriteCmd(0x38);
    
    LCD_WriteCmd(0x08);  // 显示关闭
    LCD_WriteCmd(0x01);  // 清屏
    delay_ms(2);
    LCD_WriteCmd(0x06);  // 地址自动+1,光标右移
    LCD_WriteCmd(0x0C);  // 显示开,光标关,闪烁关
}

/**
 * 清屏
 */
void LCD1602_Clear(void)
{
    LCD_WriteCmd(0x01);
    delay_ms(2);
}

/**
 * 设置光标位置
 */
void LCD1602_SetCursor(uint8_t row, uint8_t col)
{
    uint8_t addr = (row == 0) ? (0x80 + col) : (0xC0 + col);
    LCD_WriteCmd(addr);
}

/**
 * 显示字符
 */
void LCD1602_ShowChar(uint8_t row, uint8_t col, char ch)
{
    LCD1602_SetCursor(row, col);
    LCD_WriteData(ch);
}

/**
 * 显示字符串
 */
void LCD1602_ShowString(uint8_t row, uint8_t col, char *str)
{
    LCD1602_SetCursor(row, col);
    while (*str != '\0')
    {
        LCD_WriteData(*str++);
    }
}

/**
 * 显示数字
 */
void LCD1602_ShowNum(uint8_t row, uint8_t col, uint16_t num, uint8_t len)
{
    LCD1602_SetCursor(row, col);
    
    for (int i = len - 1; i >= 0; i--)
    {
        uint8_t digit = (num / (uint16_t)pow(10, i)) % 10;
        LCD_WriteData('0' + digit);
    }
}

5.3 主程序

📄 创建文件:User/main.c

/**
 * STM32 RTC电子万年历与闹钟系统
 * 主程序
 */

#include "stm32f10x.h"
#include "sys.h"
#include "rtc.h"
#include "lcd1602.h"
#include "delay.h"
#include <stdio.h>
#include <string.h>

// 系统状态
typedef enum {
    STATE_NORMAL = 0,   // 正常显示
    STATE_SET_TIME,     // 设置时间
    STATE_SET_ALARM,    // 设置闹钟
    STATE_ALARM_RING    // 闹钟响铃
} SystemState;

// 全局变量
volatile SystemState system_state = STATE_NORMAL;
RTC_Time current_time;
RTC_Alarm alarm_setting = {7, 0, 1, 0, 5};  // 默认7:00
volatile uint8_t alarm_triggered = 0;
volatile uint8_t alarm_snooze_count = 0;

// 星期字符串
const char *weekday_str[8] = {
    "", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"
};

// 函数声明
void System_Init(void);
void Display_Update(void);
void Key_Scan(void);
void Alarm_Handler(void);
void Enter_LowPower(void);

int main(void)
{
    System_Init();
    
    LCD1602_ShowString(0, 0, "RTC Calendar");
    LCD1602_ShowString(1, 0, "Initializing...");
    delay_ms(1000);
    
    while (1)
    {
        // 获取当前时间
        RTC_GetTime(&current_time);
        
        // 更新显示
        Display_Update();
        
        // 扫描按键
        Key_Scan();
        
        // 处理闹钟
        Alarm_Handler();
        
        // 延时
        delay_ms(100);
    }
}

/**
 * 系统初始化
 */
void System_Init(void)
{
    // 时钟配置
    SystemClock_Config();
    
    // 延时初始化
    delay_init();
    
    // LCD初始化
    LCD1602_Init();
    
    // RTC初始化
    RTC_Init();
    
    // 配置RTC中断
    NVIC_InitTypeDef NVIC_InitStructure;
    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);
    
    // 按键GPIO初始化
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 蜂鸣器初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_ResetBits(GPIOA, GPIO_Pin_8);
}

/**
 * 更新显示
 */
void Display_Update(void)
{
    char buf[17];
    
    if (system_state == STATE_NORMAL)
    {
        // 第一行:时间 HH:MM:SS
        sprintf(buf, "%02d:%02d:%02d  %s",
                current_time.hour,
                current_time.minute,
                current_time.second,
                weekday_str[current_time.weekday]);
        LCD1602_ShowString(0, 0, buf);
        
        // 第二行:日期 YYYY-MM-DD
        sprintf(buf, "%04d-%02d-%02d  ",
                current_time.year,
                current_time.month,
                current_time.day);
        LCD1602_ShowString(1, 0, buf);
        
        // 显示闹钟状态
        if (alarm_setting.enable)
        {
            LCD1602_ShowChar(1, 14, 'A');
        }
        else
        {
            LCD1602_ShowChar(1, 14, ' ');
        }
    }
}

/**
 * 按键扫描
 */
void Key_Scan(void)
{
    static uint8_t key_state = 0;
    
    // 读取按键状态(低电平有效)
    uint8_t key_set = !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3);
    uint8_t key_up = !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4);
    uint8_t key_down = !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5);
    uint8_t key_ok = !GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6);
    
    // 设置键
    if (key_set && !(key_state & 0x01))
    {
        key_state |= 0x01;
        
        if (system_state == STATE_ALARM_RING)
        {
            // 停止闹钟
            alarm_triggered = 0;
            GPIO_ResetBits(GPIOA, GPIO_Pin_8);
            system_state = STATE_NORMAL;
        }
        else if (system_state == STATE_NORMAL)
        {
            system_state = STATE_SET_TIME;
        }
        else
        {
            system_state = STATE_NORMAL;
        }
        
        LCD1602_Clear();
    }
    else if (!key_set)
    {
        key_state &= ~0x01;
    }
    
    // 其他按键处理...
}

/**
 * 闹钟处理
 */
void Alarm_Handler(void)
{
    if (alarm_triggered)
    {
        // 蜂鸣器响
        static uint8_t beep_state = 0;
        static uint32_t beep_time = 0;
        
        if (HAL_GetTick() - beep_time > 500)
        {
            beep_time = HAL_GetTick();
            beep_state = !beep_state;
            
            if (beep_state)
            {
                GPIO_SetBits(GPIOA, GPIO_Pin_8);
            }
            else
            {
                GPIO_ResetBits(GPIOA, GPIO_Pin_8);
            }
        }
        
        // 显示闹钟界面
        LCD1602_ShowString(0, 0, "  ALARM!!!  ");
        sprintf(buf, "%02d:%02d  SNOOZE:%d", 
                current_time.hour, current_time.minute, alarm_snooze_count);
        LCD1602_ShowString(1, 0, buf);
    }
}

/**
 * RTC中断服务程序
 */
void RTC_IRQHandler(void)
{
    if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
    {
        RTC_ClearITPendingBit(RTC_IT_SEC);
        
        // 秒中断处理
        // 可以在这里添加秒闪等功能
    }
    
    if (RTC_GetITStatus(RTC_IT_ALR) != RESET)
    {
        RTC_ClearITPendingBit(RTC_IT_ALR);
        
        // 闹钟中断
        if (alarm_setting.enable)
        {
            alarm_triggered = 1;
            system_state = STATE_ALARM_RING;
        }
    }
}

六、低功耗设计

6.1 进入待机模式

/**
 * 进入待机模式
 * 闹钟中断唤醒
 */
void Enter_Standby(void)
{
    // 关闭外设
    LCD1602_Clear();
    
    // 使能WKUP引脚唤醒
    PWR_WakeUpPinCmd(ENABLE);
    
    // 进入待机模式
    PWR_EnterSTANDBYMode();
}

6.2 备份域数据保持

/**
 * 保存闹钟设置到备份寄存器
 */
void Save_Alarm_To_BKP(void)
{
    BKP_WriteBackupRegister(BKP_DR2, alarm_setting.hour);
    BKP_WriteBackupRegister(BKP_DR3, alarm_setting.minute);
    BKP_WriteBackupRegister(BKP_DR4, alarm_setting.enable);
}

/**
 * 从备份寄存器读取闹钟设置
 */
void Load_Alarm_From_BKP(void)
{
    alarm_setting.hour = BKP_ReadBackupRegister(BKP_DR2);
    alarm_setting.minute = BKP_ReadBackupRegister(BKP_DR3);
    alarm_setting.enable = BKP_ReadBackupRegister(BKP_DR4);
}

七、常见问题

7.1 时间不准

原因:

  • 晶振精度问题
  • 温度影响
  • 负载电容不匹配

解决:

  • 使用高精度晶振
  • 软件校准
  • 调整负载电容

7.2 备用电池不工作

原因:

  • 电池没电
  • Vbat引脚未连接
  • 电池极性错误

解决:

  • 更换电池
  • 检查电路连接
  • 确认电池方向

八、总结

8.1 核心知识点

  • RTC工作原理与配置
  • 闰年与日期计算
  • 闹钟中断处理
  • 备份寄存器使用
  • 低功耗模式应用

8.2 扩展方向

  • 添加温度补偿
  • 实现多时区切换
  • 添加定时器功能
  • 连接WiFi自动对时

如果本教程对你有帮助,欢迎点赞、收藏、关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LCG元

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值