Personal nest in the information age

80C51单片机Proteus仿真实验:设计一个可调时、定时报警的时钟

功能

  • 使用单片机定时器中断计时;
  • 实现时、分、秒循环计时;
  • 可以使用4×4键盘调节时钟时间;
  • 可以设置报警时间,报警响两秒;
  • 按下按键具有声音反馈;
  • 调节数码管显示器中指定位置的时间时,数码管对应的位置闪烁。

仿真工具

  • Windows 10 x64
  • Proteus Professional 8.9 SP0
  • Keil C51 Version 9.59

仿真器件选型

  • 单片机:80C51,时钟频率(Clock Frequency)默认12MHz;
  • 数码管:7SEG-MPX8-CA-BLUE,该数码管为共阳极7段8位蓝色数码管;
  • 蜂鸣器:BUZZER,工作电压(Operating Voltage)调为5V;
  • 电阻:RES,电阻阻值(Resistance)调为1k;
  • 三极管:PNP;
  • 键盘:KEYPAD-SMALLCALC,该键盘为4*4计算器键盘;
  • 排阻:RESPACK-8,作P0口上拉电阻;
  • 电源:POWER两个;
  • 接地:GROUND一个;

仿真器件连接方式及最终实验效果图

图1 Proteus仿真实验效果图

实现原理

main函数

main函数是程序入口,其中有三个模式:

  1. 模式0(mode=0):为显示计时模式,程序一运行默认进入该模式,并且程序立即从0时0分0秒开始计时。当处在其它模式时,按“÷”键便可返回模式0;
  2. 模式1(mode=1):为报警设定模式,按“×”键进入该模式。进入后可以设定报警时间,当到达报警时间时,蜂鸣器持续蜂鸣两秒钟。另外,报警时间默认值为0时0分0秒,即刚启动程序时便会警报一次。
  3. 模式2(mode=2):为计时调节模式,按“=”键进入该模式,进入后可调节当前计时时间。

定时器计时1s的计算

因为定时器工作方式1可以使用16位计数结构,是所有定时器工作方式中位数最大的,故使用定时器0的工作方式1中断的形式实现计时。但是工作方式1的16位也不足以定时1秒。在12MHz晶振周期的定时器工作方式1下的最大定时时间为:

$$\rm (2^{16}-0)\times\frac{1}{12MHz}\times 12=65536 \mu s=65.536ms$$

可以看出在定时器工作方式1下,最大定时时间远远达到1秒。故采用多次中断时间累计的形式实现1秒计时。我们知道50 ms<65.536 ms,且20×50 ms=1 s,因此,可以使用设置定时器0每50 ms中断1次,累计20次为1秒。这时定时器0的初值计算如下:

$$\rm 计数初值=2^{16}-50ms\times \frac{12MHz}{12}=15536=(3CB0)_{16}$$

故定时器0的计数初值可设TH0=0x3C、TL0=0xB0。

另外,在Proteus中80C51的默认时钟频率为12 MHz,当市面上很多C51单片机的时钟频率往往是11.0592 MHz。为此,我又计算了一份针对晶振周期为11.0592MHz的计数初值:

$$\rm 计数初值=2^{16}-50ms\times \frac{11.0592MHz}{12}=19456=(4C00)_{16}$$

调时或报警设定中数字闪烁的定时时间计算

本定时系统在设定时间的时候,当当前设定位为时十位的时候,该位数字会闪烁,当前设定位为时个位的时候,时个位数字会闪烁。对于这一功能,本系统使用定时器1的工作方式1实现。本系统设定每900 ms闪烁一个周期,即每450 ms亮一下,450 ms灭一下。

该功能计时方式与1 s计时的实现方式相同,都是使用50 ms作为基本单位。该功能使用变量“timeflash”统计定时器1中断次数,当timeflash=9或17时,即累计时间达到450 ms或900 ms时,设定位数字的显示状态发生切换。

Keil C51代码

// Haar Blog
// https://haar.xyz

#include <reg51.h>	//调用含有51专用寄存器的头文件

#define F_12MHz	//Proteus的80C51单片机默认使用12MHz晶振

typedef unsigned char uchar;
typedef unsigned int uint;

sbit buzzer=P3^7;	//蜂鸣器
uchar code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};	//共阳极数码管段码表

int sec=0,min=0,hour=0;	 //显示时间
int sec_b=0,min_b=0,hour_b=0;	// 蜂鸣器报警时间
int position=0;	//时间设置选择位
int mode=0;	// 模式,0->显示模式,1->报警设置模式,2->调时模式
uint temp;	//临时变量
uchar m;	//50ms,1s=20*50ms
uchar timeflash;	//数字闪烁控制

/**** 延时子函数 *****/
void delayms(uint i){
	uchar j;
	while(i--){
		for(j=0;j<115;j++);	  //1ms基准延时程序
	}
}

/**** 显示函数 ******/
void display(int sec,int min,int hour){
	uchar flash=0;
	if(mode !=0 && timeflash>=9){
		flash=1;
	}
	P2=0x80;
	P0=(flash==1 && position%6==5)? 0xFF : table[sec%10];	//显示秒个位
	delayms(3);
	P2=0x40;
	P0=(flash==1 && position%6==4)? 0xFF : table[sec/10];	//显示秒十位
	delayms(3);
	P2=0x20;	//显示横线
	P0=0xbf;
	delayms(3);
	P2=0x10;	//显示分个位
	P0=(flash==1 && position%6==3)? 0xFF : table[min%10];
	delayms(3);
	P2=0x08;	//显示分十位
	P0=(flash==1 && position%6==2)? 0xFF : table[min/10];
	delayms(3);
	P2=0x04;	//显示横线
	P0=0xbf;
	delayms(3);
	P2=0x02;	//显示时个位
	P0=(flash==1 && position%6==1)? 0xFF : table[hour%10];
	delayms(3);
	P2=0x01;	//显示时十位
	P0=(flash==1 && position%6==0)? 0xFF : table[hour/10];
	delayms(3);
}

/* 键盘扫描函数
 * 若无数字键按下,return -l
 */
int keyscan(int sec,int min,int hour){
	int key=-1;
	P1=0xfe;
	temp=P1;
	temp=temp&0xf0;
	if(temp!=0xf0){
		delayms(10);
		temp=P1;
		if(temp!=0xf0){
			switch(temp){
				case 0xee:	//按键"+"
					position=(++position)%6;
					break;
				case 0xde:	//按键"-"
					position= position==0 ? 5 : position-1;
					break;
				case 0xbe:	//按键"X"
					mode=1;	// 报警器调节模式
					position=0;
					TR1=1;
					break;
				case 0x7e:	//按键"/"
					mode=0;	//显示模式
					position=0;
					break;
			}
			while(temp!=0xf0) {
				temp=P1;
				temp=temp&0xf0;
				buzzer=0;
				display(sec,min,hour);
			}
			buzzer=1;
		}
	}
	P1=0xfd;
	temp=P1;
	temp=temp&0xf0;
	if(temp!=0xf0){
		delayms(10);
		temp=P1;
		if(temp!=0xf0){
			switch(temp){
				case 0xed:	//按键"="
					//TR0=0;
					mode=2;	//调时模式
					position=0;
					TR1=1;	//启动数字闪烁定时
					break;
				case 0xdd:
					key=3;
					break;
				case 0xbd:
					key=6;
					break;
				case 0x7d:
					key=9;
					break;
			}
			while(temp!=0xf0){
				temp=P1;
				temp=temp&0xf0;
				buzzer=0;
				display(sec,min,hour);
			}
			buzzer=1;
		}
	}
	P1=0xfb;
	temp=P1;
	temp=temp&0xf0;
	if(temp!=0xf0){
		delayms(10);
		if(temp!=0xf0){
			temp=P1;
			switch(temp){
				case 0xeb:
					key=0;
					break;
				case 0xdb:
					key=2;
					break;
				case 0xbb:
					key=5;
					break;
				case 0x7b:
					key=8;
					break;
			}
			while(temp!=0xf0){
				temp=P1;
				temp=temp&0xf0;
				buzzer=0;
				display(sec,min,hour);
			}
			buzzer=1;
		}
	}
	P1=0xf7;
	temp=P1;
	temp=temp&0xf0;
	if(temp!=0xf0){
		delayms(10);
		if(temp!=0xf0){
			temp=P1;
			switch(temp){
				case 0xe7:	//按键"ON/C"
					TR0=~TR0;	//启动关闭计时
					break;
				case 0xd7:
					key=1;
					break;
				case 0xb7:
					key=4;
					break;
				case 0x77:
					key=7;
					break;
			}
			while(temp!=0xf0){
				temp=P1;
				temp=temp&0xf0;
				buzzer=0;
				display(sec,min,hour);
			}
			buzzer=1;
		}
	}
	return key;
}

/********  设置时间  ********/
void setTime(int key,int *sec,int *min,int *hour){
	uint tempTime;
	switch(position%6){
		case 0:tempTime=key*10+*hour%10;
			if(tempTime<24) *hour=tempTime;
			break;
		case 1:tempTime=key-(*hour%10)+*hour;
			if(tempTime<24) *hour=tempTime;
			break;
		case 2:if(key<6) *min=key*10+*min%10;break;
		case 3:*min=(*min/10)*10+key;break;
		case 4:if(key<6) *sec=key*10+*sec%10;break;
		case 5:*sec=(*sec/10)*10+key;break;
	}
}

/********* 初始化定时器及定时器中断 *********/
void initTimer(void){
	ET0=1;	//允许定时器0中断
	ET1=1;	//允许定时器1中断
	EA=1;	//总中断控制位(允许)
	TMOD=0x11;	//工作方式1
#ifdef F_12MHz
	TH0=0x3C;	//初值15336,按晶振频率12MHz,计时50ms,2^16-50000us*12MHz/12=15336=3CB0H
	TL0=0xB0;
#else
	TH0=0x4C;	//初值19456,按晶振频率11.0592MHz,计时50ms,2^16-50000us*11.0592MHz/12=19456=4C00H
	TL0=0x00;
#endif
	TR0=1;	//开启定时器0
	TF0=0;
#ifdef F_12MHz
	TH1=0x3C;	//初值15336,按晶振频率12MHz,计时50ms,2^16-50000us*12MHz/12=15336=3CB0H
	TL1=0xB0;
#else
	TH1=0x4C;	//初值19456,按晶振频率11.0592MHz,计时50ms,2^16-50000us*11.0592MHz/12=19456=4C00H
	TL1=0x00;
#endif
	TR1=0;	//关闭定时器1
	TF1=0;
}

/*************主函数************/
void main(void){
	int key=-1;
	buzzer=1;	//蜂鸣器
	initTimer();
	while(1){
		if(mode==0){	//显示模式
			display(sec,min,hour);	//调用显示函数
			key=keyscan(sec,min,hour);	//调用按键处理函数
		}else if(mode==2){	// 调时模式
			display(sec,min,hour);	//调用显示函数
			key=keyscan(sec,min,hour);	//调用按键处理函数
			if(key != -1){
				setTime(key,&sec,&min,&hour);
			}
		}else if(mode==1){	//设置报警时间模式
			display(sec_b,min_b,hour_b);
			key=keyscan(sec_b,min_b,hour_b);
			if(key != -1){
				setTime(key,&sec_b,&min_b,&hour_b);
			}
		}
	}
}

/********* 设置报警持续时间 ***********/
void buzzerKeepTime(uint keepTime){
	uint time=sec+min*60+hour*3600;
	uint time_b=sec_b+min_b*60+hour_b*3600;
	if(time<time_b) time+=86400;
	if(time==time_b+keepTime)
		buzzer=1;
}

/************定时器0中断计时**********/
void timer() interrupt 1{
#ifdef F_12MHz
	TH0=0x3C;	//初值15336,按晶振频率12MHz,计时50ms,2^16-50000us*12MHz/12=15336=3CB0H
	TL0=0xB0;
#else
	TH0=0x4C;	//初值19456,按晶振频率11.0592MHz,计时50ms,2^16-50000us*11.0592MHz/12=19456=4C00H
	TL0=0x00;
#endif
	if(++m>20){	// m=20说明1秒时间到
		m=0;
		if(++sec>59){	//判断到达60s
			sec=0;
			if(++min>59){	 //判断到达60分
				min=0;
				if(++hour>23) hour=0;  // 判断到达24小时
			}
		}
	}
	if(sec==sec_b && min==min_b && hour==hour_b){
		buzzer=0;
	}else{
		buzzerKeepTime(2);	//响两秒
	}
}

/****** 定时器1,数字闪烁定时 *******/
void timer1() interrupt 3{
#ifdef F_12MHz
	TH1=0x3C;	//初值15336,按晶振频率12MHz,计时50ms,2^16-50000us*12MHz/12=15336=3CB0H
	TL1=0xB0;
#else
	TH1=0x4C;	//初值19456,按晶振频率11.0592MHz,计时50ms,2^16-50000us*11.0592MHz/12=19456=4C00H
	TL1=0x00;
#endif
	timeflash=(++timeflash)%18;
}

其它

Proteus的80C51单片机模块默认使用12MHz的时钟频率,无需外接晶振和复位电路,这是该软件为了方便用户使用所作的特性。在现实中还是需要外接一个晶振和复位电路的。

Bibiliography

Leave a comment

Your email address will not be published. Required fields are marked *