跳转至

RTC实时时钟

概述

RTC(Real-Time Clock,实时时钟)是独立于主CPU的硬件时钟,通常由备用电池供电,用于保持时间和日期信息。

RTC特点

  1. 独立供电:由备用电池或超级电容供电
  2. 低功耗:功耗通常小于1μA
  3. 日历功能:年、月、日、时、分、秒
  4. 闹钟功能:可设置闹钟唤醒系统
  5. 周期性唤醒:可配置周期唤醒事件

STM32 RTC编程

HAL库初始化

C
#include "stm32f4xx_hal.h"

RTC_HandleTypeDef hrtc;

void RTC_Init(void)
{
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;
    hrtc.Init.SynchPrediv = 255;
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
    
    HAL_RTC_Init(&hrtc);
}

设置和读取时间

C
// 设置时间
void set_time(uint8_t hour, uint8_t min, uint8_t sec)
{
    RTC_TimeTypeDef sTime = {0};
    
    sTime.Hours = hour;
    sTime.Minutes = min;
    sTime.Seconds = sec;
    sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sTime.StoreOperation = RTC_STOREOPERATION_RESET;
    
    HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
}

// 设置日期
void set_date(uint8_t year, uint8_t month, uint8_t day, uint8_t weekday)
{
    RTC_DateTypeDef sDate = {0};
    
    sDate.Year = year;        // 0-99
    sDate.Month = month;      // 1-12
    sDate.Date = day;         // 1-31
    sDate.WeekDay = weekday;  // 1-7
    
    HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}

// 读取时间
void get_time(uint8_t *hour, uint8_t *min, uint8_t *sec)
{
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
    
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
    
    *hour = sTime.Hours;
    *min = sTime.Minutes;
    *sec = sTime.Seconds;
}

// 读取日期
void get_date(uint8_t *year, uint8_t *month, uint8_t *day)
{
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
    
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
    
    *year = sDate.Year;
    *month = sDate.Month;
    *day = sDate.Date;
}

闹钟功能

C
// 设置闹钟A
void set_alarm_a(uint8_t hour, uint8_t min, uint8_t sec)
{
    RTC_AlarmTypeDef sAlarm = {0};
    
    sAlarm.AlarmTime.Hours = hour;
    sAlarm.AlarmTime.Minutes = min;
    sAlarm.AlarmTime.Seconds = sec;
    sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
    sAlarm.AlarmMask = RTC_ALARMMASK_NONE;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
    sAlarm.AlarmDateWeekDay = 1;
    sAlarm.Alarm = RTC_ALARM_A;
    
    HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
    
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

// 闹钟中断处理
void RTC_Alarm_IRQHandler(void)
{
    HAL_RTC_AlarmIRQHandler(&hrtc);
}

// 闹钟回调
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    // 闹钟触发
    printf("Alarm triggered!\n");
}

周期性唤醒

C
// 配置唤醒定时器
void set_wakeup_timer(uint16_t period_seconds)
{
    // period_seconds: 唤醒周期(秒)
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, period_seconds, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
    
    HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
}

// 唤醒中断
void RTC_WKUP_IRQHandler(void)
{
    HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
}

// 唤醒回调
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
    // 周期唤醒事件
    printf("Wakeup event!\n");
}

Linux RTC编程

用户态操作

Bash
# 查看RTC设备
ls /dev/rtc*

# 查看时间
cat /proc/driver/rtc
hwclock --show
hwclock -r

# 设置系统时间
date -s "2024-01-01 12:00:00"

# 同步RTC时间
hwclock --systohc    # 系统时间写入RTC
hwclock -w

# 从RTC读取到系统
hwclock --hctosys    # RTC时间写入系统
hwclock -s

C语言编程

C
#include <linux/rtc.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int rtc_fd;

void rtc_init(void)
{
    rtc_fd = open("/dev/rtc0", O_RDWR);
}

// 读取时间
void rtc_read_time(void)
{
    struct rtc_time tm;
    
    ioctl(rtc_fd, RTC_RD_TIME, &tm);
    
    printf("%04d-%02d-%02d %02d:%02d:%02d\n",
           tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
           tm.tm_hour, tm.tm_min, tm.tm_sec);
}

// 设置时间
void rtc_set_time(int year, int month, int day, int hour, int min, int sec)
{
    struct rtc_time tm;
    
    tm.tm_year = year - 1900;
    tm.tm_mon = month - 1;
    tm.tm_mday = day;
    tm.tm_hour = hour;
    tm.tm_min = min;
    tm.tm_sec = sec;
    
    ioctl(rtc_fd, RTC_SET_TIME, &tm);
}

// 设置闹钟
void rtc_set_alarm(int hour, int min, int sec)
{
    struct rtc_wkalrm alarm;
    
    alarm.time.tm_hour = hour;
    alarm.time.tm_min = min;
    alarm.time.tm_sec = sec;
    alarm.enabled = 1;
    
    ioctl(rtc_fd, RTC_WKALM_SET, &alarm);
}

// 关闭
void rtc_close(void)
{
    close(rtc_fd);
}

常用RTC芯片

DS3231(I2C接口)

C
#define DS3231_ADDR    0x68

uint8_t bcd_to_dec(uint8_t bcd)
{
    return (bcd >> 4) * 10 + (bcd & 0x0F);
}

uint8_t dec_to_bcd(uint8_t dec)
{
    return ((dec / 10) << 4) | (dec % 10);
}

void ds3231_read_time(uint8_t *hour, uint8_t *min, uint8_t *sec)
{
    uint8_t buf[7];
    
    // 读取寄存器0x00-0x06
    uint8_t reg = 0x00;
    HAL_I2C_Master_Transmit(&hi2c, DS3231_ADDR << 1, &reg, 1, 100);
    HAL_I2C_Master_Receive(&hi2c, DS3231_ADDR << 1, buf, 7, 100);
    
    *sec = bcd_to_dec(buf[0]);
    *min = bcd_to_dec(buf[1]);
    *hour = bcd_to_dec(buf[2] & 0x3F);  // 24小时模式
}

void ds3231_set_time(uint8_t hour, uint8_t min, uint8_t sec)
{
    uint8_t buf[8];
    
    buf[0] = 0x00;  // 起始寄存器地址
    buf[1] = dec_to_bcd(sec);
    buf[2] = dec_to_bcd(min);
    buf[3] = dec_to_bcd(hour);
    
    HAL_I2C_Master_Transmit(&hi2c, DS3231_ADDR << 1, buf, 4, 100);
}

// 读取温度(DS3231内置温度传感器)
float ds3231_read_temp(void)
{
    uint8_t buf[2];
    uint8_t reg = 0x11;
    
    HAL_I2C_Master_Transmit(&hi2c, DS3231_ADDR << 1, &reg, 1, 100);
    HAL_I2C_Master_Receive(&hi2c, DS3231_ADDR << 1, buf, 2, 100);
    
    int16_t temp = (buf[0] << 8) | buf[1];
    return temp / 256.0;
}

PCF8563(I2C接口)

C
#define PCF8563_ADDR   0x51

void pcf8563_init(void)
{
    uint8_t buf[2];
    
    // 使能RTC
    buf[0] = 0x00;  // Control_status1
    buf[1] = 0x00;  // 正常模式
    HAL_I2C_Master_Transmit(&hi2c, PCF8563_ADDR << 1, buf, 2, 100);
}

时间戳转换

C
#include <time.h>

// 结构体转时间戳
time_t rtc_to_timestamp(struct tm *tm)
{
    return mktime(tm);
}

// 时间戳转结构体
void timestamp_to_rtc(time_t ts, struct tm *tm)
{
    struct tm *t = localtime(&ts);
    *tm = *t;
}

// 获取当前时间戳
time_t get_current_timestamp(void)
{
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
    struct tm tm;
    
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
    
    tm.tm_year = sDate.Year + 100;  // RTC年份从2000年开始
    tm.tm_mon = sDate.Month - 1;
    tm.tm_mday = sDate.Date;
    tm.tm_hour = sTime.Hours;
    tm.tm_min = sTime.Minutes;
    tm.tm_sec = sTime.Seconds;
    tm.tm_isdst = 0;
    
    return mktime(&tm);
}

低功耗模式配合

RTC常用于从低功耗模式唤醒系统。

C
// 进入停止模式,RTC唤醒
void enter_stop_mode_with_rtc_wakeup(uint32_t wakeup_seconds)
{
    // 设置RTC唤醒定时器
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, wakeup_seconds, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
    
    // 进入停止模式
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    
    // 唤醒后重新初始化系统时钟
    SystemClock_Config();
    
    // 关闭唤醒定时器
    HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
}

参考资料