利用51单片机的定时器设计一个时钟
一、功能要求
- 1602液晶显示时间,且每秒更新,自动计时。
- 用3个按键实现调节时、分、秒更能,可以定义为功能键、增加键、减小键。
- 当按键被按下时,蜂鸣器响一声提示。
- 利用AT24C02实现掉电记忆功能。
二、 原理图设计
三、源程序 - 在Keil中的同一个文件夹下新建一个AT24C02.h文件和Clock.c文件
//*********************** AT24C02.h源程序***************************//
bit write=0; //写AT24C02的标志
sbit SDA=P2^0; //定义数据串行口,用于单片机通信
sbit SCL=P2^1; //定义时钟串行口,用于单片机通信
void delayus() //定义us级延时函数
{;;}
void start() //起始信号
{
SDA=1;
delayus();
SCL=1;
delayus();
SDA=0;
delayus();
}
void stop()//终止信号
{
SDA=0;
delayus();
SCL=1;
delayus();
SDA=1;
delayus();
}
void ack()//应答信号
{
uchar i;
SCL=1;
delayus();
while((SDA=1)&&i<250)i++;
SCL=0;
delayus();
}
void init_AT24C02() //初始化
{
SDA=1;
delayus();
SCL=1;
delayus();
}
void write_byte(uchar dat) //定义写1个字节程序
{
uchar i,temp;
temp=dat; //将数据赋值给temp。例如:dat为0x01,则temp=0x01=0000 0001。方便后续移位操作用。
for(i=0;i<8;i++) //1个字节包括8位二进制数
{
temp=temp<<1; //将temp左移1位,最低位补0
SCL=0; //将SCL置0允许SDA数据变化
delayus();
SDA=CY; //将最高位移出的CY位赋给SDA
delayus();
SCL=1; //置1将数据写入
delayus();
}
SCL=0; //置0允许数据变化,用于SDA置1释放总线。
delayus();
SDA=1; //释放总线
delayus();
}
uchar read_byte() //读1个带返回值的字节
{
uchar i,k;
SCL=0; //置0允许数据变化
delayus();
SDA=1; //假设SDA为1,也可以SDA=0。当SDA=0时,下面for语句会有所差别。
delayus();
for(i=0;i<8;i++)
{
SCL=1; //上升沿时,IIC设备将数据放在sda线上,并在高电平期间数据已经稳定,可以接收啦
delayus();
k=(k<<1)|SDA; //将k左移一位跟SDA或运算,再赋给k
SCL=0; //拉低SCL,使发送端可以把数据放在SDA上
delayus();
}
return k;
}
void write_add(uchar address, uchar dat) //指定地址写一个字节
{
start();
write_byte(0xa0);
ack();
write_byte(address);
ack();
write_byte(dat);
ack();
stop();
}
char read_add(uchar address) //随机读取一个字节
{
uchar dat;
start();
write_byte(0xa0);
ack();
write_byte(address);
ack();
start();
write_byte(0xa1);
ack();
dat=read_byte();
stop();
return dat;
}
//****************************Main主程序**************************************//
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
#include"AT24C02.h" //包含AT24C02头文件
sbit s1=P3^0;
sbit s2=P3^1;
sbit s3=P3^2;
sbit rd=P3^7;
sbit lcden=P3^4;
sbit rs=P3^5;
sbit dula=P2^6;
sbit wela=P2^7;
sbit beep=P2^3;
char shi,fen,miao;
uchar s1num,count;
uchar code table0[]=" 2020-6-4 MON"; //字符2020前面3个空格
uchar code table1[]="00:00:00";
void delay_us() //us级延时程序
{;;}
void delay_ms(uchar z) //ms级延时程序
{
uint i,j;
for(i=z;i>0;i--)
for(j=110;j>0;j--);
}
void buzzer() //定义蜂鸣器程序
{
beep=0;
delay_ms(100);
beep=1;
}
void write_com(uchar com) //液晶写指令
{
rs=0;
lcden=0;
P0=com;
delay_ms(5);
lcden=1;
delay_ms(5);
lcden=0;
}
void write_dat(uchar dat) //液晶写数据
{
rs=1;
lcden=0;
P0=dat;
delay_ms(5);
lcden=1;
delay_ms(5);
lcden=0;
}
void write_sfm(uchar add,uchar dat) //写时分秒程序
{
uchar shi,ge;
shi=dat/10; //将时分秒分别分解成一个两位数,以便后续的位操作
ge=dat%10;
write_com(0x80+0x40+add); //在液晶第2行写指令
write_dat(0x30+shi); //0x30地址的数据为0
write_dat(0x30+ge);
}
void init_1602() //初始化液晶
{
uchar num;
dula=0;
wela=0;
num=0;
s1num=0;
count=0;
rd=0;
lcden=0;
shi=0;
fen=0;
miao=0;
init_AT24C02_02();
write_com(0x38);
write_com(0x0c);
write_com(0x06);
write_com(0x08);
write_com(0x01);
write_com(0x80);
for(num=0;num<14;num++) //显示日期,加空格总共14个字节
{
write_dat(table0[num]);
delay_ms(5);
}
write_com(0x80+0x40+4);
for(num=0;num<8;num++) //显示时分秒
{
write_dat(table1[num]);
delay_ms(5);
}
miao=read_add(1); //首次上电从AT24C02中读取出存储器的数据,地址可以随机定义,只要整个程序用的地址相同即可。
fen=read_add(2);
shi=read_add(3);
write_sfm(10,miao); //在第十位写秒数据
write_sfm(7,fen);
write_sfm(4,shi);
TMOD=0x01; //设置T0定时器为工作方式1
TH0=(65536-50000)/256; //装初值
TL0=(65536-50000)%256;
EA=1; //开总中断
ET0=1; //开定时器0中断
TR0=1; //启动定时器0
}
void keyscan() //按键扫描程序
{
if(s1==0) //先定义功能键被按下,不然加减键为无效。
{
delay_ms(5);
if(s1==0) //再次确认被按下,即去抖动
{
s1num++; //记录功能键被按下次数
while(!s1); //按键释放
buzzer(); //蜂鸣器短鸣提示
if(s1num==1) //功能键按下1次
{
TR0=0; //定时器停止计时
write_com(0x80+0x40+11); //光标在11位处,调节11的大小可以进行光标位置设置
write_com(0x0f); //光标闪烁
}
if(s1num==2)
{
write_com(0x80+0x40+8);
write_com(0x0f);
}
if(s1num==3)
{
write_com(0x80+0x40+5);
write_com(0x0f);
}
if(s1num==4) //功能键按下4次
{
s1num=0; //按键次数清0
write_com(0x0c); //取消光标闪烁
TR0=1; //定时器继续计数
}
}
}
if(s1num!=0) //只有功能键被按下,加减键才有效
{
if(s2==0) //增加键被按下
{
delay_ms(5);
if(s2==0) //再次确认增加键按下,去抖动
{
while(!s2); //按键释放,只有按键释放了才可以认为一个完整的按键完成
buzzer();
if(s1num==1) //功能键按下一次,此时选择调整秒
{
miao++;
if(miao==60) //当秒数大于60就清0
miao=0;
write_sfm(10,miao); //在第10位写秒,此时写的时一个两位数,调用的write_sfm()函数
write_com(0x80+0x40+11); //光标定位在11位,由于前面已经定义光标闪烁,所以这里不用写write_com(0x0f)
write_add(1,miao); //将变化的值写入AT24C02记忆起来,掉电恢复时可以显示此值
}
if(s1num==2)
{
fen++;
if(fen==60)
fen=0;
write_sfm(7,fen);
write_com(0x80+0x40+8);
write_add(2,fen);
}
if(s1num==3)
{
shi++;
if(shi==24)
shi=0;
write_sfm(4,shi);
write_com(0x80+0x40+5);
write_add(3,shi);
}
}
}
if(s3==0); //减少键被按下
{
delay_ms(5);
if(s3==0) //再次确认,去抖
{
while(!s3);
buzzer();
if(s1num==1)
{
miao--;
if(miao==-1)//当秒数被减到-1时,秒数清0。
miao=59;
write_sfm(10,miao);
write_com(0x80+0x40+11);
write_add(1,miao);
}
if(s1num==2)
{
fen--;
if(fen==-1)
fen=59;
write_sfm(7,fen);
write_com(0x80+0x40+8);
write_add(2,fen);
}
if(s1num==3)
{
shi--;
if(shi==-1)
shi=23;
write_sfm(4,shi);
write_com(0x80+0x40+5);
write_add(3,shi);
}
}
}
}
}
void main() //主程序
{
init_1602(); //初始化1602液晶
while(1) //进入while语句不停的进行按键扫描,来确认是否有按键按下
{
keyscan();
}
}
void timer0() interrupt 1 //定时器0工作方式1
{
TH0=(65536-50000)/256; //装初值,因为工作方式1无法自动重装初值,所以这里再次装初值。
TL0=(65536-50000)%256;
count++; //将1s拆分成20个50ms,这里对50ms进行计数来累加到1s
if(count==20) //当计数20次时,即为1s
{
count=0; //清0重新计数
miao++;
if(miao==60) //这里是没有按键被按下时,时钟自动的计数
{
miao=0;
fen++;
if(fen==60)
{
fen=0;
shi++;
if(shi==24);
{
shi=0;
}
write_sfm(4,shi); //在第4位写小时的数据
write_add(3,shi); //写入AT24C02进行掉电记忆,这里的3为地址,整个程序都要相同。
}
write_sfm(7,fen);
write_add(2,fen);
}
write_sfm(10,miao);
write_add(1,miao);
}
}
1602字符库