单片机程序设计经典入门项目:电子表程序设计!大家快收藏!
导读
单片机的定时器中断能够产生精确的时间,因此利用单片机很容易实现电子表的设计。本项目是在6位动态显示的基础上加上按键完成的,也是单片机技术学习过程中最典型的实训项目。本项目实现的电子表基本要求为:利用6位数码管显示时分秒,并用4个按键调整。具有自动准确走时,调时调分调秒、定时定闹钟,闹钟输出可驱动蜂鸣器发出声响,或驱动继电器吸合。
图1 电子表显示电路
电子表程序采用模块化设计,可以先根据电子表的功能需求对程序进行模块划分,这个过程是单片机程序模块化设计的基本设计步骤。电子表程序根据功能可以划分显示模块、按键模块和主程序模块,按键模块可以独立设计一个按键子程序,显示模块可采用动态显示程序,包含在定时器中断,主程序模块调用子程序,主函数调用子函数。
(2)按键程序
根据功能要求,按下key1进入时间、定闹钟调整状态,由于需要对不同对象进行调整,并且要控制所调整的对象闪烁显示,因此需要设定一个记录key1按下次数的状态变量,比如key1_flag。未按下key1时,key1_flag值为0,电子表正常走时;第一次按下key1,key1_flag为1,进入调时状态,同时小时闪烁;再按下key1,key1_flag为2,此时调分钟,分钟闪烁;依次按下KEY1,可以分别进入其他时间调整。由于本项目中的电子表只需调整5个对象,key1_flag值增加到6时预置为1,重新进入调时状态。按键功能分配见表1,程序设计流程如图2所示。
表1 key1_flag值所对应的功能
图2 电子表按键程序流程
知道了按键功能分配以及程序流程很容易设计按键程序,首先要声明一些全局变量,如时间变量hour(时)、min(分)、sec(秒),调整中间变量hour_t、min_t、sec_t以及定闹所使用的变量hour_r(定闹时)、min_r(定闹分),以便在程序设计中用这些变量保存现在时间、调整时间、定闹钟时间。按键程序key.c的设计如下:
/**********************************************************/
//按键子程序
/**********************************************************/
#include<reg51.h>
sbit key1 = P1^4;
sbit key2 = P1^5;
sbit key3 = P1^6;
sbit key4 = P1^7;
bit key1_s,key2_s,key3_s,key4_s; //按键按下状态
unsigned char hour = 12,min = 30,sec =30; //正在走的时间变量
char hour_t,min_t,sec_t; //调整的中间时间变量
unsigned char min_r,hour_r; //定闹的时间变量
unsigned char key1_flag; //调整控制变量,控制调整对象、控制显示对象
/***********************简单延时函数************************/
void delay(unsigned int x)
{
while(x--);
}
/*************************按键函数**************************/
void key(void)
{
//按键key1处理
if(key1== 0)
{
delay(300);
if(key1== 0);
key1_s= 1;
}
if(key1== 1 && key1_s == 1)
{
key1_s= 0;
key1_flag++;
if(key1_flag>= 6)key1_flag = 1;
hour_t= hour;//把正在走的时间给调整值
min_t= min;
sec_t= sec;
}
//按键key2处理
if(key2== 0)
{
delay(300);
if(key2== 0);
key2_s= 1;
}
if(key2== 1 && key2_s == 1)
{
key2_s= 0;
if(key1_flag== 1)
hour_t++;if(hour_t>= 24)hour_t = 0;hour = hour_t; //把调整后的时间给走的时间
if(key1_flag== 2)min_t++;if(min_t >= 60)min_t = 0;min = min_t;
if(key1_flag== 3)sec_t++;if(sec_t >= 60)sec_t = 0;sec = sec_t;
if(key1_flag== 4)hour_r++;if(hour_r >= 24)hour = 0;
if(key1_flag== 5)min_r++;if(min_r >= 24)min_r = 0;
}
//按键key3处理
if(key3== 0)
{
delay(300);
if(key3== 0);
key3_s= 1;
}
if(key3== 1 && key3_s == 1)
{
key3_s= 0;
if(key1_flag== 1)hour_t--;if(hour_t <= 0)hour_t = 23;hour = hour_t;
if(key1_flag== 2)min_t--;if(min_t <= 0)min_t = 59;min = min_t;
if(key1_flag== 3)sec_t--;if(sec_t <= 0)sec_t = 59;sec = sec_t;
if(key1_flag== 4)hour_r--;if(hour_r <= 0)hour = 23;
if(key1_flag== 5)min_r--;if(min_r <= 0)min_r = 59;
}
//按键key4处理
if(key4 == 0)
{
delay(300);
if(key4== 0);
key4_s= 1;
}
if(key4== 1 && key4_s == 1)
{
key1_flag= 0;
key4_s= 0;
}
}
按键子程序保存在key.c文件中,用于主程序调用。程序的模块化设计是单片机C语言程序设计的优点,把一个完整小程序单元模块化,可以很容易在被主程序或其他程序调用。这在以后的程序设计中会经常用到。
(3)主程序
主程序包含主函数、中断服务函数和T0初始化函数,主要完成时间计数、显示和定闹钟处理。由于电子表是采用6位数码管动态显示,并利用关键变量key1_flag控制显示状态,因此电子表的程序设计可以在动态显示的基础上实现。
主函数主要调用T0初始化函数,并实时比较现在时间与定闹钟时间是否一致,同时等待T0中断。T0中断服务函数不但要完成计时,而且要完成各种状态,如正常状态、调整时间状态等显示与驱动。T0中断服务函数是程序设计的关键,也是本项目的难点。其程序流程如图3所示。
图3 电子表显示程序流程
程序中使用全局变量key1_flag必须在主程序中声明,主要是因为程序在编译时的顺序原因造成的,在程序设计时,不同的程序可以通过全局变量关联,如同全局变量使用在同一程序中不同函数之间一样。电子表的程序如下:
/***************************************************************************/
//电子表主程序,具有调整时间、定闹功能
/***************************************************************************/
#include<reg51.h>
#include<key.c>
unsigned char i,j,k;
unsigned char seven_seg[] ={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
unsigned char flash;
sbit SW = P3^3; //接继电器驱动电路,低电平继电器吸合,控制电铃
/***************************************************************************/
void timer0_isr(void) interrupt 1
{
TH0 =0xf8;
TL0 = 0x2f;
i++;
if(i>= 250) //半秒到
{
flash= ~flash;// 得到8位闪烁变量
i= 0;
j++;
}
if(j>= 2) //1分钟到
{
sec++;
j= 0;
}
if(sec>= 60) //1分钟到
{
min++;
sec= 0;
}
if(min>= 60) //1小时到
{
hour++;
min= 0;
}
if(hour>= 24)
hour = 0;
P0= 0xff;//仿真消隐
if(key1_flag== 0)//正常走时
{
switch(k)
{
case0:P0 = seven_seg[sec % 10]; P2 = ~0x01;break;
case1:P0 = seven_seg[sec / 10]; P2 = ~0x02;break;
case2:P0 = seven_seg[min % 10] & (0x7f| flash); P2 = ~0x04;break;//小数点闪烁
case3:P0 = seven_seg[min / 10]; P2 = ~0x08;break;
case4:P0 = seven_seg[hour % 10] & (0x7f| flash); P2 = ~0x10;break; //小数点闪烁
case5:P0 = seven_seg[hour / 10]; P2 = ~0x20;break;
}
}
if(key1_flag== 1)//调小时,小时闪烁
{
j= 0;
switch(k)
{
case0: P0 = seven_seg[sec_t % 10]; P2 = ~0x01;break;
case1: P0 = seven_seg[sec_t / 10]; P2 = ~0x02;break;
case2: P0 = seven_seg[min_t % 10]; P2 = ~0x04;break;
case3: P0 = seven_seg[min_t / 10]; P2 = ~0x08;break;
case4: P0 = seven_seg[hour_t % 10] | flash; P2 = ~0x10;break; //小时闪烁
case5: P0 = seven_seg[hour_t / 10] | flash; P2 = ~0x20;break; //小时闪烁
}
}
if(key1_flag== 2)//调分钟
{
j= 0;
switch(k)
{
case0: P0 = seven_seg[sec_t % 10]; P2 = ~0x01;break;
case1: P0 = seven_seg[sec_t / 10]; P2 = ~0x02;break;
case2: P0 = seven_seg[min_t % 10]| flash; P2 = ~0x04;break; //分闪烁
case3: P0 = seven_seg[min_t / 10]| flash; P2 = ~0x08;break; //分闪烁
case4: P0 = seven_seg[hour_t % 10] ; P2 = ~0x10;break;
case5: P0 = seven_seg[hour_t / 10]; P2 = ~0x20;break;
}
}
if(key1_flag== 3)//调秒
{
j= 0;
switch(k)
{
case0: P0 = seven_seg[sec_t % 10]| flash; P2 = ~0x01;break; //秒闪烁
case1: P0 = seven_seg[sec_t / 10]| flash; P2 = ~0x02;break; //秒闪烁
case2: P0 = seven_seg[min_t % 10]; P2 = ~0x04;break;
case3: P0 = seven_seg[min_t / 10]; P2 = ~0x08;break;
case4: P0 = seven_seg[hour_t % 10] ; P2 = ~0x10;break;
case5: P0 = seven_seg[hour_t / 10]; P2 = ~0x20;break;
}
}
if(key1_flag== 4)//调定闹小时
{
j= 0;
switch(k)
{
case0: P0 = seven_seg[min_r % 10]; P2 = ~0x01;break;
case1: P0 = seven_seg[min_r / 10]; P2 = ~0x02;break;
case2: P0 = seven_seg[hour_r % 10]| flash; P2 = ~0x04;break; //定闹小时闪烁
case3: P0 = seven_seg[hour_r / 10]| flash;P2 = ~0x08;break; //定闹小时闪烁
case4: P0 = 0xff;P2 = ~0x10;break;//不显示
case5: P0 = 0x0c;P2 =~0x20;break;//显示字符“P”
}
}
if(key1_flag== 5) //调定闹分钟
{
j= 0;
switch(k)
{
case0: P0 = seven_seg[min_r % 10]| flash; P2 = ~0x01;break; //定闹分闪烁
case1: P0 = seven_seg[min_r / 10]| flash; P2 = ~0x02;break; //定闹分闪烁
case2: P0 = seven_seg[hour_r % 10]; P2 = ~0x04;break;
case3: P0 = seven_seg[hour_r / 10]; P2 =~0x08;break;
case4: P0 = 0xff;P2 = ~0x10;break;//不显示
case5: P0 = 0x0c;P2 =~0x20;break;//显示字符“P”
}
}
k++;
if(k>= 6)k = 0;
}
/***************************************************************************/
void timer0_initi(void)
{
TMOD =0x01;
TH0 =0xf8;
TL0 = 0x2f;
EA = 1;
ET0 = 1;
TR0 = 1;
}
/***************************************************************************/
void main(void)
{
timer0_initi();
while(1)
{
key();
if(min== min_r && hour == hour_r)
SW= 0;
else
SW= 1;
}
}
按键子程序和主程序设计完成后保存在同一个目录下。采用Keil设计程序时,在新建工程后直接添加主程序即可。由于程序中有很多类似的结构和程序,因此在编写电子表程序时,应尽量使用复制和粘贴功能,只更改不同的变量值即可,从而使程序输入量大大减小。
本项目重点练习单片机程序设计基本技能,当大家在熟悉项目的任务后,只要按键功能分配合理、编写思路清晰,可以在很短的时间内完成电子表的设计。利用单片机完成电子表的设计是单片机程序设计中最为典型的实训项目,因此,具备独立完成电子表设计能力是单片机项目开发和产品设计的最低要求。
选自《单片机开发从入门到精通》白林峰等编著
往期精彩