目录
- 系列文章目录
- 前言
- 一、效果展示
- 二、原理分析
- 三、各模块代码
- 1、延时函数
- 2、定时器0
- 3、定时器1
- 4、独立按键
- 5、DS3231时钟模块
- 6、LCD1602模块(PCF8574T驱动)
- 四、主函数
- 总结
系列文章目录
前言
之前做过一个类似的,用到了很多外设,包括:独立按键、LCD1602液晶显示屏、DHT11湿度传感器、DS18B20温度传感器、DS1302时钟模块、AT24C02存储模块、无源蜂鸣器模块,自己日常也在用,但是感觉用到的外设太多了,有点凌乱。所以,利用精度更加高的DS3231时钟模块做了一个外设少一点的,除了不能测湿度,其他功能跟原来的一样。
有两个版本:
①普中开发板版本
②最小开发板版本
本文代码对应的是普中开发板版本。
两个版本用到的单片机都是:STC89C52RC。
普中开发板版本用了板载的蜂鸣器,最小开发板版本用了一个无源蜂鸣器模块。
效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。
一、效果展示
1、普中开发板版本
(1)正常走时模式
(2)正常走时模式(有闹钟生效的显示)
(3)正计时
(4)倒计时
(5)设置闹钟
(5)设置时间
(6)闹钟响(右半屏显示闹钟时间,并且对应的闹钟时间会闪烁)
(7)倒计时结束(闹钟响,第二行闪烁)
2、最小开发板版本
二、原理分析
DS3231时钟芯片除了计时精确外,也能测量温度,还能存储闹钟数据,无需再接DS18B20温度传感器和AT24C02存储芯片,所接外设减少了很多。
单片机与DS3231时钟芯片的通信跟单片机与DS1302时钟芯片的通信方式,不能说很类似,只能说一模一样,所以如果会用DS1302,那肯定也会用DS3231。
本案例所用到的DS3231时钟模块跟LCD1602(PCF8574T驱动)模块都是通过I2C协议通信,本代码没有共用相同的I2C模块,而是DS3231和LCD1602都有自己的I2C通信的底层函数,虽然有点重复,但是移植DS3231或LCD1602的代码的时候不用复制多两个文件。而且如果共用相同的I2C模块需要DS3231和LCD1602的SDA接在相同的IO口,SCL也接在相同的IO口,需要接多一个I2C扩展板,比较麻烦,现在只是做一个多功能可调时钟,IO口很充裕,无需省IO口。
本案例用的是无源蜂鸣器,通过定时器1(Timer1)以1KHz的频率翻转IO口。如果用有源蜂鸣器,无需频繁翻转IO口,在定时器中控制导通的时长就行了。
按键检测通过定时器,每隔20ms检测一次按键状态,这样就不用进行消抖处理,也不会阻塞主函数的程序运行。有短按、长按、松手检测。独立按键模块中,通过计数让按键长按200ms才被检测出是长按,这样可以防止短按的按键码获取不到的情况。需要注意的是,如果在下一次检测按键状态前主循环中第二次获取按键码的话,一定返回0,所以在main函数的while(1)中的某些代码需要加适当的延时,延时要大于定时器检测按键的周期。
为了防止频繁进入中断,影响主函数的运行,定时器0的定时设置为了10ms。
LCD1602模块用到了一些自定义的字符,需要写入自定义字符的字模到CGRAM中,总共可以自定义8个字符,本案例只用到了5个。
调整时间模式中进行了越界判断,调整日期时有大小月的判断,也有平年二月、闰年二月的判断,如果日期越界,会将日调成1号。此模式中,如果不按K3或K4调整时间的话,是可以正常走时的,按K3或K4调整时间后,松手瞬间会将设置后的时间写入DS3231芯片,再从DS3231芯片中不停地读取时间。星期用英文的前三个字母来表示。
三、各模块代码
1、延时函数
h文件
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
c文件
/**
* @brief 延时函数,晶振@11.0592MHz时,输入参数延时的单位是1ms
* @param xms 延时的时间,范围:0~65535
* @retval 无
*/
void Delay(unsigned int xms)
{
unsigned char i,j;
while(xms)
{
i=2;
j=199;
do
{
while(--j);
} while(--i);
xms--;
}
}
2、定时器0
h文件
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
#endif
c文件
#include <REGX52.H>
/**
* @brief 定时器0初始化
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)
TMOD|=0x01; //设置定时器模式(通过低四位设为“定时器0工作方式1”的模式)
TL0=0x66; //设置定时初值,定时1ms,晶振@11.0592MHz
TH0=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHz
TF0=0; //清除TF0标志
TR0=1; //定时器0开始计时
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
PT0=0; //当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}
/*定时器中断函数模板
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
static unsigned int T0Count; //定义静态变量
TL0=0x66; //设置定时初值,定时1ms,晶振@11.0592MHz
TH0=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHz
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}
*/
3、定时器1
h文件
#ifndef __TIMER1_H__
#define __TIMER1_H__
void Timer1_Init(void);
#endif
c文件
#include <REGX52.H>
/**
* @brief 定时器1初始化
* @param 无
* @retval 无
*/
void Timer1_Init(void)
{
TMOD&=0x0F; //设置定时器模式(低四位不变,高四位清零)
TMOD|=0x10; //设置定时器模式(通过高四位设为“定时器1工作方式1”的模式)
TL1=0x66; //设置定时初值,定时1ms,晶振@11.0592MHz
TH1=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHz
TF1=0; //清除TF1标志
TR1=1; //定时器1开始计时
ET1=1; //打开定时器1中断允许
EA=1; //打开总中断
PT1=1; //当PT1=0时,定时器1为低优先级,当PT1=1时,定时器1为高优先级
}
/*定时器中断函数模板
void Timer1_Routine() interrupt 3 //定时器1中断函数
{
static unsigned int T1Count; //定义静态变量
TL1=0x66; //设置定时初值,定时1ms,晶振@11.0592MHz
TH1=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHz
T1Count++;
if(T1Count>=1000)
{
T1Count=0;
}
}
*/
4、独立按键
h文件
#ifndef __KEYSCAN_H__
#define __KEYSCAN_H__
unsigned char Key(void);
void Key_Tick(void);
#endif
c文件
#include <REGX52.H>
sbit Key1=P3^1;
sbit Key2=P3^0;
sbit Key3=P3^2;
sbit Key4=P3^3;
unsigned char KeyNumber;
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围:0,1~13,0表示无按键按下
*/
unsigned char Key(void)
{
unsigned char KeyTemp=0;
KeyTemp=KeyNumber;
KeyNumber=0; //主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回0
return KeyTemp;
}
/**
* @brief 获取当前按键的状态,无消抖及松手检测
* @param 无
* @retval 按下的按键,范围:0~5,无按键按下时返回值为0
*/
unsigned char Key_GetState()
{
unsigned char KeyValue=0;
if(Key1==0){KeyValue=1;}
if(Key2==0){KeyValue=2;}
if(Key3==0){KeyValue=3;}
if(Key4==0){KeyValue=4;}
if(Key1==0 && Key2==0){KeyValue=5;}
return KeyValue;
}
/**
* @brief 按键驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Key_Tick(void)
{
static unsigned char NowState,LastState;
static unsigned int KeyCount;
LastState=NowState; //按键状态更新
NowState=Key_GetState(); //获取当前按键状态
//如果上个时间点按键未按下,这个时间点按键按下,则是按下瞬间
if(LastState==0)
{
switch(NowState)
{
case 1:KeyNumber=1;break;
case 2:KeyNumber=2;break;
case 3:KeyNumber=3;break;
case 4:KeyNumber=4;break;
default:break;
}
}
//如果上个时间点按键按下,这个时间点按键按下,则是一直按住按键
if(LastState && NowState)
{
KeyCount++;
if(KeyCount>=10) //按下超过200ms才被检测为长按(定时器中断函数中每隔20ms检测一次按键)
{
if(LastState==1 && NowState==1){KeyNumber=5;}
if(LastState==2 && NowState==2){KeyNumber=6;}
if(LastState==3 && NowState==3){KeyNumber=7;}
if(LastState==4 && NowState==4){KeyNumber=8;}
}
}
else
{
KeyCount=0;
}
//如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间
if(NowState==0)
{
switch(LastState)
{
case 1:KeyNumber=9;break;
case 2:KeyNumber=10;break;
case 3:KeyNumber=11;break;
case 4:KeyNumber=12;break;
default:break;
}
}
//如果同时按下Key1和Key2
if(NowState==5 && LastState==5){KeyNumber=13;}
}
5、DS3231时钟模块
h文件
#ifndef __DS3231_H__
#define __DS3231_H__
extern char DS3231_Time[];
extern char DS3231_Alarm[];
void DS3231_I2C_Start(void);
void DS3231_I2C_Stop(void);
void DS3231_I2C_SendByte(unsigned char Byte);
unsigned char DS3231_I2C_ReceiveByte(unsigned char AckBit);
void DS3231_WriteByte(unsigned char WordAddress,Data);
unsigned char DS3231_ReadByte(unsigned char WordAddress);
void DS3231_SetTime(void);
void DS3231_ReadTime(void);
void DS3231_SetAlarm(void);
void DS3231_ReadAlarm(void);
void DS3231_ConvertT(void);
unsigned char DS3231_CheckBusy(void);
float DS3231_ReadT(void);
#endif
c文件
#include <REGX52.H>
#define DS3231_ADDRESS 0xD0 //DS3231的I2C地址
//DS3231引脚定义
sbit DS3231_SDA=P1^0;
sbit DS3231_SCL=P1^1;
//DS3231的时间地址:年,月,日,时,分,秒,星期
unsigned char code DS3231_TimeAddress[7]={0x06,0x05,0x04,0x02,0x01,0x00,0x03,};
//DS3231的闹钟地址:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
unsigned char code DS3231_AlarmAddress[7]={0x0A,0x09,0x08,0x07,0x0D,0x0C,0x0B,};
//时间数组:年,月,日,时,分,秒,星期
char DS3231_Time[]={24,10,24,9,22,23,4}; //时间设置的初始值
//闹钟数组:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
char DS3231_Alarm[]={1,6,7,0,1,6,7}; //闹钟设置的初始值
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void DS3231_I2C_Start(void)
{
DS3231_SDA=1;
DS3231_SCL=1;
DS3231_SDA=0;
DS3231_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void DS3231_I2C_Stop(void)
{
DS3231_SDA=0;
DS3231_SCL=1;
DS3231_SDA=1;
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的字节
* @retval 无
*/
void DS3231_I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
DS3231_SDA=Byte&(0x80>>i);
DS3231_SCL=1;
DS3231_SCL=0;
}
DS3231_SCL=1; //额外一个时钟,不处理应答信号
DS3231_SCL=0;
}
/**
* @brief I2C接收一个字节
* @param AckBit 要发送的应答位,0为应答,1为非应答
* @retval 接收到的一个字节数据
*/
unsigned char DS3231_I2C_ReceiveByte(unsigned char AckBit)
{
unsigned char i,Byte=0x00;
DS3231_SDA=1;
for(i=0;i<8;i++)
{
DS3231_SCL=1;
if(DS3231_SDA){Byte|=(0x80>>i);}
DS3231_SCL=0;
}
DS3231_SDA=AckBit; //发送应答位
DS3231_SCL=1;
DS3231_SCL=0;
return Byte;
}
/**
* @brief DS3231写入一个字节
* @param WordAddress 要写入字节的地址
* @param Data 要写入的数据
* @retval 无
*/
void DS3231_WriteByte(unsigned char WordAddress,Data)
{
DS3231_I2C_Start();
DS3231_I2C_SendByte(DS3231_ADDRESS);
DS3231_I2C_SendByte(WordAddress);
DS3231_I2C_SendByte(Data);
DS3231_I2C_Stop();
}
/**
* @brief DS3231读取一个字节
* @param WordAddress 要读出字节的地址
* @retval 读出的数据
*/
unsigned char DS3231_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
DS3231_I2C_Start();
DS3231_I2C_SendByte(DS3231_ADDRESS);
DS3231_I2C_SendByte(WordAddress);
DS3231_I2C_Start();
DS3231_I2C_SendByte(DS3231_ADDRESS|0x01);
Data=DS3231_I2C_ReceiveByte(1);
DS3231_I2C_Stop();
return Data;
}
/**
* @brief DS3231设置时间,调用之后,DS3231_Time数组的数字会被设置到DS3231中
* @param 无
* @retval 无
*/
void DS3231_SetTime(void)
{
unsigned char i;
for(i=0;i<7;i++) //依次写入:年,月,日,时,分,秒,星期
{
DS3231_WriteByte(DS3231_TimeAddress[i],DS3231_Time[i]/10*16+DS3231_Time[i]%10); //十进制转换为BCD码
}
}
/**
* @brief DS3231读取时间,调用之后,DS3231中的数据会被读取到DS3231_Time数组中
* @param 无
* @retval 无
*/
void DS3231_ReadTime(void)
{
unsigned char Temp,i;
for(i=0;i<7;i++) //依次读取:年,月,日,时,分,秒,星期
{
Temp=DS3231_ReadByte(DS3231_TimeAddress[i]);
DS3231_Time[i]=Temp/16*10+Temp%16; //BCD码转十进制后读取
}
}
/**
* @brief DS3231设置闹钟数据
* @param 无
* @retval 无
*/
void DS3231_SetAlarm(void)
{
unsigned char i;
for(i=0;i<7;i++) //依次写入:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
{
DS3231_WriteByte(DS3231_AlarmAddress[i],DS3231_Time[i]/10*16+DS3231_Time[i]%10); //十进制转换为BCD码
}
}
/**
* @brief DS3231读取闹钟数据
* @param 无
* @retval 无
*/
void DS3231_ReadAlarm(void)
{
unsigned char Temp,i;
for(i=0;i<7;i++) //依次读取:A1日期/星期,A1时,A1分,A1秒,A2日期/星期,A2时,A2分
{
Temp=DS3231_ReadByte(DS3231_AlarmAddress[i]);
DS3231_Alarm[i]=Temp/16*10+Temp%16; //BCD码转十进制后读取
}
}
/**
* @brief DS3231转换温度
* @param 无
* @retval 无
*/
void DS3231_ConvertT(void)
{
unsigned char Data;
Data=DS3231_ReadByte(0x0E);
DS3231_WriteByte(0x0E,Data|0x20);
}
/**
* @brief DS3231检查是否已转换完温度
* @param 无
* @retval 无
*/
unsigned char DS3231_CheckBusy(void)
{
unsigned char BusyState;
BusyState=DS3231_ReadByte(0x0F);
BusyState&=0x04;
return BusyState;
}
/**
* @brief DS3231读取温度
* @param 无
* @retval 无
*/
float DS3231_ReadT(void)
{
unsigned char TM,TL;
int Temp;
float T;
TM=DS3231_ReadByte(0x11);
TL=DS3231_ReadByte(0x12);
Temp=(TM<<8)|TL;
Temp>>=6;
T=Temp/4.0;
return T;
}
6、LCD1602模块(PCF8574T驱动)
h文件
#ifndef __LCD1602_I2C_H__
#define __LCD1602_I2C_H__
void PCF8574T_WriteByte(unsigned char Byte);
void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_Init();
void LCD_SetCursor(unsigned char Line,unsigned char Column);
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_MakeChar(void);
void LCD_Clear(void);
void LCD_MoveLeft(void);
void LCD_MoveRight(void);
#endif
c文件
#include <REGX52.H>
#define PCF8574T_ADDRESS 0x4E
sbit LCD1602_SDA=P1^2;
sbit LCD1602_SCL=P1^3;
//自定义字符,最多8个
unsigned char code CGRAMData[]={
0x07,0x05,0x07,0x00,0x00,0x00,0x00,0x00, //摄氏度的圆圈
0x04,0x0C,0x04,0x04,0x0E,0x00,0x00,0x00, //小1
0x0E,0x02,0x0E,0x08,0x0E,0x00,0x00,0x00, //小2
0x00,0x0A,0x1F,0x1F,0x0E,0x04,0x00,0x00, //小心形
0x00,0x0A,0x00,0x00,0x11,0x0E,0x00,0x00, //小笑脸
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //无
};
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void LCD1602_I2C_Start(void)
{
LCD1602_SDA=1;
LCD1602_SCL=1;
LCD1602_SDA=0;
LCD1602_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void LCD1602_I2C_Stop(void)
{
LCD1602_SDA=0;
LCD1602_SCL=1;
LCD1602_SDA=1;
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的字节
* @retval 无
*/
void LCD1602_I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
LCD1602_SDA=Byte&(0x80>>i);
LCD1602_SCL=1;
LCD1602_SCL=0;
}
LCD1602_SCL=1; //额外一个时钟,不处理应答信号
LCD1602_SCL=0;
}
/**
* @brief PCF8574T写入一个字节
* @param Byte 要写入的字节
* @retval 无
*/
void PCF8574T_WriteByte(unsigned char Byte)
{
LCD1602_I2C_Start();
LCD1602_I2C_SendByte(PCF8574T_ADDRESS);
LCD1602_I2C_SendByte(Byte);
LCD1602_I2C_Stop();
}
/**
* @brief LCD1602写指令
* @param Command 要写的指令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
unsigned char Temp; //临时变量保存处理后的数据
Temp=Command|0x0F; //准备写入指令的高四位
PCF8574T_WriteByte(Temp&0xFC); //xxxx 1(控制背光) 1(EN) 0(RW) 0(RS),EN=1
PCF8574T_WriteByte(Temp&0xF8); //xxxx 1(控制背光) 0(EN) 0(RW) 0(RS),EN=0,下降沿写入指令高四位
Temp=(Command<<4)|0x0F; //准备写入指令的低四位
PCF8574T_WriteByte(Temp&0xFC);
PCF8574T_WriteByte(Temp&0xF8);
}
/**
* @brief LCD1602写数据
* @param Data 要写的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
unsigned char Temp;
Temp=Data|0x0F;
PCF8574T_WriteByte(Temp&0xFD);
PCF8574T_WriteByte(Temp&0xF9);
Temp=(Data<<4)|0x0F;
PCF8574T_WriteByte(Temp&0xFD);
PCF8574T_WriteByte(Temp&0xF9);
}
void LCD_Init()
{
LCD_WriteCommand(0x28); //发送第一个指令的高四位的时候LCD就已经知道选择的是四位数据接口的模式
PCF8574T_WriteByte(0x0C); //低四位暂时没什么用,如果急着发送下一个指令,则第一个指令的低四位和第二个指令的高四位会组成一个字节
PCF8574T_WriteByte(0x08); //所以不能急着发送下一个指令,要再使能一次再写其他指令
LCD_WriteCommand(0x28); //四位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0C); //显示开,光标关,闪烁关
LCD_WriteCommand(0x06); //数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01); //光标复位,清屏
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
/**
* @brief 在CGRAM中写入8个自定义字符的64个字节,每个5*8点阵横向取模,使用每个字节的低5位
* @param 无
* @retval 无
*/
void LCD_MakeChar(void)
{
unsigned char i;
LCD_WriteCommand(0x40); //起始地址为0x40
for(i=0;i<64;i++)
{
LCD_WriteData(CGRAMData[i]);
}
}
/**
* @brief LCD1602的光标复位,清屏
* @param 无
* @retval 无
*/
void LCD_Clear(void)
{
LCD_WriteCommand(0x01);
}
/**
* @brief LCD1602的屏幕向左移动一个字符位,光标不动
* @param 无
* @retval 无
*/
void LCD_MoveLeft(void)
{
LCD_WriteCommand(0x18);
}
/**
* @brief LCD1602的屏幕向右移动一个字符位,光标不动
* @param 无
* @retval 无
*/
void LCD_MoveRight(void)
{
LCD_WriteCommand(0x1C);
}
四、主函数
main.c
/*
by甘腾胜@20250104
操作和功能:可以在B站搜索“甘腾胜”或“gantengsheng”查看
单片机:STC89C52RC
晶振:12T@11.0592MHz
外设:独立按键、无源蜂鸣器、DS3231时钟模块、LCD1602液晶显示屏(PCF8574T驱动,I2C通讯)
注意:计时不准的话,可以修改定时器0所赋初值进行补偿
操作说明:
K1 K2 K3 K4
一、正常走时模式:
【K1】切换模式
【K2】长按:显示闹钟时间;松手:返回正常走时模式
【K3】控制闹钟1生不生效
【K4】控制闹钟2生不生效
【K2&K1】先按K2再按K1:显示作者和编程日期等;松手:返回正常走时模式
二、计时模式
【K1】切换模式
【K2】计时暂停时:切换计时模式/选择要设置的倒计时的时间(时分秒)
【K3】正/倒计时暂停时:清空计时数据;倒计时设置时间时:设置倒计时的时间(数值只能加,不能减),短按加1,长按连续增加
【K4】计时中:开始/暂停/继续;设置倒计时时间且数值均为零时:切换为正计时并开始计时
三、设置闹钟模式:
【K1】切换模式
【K2】切换选择
【K3】减,短按减1,长按连续减小
【K4】增,短按加1,长按连续增加
四、设置闹钟模式:
【K1】切换模式
【K2】切换选择要设置的时间
【K3】减,短按减1,长按连续减小(设置秒的时候,清零)
【K4】增,短按加1,长按连续增加(设置秒的时候,清零)
*/
#include <REGX52.H>
#include "Delay.h"
#include "KeyScan.h"
#include "DS3231.h"
#include "LCD1602_I2C.h"
#include "Timer0.h"
#include "Timer1.h"
sbit Buzzer=P2^5; //蜂鸣器端口
#define AlarmTimeLength 120 //闹钟响的时长
unsigned char KeyNum; //保存键码值的变量
unsigned char FlashFlag; //设置时间时闪烁的标志,1:不显示,0:显示
unsigned char Mode; //时间模式,0:正常走时模式,1:计时模式,2:设置闹钟模式,3:设置时间模式
unsigned char TimeSelect; //时间选择变量
unsigned char ReadTimeFlag; //读取时间的标志,1:读取,0:不读取
unsigned char ReadTempFlag; //读取温度的标志,1:读取,0:不读取
float T; //存储温度数据的变量
unsigned char ExecuteOnceFlag; //各模式中只执行一次的标志,用于清屏和显示静态字符
char TimeCount[4]; //存储计时的变量,时、分、秒、十毫秒
unsigned char TimeIsUpFlag; //计时模式倒计时时间到了的标志,1:时间到了,0:时间未到
unsigned char TimeCountModeFlag=1; //计时模式标志,1:正计时,0:倒计时
unsigned char TimeCountRunFlag; //计时启停的标志,1:启动,0:暂停
char AlarmClock[4]; //存储两个闹钟时间的变量,闹钟1-时,闹钟1-分,闹钟2-时,闹钟2-分
unsigned char Alarm1RunFlag,Alarm2RunFlag; //闹钟生效的标志,1:闹钟生效,0:闹钟不生效
unsigned char KeypressFlag; //按键按下标志,1:有按键按下,0:无按键按下
unsigned char KeypressSoundTime; //按键声时长
unsigned char Alarm1MatchFlag,Alarm2MatchFlag; //闹钟到了的标志,1:走时到了闹钟的时间,0:走时未到闹钟的时间
unsigned char AlarmSoundCount; //闹钟时长计数,控制闹钟响的方式
unsigned int AlarmSoundTime; //闹钟响的时长,控制闹钟响的时长
unsigned char NormalShowFlag; //闹钟停止时恢复正常走时显示的标志,1:恢复正常走时的显示,0:不用恢复原来显示
unsigned char AlarmFlag; //闹钟响的标志,1:闹钟响,0:闹钟不响
unsigned char LastKeyNum; //上次获取的键码值(用来控制正常走时模式长按K2显示闹钟时间和同时按K2、K1显示作者信息)
unsigned char NowKeyNum; //本次获取的键码值(用来控制正常走时模式长按K2显示闹钟时间和同时按K2、K1显示作者信息)
unsigned char SwitchDisplayFlag; //正常走时模式长按K2和同时按下K2、K1切换显示的标志,
//1:切换显示,0:不重复显示静态字符,进入函数后会清屏和使此标志清零
unsigned char ChangedFlag; //计时模式和设置闹钟模式有变动(有按下K2、K3或K4)的标志,1:有变动,0:无变动
void TimeShow(void) //时间显示功能
{
if(Mode==3 && FlashFlag==1 && TimeSelect==0){LCD_ShowString(1,1," ");} //设置时间时闪烁
else{LCD_ShowNum(1,1,DS3231_Time[0],2);} //显示年
if(Mode==3 && FlashFlag==1 && TimeSelect==1){LCD_ShowString(1,4," ");}
else{LCD_ShowNum(1,4,DS3231_Time[1],2);} //显示月
if(Mode==3 && FlashFlag==1 && TimeSelect==2){LCD_ShowString(1,7," ");}
else{LCD_ShowNum(1,7,DS3231_Time[2],2);} //显示日
if(AlarmFlag==0) //如果闹钟不响,才显示周
{
if(Mode==3 && FlashFlag==1 && TimeSelect==3){LCD_ShowString(1,12," ");}
else
{
switch(DS3231_Time[6])
{
case 1 : LCD_ShowString(1,12,"Mon");break;
case 2 : LCD_ShowString(1,12,"Tue");break;
case 3 : LCD_ShowString(1,12,"Wen");break;
case 4 : LCD_ShowString(1,12,"Thu");break;
case 5 : LCD_ShowString(1,12,"Fri");break;
case 6 : LCD_ShowString(1,12,"Sat");break;
case 7 : LCD_ShowString(1,12,"Sun");break;
}
} //显示周
}
if(Mode==3 && FlashFlag==1 && TimeSelect==4){LCD_ShowString(2,1," ");}
else{LCD_ShowNum(2,1,DS3231_Time[3],2);} //显示时
if(Mode==3 && FlashFlag==1 && TimeSelect==5){LCD_ShowString(2,4," ");}
else{LCD_ShowNum(2,4,DS3231_Time[4],2);} //显示分
if(Mode==3 && FlashFlag==1 && TimeSelect==6){LCD_ShowString(2,7," ");}
else{LCD_ShowNum(2,7,DS3231_Time[5],2);} //显示秒
if(AlarmFlag==0) //如果闹钟不响
{ //小心形闪烁
if(DS3231_Time[5]%2 && Mode==0){LCD_ShowChar(1,11,3);LCD_ShowChar(1,15,3);} //小心形
else {LCD_ShowChar(1,11,0x20);LCD_ShowChar(1,15,0x20);} //无显示
}
}
void TimeCheck(void) //时间越界判断,防止向DS1302写入错误数据
{
if(DS3231_Time[0]<0){DS3231_Time[0]=99;} //年越界判断,范围:0~99
if(DS3231_Time[0]>99){DS3231_Time[0]=0;}
if(DS3231_Time[1]<1){DS3231_Time[1]=12;} //月越界判断,,范围:1~12
if(DS3231_Time[1]>12){DS3231_Time[1]=1;}
if(DS3231_Time[1]==1 || DS3231_Time[1]==3 || DS3231_Time[1]==5 || DS3231_Time[1]==7 ||
DS3231_Time[1]==8 || DS3231_Time[1]==10 || DS3231_Time[1]==12) //日越界判断,区分大小月、闰年2月、平年2月
{
if(DS3231_Time[2]<1){DS3231_Time[2]=31;} //大月
if(DS3231_Time[2]>31){DS3231_Time[2]=1;}
}
else if(DS3231_Time[1]==4 || DS3231_Time[1]==6 || DS3231_Time[1]==9 || DS3231_Time[1]==11)
{
if(DS3231_Time[2]<1){DS3231_Time[2]=30;} //小月
if(DS3231_Time[2]>30){DS3231_Time[2]=1;}
}
else if(DS3231_Time[1]==2)
{
if(DS3231_Time[0]%4==0)
{
if(DS3231_Time[2]<1){DS3231_Time[2]=29;} //闰年2月
if(DS3231_Time[2]>29){DS3231_Time[2]=1;}
}
else
{
if(DS3231_Time[2]<1){DS3231_Time[2]=28;} //平年2月
if(DS3231_Time[2]>28){DS3231_Time[2]=1;}
}
}
if(DS3231_Time[3]<0){DS3231_Time[3]=23;} //时越界判断,范围:0~24
if(DS3231_Time[3]>23){DS3231_Time[3]=0;}
if(DS3231_Time[4]<0){DS3231_Time[4]=59;} //分越界判断,范围:0~59
if(DS3231_Time[4]>59){DS3231_Time[4]=0;}
if(DS3231_Time[6]<1){DS3231_Time[6]=7;} //周越界判断,范围:1~7
if(DS3231_Time[6]>7){DS3231_Time[6]=1;}
}
void TimeUpdateDisplay(void) //设置时按下(短按或长按)K3和K4更新显示时间
{
LCD_ShowNum(1,1,DS3231_Time[0],2); //显示年
LCD_ShowNum(1,4,DS3231_Time[1],2); //显示月
LCD_ShowNum(1,7,DS3231_Time[2],2); //显示日
switch(DS3231_Time[6]) //显示周
{
case 1 : LCD_ShowString(1,12,"Mon");break;
case 2 : LCD_ShowString(1,12,"Tue");break;
case 3 : LCD_ShowString(1,12,"Wen");break;
case 4 : LCD_ShowString(1,12,"Thu");break;
case 5 : LCD_ShowString(1,12,"Fri");break;
case 6 : LCD_ShowString(1,12,"Sat");break;
case 7 : LCD_ShowString(1,12,"Sun");break;
}
LCD_ShowNum(2,1,DS3231_Time[3],2); //显示时
LCD_ShowNum(2,4,DS3231_Time[4],2); //显示分
LCD_ShowNum(2,7,DS3231_Time[5],2); //显示秒
}
void AlarmStop(void) //闹钟停止
{
AlarmSoundCount=0; //闹钟时间计数清零
AlarmSoundTime=0; //闹钟时间计时清零
Alarm1MatchFlag=0; //闹钟1标志清零
Alarm2MatchFlag=0; //闹钟2标志清零
if(AlarmFlag==1){NormalShowFlag=1;} //如果通过按键取消闹钟,则切换为正常走时的显示
AlarmFlag=0; //闹钟响的标志清零
Buzzer=1; //蜂鸣器低电平导通,防止不响的时候导通
TimeIsUpFlag=0; //时间到了的标记清零(倒计时模式)
}
void main()
{
DS3231_ConvertT(); //转换温度
LCD_Init(); //LCD1602初始化
Timer0_Init(); //定时器0初始化
Timer1_Init(); //定时器1初始化
LCD_MakeChar(); //向LCD1602的CGRAM中写入自定义字符数据
DS3231_ReadAlarm(); //从DS3231中读出闹钟数据
AlarmClock[0]=DS3231_Alarm[1];
AlarmClock[1]=DS3231_Alarm[2];
AlarmClock[2]=DS3231_Alarm[5];
AlarmClock[3]=DS3231_Alarm[6];
Alarm1RunFlag=DS3231_Alarm[0]; //闹钟1是否生效的标志
Alarm2RunFlag=DS3231_Alarm[4]; //闹钟2是否生效的标志
if(AlarmClock[0]>=24 || AlarmClock[0]<0){AlarmClock[0]=0;} //判断闹钟时间是否越界,如果越界,则清零
if(AlarmClock[1]>=60 || AlarmClock[1]<0){AlarmClock[1]=0;}
if(AlarmClock[2]>=24 || AlarmClock[2]<0){AlarmClock[2]=0;}
if(AlarmClock[3]>=60 || AlarmClock[3]<0){AlarmClock[3]=0;}
if(Alarm1RunFlag>1){Alarm1RunFlag=0;} //判断闹钟是否生效的标志是否越界,如果越界,则清零
if(Alarm2RunFlag>1){Alarm2RunFlag=0;}
ExecuteOnceFlag=1; //上电正常走时模式清屏和显示静态字符
while(1)
{
KeyNum=Key(); //获取键码值
if(KeyNum) //如果键码值不为零
{
if(KeyNum==1) //如果K1短按
{
KeypressFlag=1; //按键声标志置1
AlarmStop(); //闹钟停止
if(ChangedFlag==1 && (Mode==1 || Mode==2))
{ //如果计时模式或设置闹钟模式有变动
Mode=0; //按下模式键则返回正常走时模式
ChangedFlag=0; //计时模式或设置闹钟模式有变动的标志清零
ExecuteOnceFlag=1;
}
else
{
Mode++; //转换模式
Mode%=4; //越界清零
}
if(Mode){ExecuteOnceFlag=1;} //设置时间模式切换到正常走时模式不用清屏
TimeSelect=0; //时间选择变量清零
if(TimeCount[0]==0 && TimeCount[1]==0 && TimeCount[2]==0 && TimeCount[3]==0)
{ //切换到计时模式时,如果没在倒计时,则切换为正计时模式
TimeCountModeFlag=1;
}
}
if(KeyNum==2) //如果K2短按
{
KeypressFlag=1; //按键声标志置1
AlarmStop(); //闹钟停止
if(Mode==1) //如果是模式1(计时模式)
{
ChangedFlag=1; //计时模式有变动
if(TimeCountRunFlag==0) //停止计时才能切换正计时和倒计时模式
{
TimeSelect++; //时间选择变量自增
TimeSelect%=4; //越界清零
//按下K2,如果TimeSelect为0,是正计时模式,如果TimeSelect为1/2/3,是倒计时模式
if(TimeSelect){TimeCountModeFlag=0;}
else {TimeCountModeFlag=1;}
}
}
if(Mode==2) //如果是模式2(设置闹钟模式)
{
ChangedFlag=1; //设置闹钟模式有变动
TimeSelect++; //时间选择变量自增
TimeSelect%=4; //越界清零
}
if(Mode==3) //如果是模式3(设置时间模式)
{
TimeSelect++; //时间选择变量自增
TimeSelect%=7; //越界清零
}
}
if(KeyNum==3) //如果K3短按
{
KeypressFlag=1; //按键声标志置1
AlarmStop(); //闹钟停止
if(Mode==0) //如果是模式0(正常走时模式)
{
Alarm1RunFlag=!Alarm1RunFlag; //更改闹钟1是否生效的标志的值
DS3231_WriteByte(0x0A,Alarm1RunFlag); //将闹钟设置状态保存到DS3231
if(Alarm1RunFlag){LCD_ShowChar(1,9,1);} //显示小1,表示闹钟1生效了
else {LCD_ShowChar(1,9,0x20);} //无显示,表示闹钟1不生效
}
if(Mode==1) //如果是模式1(计时模式)
{
ChangedFlag=1; //计时模式有变动
if(TimeCountRunFlag==0)
{ //停止计时才能修改时间或清零
if(TimeCountModeFlag) //如果是正计时
{
TimeCount[0]=0; //计时数据清零
TimeCount[1]=0;
TimeCount[2]=0;
TimeCount[3]=0;
}
else //如果是倒计时
{
switch(TimeSelect)
{
case 0: //倒计时暂停后,按K3将数据清零
TimeCount[0]=0;
TimeCount[1]=0;
TimeCount[2]=0;
TimeCount[3]=0;
break;
case 1: //按键有限,设置倒计时的时间只能加,不能减
TimeCount[0]++;if(TimeCount[0]>=100){TimeCount[0]=0;}break; //最大计时,99h59m59s
case 2:TimeCount[1]++;if(TimeCount[1]>=60){TimeCount[1]=0;}break;
case 3:TimeCount[2]++;if(TimeCount[2]>=60){TimeCount[2]=0;}break;
default:break;
}
LCD_ShowNum(2,1,TimeCount[0],2); //更新显示
LCD_ShowNum(2,4,TimeCount[1],2);
LCD_ShowNum(2,7,TimeCount[2],2);
LCD_ShowNum(2,10,TimeCount[3],2);
Delay(200); //短按K3调节时间后暂不闪烁
}
}
}
if(Mode==2) //如果是模式2(设置闹钟模式)
{
ChangedFlag=1; //设置闹钟模式有变动
switch(TimeSelect) //时间数值减小
{
case 0:
AlarmClock[0]--;
if(AlarmClock[0]<0){AlarmClock[0]=23;} //限制闹钟时间范围
LCD_ShowNum(1,8,AlarmClock[0],2); //更新显示
break;
case 1:
AlarmClock[1]--;
if(AlarmClock[1]<0){AlarmClock[1]=59;}
LCD_ShowNum(1,11,AlarmClock[1],2);
break;
case 2:
AlarmClock[2]--;
if(AlarmClock[2]<0){AlarmClock[2]=23;}
LCD_ShowNum(2,8,AlarmClock[2],2);
break;
case 3:
AlarmClock[3]--;
if(AlarmClock[3]<0){AlarmClock[3]=59;}
LCD_ShowNum(2,11,AlarmClock[3],2);
break;
default:break;
}
Delay(200); //防止短按松手后检测不出松手的键码值
}
if(Mode==3) //如果是模式3(设置时间模式)
{
switch(TimeSelect) //时间数值减小
{
case 0:DS3231_Time[0]--;break;
case 1:DS3231_Time[1]--;break;
case 2:DS3231_Time[2]--;break;
case 3:DS3231_Time[6]--;break;
case 4:DS3231_Time[3]--;break;
case 5:DS3231_Time[4]--;break;
case 6:DS3231_Time[5]=0;break; //按下按键3,秒的数值清零
}
TimeCheck(); //检查时间值是否越界
TimeUpdateDisplay(); //更新显示
Delay(200); //防止短按松手后检测不出松手的键码值
}
}
if(KeyNum==4) //如果K4短按
{
KeypressFlag=1; //按键声标志置1
AlarmStop(); //闹钟停止
if(Mode==0) //如果是模式0(正常走时模式)
{
Alarm2RunFlag=!Alarm2RunFlag; //更改闹钟2是否生效的标志的值
DS3231_WriteByte(0x0D,Alarm1RunFlag); //将闹钟设置状态保存到DS3231
if(Alarm2RunFlag){LCD_ShowChar(2,9,2);} //显示小2,表示闹钟2生效了
else {LCD_ShowChar(2,9,0x20);} //无显示,表示闹钟2不生效
}
if(Mode==1) //如果是模式1(计时模式)
{
ChangedFlag=1; //计时模式有变动
if(TimeCountModeFlag){TimeCountRunFlag=!TimeCountRunFlag;} //如果是正计时,切换启动和暂停的状态
else if(TimeCount[0]==0 && TimeCount[1]==0 && TimeCount[2]==0 && TimeCount[3]==0) //如果是倒计时
{ //如果所有数据均为0,则转变成正计时模式
TimeSelect=0;
TimeCountModeFlag=1;
TimeCountRunFlag=1;
}
else //如果数据不都为零
{
TimeSelect=0; //启动后再暂停,使时分秒的显示不闪烁
TimeCountRunFlag=!TimeCountRunFlag; //切换启动和暂停的状态
}
}
if(Mode==2) //如果是模式2(设置闹钟模式)
{
ChangedFlag=1; //设置闹钟模式有变动
switch(TimeSelect) //时间数值增加
{
case 0:
AlarmClock[0]++;
if(AlarmClock[0]>=24){AlarmClock[0]=0;} //限制闹钟时间范围
LCD_ShowNum(1,8,AlarmClock[0],2); //更新显示
break;
case 1:
AlarmClock[1]++;
if(AlarmClock[1]>=60){AlarmClock[1]=0;}
LCD_ShowNum(1,11,AlarmClock[1],2);
break;
case 2:
AlarmClock[2]++;
if(AlarmClock[2]>=24){AlarmClock[2]=0;}
LCD_ShowNum(2,8,AlarmClock[2],2);
break;
case 3:
AlarmClock[3]++;
if(AlarmClock[3]>=60){AlarmClock[3]=0;}
LCD_ShowNum(2,11,AlarmClock[3],2);
break;
default:break;
}
Delay(200); //防止短按松手后检测不出松手的键码值
}
if(Mode==3) //如果是模式3(设置时间模式)
{
switch(TimeSelect) //时间数值增加
{
case 0:DS3231_Time[0]++;break;
case 1:DS3231_Time[1]++;break;
case 2:DS3231_Time[2]++;break;
case 3:DS3231_Time[6]++;break;
case 4:DS3231_Time[3]++;break;
case 5:DS3231_Time[4]++;break;
case 6:DS3231_Time[5]=0;break;
}
TimeCheck(); //检查时间值是否越界
TimeUpdateDisplay(); //更新显示
Delay(200); //防止短按松手后检测不出松手的键码值
}
}
if(KeyNum==7) //如果K3长按
{
if(Mode==1) //如果是模式1(计时模式)
{
if(TimeCountModeFlag==0 && TimeCountRunFlag==0) //倒计时设置时间(暂停状态才能修改)
{
switch(TimeSelect)
{
case 1:
TimeCount[0]++; //按键有限,设置倒计时的时间只能加,不能减
if(TimeCount[0]>=100){TimeCount[0]=0;} //最大计时,99h59m59s
LCD_ShowNum(2,1,TimeCount[0],2); //更新显示
break;
case 2:
TimeCount[1]++;
if(TimeCount[1]>=60){TimeCount[1]=0;}
LCD_ShowNum(2,4,TimeCount[1],2);
break;
case 3:
TimeCount[2]++;
if(TimeCount[2]>=60){TimeCount[2]=0;}
LCD_ShowNum(2,7,TimeCount[2],2);
break;
default:break;
}
LCD_ShowNum(2,10,TimeCount[3],2);
Delay(50); //防止数值变化过快,也防止长按按键时数据闪烁
}
}
if(Mode==2) //如果是模式2(设置闹钟模式)
{
switch(TimeSelect) //时间数值减小
{
case 0:
AlarmClock[0]--;
if(AlarmClock[0]<0){AlarmClock[0]=23;} //限制闹钟时间范围
LCD_ShowNum(1,8,AlarmClock[0],2); //更新显示
break;
case 1:
AlarmClock[1]--;
if(AlarmClock[1]<0){AlarmClock[1]=59;}
LCD_ShowNum(1,11,AlarmClock[1],2);
break;
case 2:
AlarmClock[2]--;
if(AlarmClock[2]<0){AlarmClock[2]=23;}
LCD_ShowNum(2,8,AlarmClock[2],2);
break;
case 3:
AlarmClock[3]--;
if(AlarmClock[3]<0){AlarmClock[3]=59;}
LCD_ShowNum(2,11,AlarmClock[3],2);
break;
default:break;
}
Delay(50); //防止数值变化过快,也防止长按按键时数据闪烁
}
if(Mode==3) //如果是模式3(设置时间模式)
{
switch(TimeSelect) //时间数值减小
{
case 0:DS3231_Time[0]--;break;
case 1:DS3231_Time[1]--;break;
case 2:DS3231_Time[2]--;break;
case 3:DS3231_Time[6]--;break;
case 4:DS3231_Time[3]--;break;
case 5:DS3231_Time[4]--;break;
case 6:DS3231_Time[5]=0;break;
}
TimeCheck(); //检查时间值是否越界
TimeUpdateDisplay(); //更新显示
Delay(50); //防止数值变化过快,也防止长按按键时数据闪烁
}
}
if(KeyNum==8) //如果K4长按
{
if(Mode==2) //如果是模式2(设置闹钟模式)
{
switch(TimeSelect) //时间数值增加
{
case 0:
AlarmClock[0]++;
if(AlarmClock[0]>=24){AlarmClock[0]=0;} //限制闹钟时间范围
LCD_ShowNum(1,8,AlarmClock[0],2); //更新显示
break;
case 1:
AlarmClock[1]++;
if(AlarmClock[1]>=60){AlarmClock[1]=0;}
LCD_ShowNum(1,11,AlarmClock[1],2);
break;
case 2:
AlarmClock[2]++;
if(AlarmClock[2]>=24){AlarmClock[2]=0;}
LCD_ShowNum(2,8,AlarmClock[2],2);
break;
case 3:
AlarmClock[3]++;
if(AlarmClock[3]>=60){AlarmClock[3]=0;}
LCD_ShowNum(2,11,AlarmClock[3],2);
break;
default:break;
}
Delay(50); //防止数值变化过快,也防止长按按键时数据闪烁
}
if(Mode==3) //如果是模式3(设置时间模式)
{
switch(TimeSelect) //时间数值增加
{
case 0:DS3231_Time[0]++;break;
case 1:DS3231_Time[1]++;break;
case 2:DS3231_Time[2]++;break;
case 3:DS3231_Time[6]++;break;
case 4:DS3231_Time[3]++;break;
case 5:DS3231_Time[4]++;break;
case 6:DS3231_Time[5]=0;break;
}
TimeCheck(); //检查时间值是否越界
TimeUpdateDisplay(); //更新显示
Delay(50); //防止数值变化过快,也防止长按按键时数据闪烁
}
}
if(KeyNum==11 || KeyNum==12) //如果K3或K4松开(松开瞬间)
{
if(Mode==2) //如果是模式2(设置闹钟模式)
{
switch(TimeSelect) //将修改后的闹钟时间写入DS3231
{
case 0:
DS3231_WriteByte(0x09,AlarmClock[0]/10*16+AlarmClock[0]%10);
break;
case 1:
DS3231_WriteByte(0x08,AlarmClock[1]/10*16+AlarmClock[1]%10);
break;
case 2:
DS3231_WriteByte(0x0C,AlarmClock[2]/10*16+AlarmClock[2]%10);
break;
case 3:
DS3231_WriteByte(0x0B,AlarmClock[3]/10*16+AlarmClock[3]%10);
break;
default:break;
}
}
if(Mode==3) //如果是模式3(设置时间模式)
{
DS3231_SetTime(); //将修改后的时间写入DS1302
}
}
LastKeyNum=NowKeyNum;
NowKeyNum=KeyNum; //更新获取的键码值
//如果上次键码值不是6,本次键码值是6,说明按键2长按,显示闹钟时间
if(LastKeyNum!=6 && NowKeyNum==6 && Mode==0){SwitchDisplayFlag=1;} //用于长按K2瞬间清屏和显示静态字符
//如果上次键码值是6,本次键码值不是6,说明按键2由长按变成松开
if(LastKeyNum==6 && NowKeyNum!=6 && Mode==0){ExecuteOnceFlag=1;} //用于松手瞬间清屏和恢复显示
//如果上次键码值不是13,本次键码值是13,说明同时按下K1、K2(要先按K2,再按K1),显示作者信息和写程序时间
if(LastKeyNum!=13 && NowKeyNum==13 && Mode==0){SwitchDisplayFlag=1;} //用于同时按K2和K1瞬间清屏和显示静态字符
//如果上次键码值是13,本次键码值不是13,说明不同时按下K1、K2了
if(LastKeyNum==13 && NowKeyNum!=13 && Mode==0){ExecuteOnceFlag=1;} //用于松手瞬间清屏和恢复显示
}
if(DS3231_Time[3]==AlarmClock[0] && DS3231_Time[4]==AlarmClock[1] && DS3231_Time[5]==0) //判断走时是否到了闹钟时间
{
Alarm1MatchFlag=1; //闹钟1到了的标志置1
Alarm2MatchFlag=0; //如果闹钟2正在响,则闹钟2停止
AlarmSoundTime=0; //闹钟计时清零
}
if(DS3231_Time[3]==AlarmClock[2] && DS3231_Time[4]==AlarmClock[3] && DS3231_Time[5]==0) //判断走时是否到了闹钟时间
{
Alarm2MatchFlag=1; //闹钟2到了的标志置1
Alarm1MatchFlag=0; //如果闹钟1正在响,则闹钟1停止
AlarmSoundTime=0; //闹钟计时清零
}
if((Alarm1RunFlag==1 && Alarm1MatchFlag==1) || (Alarm2RunFlag==1 && Alarm2MatchFlag==1) || TimeIsUpFlag==1)
{
AlarmFlag=1; //闹钟响(到闹钟时间或倒计时结束)的标志置1
}
if(Mode==0 || Mode==3) //正常走时模式和设置时间模式
{
if(KeyNum!=7 && KeyNum!=8) //如果设置时间模式长按K3或K4则不执行此模式中的代码,防止闪烁
{
if((Mode==0 && KeyNum==6) || (Mode==0 && KeyNum==13)) //如果正常走时模式K2长按或先按K2再按K1
{
if(SwitchDisplayFlag)
{
if(KeyNum==6) //如果K2长按则显示闹钟
{
SwitchDisplayFlag=0; //切换显示标志清零(全部是静态字符,长按按K2,函数内程序只执行1次)
LCD_Clear(); //清屏
LCD_ShowString(1,1,"Alarm1");
LCD_ShowString(2,1,"Alarm2");
LCD_ShowChar(1,10,':');
LCD_ShowChar(2,10,':');
LCD_ShowNum(1,8,AlarmClock[0],2); //显示时(闹钟1)
LCD_ShowNum(1,11,AlarmClock[1],2); //显示分(闹钟1)
LCD_ShowNum(2,8,AlarmClock[2],2); //显示时(闹钟2)
LCD_ShowNum(2,11,AlarmClock[3],2); //显示分(闹钟2)
}
else //如果先按K2再按K1
{ //同时按下K2和K1则显示作者信息和编程日期
SwitchDisplayFlag=0; //切换显示标志清零(全部是静态字符,同时按下K2和K1,函数内程序只执行1次)
LCD_Clear(); //清屏
LCD_ShowString(1,1,"by gantengsheng Never give up!"); //显示作者信息和编写时间
LCD_ShowString(2,1,"at 20250104 You are the best!");
LCD_ShowChar(1,36,3); //显示小心形
LCD_ShowChar(2,36,3); //显示小心形
LCD_ShowChar(1,38,4); //显示小笑脸
LCD_ShowChar(2,38,4); //显示小笑脸
}
}
if(KeyNum==13){Delay(500);LCD_MoveLeft();} //移屏效果,500ms移动一次
Delay(25); //定时器扫描按键的周期是20ms,要延时一小段时间,否则显示不正常
//在下次扫描按键前,如果再次获取键码值,得到的键码值是0,如果不加延时,下次就会执行下面else的内容
}
else //如果不是长按K2和同时按K2、K1
{
if(ExecuteOnceFlag) //切换模式前,此if内容只执行一次
{
ExecuteOnceFlag=0;
LCD_Clear(); //清屏
LCD_ShowString(1,1," - - "); //显示静态字符
LCD_ShowString(2,1," : : ");
LCD_ShowChar(2,13,'.');
LCD_ShowChar(2,15,0); //在2行15列显示CGRAM中编码0x00的自定义字符(摄氏度的圈)
LCD_ShowChar(2,16,'C');
if(Alarm1RunFlag){LCD_ShowChar(1,9,1);} //显示小1,表示闹钟1生效了
else {LCD_ShowChar(1,9,0x20);} //无显示,表示闹钟1不生效
if(Alarm2RunFlag){LCD_ShowChar(2,9,2);} //显示小2,表示闹钟2生效了
else {LCD_ShowChar(2,9,0x20);} //无显示,表示闹钟2不生效
}
if(AlarmFlag)
{ //如果闹钟响了,则对应的闹钟图标(小1和小2)和闹钟时间闪烁
if(Alarm1MatchFlag && FlashFlag) //如果是闹钟1响
{
LCD_ShowString(1,9," "); //清空上右半屏
}
else //如果是闹钟1
{
if(Alarm1RunFlag){LCD_ShowChar(1,9,1);} //显示小1,表示闹钟1生效了
else {LCD_ShowChar(1,9,0x20);} //无显示,表示闹钟1不生效
LCD_ShowChar(1,10,0x20);
LCD_ShowChar(1,13,':');
LCD_ShowNum(1,11,AlarmClock[0],2);
LCD_ShowNum(1,14,AlarmClock[1],2);
LCD_ShowChar(1,16,0x20);
}
if(Alarm2MatchFlag && FlashFlag) //如果是闹钟2响
{
LCD_ShowString(2,9," "); //清空下右半屏
}
else
{
if(Alarm2RunFlag){LCD_ShowChar(2,9,2);} //显示小2,表示闹钟2生效了
else {LCD_ShowChar(2,9,0x20);} //无显示,表示闹钟2不生效
LCD_ShowChar(2,10,0x20);
LCD_ShowChar(2,13,':');
LCD_ShowNum(2,11,AlarmClock[2],2);
LCD_ShowNum(2,14,AlarmClock[3],2);
LCD_ShowChar(2,16,0x20);
}
}
if(NormalShowFlag) //闹钟响完之后恢复原来显示
{
NormalShowFlag=0; //恢复正常走时显示的标志清零
LCD_ShowString(1,10," "); //清空右半屏和显示静态字符
LCD_ShowString(2,10," . "); //清空右半屏和显示静态字符
LCD_ShowChar(2,15,0); //在2行15列显示CGRAM中编码0x00的自定义字符(摄氏度的圈)
LCD_ShowChar(2,16,'C');
if(Alarm1RunFlag){LCD_ShowChar(1,9,1);} //显示小1,表示闹钟1生效了
else {LCD_ShowChar(1,9,0x20);} //无显示,表示闹钟1取消了
if(Alarm2RunFlag){LCD_ShowChar(2,9,2);} //显示小2,表示闹钟2生效了
else {LCD_ShowChar(2,9,0x20);} //无显示,表示闹钟2取消了
}
if(ReadTimeFlag && KeyNum!=3 && KeyNum!=4) //如果读取时间标志为1
{ //短按K3或K4不读取时间,防止数据未写入就读取设置时间前的时间,等松手后数据写入DS1302再读取时间
ReadTimeFlag=0; //读取时间标志清零
DS3231_ReadTime(); //读取时间
TimeShow(); //显示时间
}
if(ReadTempFlag && KeypressFlag==0 && AlarmFlag==0) //如果读取温度标志为1
{ //闹钟响时无需显示温度
ReadTempFlag=0; //读取温度标志清零
if(DS3231_CheckBusy()==0)
{
T=DS3231_ReadT();
DS3231_ConvertT();
}
if(T<0) //如果温度小于0
{
LCD_ShowChar(2,10,'-'); //显示负号
T=-T; //将温度变为正数
}
else //如果温度大于等于0
{
LCD_ShowChar(2,10,'+'); //显示正号
}
LCD_ShowNum(2,11,T,2); //显示温度整数部分
LCD_ShowNum(2,14,(unsigned long)(T*10)%10,1); //显示温度小数部分,显示1位小数
}
}
}
}
if(Mode==1) //计时模式
{
if(KeyNum!=7) //如果长按K3调整倒计时的时间时不执行此模式中的代码,防止闪烁
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0;
LCD_Clear(); //清屏
LCD_ShowString(1,1,"TimeCount:"); //显示静态字符
}
if(TimeCountModeFlag) //如果是正计时
{
LCD_ShowString(1,12,"UP "); //显示计时模式
LCD_ShowChar(2,3,':');
LCD_ShowChar(2,6,':');
LCD_ShowNum(2,1,TimeCount[0],2); //显示时
LCD_ShowNum(2,4,TimeCount[1],2); //显示分
LCD_ShowNum(2,7,TimeCount[2],2); //显示秒
LCD_ShowNum(2,10,TimeCount[3],2); //显示十毫秒
}
else if(TimeIsUpFlag==0) //如果是倒计时,且还没计时完
{
LCD_ShowString(1,12,"Down"); //显示计时模式
LCD_ShowChar(2,3,':');
LCD_ShowChar(2,6,':');
if(TimeSelect==1 && FlashFlag==1 && TimeCountRunFlag==0){LCD_ShowString(2,1," ");} //设置倒计时时间时闪烁
else {LCD_ShowNum(2,1,TimeCount[0],2);} //显示时
if(TimeSelect==2 && FlashFlag==1 && TimeCountRunFlag==0){LCD_ShowString(2,4," ");}
else {LCD_ShowNum(2,4,TimeCount[1],2);} //显示分
if(TimeSelect==3 && FlashFlag==1 && TimeCountRunFlag==0){LCD_ShowString(2,7," ");}
else {LCD_ShowNum(2,7,TimeCount[2],2);} //显示秒
LCD_ShowNum(2,10,TimeCount[3],2); //显示十毫秒
}
else if(FlashFlag==1) //如果倒计时结束,则第二行闪烁
{
LCD_ShowString(2,1," "); //清空第二行
}
else
{
LCD_ShowString(2,1,"00:00:00 00"); //第二行显示
}
}
}
if(Mode==2) //设置闹钟模式
{
if(KeyNum!=7 && KeyNum!=8) //如果长按K3或K4则不执行此模式中的代码,防止闪烁
{
if(ExecuteOnceFlag)
{
ExecuteOnceFlag=0; //静态字符显示标志清零
LCD_Clear(); //清屏
LCD_ShowString(1,1,"Alarm1 : "); //显示静态字符
LCD_ShowString(2,1,"Alarm2 : "); //显示静态字符
}
if(TimeSelect==0 && FlashFlag==1){LCD_ShowString(1,8," ");}
else {LCD_ShowNum(1,8,AlarmClock[0],2);} //显示时(闹钟1)
if(TimeSelect==1 && FlashFlag==1){LCD_ShowString(1,11," ");}
else {LCD_ShowNum(1,11,AlarmClock[1],2);} //显示分(闹钟1)
if(TimeSelect==2 && FlashFlag==1){LCD_ShowString(2,8," ");}
else {LCD_ShowNum(2,8,AlarmClock[2],2);} //显示时(闹钟2)
if(TimeSelect==3 && FlashFlag==1){LCD_ShowString(2,11," ");}
else {LCD_ShowNum(2,11,AlarmClock[3],2);} //显示分(闹钟2)
}
}
}
}
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
static unsigned int T0Count0,T0Count1,T0Count2,T0Count3,T0Count4,T0Count5; //定义静态变量
TL0=0x00; //设置定时初值,定时10ms,晶振@11.0592Hz
TH0=0xDC; //设置定时初值,定时10ms,晶振@11.0592Hz
if(TimeCountRunFlag) //如果启动标志为1,则进行计时
{
if(TimeCountModeFlag) //正计时
{
T0Count0++;
if(T0Count0>=1)
{
T0Count0=0;
TimeCount[3]++;
if(TimeCount[3]>=100) //进位
{
TimeCount[3]=0;
TimeCount[2]++;
if(TimeCount[2]>=60)
{
TimeCount[2]=0;
TimeCount[1]++;
if(TimeCount[1]>=60)
{
TimeCount[1]=0;
TimeCount[0]++;
if(TimeCount[0]>=100) //最大计时,99h59m59s
{
TimeCount[0]=0;
}
}
}
}
}
}
else //倒计时
{
T0Count0++;
if(T0Count0>=1)
{
T0Count0=0;
TimeCount[3]--;
if(TimeCount[3]<0)
{
if(TimeCount[0]==0 && TimeCount[1]==0 && TimeCount[2]==0)
{
TimeCount[3]=0;
TimeCountRunFlag=0; //暂停计时
TimeIsUpFlag=1; //倒计时结束
}
else
{
TimeCount[3]=99;
TimeCount[2]--;
if(TimeCount[2]<0)
{
TimeCount[2]=59;
TimeCount[1]--;
if(TimeCount[1]<0)
{
TimeCount[1]=59;
TimeCount[0]--;
}
}
}
}
}
}
}
T0Count1++;
T0Count2++;
T0Count3++;
T0Count4++;
T0Count5++;
if(T0Count1>=50) //设置时闪烁周期为1s
{
T0Count1=0;
FlashFlag=!FlashFlag;
}
if(T0Count2>=2) //每隔20ms检测一次按键
{
T0Count2=0;
Key_Tick();
}
if(T0Count3>=10) //每隔100ms读取一次时间
{
T0Count3=0;
ReadTimeFlag=1;
}
if(T0Count4>=30) //每隔300ms读取一次温度
{
T0Count4=0;
ReadTempFlag=1;
}
}
void Timer1_Routine() interrupt 3 //定时器1中断函数
{
static unsigned char T1Count; //定义静态变量
TL1=0x33; //设置定时初值,定时500us,晶振@11.0592Hz
TH1=0xFE; //设置定时初值,定时500us,晶振@11.0592Hz
if(KeypressFlag) //如果有按键按下
{
Buzzer=!Buzzer;
KeypressSoundTime++;
if(KeypressSoundTime==200) //按键声响100ms
{
KeypressSoundTime=0; //按键声计时清零
KeypressFlag=0; //按键按下标志置0
Buzzer=1; //蜂鸣器低电平导通,防止不响的时候导通
}
}
if(AlarmFlag) //闹钟1或闹钟2或倒计时结束
{
if(AlarmSoundCount==1 || AlarmSoundCount==3){Buzzer=!Buzzer;} //第1个和第3个100ms翻转蜂鸣器,让它响
T1Count++;
if(T1Count>=200) //100ms
{
T1Count=0;
AlarmSoundCount++;
if(AlarmSoundCount==10)
{
AlarmSoundCount=0;
AlarmSoundTime++; //约每隔1s钟AlarmSoundTime自增
if(AlarmSoundTime==AlarmTimeLength){AlarmStop();} //时间到达设定的长度就停止闹钟
}
}
}
}
总结
感觉还是比较实用,睡觉前设置好闹钟放在桌面,可以有效防止迷迷糊糊按掉手机闹钟导致迟到。而且DS3231时钟模块比DS1302时钟模块的走时要准很多,设置好时间后,可以很久都不用调整时间。