实现功能:利用51单片机的定时器原理制作时钟,显示使用LCD1602,可以显示时分秒年月日和星期 ,识别平年闰年,按键可调整时间日期,整点报时。
1、定时器中断实现时钟基本原理
若系统晶振为12MHZ,定时器工作在mode1模式时,每次最多可计数至65536,花费时间约为65ms。若只计数至50000,需要时间50ms,实现1s的定时需要重复上述过程20次。如此就利用
定时器实现了一秒钟的产生。我们仅需对定时器赋初值TH0=(65536-50000)/256;TL0=(65536-50000)%256;代码段如下
TH0 = 0x4c; //高八位赋初值 晶振位11.0592,赋初值位0x4c00
TL0 = 0x00; //低八位赋初值
i++;
if(i == 20){
i = 0; //满一秒清零
miao++; //i = 100 为一,秒标志位加 1
if(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; //启用外部中断1
LCD_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,也就是说进位标志位年月日时分秒的值就会+1
if ((((*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表示对应值减1
delay(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 为一,秒标志位加 1
if(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位显示0
delay(50); //当miao%10的值为1时即(*time[1])==1, 此时num[1]=0x31 对应1602字符库中的1
LCD_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 P0
sbit 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); //蜂鸣器用于key123
void mybeeplong(void); //用于key4
void 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, //一,代码 0x01
0x00,0x00,0x00,0x0e,0x00,0xff,0x00,0x00, //二,代码 0x02
0x00,0x00,0xff,0x00,0x0e,0x00,0xff,0x00, //三,代码 0x03
0x00,0x00,0xff,0xf5,0xfb,0xf1,0xff,0x00, //四,代码 0x04
0x00,0xfe,0x08,0xfe,0x0a,0x0a,0xff,0x00, //五,代码 0x05
0x00,0x04,0x00,0xff,0x00,0x0a,0x11,0x00, //六,代码 0x06
0x00,0x1f,0x11,0x1f,0x11,0x11,0x1f,0x00, //日,代码 0x07
};
void CgramWrite(void) { // 装入CGRAM //
uint i;
LCD_write_command(0x06); // CGRAM地址自动加1
LCD_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为最终地址
else
address=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;
else
pos=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; //工作方式 1
TH0 = 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);
}
}