基于STC8单片机的闹钟寄存器中断

在嵌入式系统中,精准的定时唤醒功能是许多应用的核心需求——从智能家居的定时开关,到工业设备的周期性巡检,再到便携式设备的低功耗设计,都离不开可靠的闹钟中断机制。STC8系列单片机内置的RTC闹钟模块,通过丰富的寄存器配置,可实现年月日级到秒级的精准定时,且功耗低至微安级,是替代传统定时芯片的理想选择。
在这里插入图片描述

一、STC8闹钟中断的核心寄存器

STC8的RTC闹钟功能由专用寄存器组控制,这些寄存器负责设置闹钟时间、配置中断触发条件和管理中断状态。以STC8H1K08为例,核心寄存器包括:

寄存器名称 地址 功能描述
RTC_ALARM_YEAR 0xF0 闹钟年份寄存器(BCD码,00-99)
RTC_ALARM_MONTH 0xF1 闹钟月份寄存器(BCD码,01-12)
RTC_ALARM_DAY 0xF2 闹钟日期寄存器(BCD码,01-31)
RTC_ALARM_HOUR 0xF3 闹钟小时寄存器(BCD码,00-23)
RTC_ALARM_MIN 0xF4 闹钟分钟寄存器(BCD码,00-59)
RTC_ALARM_SEC 0xF5 闹钟秒寄存器(BCD码,00-59,部分型号无)
RTC_CON 0xF6 RTC控制寄存器,包含闹钟中断使能位
RTC_FLAG 0xF7 RTC标志寄存器,包含闹钟中断标志位

这些寄存器采用BCD码存储时间(如"15时"存储为0x15),与RTC实时时间寄存器的格式保持一致,便于比较匹配。

二、关键寄存器深度解析

1. 闹钟时间寄存器(RTC_ALARM_*)

闹钟时间寄存器用于设置触发时刻,每个字段的含义与RTC实时时间寄存器一一对应:

  • 年份:00-99(与RTC_YEAR对应)
  • 月份:01-12(与RTC_MONTH对应)
  • 日期:01-31(与RTC_DAY对应)
  • 小时:00-23(与RTC_HOUR对应)
  • 分钟:00-59(与RTC_MIN对应)

特殊值与掩码功能:部分型号支持"不比较"掩码,例如将月份设为0x00表示"不比较月份",仅当其他字段匹配时触发闹钟。这种设计让闹钟可以灵活配置为"每天固定时间"(忽略年月日)或"每月固定日期"(忽略年份)等模式。

2. RTC控制寄存器(RTC_CON)

RTC_CON的关键位定义(不同型号可能有差异):bit7: RTC_EN - RTC使能位(1=开启RTC)
bit6: RTC_RDY - RTC就绪标志(1=RTC稳定运行)
bit5: ALARM_EN - 闹钟中断使能位(1=允许闹钟中断)
bit4: … - 其他功能位(如温度补偿、时钟源选择)
配置示例:使能闹钟中断RTC_CON |= (1 << 5); // ALARM_EN=1

3. RTC标志寄存器(RTC_FLAG)

RTC_FLAG用于记录中断状态,必须在中断服务程序中手动清除:bit0: ALARM_FLAG - 闹钟中断标志(1=闹钟触发)
清除方法:RTC_FLAG &= ~(1 << 0); // 清除闹钟中断标志

三、闹钟中断的完整配置流程

实现闹钟中断需要三个核心步骤:初始化RTC、配置闹钟时间、使能中断并编写服务程序。以下是基于STC8H1K的完整代码示例。

1. 寄存器定义与类型声明// 寄存器定义(根据数据手册调整地址)

// 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
}

// 外部中断3配置
/******************** INT配置 ********************/
static void	Exti_config(void)
{
    EXTI_InitTypeDef	Exti_InitStructure;							//结构定义

    Exti_InitStructure.EXTI_Mode      = EXT_MODE_RiseFall;//中断模式,   EXT_MODE_RiseFall,EXT_MODE_Fall
    Ext_Inilize(EXT_INT3,&Exti_InitStructure);				//初始化
    NVIC_INT3_Init(ENABLE,Priority_0);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
}

// PCF8563初始化
void PCF8563_init() {
	EAXSFR();		/* 扩展寄存器访问使能 */
	
	// P32  P33  开漏
    P3_MODE_OUT_OD(GPIO_Pin_2 | GPIO_Pin_3);
	
	// P37  准双向
	P3_MODE_IO_PU(GPIO_Pin_7);
	
	I2C_config(); // I2C配置
	Exti_config(); // 外部中断3
}

时间结构体(BCD码转十进制)

typedef struct {
	u16 year; 
	u8 month;
	u8 day;
	u8 weekday;
	u8 hour;
	u8 minute;
	u8 second;
} Clock_t;

2. RTC与闹钟初始化

// 设置时间
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;
}


// 设置闹钟
void PCF8563_set_alarm(Alarm_t alarm) {
	u8 p[4] = {0};
	//===================2.1 闹钟时间设置 寄存器地址 0x09
    // 分: 第0~3位,记录个数, 第4~6位记录十位, 第7位:置0启动, 置1禁用
	if (alarm.minute != -1) p[0] = WRITE_BCD(alarm.minute);
	else p[0] = 0x80;
	
    // 时: 第0~3位,记录个数, 第4~5位记录十位, 第7位:置0启动, 置1禁用
	p[1] = alarm.hour != -1 ? WRITE_BCD(alarm.hour) : 0x80 ;
    // 日: 第0~3位,记录个数, 第4~5位记录十位, 第7位:置0启动, 置1禁用
	p[2] = alarm.day != -1 ? WRITE_BCD(alarm.day) : 0x80 ;
    // 周: 第0~2位,记录个数, 第7位:置0启动, 置1禁用
	p[3] = alarm.weekday != -1 ? alarm.weekday : 0x80 ;
	I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x09, p, 4);
}

// 启用闹钟
void PCF8563_enable_alarm() {
	u8 cfg;
    //===================2.2 闹钟开启 寄存器地址 0x01
    //a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)
	I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
    //b) 在原来配置的基础上,清除标志位  第3位:置0清除标志位,置1维持不变
	cfg &= ~(1 << 3);
    //c) 在原来配置基础上,启动闹钟,第1位:置0禁用,置1启动
	cfg |= (1 << 1);	
    //d) 重新写入配置
	I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}

// 禁用闹钟Alarm
void PCF8563_disable_alarm() {
	u8 cfg;
    //===================2.2 闹钟 寄存器地址 0x01
    //a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)
	I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
    //b) 在原来配置的基础上,清除标志位  第3位:置0清除标志位,置1维持不变
	cfg &= ~(1 << 3);
    //c) 在原来配置基础上,禁用闹钟,第1位:置0禁用,置1启动
	cfg &= ~(1 << 1);	
    //d) 重新写入配置
	I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}

// 清理闹钟标记
void PCF8563_alarm_clear_flag() {
	u8 cfg;
	// 清除闹钟的标志位,才能重复触发闹钟
	//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)
	I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
    //b) 在原来配置的基础上,清除标志位  第3位:置0清除标志位,置1维持不变
	cfg &= ~(1 << 3);	
    //c) 重新写入配置
	I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}

3. 闹钟中断服务程序// 闹钟中断服务函数(复用T2中断,向量地址0x2B)

Exti.Isr.c代码文件

void int3_callback(); // 声明
/========================================================================
// 函数: INT3_ISR_Handler
// 描述: INT3中断函数.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2020-09-23
//========================================================================
void INT3_ISR_Handler (void) interrupt INT3_VECTOR		//进中断时已经清除标志
{
	// TODO: 在此处添加用户代码
//	P03 = ~P03;
	int3_callback(); // 调用
	
	WakeUpSource = 4;
}

4. 主函数调用void main() {

#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 int3_callback() { // 外部中断3的回调
	Clock_t temp;

	printf("===========alarm===========\n");
	// 清理闹钟标记
	PCF8563_alarm_clear_flag();

	// ============== 重置时间,为了重复验证闹钟
	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); // 设置时间
}

void main() {    
    Clock_t temp; // 结构体变量
	Alarm_t alarm;
	
	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); // 设置时间
	
	
	// ============================== 闹钟
	// 不匹配 day 和 weekday
	alarm.day = -1, alarm.weekday = -1, alarm.hour = 0, alarm.minute = 0;
	PCF8563_set_alarm(alarm); // 设置闹钟时间


	PCF8563_enable_alarm(); // 启动闹钟
//	PCF8563_disable_alarm(); // 禁用闹钟
	
    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);
    }
}

四、实战技巧与优化方案

1. 闹钟模式灵活配置

通过设置"不比较"掩码,可实现多种定时模式:

  • 每日闹钟:年、月、日设为00,仅匹配时、分
  • 每月闹钟:年设为00,匹配月、日、时、分
  • 单次闹钟:全字段匹配,触发后关闭闹钟使能

2. 低功耗设计要点

  • 主程序在等待闹钟时可进入掉电模式(Power-Down)
  • 通过RTC闹钟中断唤醒单片机,电流可降至1μA以下// 进入掉电模式等待闹钟
    PCON |= 0x02; // 置位PD位进入掉电模式
    nop();

3. 时间同步与校准

  • 定期通过串口或NTP服务器同步时间到RTC
  • 利用RTC的校准寄存器(如RTC_CAL)补偿晶振误差

结语

STC8单片机的闹钟寄存器中断系统,通过精细化的寄存器配置,为嵌入式设备提供了低功耗、高精度的定时解决方案。掌握RTC_ALARM_*系列寄存器的配置逻辑,理解闹钟中断的使能与标志管理机制,是实现可靠定时功能的核心。无论是简单的每日提醒,还是复杂的多模式定时任务,STC8的闹钟中断都能以简洁的硬件设计和高效的软件逻辑满足需求,成为嵌入式系统中的"精准时间管家"。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐