基于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 功能都能成为可靠的 "时间基准",为嵌入式系统注入精准的时序灵魂。

相关推荐
安庆平.Я1 小时前
STM32——GPIO
stm32·单片机·嵌入式硬件
唯创知音2 小时前
电风扇离线语音芯片方案设计与应用场景:基于 8 脚 MCU 与 WTK6900P 的创新融合
单片机·嵌入式硬件·离线语音芯片
echo_pen4 小时前
蓝桥杯---第六届省赛单片机组真题
单片机·蓝桥杯
云山工作室6 小时前
基于单片机电子负载的设计(论文+源码)
单片机·嵌入式硬件
minichao_sz9 小时前
Cherryusb UAC例程对接STM32内置ADC和PWM播放音乐和录音(下)=>UAC+STM32 ADC+PWM实现录音和播放
stm32·单片机·usb
望兮9 小时前
现代永磁同步电机FOC控制技术指南
嵌入式
GalaxySinCos10 小时前
09 51单片机之LED点阵屏
单片机·嵌入式硬件·51单片机
针不戳2022092611 小时前
PFC是什么
嵌入式硬件·学习·硬件工程
点灯小铭11 小时前
基于51单片机射频RFID卡考勤上课上班人数计数系统设计
单片机·嵌入式硬件·毕业设计·51单片机·课程设计