基于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 模块

c 复制代码
// I2C 配置函数定义
/****************  I2C初始化函数 *****************/
static void	I2C_config(void)
{
	I2C_InitTypeDef		I2C_InitStructure;

	I2C_InitStructure.I2C_Mode      = I2C_Mode_Master;	//主从选择   I2C_Mode_Master, I2C_Mode_Slave
	I2C_InitStructure.I2C_Enable    = ENABLE;			//I2C功能使能,   ENABLE, DISABLE
	I2C_InitStructure.I2C_MS_WDTA   = DISABLE;			//主机使能自动发送,  ENABLE, DISABLE
	I2C_InitStructure.I2C_Speed     = 13;				//总线速度=Fosc/2/(Speed*2+4),      0~63
                                                        // 400k, 24M => 13
	I2C_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_3

	I2C_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,世纪数为21xx
    p[5] = WRITE_BCD(temp.month);
    // 月的第7位
    if (temp.year >= 2100) { // 第7位置1
        p[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 稳定性的三个要点

  1. 电源滤波:在 VBAT 引脚与地之间并联 10uF 电解电容和 0.1uF 陶瓷电容,减少电池供电时的纹波干扰。

  2. 软件校准:定期通过 NTP 网络或 GPS 获取标准时间,计算误差后通过 RTC 的校准寄存器(RTC_CAL)进行补偿。

  3. 数据备份:将重要的时间信息同时存储在 EEPROM 中,RTC 故障时可从 EEPROM 恢复最近时间。

应用案例:基于 STC8 的PCF8563S时钟芯片

PCF8563.h代码//自己写的封装的库函数的声明

c 复制代码
#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		0x02

typedef 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

代码//自己写的封装的库函数的实现

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_Slave
	I2C_InitStructure.I2C_Enable    = ENABLE;			//I2C功能使能,   ENABLE, DISABLE
	I2C_InitStructure.I2C_MS_WDTA   = DISABLE;			//主机使能自动发送,  ENABLE, DISABLE
	I2C_InitStructure.I2C_Speed     = 13;				//总线速度=Fosc/2/(Speed*2+4),      0~63
                                                        // 400k, 24M => 13
	I2C_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_3

	I2C_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,世纪数为21xx
    p[5] = WRITE_BCD(temp.month);
    // 月的第7位
    if (temp.year >= 2100) { // 第7位置1
        p[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;
}

主函数

c 复制代码
#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_BRTx
    COMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
    COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200
    COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLE
    COMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLE
    UART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4

  	NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
    UART1_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;  // 如果是星期日,是0
    temp.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 功能都能成为可靠的 "时间基准",为嵌入式系统注入精准的时序灵魂。

相关推荐
cjy_Somnr2 小时前
keil5报错显示stm32的SWDIO未连接不能烧录
stm32·单片机·嵌入式硬件
Lay_鑫辰3 小时前
西门子诊断-状态和错误位(“轴”工艺对象 V1...3)
服务器·网络·单片机·嵌入式硬件·自动化
无垠的广袤5 小时前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
雲烟7 小时前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞7 小时前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
田甲8 小时前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up8 小时前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
纳祥科技17 小时前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
资料,小偿17 小时前
4.1.2基于51单片机汇编语言出租车计价器proteus仿真出租车计价器,汇编语言51单片机
汇编·51单片机·proteus
单片机日志18 小时前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息