闹钟时间到震动与声响提醒的实现-库函数版(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_PP
GPIO_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_PP
GPIO_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_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 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 闹钟设置 寄存器地址 0x09
alarm.day = -1, alarm.hour = 0, alarm.minute = 0, alarm.weekday = -1;
PCF8563_set_alarm(alarm);
//===================2.2 闹钟开启 寄存器地址 0x01
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("%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_PP
GPIO_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_PP
GPIO_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_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
}
/******************** 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() {
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,世纪数为21xx
p[5] = WRITE_BCD(temp.month);
if(temp.year >= 2100) { // p[5] 的第7位为1
p[5] |= (1 << 7);
} else { // p[5] 的第7位为0
p[5] &= ~(1 << 7);
}
// 年:第0~3位,保存个数,第4到7位,保存十位
// 2025 => 25 => 2 和 5
p[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) { // 世纪数为21xx
p->year += 2100;
}else {
p->year += 2000;
}
}
//=============================闹钟
// 设置闹钟
void PCF8563_set_alarm(Alarm_t alarm) {
u8 p[4];
// 分: 第0~3位,记录个数, 第4~6位记录十位, 第7位:置0启动, 置1禁用
// 默认第7位为0
p[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_PP
GPIO_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 闹钟设置 寄存器地址 0x09
alarm.day = -1, alarm.hour = 0, alarm.minute = 0, alarm.weekday = -1;
PCF8563_set_alarm(alarm);
//===================2.2 闹钟开启 寄存器地址 0x01
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("%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 单片机实现的闹钟系统,以其低成本、易实现的特点,能很好地满足震动加声响的提醒需求,无论是在个人电子设备还是小型工业控制中都具有实用价值。