跳转至

RTC实时时钟应用完全指南

概述

RTC(Real-Time Clock,实时时钟)是嵌入式系统中用于保持准确时间的重要组件。即使在系统断电的情况下,RTC也能通过备用电池继续运行,确保时间信息不丢失。RTC广泛应用于数据记录、定时任务、时间戳生成等场景。

什么是RTC

RTC是一个独立的时钟电路,具有以下特点:

  1. 独立运行:不依赖主处理器,有独立的晶振和电源
  2. 低功耗:通常只需要几微安的电流
  3. 电池备份:使用纽扣电池(如CR2032)在断电时保持运行
  4. 高精度:温度补偿晶振可达±2ppm精度
  5. 多功能:支持闹钟、定时器、方波输出等功能

RTC的基本组成

RTC系统组成:

┌─────────────────────────────────────┐
│         RTC芯片                      │
│  ┌──────────────────────────────┐  │
│  │  时钟计数器                   │  │
│  │  - 秒、分、时                 │  │
│  │  - 日、月、年                 │  │
│  │  - 星期                       │  │
│  └──────────────────────────────┘  │
│  ┌──────────────────────────────┐  │
│  │  闹钟寄存器                   │  │
│  │  - 闹钟1                      │  │
│  │  - 闹钟2                      │  │
│  └──────────────────────────────┘  │
│  ┌──────────────────────────────┐  │
│  │  控制寄存器                   │  │
│  │  - 使能控制                   │  │
│  │  - 中断控制                   │  │
│  │  - 方波输出                   │  │
│  └──────────────────────────────┘  │
│  ┌──────────────────────────────┐  │
│  │  32.768kHz晶振               │  │
│  └──────────────────────────────┘  │
└─────────────────────────────────────┘
         │              │
         │              │
    I2C/SPI接口    电池备份电源
         │              │
         ↓              ↓
    主控制器        CR2032电池

RTC vs 软件定时器

RTC与软件定时器对比:

┌──────────────┬─────────┬─────────────┐
│   特性       │  RTC    │ 软件定时器   │
├──────────────┼─────────┼─────────────┤
│ 精度         │  高     │  中等       │
│ 功耗         │  极低   │  依赖MCU    │
│ 断电保持     │  是     │  否         │
│ 独立性       │  独立   │  依赖MCU    │
│ 成本         │  增加   │  无额外成本 │
│ 复杂度       │  中等   │  简单       │
│ 长期准确性   │  好     │  一般       │
└──────────────┴─────────┴─────────────┘

使用场景:
RTC:
- 需要长期准确时间
- 系统会断电
- 需要定时唤醒
- 数据记录时间戳

软件定时器:
- 短期计时
- 系统不断电
- 对精度要求不高
- 相对时间测量

应用场景

RTC在嵌入式系统中的典型应用:

  • 数据记录器:为传感器数据添加时间戳
  • 定时控制:定时开关设备、定时采集数据
  • 日程管理:闹钟、提醒、日程安排
  • 电力系统:电能表、事件记录
  • 工业控制:生产记录、设备维护提醒
  • 物联网设备:时间同步、定时上报
  • 医疗设备:用药提醒、数据记录
  • 安防系统:事件时间戳、定时布防

本文内容概览

本教程将涵盖以下内容:

  1. RTC基础知识:RTC原理、晶振选择、精度分析
  2. 常用RTC芯片:DS1307、DS3231、PCF8563特性对比
  3. I2C RTC应用:DS1307/DS3231驱动开发
  4. SPI RTC应用:高速RTC接口实现
  5. 时间管理:时间读写、格式转换、时区处理
  6. 闹钟功能:闹钟配置、中断处理、定时唤醒
  7. 电池备份:电池选择、功耗优化、电量监测
  8. 温度补偿:TCXO原理、温度读取、精度提升
  9. 时间同步:NTP同步、GPS同步、手动校准
  10. 实战项目:数据记录器、定时控制器、电子日历

第一部分:RTC基础知识

1.1 RTC工作原理

时钟计数原理

RTC时钟计数原理:

32.768kHz晶振
┌─────────┐
│ 分频器  │  ÷ 32768
└─────────┘
   1 Hz
┌─────────┐
│ 秒计数器│  0-59
└─────────┘
┌─────────┐
│ 分计数器│  0-59
└─────────┘
┌─────────┐
│ 时计数器│  0-23 (24小时制)
└─────────┘         0-12 (12小时制)
┌─────────┐
│ 日计数器│  1-31
└─────────┘
┌─────────┐
│ 月计数器│  1-12
└─────────┘
┌─────────┐
│ 年计数器│  00-99 (两位年)
└─────────┘         2000-2099 (四位年)

为什么使用32.768kHz?
- 2^15 = 32768
- 分频15次得到1Hz
- 便于数字电路实现
- 功耗低
- 晶振尺寸小

BCD码存储

/**
 * @brief  BCD码(Binary-Coded Decimal)
 * 
 * BCD码用4位二进制表示一位十进制数
 * 
 * 示例:
 * 十进制 59 = BCD 0x59
 * 十进制 23 = BCD 0x23
 * 
 * 高4位:十位
 * 低4位:个位
 */

/**
 * @brief  十进制转BCD码
 * @param  dec: 十进制数(0-99)
 * @return BCD码
 */
uint8_t dec_to_bcd(uint8_t dec)
{
    return ((dec / 10) << 4) | (dec % 10);
}

/**
 * @brief  BCD码转十进制
 * @param  bcd: BCD码
 * @return 十进制数
 */
uint8_t bcd_to_dec(uint8_t bcd)
{
    return ((bcd >> 4) * 10) + (bcd & 0x0F);
}

1.2 晶振选择

32.768kHz晶振特性

晶振参数:

1. 频率精度
   - 标准晶振:±20ppm
   - 高精度晶振:±10ppm
   - TCXO(温补晶振):±2ppm

   精度影响:
   ±20ppm = ±1.7秒/天 = ±52秒/月
   ±10ppm = ±0.86秒/天 = ±26秒/月
   ±2ppm = ±0.17秒/天 = ±5秒/月

2. 负载电容
   - 典型值:6pF, 7pF, 9pF, 12.5pF
   - 需要匹配外部电容
   - 影响频率精度

3. ESR(等效串联电阻)
   - 典型值:35kΩ - 70kΩ
   - 影响起振时间和功耗

4. 温度特性
   - 标准晶振:抛物线特性
   - 25°C时精度最高
   - 温度偏离时精度下降

5. 功耗
   - 典型值:1-2μW
   - 影响电池寿命

外部电容计算

/**
 * @brief  晶振外部电容计算
 * 
 * CL = (C1 * C2) / (C1 + C2) + Cstray
 * 
 * 其中:
 * CL: 晶振负载电容(数据手册给出)
 * C1, C2: 外部电容
 * Cstray: 寄生电容(约5pF)
 * 
 * 通常选择 C1 = C2,则:
 * C1 = C2 = 2 * (CL - Cstray)
 * 
 * 示例:
 * CL = 12.5pF
 * Cstray = 5pF
 * C1 = C2 = 2 * (12.5 - 5) = 15pF
 */

// 常用配置
// CL = 6pF:  C1 = C2 = 2pF (不常用)
// CL = 7pF:  C1 = C2 = 4pF
// CL = 9pF:  C1 = C2 = 8pF
// CL = 12.5pF: C1 = C2 = 15pF (最常用)

1.3 精度分析

影响RTC精度的因素

RTC精度影响因素:

1. 晶振精度
   - 初始精度:±20ppm
   - 老化:±3ppm/年
   - 温度漂移:±0.04ppm/°C²

2. 温度影响
   - 标准晶振温度系数:-0.04ppm/°C²
   - 25°C时精度最高
   - 温度偏离25°C时精度下降

   温度漂移计算:
   ΔT = T - 25°C
   Δf = -0.04 * ΔT² ppm

   示例:
   T = 0°C, ΔT = -25°C
   Δf = -0.04 * 625 = -25ppm
   误差 = 2.16秒/天

3. 电源电压
   - 电压变化影响振荡频率
   - 典型值:±0.5ppm/V

4. 负载电容
   - 电容偏差影响频率
   - 典型值:±1ppm/pF

5. PCB布局
   - 寄生电容
   - 寄生电感
   - 干扰

精度提升方法:
- 使用TCXO(温补晶振)
- 软件温度补偿
- 定期校准
- 优化PCB布局

第二部分:常用RTC芯片

2.1 DS1307 RTC芯片

DS1307特性

DS1307 实时时钟芯片:

基本特性:
- 接口:I2C(100kHz)
- 地址:0x68(固定)
- 电源:5V(4.5V-5.5V)
- 备用电源:3V(2.0V-3.5V)
- 功耗:1.5mA(5V)/ 500nA(备用)
- 精度:±2分钟/月(25°C)
- 晶振:32.768kHz
- 日历:2000-2099年
- RAM:56字节

功能:
- 秒、分、时、日、月、年、星期
- 12/24小时制
- 闰年自动调整
- 方波输出(1Hz, 4kHz, 8kHz, 32kHz)
- 56字节NVRAM

引脚定义:
1. X1    - 晶振输入
2. X2    - 晶振输出
3. VBAT  - 备用电池
4. GND   - 地
5. SDA   - I2C数据线
6. SCL   - I2C时钟线
7. SQW   - 方波输出
8. VCC   - 电源(5V)

优点:
- 价格低廉
- 使用简单
- 5V兼容

缺点:
- 精度较低
- 无温度补偿
- 功耗较高

DS1307寄存器映射

/**
 * @brief  DS1307寄存器地址
 */
#define DS1307_ADDR         0x68    // I2C地址

#define DS1307_REG_SECOND   0x00    // 秒寄存器(00-59)
#define DS1307_REG_MINUTE   0x01    // 分寄存器(00-59)
#define DS1307_REG_HOUR     0x02    // 时寄存器(00-23/01-12)
#define DS1307_REG_DAY      0x03    // 星期寄存器(01-07)
#define DS1307_REG_DATE     0x04    // 日寄存器(01-31)
#define DS1307_REG_MONTH    0x05    // 月寄存器(01-12)
#define DS1307_REG_YEAR     0x06    // 年寄存器(00-99)
#define DS1307_REG_CONTROL  0x07    // 控制寄存器
#define DS1307_REG_RAM      0x08    // RAM起始地址(08-3F)

/**
 * @brief  DS1307寄存器位定义
 */
// 秒寄存器
#define DS1307_CH_BIT       0x80    // 时钟停止位(1=停止,0=运行)

// 时寄存器
#define DS1307_12_24_BIT    0x40    // 12/24小时制(1=12小时,0=24小时)
#define DS1307_AM_PM_BIT    0x20    // AM/PM位(1=PM,0=AM)

// 控制寄存器
#define DS1307_OUT_BIT      0x80    // 输出控制位
#define DS1307_SQWE_BIT     0x10    // 方波使能位
#define DS1307_RS1_BIT      0x02    // 速率选择位1
#define DS1307_RS0_BIT      0x01    // 速率选择位0

/**
 * @brief  方波频率选择
 */
typedef enum {
    DS1307_SQW_1HZ = 0,     // 1Hz
    DS1307_SQW_4KHZ,        // 4.096kHz
    DS1307_SQW_8KHZ,        // 8.192kHz
    DS1307_SQW_32KHZ        // 32.768kHz
} ds1307_sqw_freq_t;

2.2 DS3231 RTC芯片

DS3231特性

DS3231 高精度实时时钟芯片:

基本特性:
- 接口:I2C(400kHz)
- 地址:0x68(固定)
- 电源:2.3V-5.5V
- 备用电源:2.3V-5.5V
- 功耗:200μA(3.3V)/ 2μA(备用)
- 精度:±2ppm(0°C-40°C)= ±1分钟/年
- 晶振:内置TCXO(温补晶振)
- 日历:2000-2099年
- 温度传感器:±3°C精度

功能:
- 秒、分、时、日、月、年、星期
- 12/24小时制
- 闰年自动调整
- 两个可编程闹钟
- 可编程方波输出(1Hz-8kHz)
- 温度补偿
- 温度传感器
- 32kHz输出
- 老化偏移寄存器

引脚定义:
1. 32KHZ  - 32kHz输出
2. VCC    - 电源
3. INT/SQW - 中断/方波输出
4. RST    - 复位(低电平有效)
5. GND    - 地
6. VBAT   - 备用电池
7. SDA    - I2C数据线
8. SCL    - I2C时钟线

优点:
- 高精度(±2ppm)
- 内置TCXO
- 温度补偿
- 低功耗
- 宽电压范围
- 两个闹钟

缺点:
- 价格较高
- 无NVRAM

DS3231寄存器映射

/**
 * @brief  DS3231寄存器地址
 */
#define DS3231_ADDR             0x68

// 时间寄存器
#define DS3231_REG_SECOND       0x00
#define DS3231_REG_MINUTE       0x01
#define DS3231_REG_HOUR         0x02
#define DS3231_REG_DAY          0x03
#define DS3231_REG_DATE         0x04
#define DS3231_REG_MONTH        0x05
#define DS3231_REG_YEAR         0x06

// 闹钟1寄存器
#define DS3231_REG_ALARM1_SEC   0x07
#define DS3231_REG_ALARM1_MIN   0x08
#define DS3231_REG_ALARM1_HOUR  0x09
#define DS3231_REG_ALARM1_DATE  0x0A

// 闹钟2寄存器
#define DS3231_REG_ALARM2_MIN   0x0B
#define DS3231_REG_ALARM2_HOUR  0x0C
#define DS3231_REG_ALARM2_DATE  0x0D

// 控制和状态寄存器
#define DS3231_REG_CONTROL      0x0E
#define DS3231_REG_STATUS       0x0F
#define DS3231_REG_AGING        0x10

// 温度寄存器
#define DS3231_REG_TEMP_MSB     0x11
#define DS3231_REG_TEMP_LSB     0x12

/**
 * @brief  DS3231控制寄存器位定义
 */
#define DS3231_EOSC_BIT         0x80    // 振荡器使能(0=使能)
#define DS3231_BBSQW_BIT        0x40    // 电池供电时方波使能
#define DS3231_CONV_BIT         0x20    // 温度转换
#define DS3231_RS2_BIT          0x10    // 速率选择位2
#define DS3231_RS1_BIT          0x08    // 速率选择位1
#define DS3231_INTCN_BIT        0x04    // 中断控制
#define DS3231_A2IE_BIT         0x02    // 闹钟2中断使能
#define DS3231_A1IE_BIT         0x01    // 闹钟1中断使能

/**
 * @brief  DS3231状态寄存器位定义
 */
#define DS3231_OSF_BIT          0x80    // 振荡器停止标志
#define DS3231_EN32KHZ_BIT      0x08    // 32kHz输出使能
#define DS3231_BSY_BIT          0x04    // 忙标志
#define DS3231_A2F_BIT          0x02    // 闹钟2标志
#define DS3231_A1F_BIT          0x01    // 闹钟1标志

2.3 PCF8563 RTC芯片

PCF8563特性

PCF8563 低功耗实时时钟芯片:

基本特性:
- 接口:I2C(400kHz)
- 地址:0x51(固定)
- 电源:1.0V-5.5V
- 备用电源:1.0V-5.5V
- 功耗:0.25μA(3V,典型值)
- 精度:±30ppm(25°C)
- 晶振:32.768kHz
- 日历:2000-2099年

功能:
- 秒、分、时、日、月、年、星期
- 闹钟功能
- 定时器功能
- 时钟输出(32.768kHz-1/60Hz)
- 中断输出
- 低功耗模式

引脚定义:
1. OSCI  - 晶振输入
2. OSCO  - 晶振输出
3. INT   - 中断输出
4. VSS   - 地
5. SDA   - I2C数据线
6. SCL   - I2C时钟线
7. CLKOUT - 时钟输出
8. VDD   - 电源

优点:
- 超低功耗
- 宽电压范围
- 价格适中
- 定时器功能

缺点:
- 精度一般
- 无温度补偿
- 功能相对简单

芯片对比

RTC芯片对比:

┌──────────┬─────────┬─────────┬─────────┐
│   特性   │ DS1307  │ DS3231  │ PCF8563 │
├──────────┼─────────┼─────────┼─────────┤
│ 精度     │ ±2分/月 │ ±1分/年 │ ±5分/月 │
│ 电源电压 │ 5V      │ 2.3-5.5V│ 1-5.5V  │
│ 功耗(运行)│ 1.5mA  │ 200μA   │ 0.25μA  │
│ 功耗(备用)│ 500nA  │ 2μA     │ 0.25μA  │
│ I2C速度  │ 100kHz  │ 400kHz  │ 400kHz  │
│ 温度补偿 │ 无      │ 有      │ 无      │
│ 闹钟数量 │ 0       │ 2       │ 1       │
│ NVRAM    │ 56字节  │ 无      │ 无      │
│ 温度传感器│ 无     │ 有      │ 无      │
│ 价格     │ 低      │ 高      │ 中      │
└──────────┴─────────┴─────────┴─────────┘

选择建议:
- 低成本应用:DS1307
- 高精度应用:DS3231
- 低功耗应用:PCF8563
- 需要NVRAM:DS1307
- 需要温度:DS3231

第三部分:I2C RTC驱动开发

3.1 DS1307驱动实现

/**
 * @file    ds1307.c
 * @brief   DS1307 RTC驱动实现
 * @author  嵌入式知识平台
 */

#include <stdint.h>
#include <stdbool.h>
#include "stm32f1xx_hal.h"

/* I2C句柄(需要在main.c中初始化) */
extern I2C_HandleTypeDef hi2c1;

/**
 * @brief  时间结构
 */
typedef struct {
    uint8_t second;     // 秒(0-59)
    uint8_t minute;     // 分(0-59)
    uint8_t hour;       // 时(0-23)
    uint8_t day;        // 星期(1-7,1=星期一)
    uint8_t date;       // 日(1-31)
    uint8_t month;      // 月(1-12)
    uint16_t year;      // 年(2000-2099)
} rtc_time_t;

/**
 * @brief  I2C写寄存器
 * @param  reg: 寄存器地址
 * @param  data: 数据
 * @return HAL状态
 */
static HAL_StatusTypeDef ds1307_write_reg(uint8_t reg, uint8_t data)
{
    uint8_t buffer[2] = {reg, data};
    return HAL_I2C_Master_Transmit(&hi2c1, DS1307_ADDR << 1, buffer, 2, 100);
}

/**
 * @brief  I2C读寄存器
 * @param  reg: 寄存器地址
 * @param  data: 数据指针
 * @return HAL状态
 */
static HAL_StatusTypeDef ds1307_read_reg(uint8_t reg, uint8_t *data)
{
    HAL_StatusTypeDef status;

    // 发送寄存器地址
    status = HAL_I2C_Master_Transmit(&hi2c1, DS1307_ADDR << 1, &reg, 1, 100);
    if (status != HAL_OK) {
        return status;
    }

    // 读取数据
    return HAL_I2C_Master_Receive(&hi2c1, DS1307_ADDR << 1, data, 1, 100);
}

/**
 * @brief  I2C读多个寄存器
 * @param  reg: 起始寄存器地址
 * @param  data: 数据缓冲区
 * @param  len: 数据长度
 * @return HAL状态
 */
static HAL_StatusTypeDef ds1307_read_regs(uint8_t reg, uint8_t *data, uint8_t len)
{
    HAL_StatusTypeDef status;

    status = HAL_I2C_Master_Transmit(&hi2c1, DS1307_ADDR << 1, &reg, 1, 100);
    if (status != HAL_OK) {
        return status;
    }

    return HAL_I2C_Master_Receive(&hi2c1, DS1307_ADDR << 1, data, len, 100);
}

/**
 * @brief  初始化DS1307
 * @return HAL状态
 */
HAL_StatusTypeDef ds1307_init(void)
{
    uint8_t data;
    HAL_StatusTypeDef status;

    // 读取秒寄存器
    status = ds1307_read_reg(DS1307_REG_SECOND, &data);
    if (status != HAL_OK) {
        return status;
    }

    // 检查时钟停止位
    if (data & DS1307_CH_BIT) {
        // 时钟已停止,启动时钟
        data &= ~DS1307_CH_BIT;
        status = ds1307_write_reg(DS1307_REG_SECOND, data);
        if (status != HAL_OK) {
            return status;
        }
    }

    // 设置24小时制
    status = ds1307_read_reg(DS1307_REG_HOUR, &data);
    if (status != HAL_OK) {
        return status;
    }

    if (data & DS1307_12_24_BIT) {
        // 当前是12小时制,转换为24小时制
        data &= ~DS1307_12_24_BIT;
        status = ds1307_write_reg(DS1307_REG_HOUR, data);
    }

    return status;
}

/**
 * @brief  设置时间
 * @param  time: 时间结构指针
 * @return HAL状态
 */
HAL_StatusTypeDef ds1307_set_time(const rtc_time_t *time)
{
    uint8_t buffer[8];

    // 准备数据(转换为BCD码)
    buffer[0] = DS1307_REG_SECOND;
    buffer[1] = dec_to_bcd(time->second);
    buffer[2] = dec_to_bcd(time->minute);
    buffer[3] = dec_to_bcd(time->hour);
    buffer[4] = dec_to_bcd(time->day);
    buffer[5] = dec_to_bcd(time->date);
    buffer[6] = dec_to_bcd(time->month);
    buffer[7] = dec_to_bcd(time->year - 2000);

    // 写入所有时间寄存器
    return HAL_I2C_Master_Transmit(&hi2c1, DS1307_ADDR << 1, buffer, 8, 100);
}

/**
 * @brief  读取时间
 * @param  time: 时间结构指针
 * @return HAL状态
 */
HAL_StatusTypeDef ds1307_get_time(rtc_time_t *time)
{
    uint8_t buffer[7];
    HAL_StatusTypeDef status;

    // 读取所有时间寄存器
    status = ds1307_read_regs(DS1307_REG_SECOND, buffer, 7);
    if (status != HAL_OK) {
        return status;
    }

    // 转换BCD码为十进制
    time->second = bcd_to_dec(buffer[0] & 0x7F);  // 忽略CH位
    time->minute = bcd_to_dec(buffer[1]);
    time->hour = bcd_to_dec(buffer[2] & 0x3F);    // 忽略12/24位
    time->day = bcd_to_dec(buffer[3]);
    time->date = bcd_to_dec(buffer[4]);
    time->month = bcd_to_dec(buffer[5]);
    time->year = bcd_to_dec(buffer[6]) + 2000;

    return HAL_OK;
}

/**
 * @brief  设置方波输出
 * @param  freq: 方波频率
 * @param  enable: 使能标志
 * @return HAL状态
 */
HAL_StatusTypeDef ds1307_set_square_wave(ds1307_sqw_freq_t freq, bool enable)
{
    uint8_t control = 0;

    if (enable) {
        control = DS1307_SQWE_BIT | (freq & 0x03);
    }

    return ds1307_write_reg(DS1307_REG_CONTROL, control);
}

/**
 * @brief  写NVRAM
 * @param  addr: RAM地址(0-55)
 * @param  data: 数据指针
 * @param  len: 数据长度
 * @return HAL状态
 */
HAL_StatusTypeDef ds1307_write_ram(uint8_t addr, const uint8_t *data, uint8_t len)
{
    uint8_t buffer[57];  // 1字节地址 + 56字节数据

    if (addr + len > 56) {
        return HAL_ERROR;  // 超出范围
    }

    buffer[0] = DS1307_REG_RAM + addr;
    memcpy(&buffer[1], data, len);

    return HAL_I2C_Master_Transmit(&hi2c1, DS1307_ADDR << 1, buffer, len + 1, 100);
}

/**
 * @brief  读NVRAM
 * @param  addr: RAM地址(0-55)
 * @param  data: 数据缓冲区
 * @param  len: 数据长度
 * @return HAL状态
 */
HAL_StatusTypeDef ds1307_read_ram(uint8_t addr, uint8_t *data, uint8_t len)
{
    if (addr + len > 56) {
        return HAL_ERROR;
    }

    uint8_t reg = DS1307_REG_RAM + addr;
    return ds1307_read_regs(reg, data, len);
}

3.2 DS3231驱动实现

/**
 * @file    ds3231.c
 * @brief   DS3231 RTC驱动实现
 */

/**
 * @brief  初始化DS3231
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_init(void)
{
    uint8_t control, status;
    HAL_StatusTypeDef hal_status;

    // 读取控制寄存器
    hal_status = ds1307_read_reg(DS3231_REG_CONTROL, &control);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    // 启用振荡器
    control &= ~DS3231_EOSC_BIT;

    // 设置为中断模式(不是方波模式)
    control |= DS3231_INTCN_BIT;

    // 写回控制寄存器
    hal_status = ds1307_write_reg(DS3231_REG_CONTROL, control);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    // 读取状态寄存器
    hal_status = ds1307_read_reg(DS3231_REG_STATUS, &status);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    // 清除振荡器停止标志
    if (status & DS3231_OSF_BIT) {
        status &= ~DS3231_OSF_BIT;
        hal_status = ds1307_write_reg(DS3231_REG_STATUS, status);
    }

    return hal_status;
}

/**
 * @brief  设置时间(DS3231使用相同的时间格式)
 */
HAL_StatusTypeDef ds3231_set_time(const rtc_time_t *time)
{
    return ds1307_set_time(time);  // 时间寄存器格式相同
}

/**
 * @brief  读取时间
 */
HAL_StatusTypeDef ds3231_get_time(rtc_time_t *time)
{
    return ds1307_get_time(time);  // 时间寄存器格式相同
}

/**
 * @brief  读取温度
 * @param  temperature: 温度指针(单位:°C)
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_get_temperature(float *temperature)
{
    uint8_t buffer[2];
    HAL_StatusTypeDef status;

    // 读取温度寄存器
    status = ds1307_read_regs(DS3231_REG_TEMP_MSB, buffer, 2);
    if (status != HAL_OK) {
        return status;
    }

    // 转换温度值
    // MSB: 整数部分(有符号)
    // LSB: 小数部分(高2位,0.25°C分辨率)
    int8_t temp_msb = (int8_t)buffer[0];
    uint8_t temp_lsb = buffer[1] >> 6;

    *temperature = temp_msb + (temp_lsb * 0.25f);

    return HAL_OK;
}

/**
 * @brief  强制温度转换
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_force_temp_conversion(void)
{
    uint8_t control;
    HAL_StatusTypeDef status;

    // 读取控制寄存器
    status = ds1307_read_reg(DS3231_REG_CONTROL, &control);
    if (status != HAL_OK) {
        return status;
    }

    // 设置CONV位
    control |= DS3231_CONV_BIT;

    // 写回控制寄存器
    return ds1307_write_reg(DS3231_REG_CONTROL, control);
}

/**
 * @brief  闹钟匹配模式
 */
typedef enum {
    DS3231_ALARM_EVERY_SECOND,      // 每秒触发
    DS3231_ALARM_MATCH_SECOND,      // 匹配秒
    DS3231_ALARM_MATCH_MIN_SEC,     // 匹配分和秒
    DS3231_ALARM_MATCH_HOUR_MIN_SEC,// 匹配时、分、秒
    DS3231_ALARM_MATCH_DATE_HOUR_MIN_SEC,  // 匹配日期、时、分、秒
    DS3231_ALARM_MATCH_DAY_HOUR_MIN_SEC    // 匹配星期、时、分、秒
} ds3231_alarm_mode_t;

/**
 * @brief  闹钟结构
 */
typedef struct {
    uint8_t second;     // 秒(仅闹钟1)
    uint8_t minute;     // 分
    uint8_t hour;       // 时
    uint8_t day_date;   // 日期或星期
    bool is_day;        // true=星期,false=日期
    ds3231_alarm_mode_t mode;  // 匹配模式
} ds3231_alarm_t;

/**
 * @brief  设置闹钟1
 * @param  alarm: 闹钟结构指针
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_set_alarm1(const ds3231_alarm_t *alarm)
{
    uint8_t buffer[5];
    uint8_t control;
    HAL_StatusTypeDef status;

    // 准备闹钟数据
    buffer[0] = DS3231_REG_ALARM1_SEC;
    buffer[1] = dec_to_bcd(alarm->second);
    buffer[2] = dec_to_bcd(alarm->minute);
    buffer[3] = dec_to_bcd(alarm->hour);
    buffer[4] = dec_to_bcd(alarm->day_date);

    // 设置匹配模式(A1M位)
    switch (alarm->mode) {
        case DS3231_ALARM_EVERY_SECOND:
            buffer[1] |= 0x80;
            buffer[2] |= 0x80;
            buffer[3] |= 0x80;
            buffer[4] |= 0x80;
            break;
        case DS3231_ALARM_MATCH_SECOND:
            buffer[2] |= 0x80;
            buffer[3] |= 0x80;
            buffer[4] |= 0x80;
            break;
        case DS3231_ALARM_MATCH_MIN_SEC:
            buffer[3] |= 0x80;
            buffer[4] |= 0x80;
            break;
        case DS3231_ALARM_MATCH_HOUR_MIN_SEC:
            buffer[4] |= 0x80;
            break;
        case DS3231_ALARM_MATCH_DATE_HOUR_MIN_SEC:
            // 日期匹配
            break;
        case DS3231_ALARM_MATCH_DAY_HOUR_MIN_SEC:
            // 星期匹配
            buffer[4] |= 0x40;
            break;
    }

    // 写入闹钟寄存器
    status = HAL_I2C_Master_Transmit(&hi2c1, DS3231_ADDR << 1, buffer, 5, 100);
    if (status != HAL_OK) {
        return status;
    }

    // 使能闹钟1中断
    status = ds1307_read_reg(DS3231_REG_CONTROL, &control);
    if (status != HAL_OK) {
        return status;
    }

    control |= DS3231_A1IE_BIT;

    return ds1307_write_reg(DS3231_REG_CONTROL, control);
}

/**
 * @brief  设置闹钟2(无秒匹配)
 * @param  alarm: 闹钟结构指针
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_set_alarm2(const ds3231_alarm_t *alarm)
{
    uint8_t buffer[4];
    uint8_t control;
    HAL_StatusTypeDef status;

    // 准备闹钟数据(闹钟2无秒寄存器)
    buffer[0] = DS3231_REG_ALARM2_MIN;
    buffer[1] = dec_to_bcd(alarm->minute);
    buffer[2] = dec_to_bcd(alarm->hour);
    buffer[3] = dec_to_bcd(alarm->day_date);

    // 设置匹配模式
    switch (alarm->mode) {
        case DS3231_ALARM_EVERY_SECOND:  // 闹钟2最快每分钟
            buffer[1] |= 0x80;
            buffer[2] |= 0x80;
            buffer[3] |= 0x80;
            break;
        case DS3231_ALARM_MATCH_MIN_SEC:
            buffer[2] |= 0x80;
            buffer[3] |= 0x80;
            break;
        case DS3231_ALARM_MATCH_HOUR_MIN_SEC:
            buffer[3] |= 0x80;
            break;
        case DS3231_ALARM_MATCH_DATE_HOUR_MIN_SEC:
            break;
        case DS3231_ALARM_MATCH_DAY_HOUR_MIN_SEC:
            buffer[3] |= 0x40;
            break;
        default:
            break;
    }

    // 写入闹钟寄存器
    status = HAL_I2C_Master_Transmit(&hi2c1, DS3231_ADDR << 1, buffer, 4, 100);
    if (status != HAL_OK) {
        return status;
    }

    // 使能闹钟2中断
    status = ds1307_read_reg(DS3231_REG_CONTROL, &control);
    if (status != HAL_OK) {
        return status;
    }

    control |= DS3231_A2IE_BIT;

    return ds1307_write_reg(DS3231_REG_CONTROL, control);
}

/**
 * @brief  清除闹钟标志
 * @param  alarm_num: 闹钟号(1或2)
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_clear_alarm(uint8_t alarm_num)
{
    uint8_t status;
    HAL_StatusTypeDef hal_status;

    // 读取状态寄存器
    hal_status = ds1307_read_reg(DS3231_REG_STATUS, &status);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    // 清除对应的闹钟标志
    if (alarm_num == 1) {
        status &= ~DS3231_A1F_BIT;
    } else if (alarm_num == 2) {
        status &= ~DS3231_A2F_BIT;
    }

    // 写回状态寄存器
    return ds1307_write_reg(DS3231_REG_STATUS, status);
}

/**
 * @brief  检查闹钟标志
 * @param  alarm_num: 闹钟号(1或2)
 * @return true: 闹钟触发, false: 未触发
 */
bool ds3231_check_alarm(uint8_t alarm_num)
{
    uint8_t status;

    if (ds1307_read_reg(DS3231_REG_STATUS, &status) != HAL_OK) {
        return false;
    }

    if (alarm_num == 1) {
        return (status & DS3231_A1F_BIT) != 0;
    } else if (alarm_num == 2) {
        return (status & DS3231_A2F_BIT) != 0;
    }

    return false;
}

/**
 * @brief  使能32kHz输出
 * @param  enable: 使能标志
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_enable_32khz(bool enable)
{
    uint8_t status;
    HAL_StatusTypeDef hal_status;

    hal_status = ds1307_read_reg(DS3231_REG_STATUS, &status);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    if (enable) {
        status |= DS3231_EN32KHZ_BIT;
    } else {
        status &= ~DS3231_EN32KHZ_BIT;
    }

    return ds1307_write_reg(DS3231_REG_STATUS, status);
}

第四部分:时间管理

4.1 时间格式转换

/**
 * @file    rtc_time.c
 * @brief   RTC时间管理函数
 */

#include <time.h>
#include <stdio.h>

/**
 * @brief  RTC时间转Unix时间戳
 * @param  rtc_time: RTC时间结构
 * @return Unix时间戳(秒)
 */
time_t rtc_to_timestamp(const rtc_time_t *rtc_time)
{
    struct tm timeinfo = {0};

    timeinfo.tm_sec = rtc_time->second;
    timeinfo.tm_min = rtc_time->minute;
    timeinfo.tm_hour = rtc_time->hour;
    timeinfo.tm_mday = rtc_time->date;
    timeinfo.tm_mon = rtc_time->month - 1;  // tm_mon: 0-11
    timeinfo.tm_year = rtc_time->year - 1900;  // tm_year: 从1900年起
    timeinfo.tm_wday = rtc_time->day - 1;  // tm_wday: 0-6

    return mktime(&timeinfo);
}

/**
 * @brief  Unix时间戳转RTC时间
 * @param  timestamp: Unix时间戳
 * @param  rtc_time: RTC时间结构指针
 */
void timestamp_to_rtc(time_t timestamp, rtc_time_t *rtc_time)
{
    struct tm *timeinfo = localtime(&timestamp);

    rtc_time->second = timeinfo->tm_sec;
    rtc_time->minute = timeinfo->tm_min;
    rtc_time->hour = timeinfo->tm_hour;
    rtc_time->date = timeinfo->tm_mday;
    rtc_time->month = timeinfo->tm_mon + 1;
    rtc_time->year = timeinfo->tm_year + 1900;
    rtc_time->day = timeinfo->tm_wday + 1;
}

/**
 * @brief  格式化时间字符串
 * @param  rtc_time: RTC时间结构
 * @param  buffer: 输出缓冲区
 * @param  format: 格式字符串
 * 
 * 格式说明:
 * %Y - 年(4位)
 * %m - 月(2位)
 * %d - 日(2位)
 * %H - 时(24小时制,2位)
 * %M - 分(2位)
 * %S - 秒(2位)
 * %w - 星期(1-7)
 */
void rtc_format_time(const rtc_time_t *rtc_time, char *buffer, const char *format)
{
    time_t timestamp = rtc_to_timestamp(rtc_time);
    struct tm *timeinfo = localtime(&timestamp);

    strftime(buffer, 64, format, timeinfo);
}

/**
 * @brief  解析时间字符串
 * @param  str: 时间字符串(格式:YYYY-MM-DD HH:MM:SS)
 * @param  rtc_time: RTC时间结构指针
 * @return 0: 成功, -1: 失败
 */
int rtc_parse_time(const char *str, rtc_time_t *rtc_time)
{
    int year, month, date, hour, minute, second;

    if (sscanf(str, "%d-%d-%d %d:%d:%d", 
               &year, &month, &date, &hour, &minute, &second) != 6) {
        return -1;
    }

    // 验证范围
    if (year < 2000 || year > 2099 ||
        month < 1 || month > 12 ||
        date < 1 || date > 31 ||
        hour < 0 || hour > 23 ||
        minute < 0 || minute > 59 ||
        second < 0 || second > 59) {
        return -1;
    }

    rtc_time->year = year;
    rtc_time->month = month;
    rtc_time->date = date;
    rtc_time->hour = hour;
    rtc_time->minute = minute;
    rtc_time->second = second;

    // 计算星期
    rtc_time->day = rtc_calculate_day_of_week(year, month, date);

    return 0;
}

/**
 * @brief  计算星期几(Zeller公式)
 * @param  year: 年
 * @param  month: 月
 * @param  date: 日
 * @return 星期(1-7,1=星期一)
 */
uint8_t rtc_calculate_day_of_week(uint16_t year, uint8_t month, uint8_t date)
{
    if (month < 3) {
        month += 12;
        year--;
    }

    int c = year / 100;
    int y = year % 100;
    int w = (c / 4) - 2 * c + y + (y / 4) + (13 * (month + 1) / 5) + date - 1;

    w = ((w % 7) + 7) % 7;  // 确保结果为正

    // 转换为1-7(1=星期一)
    return (w == 0) ? 7 : w;
}

/**
 * @brief  判断闰年
 * @param  year: 年份
 * @return true: 闰年, false: 平年
 */
bool rtc_is_leap_year(uint16_t year)
{
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}

/**
 * @brief  获取月份天数
 * @param  year: 年份
 * @param  month: 月份(1-12)
 * @return 天数
 */
uint8_t rtc_get_days_in_month(uint16_t year, uint8_t month)
{
    const uint8_t days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    if (month < 1 || month > 12) {
        return 0;
    }

    uint8_t day_count = days[month - 1];

    // 闰年2月有29天
    if (month == 2 && rtc_is_leap_year(year)) {
        day_count = 29;
    }

    return day_count;
}

4.2 时区处理

/**
 * @brief  时区偏移(小时)
 */
static int8_t timezone_offset = 8;  // 默认东八区(北京时间)

/**
 * @brief  设置时区
 * @param  offset: 时区偏移(-12到+14)
 */
void rtc_set_timezone(int8_t offset)
{
    if (offset >= -12 && offset <= 14) {
        timezone_offset = offset;
    }
}

/**
 * @brief  获取时区
 * @return 时区偏移
 */
int8_t rtc_get_timezone(void)
{
    return timezone_offset;
}

/**
 * @brief  UTC时间转本地时间
 * @param  utc_time: UTC时间
 * @param  local_time: 本地时间
 */
void rtc_utc_to_local(const rtc_time_t *utc_time, rtc_time_t *local_time)
{
    time_t timestamp = rtc_to_timestamp(utc_time);
    timestamp += timezone_offset * 3600;  // 加上时区偏移
    timestamp_to_rtc(timestamp, local_time);
}

/**
 * @brief  本地时间转UTC时间
 * @param  local_time: 本地时间
 * @param  utc_time: UTC时间
 */
void rtc_local_to_utc(const rtc_time_t *local_time, rtc_time_t *utc_time)
{
    time_t timestamp = rtc_to_timestamp(local_time);
    timestamp -= timezone_offset * 3600;  // 减去时区偏移
    timestamp_to_rtc(timestamp, utc_time);
}

第五部分:电池备份与功耗优化

5.1 电池选择

RTC备用电池选择:

常用电池类型:

1. CR2032(最常用)
   - 电压:3V
   - 容量:220mAh
   - 尺寸:20mm × 3.2mm
   - 寿命:5-10年(典型)
   - 价格:低
   - 应用:大多数RTC

2. CR2025
   - 电压:3V
   - 容量:165mAh
   - 尺寸:20mm × 2.5mm
   - 寿命:3-7年
   - 应用:空间受限场合

3. CR1220
   - 电压:3V
   - 容量:40mAh
   - 尺寸:12.5mm × 2.0mm
   - 寿命:1-3年
   - 应用:小型设备

4. 超级电容
   - 电压:5.5V
   - 容量:0.1-1F
   - 寿命:无限次充放电
   - 保持时间:数小时到数天
   - 应用:频繁更换电池不便的场合

电池寿命计算:

寿命(年)= 电池容量(mAh)/ (RTC功耗(μA)× 24 × 365) / 1000

示例:
CR2032: 220mAh
DS3231备用功耗:2μA
寿命 = 220 / (2 × 24 × 365) / 1000 = 12.5年

实际寿命会受以下因素影响:
- 温度(高温加速放电)
- 自放电率(约1-2%/年)
- 电池质量
- 存储时间

5.2 功耗优化

/**
 * @brief  RTC功耗优化配置
 */

/**
 * @brief  配置DS3231低功耗模式
 * @return HAL状态
 */
HAL_StatusTypeDef ds3231_configure_low_power(void)
{
    uint8_t control, status;
    HAL_StatusTypeDef hal_status;

    // 读取控制寄存器
    hal_status = ds1307_read_reg(DS3231_REG_CONTROL, &control);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    // 禁用32kHz输出(节省功耗)
    hal_status = ds1307_read_reg(DS3231_REG_STATUS, &status);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    status &= ~DS3231_EN32KHZ_BIT;
    hal_status = ds1307_write_reg(DS3231_REG_STATUS, status);
    if (hal_status != HAL_OK) {
        return hal_status;
    }

    // 禁用方波输出(使用中断模式)
    control |= DS3231_INTCN_BIT;

    // 禁用电池供电时的方波输出
    control &= ~DS3231_BBSQW_BIT;

    return ds1307_write_reg(DS3231_REG_CONTROL, control);
}

/**
 * @brief  检测电池电压(需要外部电路)
 * @param  voltage: 电压指针(单位:V)
 * @return 0: 成功, -1: 失败
 */
int rtc_check_battery_voltage(float *voltage)
{
    // 这需要外部ADC电路来测量VBAT引脚电压
    // 示例代码(需要根据实际硬件调整)

    extern ADC_HandleTypeDef hadc1;
    uint32_t adc_value;

    // 启动ADC转换
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 100);
    adc_value = HAL_ADC_GetValue(&hadc1);
    HAL_ADC_Stop(&hadc1);

    // 转换为电压(假设12位ADC,参考电压3.3V,分压比1:1)
    *voltage = (adc_value * 3.3f) / 4095.0f;

    return 0;
}

/**
 * @brief  电池电量警告
 * @return true: 电量低, false: 电量正常
 */
bool rtc_battery_low_warning(void)
{
    float voltage;

    if (rtc_check_battery_voltage(&voltage) == 0) {
        // CR2032电池低于2.5V时需要更换
        return (voltage < 2.5f);
    }

    return false;
}

5.3 电源切换

RTC电源切换机制:

主电源和备用电源切换:

┌─────────────┐
│   主电源    │ VCC (3.3V/5V)
│   (VCC)     │
└──────┬──────┘
       ├──────→ RTC芯片
┌──────┴──────┐
│  电源切换   │
│   电路      │
└──────┬──────┘
┌──────┴──────┐
│  备用电源   │ VBAT (3V)
│  (VBAT)     │
└─────────────┘

切换条件:
- VCC > VBAT + 0.2V:使用主电源
- VCC < VBAT - 0.2V:切换到备用电源

切换过程:
1. 检测VCC电压下降
2. 自动切换到VBAT
3. RTC继续运行
4. 功耗降低到备用模式
5. VCC恢复后自动切换回主电源

注意事项:
- 切换过程无缝,不影响时间
- 备用模式下功耗极低(μA级)
- 某些功能可能被禁用(如温度转换)
- 中断输出可能受影响

第六部分:时间同步

6.1 NTP时间同步

/**
 * @file    rtc_ntp.c
 * @brief   RTC NTP时间同步
 */

#include <string.h>

/**
 * @brief  NTP时间戳结构
 */
typedef struct {
    uint32_t seconds;       // 从1900年1月1日起的秒数
    uint32_t fraction;      // 秒的小数部分
} ntp_timestamp_t;

/**
 * @brief  NTP数据包结构
 */
typedef struct {
    uint8_t li_vn_mode;     // 闰秒指示、版本号、模式
    uint8_t stratum;        // 层级
    uint8_t poll;           // 轮询间隔
    uint8_t precision;      // 精度
    uint32_t root_delay;    // 根延迟
    uint32_t root_dispersion; // 根离散
    uint32_t ref_id;        // 参考ID
    ntp_timestamp_t ref_timestamp;    // 参考时间戳
    ntp_timestamp_t orig_timestamp;   // 原始时间戳
    ntp_timestamp_t recv_timestamp;   // 接收时间戳
    ntp_timestamp_t trans_timestamp;  // 传输时间戳
} ntp_packet_t;

/**
 * @brief  NTP时间戳转Unix时间戳
 * @param  ntp_time: NTP时间戳
 * @return Unix时间戳
 */
time_t ntp_to_unix_time(uint32_t ntp_time)
{
    // NTP时间从1900年开始,Unix时间从1970年开始
    // 1900-1970年之间有70年 = 2208988800秒
    const uint32_t NTP_UNIX_OFFSET = 2208988800UL;

    return (time_t)(ntp_time - NTP_UNIX_OFFSET);
}

/**
 * @brief  Unix时间戳转NTP时间戳
 * @param  unix_time: Unix时间戳
 * @return NTP时间戳
 */
uint32_t unix_to_ntp_time(time_t unix_time)
{
    const uint32_t NTP_UNIX_OFFSET = 2208988800UL;
    return (uint32_t)unix_time + NTP_UNIX_OFFSET;
}

/**
 * @brief  通过NTP同步RTC时间
 * @param  ntp_server: NTP服务器地址
 * @return 0: 成功, -1: 失败
 */
int rtc_sync_with_ntp(const char *ntp_server)
{
    // 这里需要网络功能支持(WiFi/Ethernet)
    // 示例代码框架

    ntp_packet_t ntp_packet = {0};
    uint32_t ntp_time;
    time_t unix_time;
    rtc_time_t rtc_time;

    // 1. 构建NTP请求包
    ntp_packet.li_vn_mode = 0x1B;  // LI=0, VN=3, Mode=3(客户端)

    // 2. 发送NTP请求到服务器(需要UDP socket)
    // send_udp_packet(ntp_server, 123, &ntp_packet, sizeof(ntp_packet));

    // 3. 接收NTP响应
    // receive_udp_packet(&ntp_packet, sizeof(ntp_packet));

    // 4. 提取传输时间戳
    ntp_time = ntohl(ntp_packet.trans_timestamp.seconds);

    // 5. 转换为Unix时间戳
    unix_time = ntp_to_unix_time(ntp_time);

    // 6. 转换为RTC时间
    timestamp_to_rtc(unix_time, &rtc_time);

    // 7. 设置RTC时间
    return (ds3231_set_time(&rtc_time) == HAL_OK) ? 0 : -1;
}

6.2 GPS时间同步

/**
 * @brief  从GPS NMEA数据同步时间
 * @param  nmea_sentence: NMEA语句($GPRMC或$GPGGA)
 * @return 0: 成功, -1: 失败
 */
int rtc_sync_with_gps(const char *nmea_sentence)
{
    // 解析GPRMC语句
    // $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
    //        时间   状态 纬度      经度      速度  航向  日期

    char time_str[11];
    char date_str[7];
    char status;
    rtc_time_t rtc_time;

    // 检查是否是GPRMC语句
    if (strncmp(nmea_sentence, "$GPRMC", 6) != 0) {
        return -1;
    }

    // 解析NMEA语句
    if (sscanf(nmea_sentence, "$GPRMC,%10[^,],%c,%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%*[^,],%6[^,]",
               time_str, &status, date_str) != 3) {
        return -1;
    }

    // 检查GPS状态
    if (status != 'A') {
        return -1;  // GPS数据无效
    }

    // 解析时间:HHMMSS.sss
    int hour, minute, second;
    if (sscanf(time_str, "%2d%2d%2d", &hour, &minute, &second) != 3) {
        return -1;
    }

    // 解析日期:DDMMYY
    int date, month, year;
    if (sscanf(date_str, "%2d%2d%2d", &date, &month, &year) != 3) {
        return -1;
    }

    // 构建RTC时间
    rtc_time.hour = hour;
    rtc_time.minute = minute;
    rtc_time.second = second;
    rtc_time.date = date;
    rtc_time.month = month;
    rtc_time.year = 2000 + year;
    rtc_time.day = rtc_calculate_day_of_week(rtc_time.year, rtc_time.month, rtc_time.date);

    // GPS时间是UTC时间,需要转换为本地时间
    rtc_time_t local_time;
    rtc_utc_to_local(&rtc_time, &local_time);

    // 设置RTC时间
    return (ds3231_set_time(&local_time) == HAL_OK) ? 0 : -1;
}

6.3 手动校准

/**
 * @brief  RTC时间校准
 * @param  offset_seconds: 偏移秒数(正数=快,负数=慢)
 * @return 0: 成功, -1: 失败
 */
int rtc_calibrate(int32_t offset_seconds)
{
    rtc_time_t current_time;
    time_t timestamp;

    // 读取当前时间
    if (ds3231_get_time(&current_time) != HAL_OK) {
        return -1;
    }

    // 转换为时间戳
    timestamp = rtc_to_timestamp(&current_time);

    // 应用偏移
    timestamp += offset_seconds;

    // 转换回RTC时间
    timestamp_to_rtc(timestamp, &current_time);

    // 设置新时间
    return (ds3231_set_time(&current_time) == HAL_OK) ? 0 : -1;
}

/**
 * @brief  DS3231老化偏移校准
 * @param  offset: 老化偏移值(-128到127)
 * @return HAL状态
 * 
 * 老化偏移寄存器用于补偿晶振老化导致的频率偏移
 * 每个LSB约等于0.1ppm的频率调整
 */
HAL_StatusTypeDef ds3231_set_aging_offset(int8_t offset)
{
    return ds1307_write_reg(DS3231_REG_AGING, (uint8_t)offset);
}

第七部分:实战项目

7.1 项目一:数据记录器

/**
 * @file    project_data_logger.c
 * @brief   数据记录器项目
 */

/**
 * @brief  数据记录结构
 */
typedef struct {
    rtc_time_t timestamp;   // 时间戳
    float temperature;      // 温度
    float humidity;         // 湿度
    uint16_t light;         // 光照强度
} data_record_t;

/**
 * @brief  初始化数据记录器
 * @return 0: 成功, -1: 失败
 */
int data_logger_init(void)
{
    // 初始化RTC
    if (ds3231_init() != HAL_OK) {
        return -1;
    }

    // 配置低功耗模式
    ds3231_configure_low_power();

    // 设置闹钟2(每分钟触发一次)
    ds3231_alarm_t alarm = {
        .minute = 0,
        .hour = 0,
        .day_date = 0,
        .is_day = false,
        .mode = DS3231_ALARM_EVERY_SECOND  // 闹钟2最快每分钟
    };

    ds3231_set_alarm2(&alarm);

    return 0;
}

/**
 * @brief  记录数据
 * @param  record: 数据记录指针
 * @return 0: 成功, -1: 失败
 */
int data_logger_log(data_record_t *record)
{
    // 读取当前时间
    if (ds3231_get_time(&record->timestamp) != HAL_OK) {
        return -1;
    }

    // 读取传感器数据(示例)
    record->temperature = 25.5f;  // 实际应从传感器读取
    record->humidity = 60.0f;
    record->light = 500;

    // 将数据写入存储(SD卡、Flash等)
    // save_to_storage(record);

    // 格式化并打印
    char time_str[64];
    rtc_format_time(&record->timestamp, time_str, "%Y-%m-%d %H:%M:%S");

    printf("[%s] Temp: %.1f°C, Humidity: %.1f%%, Light: %d\r\n",
           time_str, record->temperature, record->humidity, record->light);

    return 0;
}

/**
 * @brief  数据记录器主循环
 */
void data_logger_loop(void)
{
    // 检查闹钟2标志
    if (ds3231_check_alarm(2)) {
        // 清除闹钟标志
        ds3231_clear_alarm(2);

        // 记录数据
        data_record_t record;
        data_logger_log(&record);
    }
}

7.2 项目二:定时控制器

/**
 * @file    project_timer_controller.c
 * @brief   定时控制器项目
 */

/**
 * @brief  定时任务结构
 */
typedef struct {
    uint8_t hour;           // 触发时间(时)
    uint8_t minute;         // 触发时间(分)
    bool enabled;           // 使能标志
    void (*callback)(void); // 回调函数
} timer_task_t;

#define MAX_TIMER_TASKS  10
static timer_task_t timer_tasks[MAX_TIMER_TASKS];

/**
 * @brief  添加定时任务
 * @param  hour: 小时
 * @param  minute: 分钟
 * @param  callback: 回调函数
 * @return 任务ID,-1表示失败
 */
int timer_add_task(uint8_t hour, uint8_t minute, void (*callback)(void))
{
    for (int i = 0; i < MAX_TIMER_TASKS; i++) {
        if (!timer_tasks[i].enabled) {
            timer_tasks[i].hour = hour;
            timer_tasks[i].minute = minute;
            timer_tasks[i].callback = callback;
            timer_tasks[i].enabled = true;
            return i;
        }
    }
    return -1;  // 任务列表已满
}

/**
 * @brief  删除定时任务
 * @param  task_id: 任务ID
 */
void timer_remove_task(int task_id)
{
    if (task_id >= 0 && task_id < MAX_TIMER_TASKS) {
        timer_tasks[task_id].enabled = false;
    }
}

/**
 * @brief  检查并执行定时任务
 */
void timer_check_tasks(void)
{
    rtc_time_t current_time;

    // 读取当前时间
    if (ds3231_get_time(&current_time) != HAL_OK) {
        return;
    }

    // 检查所有任务
    for (int i = 0; i < MAX_TIMER_TASKS; i++) {
        if (timer_tasks[i].enabled) {
            if (current_time.hour == timer_tasks[i].hour &&
                current_time.minute == timer_tasks[i].minute &&
                current_time.second == 0) {  // 在整分钟时触发

                // 执行回调
                if (timer_tasks[i].callback != NULL) {
                    timer_tasks[i].callback();
                }
            }
        }
    }
}

/**
 * @brief  示例回调函数
 */
void turn_on_light(void)
{
    printf("Turning on light at scheduled time\r\n");
    // HAL_GPIO_WritePin(LIGHT_GPIO_Port, LIGHT_Pin, GPIO_PIN_SET);
}

void turn_off_light(void)
{
    printf("Turning off light at scheduled time\r\n");
    // HAL_GPIO_WritePin(LIGHT_GPIO_Port, LIGHT_Pin, GPIO_PIN_RESET);
}

/**
 * @brief  定时控制器初始化
 */
void timer_controller_init(void)
{
    // 初始化RTC
    ds3231_init();

    // 添加定时任务
    timer_add_task(7, 0, turn_on_light);   // 早上7:00开灯
    timer_add_task(22, 0, turn_off_light); // 晚上22:00关灯
}

7.3 项目三:电子日历

/**
 * @file    project_calendar.c
 * @brief   电子日历项目
 */

/**
 * @brief  显示当前时间和日期
 */
void calendar_display(void)
{
    rtc_time_t current_time;
    char time_str[64];
    char date_str[64];
    const char *weekdays[] = {"", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};

    // 读取时间
    if (ds3231_get_time(&current_time) != HAL_OK) {
        return;
    }

    // 格式化时间
    snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d",
             current_time.hour, current_time.minute, current_time.second);

    // 格式化日期
    snprintf(date_str, sizeof(date_str), "%04d-%02d-%02d %s",
             current_time.year, current_time.month, current_time.date,
             weekdays[current_time.day]);

    // 显示(LCD/OLED/串口)
    printf("\r\n=== Electronic Calendar ===\r\n");
    printf("Time: %s\r\n", time_str);
    printf("Date: %s\r\n", date_str);

    // 读取温度(DS3231)
    float temperature;
    if (ds3231_get_temperature(&temperature) == HAL_OK) {
        printf("Temp: %.2f°C\r\n", temperature);
    }

    printf("==========================\r\n");
}

/**
 * @brief  显示月历
 * @param  year: 年份
 * @param  month: 月份
 */
void calendar_display_month(uint16_t year, uint8_t month)
{
    const char *month_names[] = {
        "", "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    };

    uint8_t days_in_month = rtc_get_days_in_month(year, month);
    uint8_t first_day = rtc_calculate_day_of_week(year, month, 1);

    printf("\r\n    %s %d\r\n", month_names[month], year);
    printf("Mon Tue Wed Thu Fri Sat Sun\r\n");
    printf("----------------------------\r\n");

    // 打印前导空格
    for (int i = 1; i < first_day; i++) {
        printf("    ");
    }

    // 打印日期
    for (int day = 1; day <= days_in_month; day++) {
        printf("%3d ", day);

        uint8_t weekday = rtc_calculate_day_of_week(year, month, day);
        if (weekday == 7) {  // 星期日,换行
            printf("\r\n");
        }
    }

    printf("\r\n");
}

第八部分:故障排除

8.1 常见问题

RTC常见问题及解决方案:

1. RTC时间不准确
   原因:
   - 晶振精度低
   - 温度影响
   - 负载电容不匹配
   - 晶振老化

   解决:
   - 使用高精度晶振或TCXO
   - 使用DS3231(内置TCXO)
   - 检查外部电容值
   - 定期校准
   - 使用NTP/GPS同步

2. RTC时间丢失
   原因:
   - 备用电池耗尽
   - 电池接触不良
   - 电池未安装
   - 振荡器停止

   解决:
   - 更换电池
   - 检查电池座
   - 检查VBAT连接
   - 检查振荡器使能位

3. I2C通信失败
   原因:
   - 上拉电阻缺失或不合适
   - 地址错误
   - 时序不正确
   - 总线冲突

   解决:
   - 添加4.7kΩ上拉电阻
   - 确认I2C地址(DS1307/DS3231: 0x68)
   - 降低I2C时钟频率
   - 检查总线上其他设备

4. 闹钟不触发
   原因:
   - 闹钟未使能
   - 中断引脚未连接
   - 匹配模式设置错误
   - 闹钟标志未清除

   解决:
   - 检查闹钟使能位
   - 连接INT引脚到MCU
   - 检查闹钟匹配模式
   - 及时清除闹钟标志

5. 时间跳变
   原因:
   - 读取时序问题
   - 秒进位时读取
   - 多线程竞争

   解决:
   - 连续读取两次,比较是否一致
   - 避免在秒进位时读取
   - 使用互斥锁保护

6. 功耗过高
   原因:
   - 方波输出未禁用
   - 32kHz输出未禁用
   - 温度转换频繁

   解决:
   - 禁用不需要的输出
   - 降低温度转换频率
   - 使用低功耗模式

7. 温度读数异常(DS3231)
   原因:
   - 温度转换未完成
   - 读取时序错误

   解决:
   - 等待温度转换完成
   - 检查BSY标志
   - 强制温度转换

8. 年份错误
   原因:
   - 年份寄存器只有两位
   - 世纪位处理错误

   解决:
   - 软件处理世纪(2000-2099)
   - 检查年份范围

8.2 调试技巧

/**
 * @brief  RTC诊断函数
 * @return 0: 正常, -1: 异常
 */
int rtc_diagnose(void)
{
    printf("\r\n=== RTC Diagnostics ===\r\n");

    // 1. 测试I2C通信
    printf("Testing I2C communication...\r\n");
    uint8_t test_data;
    if (ds1307_read_reg(DS3231_REG_SECOND, &test_data) != HAL_OK) {
        printf("ERROR: I2C communication failed\r\n");
        return -1;
    }
    printf("OK: I2C communication successful\r\n");

    // 2. 检查振荡器状态
    printf("Checking oscillator status...\r\n");
    uint8_t status;
    ds1307_read_reg(DS3231_REG_STATUS, &status);
    if (status & DS3231_OSF_BIT) {
        printf("WARNING: Oscillator stop flag set\r\n");
    } else {
        printf("OK: Oscillator running\r\n");
    }

    // 3. 读取并显示时间
    printf("Reading current time...\r\n");
    rtc_time_t current_time;
    if (ds3231_get_time(&current_time) == HAL_OK) {
        printf("Time: %04d-%02d-%02d %02d:%02d:%02d\r\n",
               current_time.year, current_time.month, current_time.date,
               current_time.hour, current_time.minute, current_time.second);
    } else {
        printf("ERROR: Failed to read time\r\n");
        return -1;
    }

    // 4. 读取温度(DS3231)
    printf("Reading temperature...\r\n");
    float temperature;
    if (ds3231_get_temperature(&temperature) == HAL_OK) {
        printf("Temperature: %.2f°C\r\n", temperature);
    } else {
        printf("WARNING: Failed to read temperature\r\n");
    }

    // 5. 检查电池电压
    printf("Checking battery voltage...\r\n");
    float voltage;
    if (rtc_check_battery_voltage(&voltage) == 0) {
        printf("Battery voltage: %.2fV\r\n", voltage);
        if (voltage < 2.5f) {
            printf("WARNING: Battery voltage low, replace soon\r\n");
        }
    }

    printf("======================\r\n");
    return 0;
}

/**
 * @brief  RTC寄存器转储
 */
void rtc_dump_registers(void)
{
    uint8_t regs[19];

    printf("\r\n=== RTC Register Dump ===\r\n");

    // 读取所有寄存器
    if (ds1307_read_regs(0x00, regs, 19) == HAL_OK) {
        printf("Addr  Value  Description\r\n");
        printf("----  -----  -----------\r\n");
        printf("0x00  0x%02X  Seconds\r\n", regs[0]);
        printf("0x01  0x%02X  Minutes\r\n", regs[1]);
        printf("0x02  0x%02X  Hours\r\n", regs[2]);
        printf("0x03  0x%02X  Day\r\n", regs[3]);
        printf("0x04  0x%02X  Date\r\n", regs[4]);
        printf("0x05  0x%02X  Month\r\n", regs[5]);
        printf("0x06  0x%02X  Year\r\n", regs[6]);
        printf("0x07  0x%02X  Alarm1 Seconds\r\n", regs[7]);
        printf("0x08  0x%02X  Alarm1 Minutes\r\n", regs[8]);
        printf("0x09  0x%02X  Alarm1 Hours\r\n", regs[9]);
        printf("0x0A  0x%02X  Alarm1 Day/Date\r\n", regs[10]);
        printf("0x0B  0x%02X  Alarm2 Minutes\r\n", regs[11]);
        printf("0x0C  0x%02X  Alarm2 Hours\r\n", regs[12]);
        printf("0x0D  0x%02X  Alarm2 Day/Date\r\n", regs[13]);
        printf("0x0E  0x%02X  Control\r\n", regs[14]);
        printf("0x0F  0x%02X  Status\r\n", regs[15]);
        printf("0x10  0x%02X  Aging Offset\r\n", regs[16]);
        printf("0x11  0x%02X  Temp MSB\r\n", regs[17]);
        printf("0x12  0x%02X  Temp LSB\r\n", regs[18]);
    }

    printf("========================\r\n");
}

第九部分:最佳实践

9.1 设计建议

RTC应用设计最佳实践:

1. 芯片选择
   - 低成本应用:DS1307
   - 高精度应用:DS3231
   - 低功耗应用:PCF8563
   - 需要温度:DS3231
   - 需要NVRAM:DS1307

2. 硬件设计
   - 使用推荐的负载电容
   - 晶振和电容靠近RTC芯片
   - 保持晶振走线短而对称
   - 避免在晶振下方走线
   - 添加I2C上拉电阻(4.7kΩ)
   - 电池座要可靠
   - 考虑电池电压监测电路

3. 软件设计
   - 初始化时检查振荡器状态
   - 定期检查电池电压
   - 实现时间同步机制
   - 使用互斥锁保护RTC访问
   - 处理闰年和月份天数
   - 实现时区支持
   - 记录时间设置历史

4. 功耗优化
   - 禁用不需要的输出
   - 使用中断而非轮询
   - 降低I2C时钟频率
   - 使用低功耗模式
   - 选择低功耗RTC芯片

5. 可靠性
   - 实现看门狗检测
   - 定期校准时间
   - 备份重要时间信息
   - 处理电源切换
   - 实现错误恢复机制

6. 用户体验
   - 提供时间设置界面
   - 显示电池状态
   - 支持多种时间格式
   - 实现夏令时调整
   - 提供时区选择

9.2 代码规范

/**
 * @brief  RTC错误处理宏
 */
#define RTC_CHECK(func) \
    do { \
        HAL_StatusTypeDef status = (func); \
        if (status != HAL_OK) { \
            printf("RTC Error: %s failed with status %d\r\n", #func, status); \
            return -1; \
        } \
    } while(0)

/**
 * @brief  安全的时间读取
 * @param  time: 时间结构指针
 * @return 0: 成功, -1: 失败
 */
int rtc_read_time_safe(rtc_time_t *time)
{
    rtc_time_t time1, time2;
    int retry = 3;

    while (retry--) {
        // 读取两次
        RTC_CHECK(ds3231_get_time(&time1));
        HAL_Delay(1);
        RTC_CHECK(ds3231_get_time(&time2));

        // 比较是否一致
        if (memcmp(&time1, &time2, sizeof(rtc_time_t)) == 0) {
            memcpy(time, &time1, sizeof(rtc_time_t));
            return 0;
        }
    }

    return -1;  // 读取失败
}

/**
 * @brief  时间有效性检查
 * @param  time: 时间结构指针
 * @return true: 有效, false: 无效
 */
bool rtc_time_is_valid(const rtc_time_t *time)
{
    if (time->year < 2000 || time->year > 2099) return false;
    if (time->month < 1 || time->month > 12) return false;
    if (time->date < 1 || time->date > 31) return false;
    if (time->hour > 23) return false;
    if (time->minute > 59) return false;
    if (time->second > 59) return false;
    if (time->day < 1 || time->day > 7) return false;

    // 检查日期是否在月份范围内
    uint8_t days = rtc_get_days_in_month(time->year, time->month);
    if (time->date > days) return false;

    return true;
}

总结

关键要点

  1. RTC基础
  2. RTC是独立的时钟电路,使用32.768kHz晶振
  3. 支持电池备份,断电后继续运行
  4. 时间以BCD码存储
  5. 精度受晶振、温度、电压等因素影响

  6. 芯片选择

  7. DS1307:低成本,5V,精度±2分/月,有NVRAM
  8. DS3231:高精度,±1分/年,内置TCXO,有温度传感器
  9. PCF8563:超低功耗,宽电压,精度±5分/月

  10. I2C通信

  11. 正确配置I2C参数
  12. 添加上拉电阻
  13. 实现读写函数
  14. 处理通信错误

  15. 时间管理

  16. BCD码与十进制转换
  17. Unix时间戳转换
  18. 时间格式化
  19. 时区处理
  20. 闰年和星期计算

  21. 闹钟功能

  22. DS3231支持两个独立闹钟
  23. 多种匹配模式
  24. 中断输出
  25. 定时唤醒

  26. 电池备份

  27. CR2032最常用
  28. 电池寿命计算
  29. 功耗优化
  30. 电压监测

  31. 时间同步

  32. NTP网络同步
  33. GPS时间同步
  34. 手动校准
  35. 老化偏移补偿

学习路径

  1. 入门阶段
  2. 理解RTC基本原理
  3. 学习I2C通信
  4. 掌握DS1307/DS3231使用
  5. 实现基本时间读写

  6. 进阶阶段

  7. 实现闹钟功能
  8. 时间格式转换
  9. 时区处理
  10. 电池管理

  11. 高级阶段

  12. 时间同步
  13. 温度补偿
  14. 功耗优化
  15. 实际项目应用

下一步学习

参考资料

  1. Maxim Integrated DS1307 Datasheet
  2. Maxim Integrated DS3231 Datasheet
  3. NXP PCF8563 Datasheet
  4. I2C-bus specification and user manual (NXP)
  5. Application Note: RTC Design Considerations
  6. IEEE 1588 Precision Time Protocol
  7. RFC 5905: Network Time Protocol Version 4

相关主题


本文档持续更新中,最后更新时间:2026-04-06