闹钟时间到震动与声响提醒的实现-库函数版(STC8)
闹钟时间到震动与声响提醒的实现-库函数版(STC8)
在日常生活和工业场景中,闹钟的提醒功能至关重要,而震动加声响的双重提醒能更有效地引起注意。基于 STC8 单片机,我们可以搭建一个低成本、高可靠性的闹钟系统,利用其内置的 RTC 模块实现精准定时,再通过驱动振动电机和蜂鸣器,让闹钟在设定时间到来时发出震动和声响。
系统整体设计框架
该闹钟系统主要由四大模块构成,各模块协同工作实现完整的闹钟功能:
-
核心控制模块:STC8 单片机(如 STC8H8K64u),负责 RTC 时钟管理、闹钟判断及外设控制。
-
定时模块:STC8 内置的 RTC 模块,提供精准的时间基准,支持年月日时分秒的计时与闹钟设置。
-
提醒输出模块:包括振动电机(通过三极管驱动)和蜂鸣器(直接或三极管驱动),用于产生震动和声响。
-
交互模块:由按键组成,用于设置时间和闹钟参数(如调整小时、分钟,开启 / 关闭闹钟)。
系统工作流程为:单片机实时运行 RTC 时钟,用户通过按键设置闹钟时间;当 RTC 时间与闹钟时间匹配时,单片机触发中断,控制振动电机和蜂鸣器工作,实现双重提醒;用户可通过按键关闭提醒。
硬件电路设计
硬件设计的核心是将各模块与 STC8 单片机正确连接,确保信号稳定传输和外设安全工作。
核心电路设计
-
RTC 供电:为保证断电后 RTC 继续计时,通过 VBAT 引脚连接 3V 纽扣电池(如 CR2032),并串联 10kΩ 限流电阻,同时通过二极管实现主电源与备用电源的自动切换。
-
振动电机驱动:振动电机工作电流较大(通常几十毫安),需通过三极管(如 8050)驱动。单片机某一 GPIO 口(如 P3.0)连接三极管基极(串联 1kΩ 限流电阻),三极管集电极连接振动电机一端,电机另一端接 VCC,发射极接地。
-
蜂鸣器驱动:若蜂鸣器为有源蜂鸣器,可将其一端接单片机 GPIO 口(如 P3.1),另一端接地(或通过三极管驱动以增大音量);无源蜂鸣器则需要单片机输出一定频率的脉冲信号驱动。
-
按键电路:采用 4 个独立按键,分别连接单片机的 4 个 GPIO 口(如 P3.2-P3.5),用于设置时间、设置闹钟、确认和取消操作,按键另一端接地,同时单片机引脚开启内部上拉电阻。
硬件注意事项
-
振动电机工作时可能产生干扰,需在电机两端并联一个 104 瓷片电容抑制火花和干扰。
-
蜂鸣器和振动电机的电源应与单片机电源分开走线,避免大电流对单片机造成影响。
-
按键电路可增加 104 电容进行硬件消抖,提高按键识别的稳定性。
软件程序实现
软件部分主要包括 RTC 初始化与时间管理、闹钟设置与判断、中断服务程序以及振动和声响驱动函数。
RTC 模块初始化与时间设置
首先需要初始化 STC8 的 RTC 模块,设置初始时间,并实现时间的读取与更新。
主函数代码:
// 库函数头文件
#include "GPIO.h"
#include "Delay.h"
#include "NVIC.h"
#include "UART.h"
#include "Switch.h"
#include "PCF8563.h"
#include "Exti.h"void GPIO_config(void)
{// P30 P31 准双向口GPIO_InitTypeDef GPIO_InitStructure; // 结构定义GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1; // 指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp; // 指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure); // 初始化// P00 P01 推挽输出GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1; //指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化// 配置推挽输出,引脚默认高电平P01 = 0; // 默认马达不震动P00 = 0; // 引脚拉低
}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 ext_int3_call() { // 外部中断3回调函数u16 i;printf("========ext_int3_call==============");PCF8563_alarm_clear_flag(); // 清除标志位// 马达震动P01 = 1;delay_ms(250);delay_ms(250);P01= 0;// 蜂鸣器响for (i = 0; i < 500; i++) {P00 = 1;delay_ms(1);P00 = 0;delay_ms(1);}
}void main()
{Clock_t temp;Alarm_t alarm;EA = 1; // 使能全局中断开关// ========记得=============EAXSFR(); /* 扩展寄存器访问使能 */GPIO_config(); // IO配置函数调用UART_config(); // UART配置函数调用// PCF8563初始化PCF8563_init();//======================================时间日期temp.year = 2025; temp.month = 4; temp.day = 11;temp.weekday = 5; // 星期几temp.hour = 23; temp.minute = 59; temp.second = 56;PCF8563_set_clock(temp);//===================2.1 闹钟设置 寄存器地址 0x09alarm.day = -1, alarm.hour = 0, alarm.minute = 0, alarm.weekday = -1;PCF8563_set_alarm(alarm);//===================2.2 闹钟开启 寄存器地址 0x01PCF8563_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("%02d:%02d:%02d\n", (int)temp.hour, (int)temp.minute, (int)temp.second);printf("星期%d\n", (int)temp.weekday);delay_ms(250);delay_ms(250);delay_ms(250);delay_ms(250);}
}
闹钟设置与中断配置
设置闹钟时间,并配置 RTC 闹钟中断,当时间匹配时触发中断。
配置的库函数代码:
#include "PCF8563.h"
static void GPIO_config(void)
{GPIO_InitTypeDef GPIO_InitStructure; // 结构定义// P32 P33 开漏模式GPIO_InitStructure.Pin = GPIO_Pin_2 | GPIO_Pin_3; // 指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_OUT_OD; // 指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure); // 初始化// P37 准双向口GPIO_InitStructure.Pin = GPIO_Pin_7; // 指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_PullUp; // 指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P3, &GPIO_InitStructure);
}// 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
}/******************** INT配置 ********************/
static void Exti_config(void)
{EXTI_InitTypeDef Exti_InitStructure; //结构定义Exti_InitStructure.EXTI_Mode = EXT_MODE_RiseFall;//中断模式, EXT_MODE_RiseFall,EXT_MODE_FallExt_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() {EA = 1;EAXSFR(); /* 扩展寄存器访问使能 */GPIO_config();I2C_config();Exti_config();
}// 设置时间
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);if(temp.year >= 2100) { // p[5] 的第7位为1p[5] |= (1 << 7);} else { // p[5] 的第7位为0p[5] &= ~(1 << 7);}// 年:第0~3位,保存个数,第4到7位,保存十位// 2025 => 25 => 2 和 5p[6] = WRITE_BCD(temp.year % 100);// 往rtc时钟芯片,写如数据I2C_WriteNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);
}// 获取时间
void PCF8563_get_clock(Clock_t *p) {u8 temp[7] = {0}; // 接收数据u8 flag;// 读取rtc时钟芯片,数据, 读操作也是用写地址,内部会把写地址,变成读地址I2C_ReadNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, temp, 7);// 秒的寄存器地址为: 0x02// 秒: 第0~3位记录个位,第4~6位记录十位p->second = READ_BCD(temp[0]); // 秒// 分: 第0~3位,保存个数,第4到6位,保存十位p->minute = READ_BCD(temp[1]); // 分// 时:第0~3位,保存个数,第4到5位,保存十位p->hour = READ_BCD(temp[2]); // 时// 日:第0~3位,保存个数,第4到5位,保存十位p->day = READ_BCD(temp[3]); // 日// 周:第0~2位,保存个数p->weekday = temp[4]; // 周// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xx// 取出月的第7位的值flag = (temp[5] & (1 << 7)) >> 7; // 取出月的第7位的值// 给第7位置零temp[5] &= ~(1 << 7); // 给第7位置零// 取出月的第4到6位的值p->month = READ_BCD(temp[5]); // 月// 年:第0~3位,保存个数,第4到7位,保存十位p->year = READ_BCD(temp[6]); // 年if (flag == 1) { // 世纪数为21xxp->year += 2100;}else {p->year += 2000;}
}//=============================闹钟
// 设置闹钟
void PCF8563_set_alarm(Alarm_t alarm) {u8 p[4];// 分: 第0~3位,记录个数, 第4~6位记录十位, 第7位:置0启动, 置1禁用// 默认第7位为0p[0] = alarm.minute != -1 ? WRITE_BCD(alarm.minute) : 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 ? WRITE_BCD(alarm.weekday): 0x80; I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x09, p, 4);
}// 启用闹钟
void PCF8563_enable_alarm() {u8 cfg;//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位 第3位:置0清除标志位cfg &= ~(1 << 3);//c) 在原来配置基础上,启动闹钟,第1位:置1启动 cfg |= (1 << 1);//d) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);}// 禁用闹钟Alarm
void PCF8563_disable_alarm() {u8 cfg;//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位 第3位:置0清除标志位cfg &= ~(1 << 3);//c) 在原来配置基础上,禁用闹钟,第1位:置0禁用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清除标志位cfg &= ~(1 << 3);//d) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}
振动与声响驱动函数
实现振动电机和蜂鸣器的开启与关闭控制。
配置io口
// P00 P01 推挽输出GPIO_InitStructure.Pin = GPIO_Pin_0 | GPIO_Pin_1; //指定要初始化的IO,GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PPGPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化// 配置推挽输出,引脚默认高电平P01 = 0; // 默认马达不震动P00 = 0; // 引脚拉低
按键处理与主函数
通过处理实现时间和闹钟的设置,主函数循环读取时间并处理按键事件。
void ext_int3_call() { // 外部中断3回调函数u16 i;printf("========ext_int3_call==============");PCF8563_alarm_clear_flag(); // 清除标志位// 马达震动P01 = 1;delay_ms(250);delay_ms(250);P01= 0;// 蜂鸣器响for (i = 0; i < 500; i++) {P00 = 1;delay_ms(1);P00 = 0;delay_ms(1);}
}
void main()
{Clock_t temp;//时间的结构体Alarm_t alarm;//闹钟结构体EA = 1; // 使能全局中断开关// ========记得=============EAXSFR(); /* 扩展寄存器访问使能 */GPIO_config(); // IO配置函数调用UART_config(); // UART配置函数调用PCF8563_init(); // PCF8563初始化//======================================时间日期temp.year = 2025; temp.month = 4; temp.day = 11;temp.weekday = 5; // 星期几temp.hour = 23; temp.minute = 59; temp.second = 56;PCF8563_set_clock(temp);//===================2.1 闹钟设置 寄存器地址 0x09alarm.day = -1, alarm.hour = 0, alarm.minute = 0, alarm.weekday = -1;PCF8563_set_alarm(alarm);//===================2.2 闹钟开启 寄存器地址 0x01PCF8563_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("%02d:%02d:%02d\n", (int)temp.hour, (int)temp.minute, (int)temp.second);printf("星期%d\n", (int)temp.weekday);delay_ms(250);delay_ms(250);delay_ms(250);delay_ms(250);}
}
系统调试与优化
-
时间精度校准:若发现 RTC 时间有偏差,可通过 RTC 的校准寄存器(如 RTC_CAL)进行补偿,或定期通过外部时间源(如串口同步)校准。
-
提醒时长控制:在中断服务程序中加入定时器,让震动和声响持续一段时间后自动关闭,避免一直提醒。
-
抗干扰优化:除硬件上的电容滤波外,软件中可在读取按键和处理中断时增加多次检测,提高系统稳定性。
应用场景拓展
该闹钟系统可根据实际需求进行拓展,例如:
-
多组闹钟:设置多个闹钟时间,满足不同时段的提醒需求。
-
渐强提醒:让蜂鸣器的音量和振动电机的强度逐渐增大,避免突然的大音量和强震动带来不适。
-
与传感器结合:加入光敏传感器,在夜间自动降低蜂鸣器音量和振动强度,避免影响他人。
通过 STC8 单片机实现的闹钟系统,以其低成本、易实现的特点,能很好地满足震动加声响的提醒需求,无论是在个人电子设备还是小型工业控制中都具有实用价值。