15-BKP-RTC.md 9.4 KB

15-BKP-RTC

原理

Unix 时间戳

  • 定义:从 1970-01-01 00:00:00 UTC 到现在的秒数
  • 2038 问题:32位有符号整数将在 2038-01-19 03:14:07 溢出
  • 闰秒:IETF 已废弃闰秒,Unix 时间戳连续递增

BKP (Backup Registers)

           ┌──────────────┐
VBAT ─────┤              │
           │  BKP Domain  │
VDD 断电    │ 10×16bit Reg │
不影响     │ RTC 寄存器   │
           └──────────────┘
  • 容量:10 个 16 位备份寄存器 (BKP_DR1 ~ BKP_DR10)
  • 供电:VDD 断电后由 VBAT 供电
  • 功能:存储系统断电不丢失的数据 (如复位次数、校准值)
  • 保护:受写保护,需 PWR_BackupAccessCmd(ENABLE) 解锁

RTC (Real-Time Clock)

           ┌──────────────┐
LSE 32.768kHz ──┤              │
(或 LSI/HSE)    │  RTC Prescaler│── 1Hz ──▶ 32位计数器
           │  (RL[14:0])  │
           └──────────────┘
                │
       ┌────────┴────────┐
       │                 │
   秒中断(SEC)       闹钟中断(ALR)

时钟源选择

时钟源 频率 特点
LSE 32.768 kHz 最精确,需外接晶振
LSI 40 kHz 内置 RC,精度差
HSE/128 HSE/128 需 HSE 存在

RTC 配置流程

1. 使能 PWR + BKP 时钟
2. 使能备份域访问
3. 选择 RTC 时钟源 (LSE)
4. 使能 RTC 时钟
5. 等待 RTC 同步 (WaitForSynchro)
6. 配置预分频 (32767 → 1秒)
7. 设置计数值 (即时间)

API 表格

BKP

函数 说明
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data) 写备份寄存器
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR) 读备份寄存器
void BKP_TamperPinCmd(FunctionalState NewState) 侵入检测引脚使能
void BKP_ITConfig(FunctionalState NewState) 侵入中断使能
FlagStatus BKP_GetFlagStatus(void) 获取侵入检测标志

RTC

函数 说明
void RTC_SetPrescaler(uint32_t Prescaler) 设置预分频值 (RL)
void RTC_SetCounter(uint32_t CounterValue) 设置计数值
uint32_t RTC_GetCounter(void) 获取计数值
void RTC_SetAlarm(uint32_t AlarmValue) 设置闹钟值
uint32_t RTC_GetDivider(void) 获取当前分频值
void RTC_WaitForLastTask(void) 等待上次操作完成
void RTC_WaitForSynchro(void) 等待 RTC 同步
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState) 中断配置

RTC 中断类型

说明
RTC_IT_OW (Overflow) 溢出中断
RTC_IT_SEC (Second) 秒中断
RTC_IT_ALR (Alarm) 闹钟中断

RTC 中断映射到 EXTI

  • RTC 秒中断 → EXTI 线 17
  • RTC 闹钟中断 → EXTI 线 17
  • RTC 溢出中断 → EXTI 线 19

完整代码示例

读写备份寄存器 — 保存复位次数

/*------------------------------------------------
 * BKP_Example.c
 *------------------------------------------------*/

#include "stm32f10x.h"

void BKP_Config(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE);    // 使能备份域访问
}

uint16_t BKP_ReadResetCount(void)
{
    return BKP_ReadBackupRegister(BKP_DR1);
}

void BKP_SaveResetCount(uint16_t count)
{
    BKP_WriteBackupRegister(BKP_DR1, count);
}

int main(void)
{
    uint16_t resetCount;

    BKP_Config();
    resetCount = BKP_ReadResetCount();
    resetCount++;
    BKP_SaveResetCount(resetCount);

    while (1);
}

RTC 实时时钟 + 秒中断显示

/*------------------------------------------------
 * RTC_Example.c
 * LSE 32.768kHz → 1秒中断, USART1 打印时间
 *------------------------------------------------*/

#include "stm32f10x.h"
#include <stdio.h>

static void Delay_ms(uint32_t ms);

/* ---- RTC 初始化 ---- */
/* 时间参考:假设 Unix 时间戳基准 2025-01-01 00:00:00 = 1735689600 */
#define TIME_EPOCH  1735689600UL

void RTC_Config(void)
{
    /* 使能 PWR + BKP */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    PWR_BackupAccessCmd(ENABLE);

    /* 检查是否已初始化 (通过备份寄存器标记) */
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
    {
        /* ----- 首次配置 RTC ----- */
        RCC_LSEConfig(RCC_LSE_ON);                     // 开启 LSE
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);

        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);        // RTC 时钟源 = LSE
        RCC_RTCCLKCmd(ENABLE);                         // 使能 RTC 时钟

        RTC_WaitForSynchro();                          // 等待同步
        RTC_WaitForLastTask();

        RTC_SetPrescaler(32767);                       // 32.768kHz / 32768 = 1Hz
        RTC_WaitForLastTask();

        RTC_SetCounter(TIME_EPOCH);                    // 设置初始时间
        RTC_WaitForLastTask();

        /* 秒中断 */
        RTC_ITConfig(RTC_IT_SEC, ENABLE);
        RTC_WaitForLastTask();

        /* EXTI 17 配置 (RTC 秒中断) */
        EXTI_InitTypeDef EXTI_InitStruct;
        EXTI_InitStruct.EXTI_Line = EXTI_Line17;
        EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
        EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
        EXTI_InitStruct.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStruct);

        /* NVIC 配置 (RTC 中断) */
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = RTC_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);

        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);      // 标记已配置
    }
}

/* ---- RTC 中断处理 ---- */
void RTC_IRQHandler(void)
{
    if (RTC_GetITStatus(RTC_IT_SEC, RESET) != RESET)
    {
        RTC_ClearITPendingBit(RTC_IT_SEC);
        /* 秒中断每 1 秒触发一次 */
        /* 此处可打印或更新显示 */
    }
}

/* ---- 读取 RTC 时间 ---- */
uint32_t RTC_GetTime(void)
{
    return RTC_GetCounter();
}

/* ---- 时间转换 (Unix → 可读) ---- */
void RTC_UnixToDateTime(uint32_t unix, uint16_t* year, uint8_t* month,
                        uint8_t* day, uint8_t* hour, uint8_t* min, uint8_t* sec)
{
    static const uint8_t daysInMon[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
    uint32_t remaining = unix;
    uint16_t y;

    *sec  = remaining % 60; remaining /= 60;
    *min  = remaining % 60; remaining /= 60;
    *hour = remaining % 24; remaining /= 24;

    for (y = 1970; ; y++)
    {
        uint16_t days = (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 ? 366 : 365;
        if (remaining < days) break;
        remaining -= days;
    }
    *year = y;

    uint8_t leap = (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
    for (*month = 0; *month < 12; (*month)++)
    {
        uint8_t d = daysInMon[*month];
        if (*month == 1 && leap) d = 29;
        if (remaining < d) break;
        remaining -= d;
    }
    (*month)++;
    *day = remaining + 1;
}

int main(void)
{
    uint32_t t;
    uint16_t y; uint8_t M, d, h, m, s;

    RTC_Config();

    while (1)
    {
        t = RTC_GetTime();
        RTC_UnixToDateTime(t, &y, &M, &d, &h, &m, &s);
        /* 打印或显示 y-M-d h:m:s */
        Delay_ms(1000);
    }
}

备份寄存器保存 RTC 配置标记

/* 复用备份寄存器:
 *   BKP_DR1: 已初始化标记 (0xA5A5)
 *   BKP_DR2: 复位次数
 *   BKP_DR3: 用户校准值
 */
void SaveUserData(void)
{
    BKP_WriteBackupRegister(BKP_DR2, BKP_ReadBackupRegister(BKP_DR2) + 1);
}

常见坑点

  1. VBAT 必须接电池或电容

    • 不接 VBAT 时,VDD 断电后 BKP/RTC 数据丢失
    • 最小系统留 VBAT 引脚悬空时,RTC 无法保持
  2. 复位后 BKP 内容不变 (需 VBAT 供电)

    • 系统复位或看门狗复位不丢失
    • 上电复位如果 VBAT 有电也不丢失
  3. RTC 寄存器需等待同步

    • 每次读写 RTC 寄存器前必须等 RTC 与 APB1 同步
    • RTC_WaitForSynchro() — 上电后调用一次
    • RTC_WaitForLastTask() — 每次操作后调用
  4. RTC 配置只能做一次

    • RTC 预分频器和时钟源选择在备份域中,一旦配置,主复位不会复位
    • 使用备份寄存器标记 (如 0xA5A5) 避免重复配置
  5. RTC 中断配置 EXTI

    • RTC 中断信号需要映射到 EXTI 线 17/19 才能从 Stop 模式唤醒
    • 在 NVIC 中要开 RTC_IRQn,同步在 EXTI 中配置对应线
  6. 备份域访问保护

    • 操作 BKP 和 RTC 前必须 PWR_BackupAccessCmd(ENABLE)
    • BKP 寄存器不能用指针直接访问,必须用库函数
  7. LSE 起振慢

    • LSE 晶振起振需要数百 ms,轮询 LSERDY 标志时要给足够时间
    • 硬件上 LSE 负载电容需匹配 (通常 6pF~12.5pF)
  8. 时间基准

    • STM32 RTC 是简单的 32 位计数器,不是年月日时钟
    • 需要手动将计数值转换为可读时间 (或使用 time.h 库)
  9. RTC_IT_OW 注意

    • 标准库中溢出中断宏是 RTC_IT_OW,不是 RTC_IT_OVF
  10. 闹钟中断

    • RTC 闹钟比较的是完整 32 位计数器值,不是时分秒
    • 设置闹钟前需要计算对应的 Unix 时间戳