基于STC8单片机的RTC时钟实现:从原理到实践
基于 STC8 单片机的 RTC 时钟实现:从原理到实践
在嵌入式开发中,实时时钟(RTC)是许多应用的核心组件,从温湿度记录仪到智能家居控制器,都需要精准的时间戳来支撑功能逻辑。对于采用 STC8 系列单片机的项目而言,无需额外外接 RTC 芯片,就能利用其内置的 RTC 模块实现可靠计时 —— 这既是 STC8 的一大优势,也需要开发者掌握其独特的配置逻辑。
STC8 系列 RTC 的核心特性
STC8A8K、STC8H1K 等主流型号均集成了硬件 RTC 模块,与传统外接芯片(如 DS3231)相比,它的核心优势在于简化硬件设计和降低功耗:
-
无需外部晶振:模块可选择使用主时钟分频或内部低速 RC 振荡器作为时钟源,避免了 32.768kHz 晶振的布线误差。
-
低功耗运行:在掉电模式下,RTC 模块可单独由后备电源(如 CR2032 纽扣电池)供电,电流消耗低至 1μA 以下,支持数年持续运行。
-
丰富的时间寄存器:内置秒、分、时、日、月、年寄存器,支持闰年自动校正,最大可记录至 2100 年。
-
中断功能:可配置秒中断、分中断或特定时间闹钟中断,方便唤醒单片机执行定时任务。
不过需要注意的是,STC8 的 RTC 精度受内部振荡器稳定性影响,常温下误差约为 ±5ppm(年误差约 158 秒),若需更高精度,可通过软件校准或外接晶振的方式补偿。
硬件设计:最小系统的 RTC 供电方案
基于 STC8 实现 RTC 功能的硬件设计非常简洁,核心在于电源切换电路的设计:
-
主电源:单片机正常工作时,由 5V 或 3.3V 系统电源供电,同时为后备电池充电(通过二极管防止反向漏电)。
-
后备电源:采用 3V 纽扣电池,通过 STC8 的 VBAT 引脚连接,当主电源掉电时,自动切换为电池供电。
-
外部晶振(可选):若对精度要求较高,可在 X32IN/X32OUT 引脚外接 32.768kHz 晶振,并搭配 22pF 负载电容。
典型电路中,VBAT 引脚需串联 10kΩ 限流电阻,电池正极通过肖特基二极管(如 1N5819)与主电源连接,确保主电正常时电池不放电,主电断开时立即切换供电。
软件实现:从寄存器配置到时间读取
STC8 的 RTC 模块通过特殊功能寄存器(SFR)配置,以下是关键步骤的代码逻辑(以 STC8H8K64u 为例):
1. 初始化 RTC 模块
// I2C 配置函数定义
/**************** I2C初始化函数 *****************/
static void I2C_config(void)
{I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode = I2C_Mode_Master; //主从选择 I2C_Mode_Master, I2C_Mode_SlaveI2C_InitStructure.I2C_Enable = ENABLE; //I2C功能使能, ENABLE, DISABLEI2C_InitStructure.I2C_MS_WDTA = DISABLE; //主机使能自动发送, ENABLE, DISABLEI2C_InitStructure.I2C_Speed = 13; //总线速度=Fosc/2/(Speed*2+4), 0~63// 400k, 24M => 13I2C_Init(&I2C_InitStructure);NVIC_I2C_Init(I2C_Mode_Master,DISABLE,Priority_0); //主从模式, I2C_Mode_Master, I2C_Mode_Slave; 中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3I2C_SW(I2C_P33_P32); //I2C_P14_P15,I2C_P24_P25,I2C_P33_P32
}// PCF8563初始化
void PCF8563_init() {EAXSFR(); /* 扩展寄存器访问使能 */// P32 P33 开漏P3_MODE_OUT_OD(GPIO_Pin_2 | GPIO_Pin_3);I2C_config(); // I2C配置
}
2. 设置时间
#define WRITE_BCD(val) ((val / 10) << 4) + (val%10) // 没有分号,宏定义
// 设备地址
#define PCF8563_DEV_ADDR 0xa2
// 存储地址(寄存器地址): 时间(秒)存储地址
#define PCF8563_REG_SECOND 0x02
// 设置时间
void PCF8563_set_clock(Clock_t temp) {u8 p[7] = {0}; // 写完整 年月日 星期几 时分秒// 秒的寄存器地址为: 0x02// 秒: 第0~3位记录个位,第4~6位记录十位// 十位 个位p[0] = WRITE_BCD(temp.second);// 分: 第0~3位,保存个数,第4到6位,保存十位p[1] = WRITE_BCD(temp.minute);// 时:第0~3位,保存个数,第4到5位,保存十位p[2] = WRITE_BCD(temp.hour);// 日:第0~3位,保存个数,第4到5位,保存十位p[3] = WRITE_BCD(temp.day);// 周:第0~2位,保存个数p[4] = temp.weekday;// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xxp[5] = WRITE_BCD(temp.month);// 月的第7位if (temp.year >= 2100) { // 第7位置1p[5] |= (1 << 7);} // 第7位置0,不处理就是0// 年:第0~3位,保存个数,第4到7位,保存十位// 2025 ===> 25 p[6] = WRITE_BCD(temp.year%100);I2C_WriteNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);
}
3. 读取时间
#define READ_BCD(val) (val >> 4) * 10 + (val & 0x0f)
// 设备地址
#define PCF8563_DEV_ADDR 0xa2
// 存储地址(寄存器地址): 时间(秒)存储地址
#define PCF8563_REG_SECOND 0x02
// 获取时间
void PCF8563_get_clock(Clock_t *temp) {u8 p[7] = {0}; u8 flag;// 读时间I2C_ReadNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);// 10进制 用 16进制表示 低4位放个位 高4位放10位// 秒: 第0~3位记录个位,第4~6位记录十位temp->second = READ_BCD(p[0]);// 分: 第0~3位,保存个数,第4到6位,保存十位temp->minute = READ_BCD(p[1]);// 时:第0~3位,保存个数,第4到5位,保存十位temp->hour = READ_BCD(p[2]);// 日:第0~3位,保存个数,第4到5位,保存十位temp->day = READ_BCD(p[3]);// 周:第0~2位,保存个数temp->weekday = p[4]; // 如果是星期日,是0// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xx// 处理第7位// 取出第7位flag = p[5] >> 7;// 第7位置0, 月的第7位,是年的标志位,不是月的有效数据p[5] &= ~(1 << 7);temp->month = READ_BCD(p[5]);// 年:第0~3位,保存个数,第4到7位,保存十位temp->year = READ_BCD(p[6]);if (flag == 1) temp->year += 2100;else temp->year += 2000;
}
实战技巧:提升 RTC 稳定性的三个要点
-
电源滤波:在 VBAT 引脚与地之间并联 10uF 电解电容和 0.1uF 陶瓷电容,减少电池供电时的纹波干扰。
-
软件校准:定期通过 NTP 网络或 GPS 获取标准时间,计算误差后通过 RTC 的校准寄存器(RTC_CAL)进行补偿。
-
数据备份:将重要的时间信息同时存储在 EEPROM 中,RTC 故障时可从 EEPROM 恢复最近时间。
应用案例:基于 STC8 的PCF8563S时钟芯片
PCF8563.h代码//自己写的封装的库函数的声明
#ifndef __PCF8563_H__
#define __PCF8563_H__#include "GPIO.h"
#include "NVIC.h"
#include "Switch.h"
#include "I2C.h"#define WRITE_BCD(val) ((val / 10) << 4) + (val%10) // 没有分号,宏定义
#define READ_BCD(val) (val >> 4) * 10 + (val & 0x0f)// 设备地址
#define PCF8563_DEV_ADDR 0xa2
// 存储地址(寄存器地址): 时间(秒)存储地址
#define PCF8563_REG_SECOND 0x02typedef struct {u16 year; u8 month;u8 day;u8 weekday;u8 hour;u8 minute;u8 second;
} Clock_t;// PCF8563初始化
void PCF8563_init();// 设置时间
void PCF8563_set_clock(Clock_t temp);// 获取时间
void PCF8563_get_clock(Clock_t *temp);#endif
PCF8563.c
代码//自己写的封装的库函数的实现
#include "PCF8563.h"// I2C 配置函数定义
/**************** I2C初始化函数 *****************/
static void I2C_config(void)
{I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode = I2C_Mode_Master; //主从选择 I2C_Mode_Master, I2C_Mode_SlaveI2C_InitStructure.I2C_Enable = ENABLE; //I2C功能使能, ENABLE, DISABLEI2C_InitStructure.I2C_MS_WDTA = DISABLE; //主机使能自动发送, ENABLE, DISABLEI2C_InitStructure.I2C_Speed = 13; //总线速度=Fosc/2/(Speed*2+4), 0~63// 400k, 24M => 13I2C_Init(&I2C_InitStructure);NVIC_I2C_Init(I2C_Mode_Master,DISABLE,Priority_0); //主从模式, I2C_Mode_Master, I2C_Mode_Slave; 中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3I2C_SW(I2C_P33_P32); //I2C_P14_P15,I2C_P24_P25,I2C_P33_P32
}// PCF8563初始化
void PCF8563_init() {EAXSFR(); /* 扩展寄存器访问使能 */// P32 P33 开漏P3_MODE_OUT_OD(GPIO_Pin_2 | GPIO_Pin_3);I2C_config(); // I2C配置
}// 设置时间
void PCF8563_set_clock(Clock_t temp) {u8 p[7] = {0}; // 写完整 年月日 星期几 时分秒// 秒的寄存器地址为: 0x02// 秒: 第0~3位记录个位,第4~6位记录十位// 十位 个位p[0] = WRITE_BCD(temp.second);// 分: 第0~3位,保存个数,第4到6位,保存十位p[1] = WRITE_BCD(temp.minute);// 时:第0~3位,保存个数,第4到5位,保存十位p[2] = WRITE_BCD(temp.hour);// 日:第0~3位,保存个数,第4到5位,保存十位p[3] = WRITE_BCD(temp.day);// 周:第0~2位,保存个数p[4] = temp.weekday;// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xxp[5] = WRITE_BCD(temp.month);// 月的第7位if (temp.year >= 2100) { // 第7位置1p[5] |= (1 << 7);} // 第7位置0,不处理就是0// 年:第0~3位,保存个数,第4到7位,保存十位// 2025 ===> 25 p[6] = WRITE_BCD(temp.year%100);I2C_WriteNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);
}// 获取时间
void PCF8563_get_clock(Clock_t *temp) {u8 p[7] = {0}; u8 flag;// 读时间I2C_ReadNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);// 10进制 用 16进制表示 低4位放个位 高4位放10位// 秒: 第0~3位记录个位,第4~6位记录十位temp->second = READ_BCD(p[0]);// 分: 第0~3位,保存个数,第4到6位,保存十位temp->minute = READ_BCD(p[1]);// 时:第0~3位,保存个数,第4到5位,保存十位temp->hour = READ_BCD(p[2]);// 日:第0~3位,保存个数,第4到5位,保存十位temp->day = READ_BCD(p[3]);// 周:第0~2位,保存个数temp->weekday = p[4]; // 如果是星期日,是0// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xx// 处理第7位// 取出第7位flag = p[5] >> 7;// 第7位置0, 月的第7位,是年的标志位,不是月的有效数据p[5] &= ~(1 << 7);temp->month = READ_BCD(p[5]);// 年:第0~3位,保存个数,第4到7位,保存十位temp->year = READ_BCD(p[6]);if (flag == 1) temp->year += 2100;else temp->year += 2000;
}
主函数
#include "GPIO.h"
#include "Delay.h"
#include "UART.h" // 串口配置 UART_Configuration
#include "NVIC.h" // 中断初始化NVIC_UART1_Init
#include "Switch.h" // 引脚切换 UART1_SW_P30_P31
#include "PCF8563.h"
void GPIO_config() { GPIO_InitTypeDef info;// ===== UART1 P30 P31 准双向info.Mode = GPIO_PullUp; // 准双向info.Pin = GPIO_Pin_0 | GPIO_Pin_1; // 引脚GPIO_Inilize(GPIO_P3, &info);
}
// 串口配置函数的定义
void UART_config(void) {// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<COMx_InitDefine COMx_InitStructure; //结构定义COMx_InitStructure.UART_Mode = UART_8bit_BRTx; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTxCOMx_InitStructure.UART_BRT_Use = BRT_Timer1; //选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)COMx_InitStructure.UART_BaudRate = 115200ul; //波特率, 一般 110 ~ 115200COMx_InitStructure.UART_RxEnable = ENABLE; //接收允许, ENABLE或DISABLECOMx_InitStructure.BaudRateDouble = DISABLE; //波特率加倍, ENABLE或DISABLEUART_Configuration(UART1, &COMx_InitStructure); //初始化串口1 UART1,UART2,UART3,UART4NVIC_UART1_Init(ENABLE,Priority_1); //中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3UART1_SW(UART1_SW_P30_P31); // 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}
void main() { Clock_t temp; // 结构体变量EA = 1; // 使能中断总开关GPIO_config(); // GPIO配置UART_config(); // 串口配置PCF8563_init(); // PCF8563 初始化// ==============================写时间temp.year = 2025, temp.month = 8, temp.day = 11;temp.weekday = 1; // 如果是星期日,是0temp.hour = 23, temp.minute = 59, temp.second = 54;PCF8563_set_clock(temp); // 设置时间while (1){PCF8563_get_clock(&temp); // 获取时间printf("%02d-%02d-%02d\n", (int)temp.year, (int)temp.month, (int)temp.day);printf("weekday: %02d\n", (int)temp.weekday);printf("%02d:%02d:%02d\n", (int)temp.hour, (int)temp.minute, (int)temp.second);delay_ms(250); // 每隔1s 发送1次delay_ms(250);delay_ms(250);delay_ms(250);}
}
结语
STC8 系列单片机的内置 RTC 模块,是平衡性能与成本的理想选择。掌握其硬件设计要点和软件配置逻辑,既能简化电路设计,又能保证时间精度满足多数应用场景。无论是开发智能时钟、数据记录仪还是工业控制设备,STC8 的 RTC 功能都能成为可靠的 “时间基准”,为嵌入式系统注入精准的时序灵魂。