万年历:由矩阵键盘控制的C51定时器与LCD1602万年历

这篇代码并未运用到DSC12C887时钟芯片,而是运用了单片机自带的定时器0完成,经试验在清翔开发板上误差为每小时快4s,且可通过硬件(矩阵键盘)更改时间!


前言

通过写万年历来达到熟练使用定时器、LCD1602(甚至是OLED12864)和矩阵键盘的练习。


提示:以下是本篇文章正文内容,下面案例可供参考

一、目标

1.熟练掌握矩阵键盘的用法
2.熟练定时器安装初值
3.熟练1602的使用方法

二、教学

1.矩阵键盘

郭天祥老师在《新概念51单片机C语言教程》一书中给了他的例程,但我认为太过于繁琐。
这里我给出另一个解决方案:
1.先给低四位一个高电平(0x0f),检测是哪一个I/O口被拉低,确定出某行Y
2.立马给高四位一个高电平(0xf0),检测是哪一个I/O口被拉低,确定出某列X
3.得出按键坐标(X,Y)

2.LCD1602

想要学会1602就应该掌握它的基本时序操作:
读状态 RS=Low,R/W=High,EN=H
读数据 RS=H,R/W=H,EN=H
写指令 RS=L,R/W=L,D0~D7=指令码,EN=高脉冲
写数据 RS=H,R/W=L,D0~D7=数据,EN=H

其控制器内部的RAM区域有80B的缓冲区可供我们写显示数据(00—0F、10—27、40—4F、50—67),其中在00—0F、40—4F中写入的数据可以直接显示出来,而另外两个地址的数据需要通过移动。可以使用“0x18”或者“0x07”,读者可自己探索。

关于1602将在例程中继续为大家讲解

3.定时器0

一般建立一个函数“void T0_time() interrupt 1”,其中interrupt 1是定时器0的中断序号,单片机通过它来识别。C51一般用的是11.0592MHz的晶振,装入初值方式如下:(例如50ms)
TH1=(65536-N)/256 ,TL1=(65536-N)%256

50mS=50 000uS
周期为12*(1/11059200)约等于1.09uS
N=50 000 /1.09 约等于 45872

三、例程(各个函数的作用我将在头文件为大家说明)

1.矩阵键盘(我使用的I/O口为P1)

Key.h

#ifndef __KEY_H
#define __KEY_H
#include "reg52.h"
/*宏定义*/
#define uchar unsigned char
#define uint unsigned int
#define Keyy P1

void key_scan();//负责扫描矩阵键盘
void delay(uint xms);//毫秒级延时

extern uchar Key_C;//列
extern uchar Key_R;//行
extern uint Key_Num;//对按键赋予名称
#endif

Key.c

#include "Key.h"
uchar Key_C;//列
uchar Key_R;//行
uint Key_Num;
void key_scan()
{
	Keyy = 0x0f;//检测低四位某行
	if(Keyy != 0x0f)//检测是否被按下
	{
		delay(1);//消抖
		if(Keyy != 0x0f)//再次检测是否被按下
		{
			switch(Keyy)//确定某行
			{			
				case(0x07): Key_R = 0; break;//第一行
				case(0x0b): Key_R = 1; break;//第二行
				case(0x0d): Key_R = 2; break;//第三行
				case(0x0e): Key_R = 3; break;//第四行
			}
		}
	}
	
	Keyy = 0xf0;//检测高四位某列
	if(Keyy != 0xf0)
	{
		delay(1);
		if(Keyy != 0Xf0)
		{
			switch(Keyy)
			{
			  case(0x70): Key_C = 0; break;//第一列
			  case(0xb0): Key_C = 1; break;//第二列
			  case(0xd0): Key_C = 2; break;//第三列
			  case(0xe0): Key_C = 3; break;//第四列
			}
		}
	}
	/*确定出按键后为其命名*/
	if(Key_R==0&&Key_C==0) Key_Num=16;
	else if(Key_R==0&&Key_C==1) Key_Num=15;
	else if(Key_R==0&&Key_C==2) Key_Num=14;
	else if(Key_R==0&&Key_C==3) Key_Num=13;
	else if(Key_R==1&&Key_C==0) Key_Num=12;
	else if(Key_R==1&&Key_C==1) Key_Num=11;
	else if(Key_R==1&&Key_C==2) Key_Num=10;
	else if(Key_R==1&&Key_C==3) Key_Num=9;
	else if(Key_R==2&&Key_C==0) Key_Num=8;
	else if(Key_R==2&&Key_C==1) Key_Num=7;
	else if(Key_R==2&&Key_C==2) Key_Num=6;
	else if(Key_R==2&&Key_C==3) Key_Num=5;
	else if(Key_R==3&&Key_C==0) Key_Num=4;
	else if(Key_R==3&&Key_C==1) Key_Num=3;
	else if(Key_R==3&&Key_C==2) Key_Num=2;
	else if(Key_R==3&&Key_C==3) Key_Num=1;
	
}

void delay(uint xms)
{
	uint i,j;
	for(i=xms ;i>0 ;i--)
		for(j=100 ;j>0 ;j--);
}

2.LCD1602

1602.h

#ifndef _1602_H
#define _1602_H

#include "reg52.h"

#define uchar unsigned char 
#define uint unsigned int
	
sbit lcdrs=P3^5;
sbit lcdrw=P3^6;
sbit lcden=P3^4;
sbit dula=P2^6;
sbit wela=P2^7;

void init();//初始化
void Write_Com(uchar com);//写指令
void Write_Data(uchar date);//写数据
void Write_Str(uchar addr,uchar length,uchar *pbuf);/*显示字符串,第一个为地址,第二个为
													长度,第三个为字符*/
void CGRAM_set();//将自定义字符数据写入CGRAM ,用户可以自定义8个字符
void lcd_wdat(uchar addr,int date);//写4个字符
void lcd_wdat1(uchar addr,int date);//写2个字符

#endif

1602.c

#include "1602.h"
#include "delay.h"

void init()
{
	dula=0;
	wela=0;
	lcden=0;//关闭数码管显示,因为我在开发板上使用,I/O口与数码管重复
	Write_Com(0x38);
	Write_Com(0x0c);
	Write_Com(0x06);
	Write_Com(0x01);
}


void Write_Com(uchar com)
{
	lcdrs=0;
	lcdrw=0;
	P0=com;
	Delay_Ms(5);
	lcden=1;
	Delay_Ms(5);
	lcden=0;
}

void Write_Data(uchar date)
{
	lcdrs=1;
	lcdrw=0;
	P0=date;
	Delay_Ms(5);
	lcden=1;
	Delay_Ms(5);
	lcden=0;
}

void Write_Str(uchar addr,uchar length,uchar *pbuf)
 
{ 
  uchar i;  
  Write_Com(addr); 
  for(i=0;i<length;i++)
  { 
    Write_Data(pbuf[i]); 
  } 
} 


void lcd_wdat(uchar addr,int date)              
{
	uchar a,b,c,d;
	a=date/1000;
	b=date%1000/100;
	c=date%100/10;
	d=date%10;
	Write_Com(addr);
	Write_Data(0x30+a);//0x30是属于ASCII码表,加上它就能显示数值
	Write_Data(0x30+b);
	Write_Data(0x30+c);
	Write_Data(0x30+d);
}

void lcd_wdat1(uchar addr,int date)              
{
	uchar a,b;
	a=date/10;
	b=date%10;
	Write_Com(addr);
	Write_Data(0x30+a);
	Write_Data(0x30+b);
}

3.延时

delay.h

#ifndef	_DELAY_H_
#define	_DELAY_H_

#include "reg52.h"
#define uchar unsigned char 
#define uint unsigned int
	
void Delay_Ms(uint xms);
void Delay_Us(uint xus);

#endif

delay.c

#include "delay.h"

void Delay_Ms(uint xms)//毫秒级
{
	int x,y;
	for(x=xms;x>0;x--)
		for(y=110;y>0;y--);
}

void Delay_Us(uint xus)//微妙级
{
	while(xus--);
}

3.主函数

按键:1时2分3秒加 5时6分7秒减
9年10月11日期加 13年14月15日期减
更改日期摁4键,先摁4再摁修改,再摁4,再修改

#include "reg52.h"
#include "1602.h"
#include "delay.h"
#include "Key.h"

uchar num;
int s;

void main()
{	
	int y,m,d;
	int h,min;
	
	TMOD=0x01;
	TH0=(65535-45872)/256;
	TL0=(65535-45872)%256;
	EA=1;
	ET0=1;
	TR0=1;
	
	init();
	
	y=2020;
	m=12;
	d=12;
	h=16;
	min=33;
	s=0;
	
/*****************************|
|******日期逻辑设置***********|
|*****************************/	
	A:/*goto标记位置*/
	while(1)
	{		
/******************************|
|***********扫描键盘***********|
|******************************/
		key_scan();

		if(Key_Num==1)
		{
			y++;
		}
		if(Key_Num==2)
		{
			m++;
		}
		if(Key_Num==3)
		{
			d++;
		}
		if(Key_Num==5)
		{
			y--;
		}
		if(Key_Num==6)
		{
			m--;
		}
		if(Key_Num==7)
		{
			d--;
		}
		if(Key_Num==9)
		{
			h++;
		}
		if(Key_Num==10)
		{
			min++;
		}
		if(Key_Num==11)
		{
			s++;
		}
		if(Key_Num==13)
		{
			h--;
		}
		if(Key_Num==14)
		{
			min--;
		}
		if(Key_Num==15)
		{
			s--;
		}
		goto B;
	}
		
		
/******************************|
|*************显示*************|
|******************************/	
		B:while(1)/*goto标记位置*/
	{
		key_scan();
		lcd_wdat(0x80,y);
		Write_Str(0x84,1,"-");
		lcd_wdat1(0x85,m);
		Write_Str(0x87,1,"-");
		lcd_wdat1(0x88,d);
		
		lcd_wdat1(0x80+0x40,h);
		Write_Str(0x82+0x40,1,":");
		lcd_wdat1(0x83+0x40,min);
		Write_Str(0x85+0x40,1,":");
		lcd_wdat1(0x86+0x40,s);
/******************************|
|***********判断进位***********|
|******************************/		
		if(m>12)
		{
			m=1;
			y++;
		}
		else if(m<1)
		{
			m=12;
			y--;
		}
		
		if(m==1||m==3||m==5||m==7||m==8||m==10||m==12)
		{
			if(d>31)
			{
				d=1;
				m++;
			}
			else if(d<1)
			{
				if(m==5||m==7||m==10||m==12)
				{
					d=30;
					m--;
				}
				else if(m==2||m==4||m==6||m==8||m==9||m==11||m==1)
				{
					d=31;
					m--;
				}
				else if(m==3&&(y%4==0)&&(y%100!=0))
				{
					d=29;
					m--;
				}
				else
				{
					d=28;
					m--;
				}
			}
		}
		
		if(m==4||m==6||m==9||m==11)
		{
			if(d>30)
			{
				d=1;
				m++;
			}
			else if(d<1)
			{
			if(m==5||m==7||m==10||m==12)
				{
					d=30;
					m--;
				}
				else if(m==2||m==4||m==6||m==8||m==9||m==11||m==1)
				{
					d=31;
					m--;
				}
				else if(m==3&&(y%4==0)&&(y%100!=0))
				{
					d=29;
					m--;
				}
				else
				{
					d=28;
					m--;
				}
			}
		}
		
		if(m==2)
		{
			if((y%4==0)&&(y%100!=0))
			{
				if(d>29)
				{
					d=1;
					m++;
				}
				if(d<1)
				{
					d=31;
					m--;
				}
			}
			else 
			{
				if(d>28)
				{
					d=1;
					m++;
				}
				if(d<1)
				{
					d=31;
					m--;
				}
			}
		}
		
		
		if(h>23)
		{
			h=0;
			d++;
		}
		else if(h<0)
		{
			h=23;
		}
		if(min>59)
		{
			min=0;
			h++;
		}
		else if(min<0)
		{
			min=59;
			h--;
		}
		if(s>59)
		{
			s=0;
			min++;
		}
		else if(s<0)
		{
			s=59;
			min--;
		}	
		
		
		if(Key_Num==4)
			goto A;
	}
}

void T0_time() interrupt 1
{
	TH0=(65535-45872)/256;
	TL0=(65535-45872)%256;
	num++;
			if(num==20)
		{
			s++;
			num=0;
		}
}

总结

这里的总结交给goto
虽然不提倡使用goto,因为会使代码结构不清晰,但是合理的运用goto更能为代码锦上添花,这里巧妙的使用了goto反而使结构思路清晰。

欢迎大家关注我
微信公众号:小白写编程
交流群:1033131250

  • 15
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值