实现功能:利用51单片机的定时器原理制作时钟,显示使用LCD1602,可以显示时分秒年月日和星期 ,识别平年闰年,按键可调整时间日期,整点报时。
1、定时器中断实现时钟基本原理
若系统晶振为12MHZ,定时器工作在mode1模式时,每次最多可计数至65536,花费时间约为65ms。若只计数至50000,需要时间50ms,实现1s的定时需要重复上述过程20次。如此就利用
定时器实现了一秒钟的产生。我们仅需对定时器赋初值TH0=(65536-50000)/256;TL0=(65536-50000)%256;代码段如下
TH0 = 0x4c; //高八位赋初值 晶振位11.0592,赋初值位0x4c00TL0 = 0x00; //低八位赋初值i++;if(i == 20){i = 0; //满一秒清零miao++;//i = 100 为一,秒标志位加 1if(miao == 60){miao = 0; //满一分清零fen++;if(fen == 60){fen = 0; //满一时清零shi++;mybeeplongplus(); //整点报时if(shi == 24){shi = 0; //24小时制fen = 0;miao = 0;
2、LCD1602基本原理介绍
关于lcd1602的介绍网上资源实在太多可以参考这位博主的文章LCD1602介绍。他讲解的挺详细的,其中有关于LCD1602自定义字符的介绍与使用,我在这里推荐一款字符取模软件zimo221,百度直接可以搜索下载。
3、C语言如何利用数组存储“变量”
c语言的数组时不能够直接储存变量的,但是却可以储存变量的地址,再利用指针来间接储存变量。此处引用c语言指针数组的使用介绍。
4、protues仿真图
此处未添加蜂鸣器,需要的可以直接接在P1^5口。
5、完整程序代码
main.c文件
//设计思路 1、利用定时器中断0二十次产生1s时间变量miao增加到60变量分+1并把miao清零,shi同理// 2、采用4个按键 长按key4进入设置模式后, key1选择调整对象 key2k3加减 // #include"LCD1602.h"#include<reg52.h>void LCD_display(void);void keykan(void);int num[10]={0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; // lcd1602 0~9字码uchar miao=0, fen=0, shi=0 , zhou=1 , yue=7 , nian=21 ; //进位标志位 秒 分 时 周 月 年 uchar ri=9;//进位标志位 日 不知道为啥放在上面会报错,单独定义就可以uint i=0; //定时器计时标志位uchar *time[]={&miao , &fen , &shi, &zhou ,&ri ,&yue , &nian}; //指针变量time存放进位标志位的地址,由于数组不能存放标量,但是却可以存放标量的地址,采用指针的方式来间接修改变量的值//uchar SetPlace=0;//功能选择标志bit mod =0; //设置模式标志位sbit key1=P3^1; //定义四个按键 key1选择需要调整的位sbit key2=P3^0; //key2加 sbit key3=P3^2; //key3减sbit key4=P3^3; //长按两秒钟进入或退出设置模式,前面三个按键需要进入设置模式才能使用void main(void){T0_init(); //定时器初始化LCD_init(); //lcd1602初始化IT1=1;//外部中断1初始化 下降沿触发EX1=1;//启用外部中断1LCD_disp_char(2,5,0x00); //显示自定义字符“周”delay(50);while(1) //死循环 { keykan();LCD_display();//lcd1602显示时分秒年月日周}}void keykan(void){if (mod==0) //如果没有进入设置模式 调整提示区域显示字符串time{LCD_row(2);LCD_DispString("TIME");delay(50);}LCD_disp_char(1,14,' '); //清空第一行第15位的显示内容delay(50);if (mod==1) //如果进入设置模式显示一个1602字符库的一个特定字符{LCD_disp_char(1,14,0xef);delay(50); if(key1==0)//检测按键key1是否按下{delay(10);//消除抖动if(key1==0){ //按一下功能选择位就+1,加到7就清零SetPlace++;if(SetPlace>=7)SetPlace=0;}if (SetPlace==0) //功能选择位为0就显示“miao” 为1就显示“fen” 以此类推,shi zhou ri yue nian{LCD_row(2);LCD_DispString("miao");delay(50);}if (SetPlace==1){LCD_row(2);LCD_DispString(" fen");delay(50);}if (SetPlace==2){LCD_row(2);LCD_DispString(" shi");delay(50);}if (SetPlace==3){LCD_row(2);LCD_DispString("zhou");delay(50);}if (SetPlace==4){LCD_row(2);LCD_DispString(" ri");delay(50);}if (SetPlace==5){LCD_row(2);LCD_DispString(" yue");delay(50);}if (SetPlace==6){LCD_row(2);LCD_DispString("nian");delay(50);}mybeep(); //蜂鸣器发声while(key1==0) //检测按键是否松开{delay(10);}}if(key2==0)//检测按键key2是否按下{delay(10);//消除抖动if(key2==0){(*time[SetPlace])++; //按键按一次 指针数组*time[SetPlace]里对应的地址的数据值就+1,也就是说进位标志位年月日时分秒的值就会+1if ((((*time[SetPlace]))>=60) && (SetPlace==0)){(*time[SetPlace])=0; //秒只能加dao59}if ((((*time[SetPlace]))>=60) && (SetPlace==1)){(*time[SetPlace])=0; //分只能加到59}if ((((*time[SetPlace]))>=24) && (SetPlace==2)){(*time[SetPlace])=0; //时只能加到23}if ((((*time[SetPlace]))>=8) && (SetPlace==3)) //周只能加到7{(*time[SetPlace])=1;}if ((((*time[SetPlace]))>=29) && (SetPlace==4) && (yue==2) && ((nian%4)!=0)){ //平年的2月只能加到28(*time[SetPlace])=1;}if ((((*time[SetPlace]))>=30) && (SetPlace==4) && (yue==2) && ((nian%4)==0)) //闰年的2月29天{(*time[SetPlace])=1;}if ((((*time[SetPlace]))>=31) && (SetPlace==4) && ((yue==4)||(yue==6)||(yue==9)||(yue==11))) //4 6 9 11 月只有30天{(*time[SetPlace])=1;}if ((((*time[SetPlace]))>=32) && (SetPlace==4) && ((yue==1)||(yue==3)||(yue==5)||(yue==7)||(yue==8)||(yue==10)||(yue==12))){(*time[SetPlace])=1;//1 3 5 7 8 10 12月有31天}if ((((*time[SetPlace]))>=13) && (SetPlace==5)) //月份只能加到12{(*time[SetPlace])=1;} mybeep(); //蜂鸣器响while(key2==0) //检测按键是否松开{delay(10);}}}if(key3==0)//检测按键key3是否按下{//key3与key2按钮基本原理相同,key3表示对应值减1delay(10);//消除抖动if(key3==0){if ((((*time[SetPlace]))<=0) && (SetPlace==0)){(*time[SetPlace])=60;}if ((((*time[SetPlace]))<=0) && (SetPlace==1)){(*time[SetPlace])=60;}if ((((*time[SetPlace]))<=0) && (SetPlace==2)){(*time[SetPlace])=24;}if ((((*time[SetPlace]))<=1) && (SetPlace==3)){(*time[SetPlace])=8;}if ((((*time[SetPlace]))<=1) && (SetPlace==4) && (yue==2) && ((nian%4)!=0)){(*time[SetPlace])=29;}if ((((*time[SetPlace]))<=1) && (SetPlace==4) && (yue==2) && ((nian%4)==0)){(*time[SetPlace])=30;}if ((((*time[SetPlace]))<=1) && (SetPlace==4)&&((yue==4)||(yue==6)||(yue==9)||(yue==11))){(*time[SetPlace])=31;}if ((((*time[SetPlace]))<=1) && (SetPlace==4)&&((yue==1)||(yue==3)||(yue==5)||(yue==7)||(yue==8)||(yue==10)||(yue==12))){(*time[SetPlace])=32;}if ((((*time[SetPlace]))<=1) && (SetPlace==5)){(*time[SetPlace])=13; }(*time[SetPlace])--;}mybeep();while(key3==0) //检测按键是否松开{delay(10);}}}}void timer0_int() interrupt 1 //定时器中断服务函数{ TH0 = 0x4c; //高八位赋初值 晶振11.0592MHZ 赋初值TL0 = 0x2c; //低八位赋初值i++;if(i == 20){i = 0; //满一秒清零miao++;//i = 100 为一,秒标志位加 1if(miao == 60){miao = 0; //满一分清零fen++;if(fen == 60){fen = 0; //满一时清零shi++;mybeeplongplus(); //整点报时if(shi == 24){shi = 0; //24小时制fen = 0;miao = 0;zhou++;ri++;if ((ri==31)&&((yue==1)||(yue==3)||(yue==5)||(yue==7)||(yue==8)||(yue==10)||(yue==12))){yue++; // 满31号回到1号ri=1;}if ((ri==28)&&(yue==2) && ((nian%4)!=0)){yue++; //平年28天回到一号ri=1;}if ((ri==29)&&(yue==2) && ((nian%4)==0)){yue++; //闰年29天ri=1;}if ((ri==30)&&((yue==4)||(yue==6)||(yue==9)||(yue==11))){yue++; //4 6 9 11月每月30天ri=1;}if (zhou==7) //满一周回到周一{zhou=1;}if (yue==12) //满12个月, 年份加1{nian++;yue=1;}}}}}}void LCD_display(void)//lcd1602显示年月日时分秒周{ LCD_disp_char(2,6,*time[3]); //显示周几 此处显示的为自定义字符 当zhou=1的时候即*time[3]=1 对应地址0x01的字符为 一 ;以此类推delay(50);LCD_disp_char(2,15,num[*time[0]%10]);//显示秒的个位 当miao%10的值为0时即(*time[0])==0, 此时num[0]=0x30 对应1602字符库中的0 ,lcd1602对应第2行第16位显示0delay(50); //当miao%10的值为1时即(*time[1])==1, 此时num[1]=0x31 对应1602字符库中的1LCD_disp_char(2,14,num[*time[0]/10]); //秒的十位delay(50);LCD_disp_char(2,13,'-');delay(50);LCD_disp_char(2,12,num[*time[1]%10]); //分的各位delay(50);LCD_disp_char(2,11,num[*time[1]/10]);//分的十位delay(50);LCD_disp_char(2,10,'-'); //字符‘-’delay(50);LCD_disp_char(2,9,num[*time[2]%10]);//时的个位delay(50);LCD_disp_char(2,8,num[*time[2]/10]);//时的十位delay(50);LCD_disp_char(1,0,'2');delay(50);//显示字符 2 0 表示21世纪LCD_disp_char(1,1,'0');delay(50);LCD_disp_char(1,2,num[*time[6]/10]); //年的个位delay(50);LCD_disp_char(1,3,num[*time[6]%10]);//年的十位delay(50);LCD_disp_char(1,4,'-');delay(50);LCD_disp_char(1,5,num[*time[5]/10]);//月的个位delay(50);LCD_disp_char(1,6,num[*time[5]%10]);//月的十位delay(50);LCD_disp_char(1,7,'-');delay(50);LCD_disp_char(1,8,num[*time[4]/10]);//日的个位delay(50);LCD_disp_char(1,9,num[*time[4]%10]);//日的十位delay(50);}void my_int1(void)interrupt 2 //外部中断1服务程序{delay_n40us(50000);//消抖if (key4==0){mod=~mod; //进入或退出设置mybeeplong(); //蜂鸣器TR0=~TR0; //打开或关闭定时器中断SetPlace=0;//模式选择位清零}while (key4==!key4);}
lcd1602.h文件
#ifndef _LCD1602_H//相当于if not define 防止程序中头文件被多次编译#define _LCD1602_H#include<reg52.h>#define uchar unsigned char #define uint unsigned int//lcd 1602 硬件接线端口#define LCD_DB P0sbit LCD_RS=P2^6;sbit LCD_RW=P2^5;sbit LCD_E=P2^7;sbit BEEP=P1^5; //蜂鸣器void LCD_init(void); //lcd初始化函数void LCD_write_command(uchar command); // 写指令函数void LCD_write_data(uchar dat); //写数据函数void LCD_disp_char(uchar x , uchar y, char dat); //在某一位置显示单个字符void LCD_check_busy(void); //检查忙函数void LCD_row(uchar row);//选择显示行数void LCD_DispString(char str[]); //显示字符串void delay_n40us( uint n); //延时函数void T0_init(void);//定时器初始化void delay(uint x); //延时void CgramWrite(void);//自定义字符装载void mybeep(void); //蜂鸣器用于key123void mybeeplong(void);//用于key4void mybeeplongplus(void); //用于整点报时#endif
lcd1602.c文件
#include"LCD1602.h"uint code Xword[]={0x1F,0x15,0x1D,0x17,0x11,0x15,0x01,0x00, //周,代码 0x00(由于这个lcd1602像素太小了5*8,这个周字属实有点认不出)0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00, //一,代码 0x010x00,0x00,0x00,0x0e,0x00,0xff,0x00,0x00, //二,代码 0x020x00,0x00,0xff,0x00,0x0e,0x00,0xff,0x00, //三,代码 0x030x00,0x00,0xff,0xf5,0xfb,0xf1,0xff,0x00, //四,代码 0x040x00,0xfe,0x08,0xfe,0x0a,0x0a,0xff,0x00, //五,代码 0x050x00,0x04,0x00,0xff,0x00,0x0a,0x11,0x00, //六,代码 0x060x00,0x1f,0x11,0x1f,0x11,0x11,0x1f,0x00, //日,代码 0x07};void CgramWrite(void) { // 装入CGRAM //uint i;LCD_write_command(0x06); // CGRAM地址自动加1LCD_write_command(0x40); // CGRAM地址设为00处for(i=0;i<64;i++) {LCD_write_data(Xword[i]);// 按数组写入数据}}void LCD_init(void) {LCD_write_command(0x38);delay_n40us(300);LCD_write_command(0x38);LCD_write_command(0X0c); //开显示 关光标 不闪烁LCD_write_command(0x06); //设定输入方式LCD_write_command(0X01); //清除显示delay_n40us(300);CgramWrite();//装载自定义字符}void LCD_write_command(uchar dat){LCD_DB=dat;LCD_RS=0;//指令LCD_RW=0; //写入LCD_E=1;//允许LCD_E=0;delay_n40us(2);}void LCD_write_data(uchar dat ){LCD_DB=dat;LCD_RS=1; //数据LCD_RW=0; //写入LCD_E=1;//允许LCD_E=0;delay_n40us(2);}void LCD_disp_char(uchar y , uchar x , uchar dat ){ //x为列 y为行uchar address;if (y==1) //传入参数为1 使用第一行显示address=0x80+x; //将lcd地址确定为第一行起始地址加上x为最终地址elseaddress=0xc0+x;//参数不为1则显示第二行LCD_write_command(address);//写入地址 LCD_write_data(dat); //写入数据delay_n40us(2);}void LCD_check_busy(){do{LCD_E=0;LCD_RS=0;LCD_RW=1;LCD_DB=0Xff;LCD_E=1;} while (LCD_DB^7==1);}void LCD_row(uchar row){uchar pos;if (row==1)pos=0x00;elsepos=0x40;LCD_write_command(pos | 0x80); }void LCD_DispString(char str[]){uchar i =0;while (str[i] !='\0'){LCD_write_data(str[i]);delay_n40us(2);++i;}}void delay_n40us(uint n){uint i;uchar j;for (i=n ; i>0; i--)for(j=0 ; j<2 ; j++);}void T0_init(){ //定时器 0 初始化 TMOD = 0x01; //工作方式 1TH0 = 0x4c; //高八位赋初值 TL0 = 0x00; //低八位赋初值EA = 1;//开总中断ET0 = 1;//开 T0 中断TR0 = 1;//启动 T0}void delay(uint x){ //延时函数 while(x--);}void mybeep(void){uint n;for ( n = 0; n < 100; n++){BEEP=~BEEP;delay(10);}}void mybeeplong(void){uint n;for (n = 0; n < 1000; n++){BEEP=~BEEP;delay(10);}}void mybeeplongplus(void){uint n;for (n = 0; n < 5000; n++){BEEP=~BEEP;delay(10);}}
如果觉得《51单片机LCD1602 定时器时钟 实现年月日时分秒显示》对你有帮助,请点赞、收藏,并留下你的观点哦!