电子表设计说明.docx
《电子表设计说明.docx》由会员分享,可在线阅读,更多相关《电子表设计说明.docx(16页珍藏版)》请在冰豆网上搜索。
![电子表设计说明.docx](https://file1.bdocx.com/fileroot1/2022-12/30/2d984547-ddc9-41c8-be3b-66528fd4c60c/2d984547-ddc9-41c8-be3b-66528fd4c60c1.gif)
电子表设计说明
8电子钟程序设计
电子表是单片机简单系统最典型的案例,本节学习使用动态扫描的方式实现6位数码管组成的电子钟的设计方法,主要目的是让读者掌握结构化程序设计方法,了解利用数组变量实现数码管数字显示技巧;并熟练掌握键盘的控制编程方式。
本案例设计分阶段进行,首先使用定时器的方式,实现时钟的显示、调整,包括调整时数字的闪动,调整范围的界定等问题。
然后实现单闹钟的功能,包括定时时间的显示,所定时间的调整,定时到时的响铃等功能。
最后实现双闹钟的全部可调的设计目标。
8.1设计任务
电子表使用6位数码管实现显示时分秒,实现双定闹。
使用4个键控制,按模式键以后调整数字加减、闹钟开启。
长按加减键,快速调整,停止闪烁。
普通模式按下闹钟键显示定时时间,闹铃时按下则停止闹铃。
进入调整模式后10秒任意键没有操作,将返回到正常显示模式。
采用的电路见图8-1所示。
六位数码管的段选连接在单片机的P0口,位选连接在单片机的P2口,从右至左分别从P2.0到P2.5。
键盘连接在P3.2到P3.5口。
蜂鸣器连接在单片机的P1.7口。
图8-1硬件电路图
8.2系统功能分析
此处以使用定时器中断计时的单时钟闹铃为例进行讲解,后附双时钟DS1302时钟的完整程序,两程序结构相似,后者在功能上更为完整,走时更为准确,具有实用价值。
在设计中主要功能可以划分为键盘控制模块、显示时间输出、定时等部分内容。
时间的计数要使用定时器中断来实现。
一、键盘控制
键盘控制是本设计中的重要部分,在键盘上实现全部的功能的调整,首先需要定义各个按键的主要功能,功能如表8-1所示。
KEY2键负责调整模式的选择,带有去抖功能,每按下一次,改变一次状态,前三次修改时间,后三次修改闹钟时间。
KEY3、KEY4在对应的模式下进行加或者减,也带有去抖功能。
KEY1在正常显示时按下,显示所定闹钟的时间和开启与否,在闹铃响起时,按下起到停止闹铃的作用。
由于一直按下时显示所定闹钟时间,故此键不能带有去抖功能。
表8-1键盘功能控制
键名
KEY1闹铃键
KEY2模式键
KEY3加键
KEY4减键
按
下
功
能
显示定时
调时
时加,23后为0
时减,0后23
闹铃时停止闹铃
调分
分加,59后为0
分减,0后59
调秒
秒加,59后为0
秒减,0后59
闹钟小时
时加,23后为0
时减,0后23
闹钟分钟
分加,59后为0
分减,0后59
定时开关
打开关闭循环
打开关闭循环
二、显示时间输出
显示终端为6位数码管,从左到右分别显示时分秒,小时、分钟和秒各占2位数码管共6位。
在定时状态下,只显示时分,右边第二位熄灭,右边第一位显示“F”表示闹铃关闭,“E”表示闹铃开启。
在调整过程中,要求对应的调整位置以0.5的速度进行闪烁,以示区别。
三、定时输出
当到所定时间时,闹铃响起,按下KEY1,闹钟停止。
在正常显示时间模式下按下,显示定时时刻及状态。
8.3设计流程
采用“自顶向下”的设计方法,根据对设计功能的分析,可以规划出本程序的主要框图,如图8-2所示。
由于使用结构化编程,程序层次清楚。
程序开始运行以后,先进行初始化,然后就不断的检查键盘按下与否,到中断产生时,就进行显示,计时,加载数据等操作。
图8-2程序结构图
一、变量声明
在程序中使用到多个变量,在编写程序前首先应对其进行定义。
定义的内容主要包括三个数组的定义,这三个数组主要是用在显示函数中;程序使用的变量定义;程序硬件接口的定义三个部分。
详细定义如下:
/**********************************************************************/
#include
unsignedcharcodeLEDDATA[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,
0x82,0xf8,0x80,0x90,0xff,0x8e,0x86};
//数码管显示的代码表,后三个为灭灯、“F”、“E”
unsignedcharcodeLEDBITDATA[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f,};
//数码管扫描代码表
unsignedcharLEDBuffer[6];//定义显示缓冲区数组
unsignedcharSecond;//秒单元
unsignedcharMinute;//分单元
unsignedcharHour;//时单元
unsignedcharBeepflag;//定时响铃标志
unsignedcharMinuterom;//定时分单元
unsignedcharHourrom;//定时时单元
unsignedcharSETFlag=0;//模式标志
unsignedcharsecond_tick;//闪动标志
unsignedcharTime;//超时计数
unsignedcharALMFlag;//定时开启标志
sbitSET_KEY=P3^3;//模式键
sbitDOWN_KEY=P3^4;//加计数键
sbitUP_KEY=P3^5;//减计数键
sbitALM_KEY=P3^2;//显示定时时间按键
sbitBeep=P1^7;//蜂鸣器接口引脚
/**********************************************************************/
二、主程序流程
程序设计采用模块化设计方式后,在主程序里面仅包含程序初始化,键盘模块和中断几个部分。
程序开始运行以后,首先进入初始化阶段,在对定时器进行初始化操作后进入到while死循环内部,反复检查键盘是否有操作、闹铃是否打开。
在这一过程中,定时器时间到就进入中断服务函数,执行相应操作。
对应的结构如图8-3所示。
图8-3主程序流程
此部分对应的程序代码如下:
/**********************************************************************/
voidmain(void)
{init();//初始化
while
(1)
{key();//调用键盘
if(ALMFlag==1)
{if(Minute!
=Minuterom)Beepflag=1;//定时和现在不同,关闭蜂鸣器
if((Hour==Hourrom)&&(Minute==Minuterom)&&(Beepflag==1))Beep=0;
//时分相同并闹铃打开就响铃
}
}
}
/**********************************************************************/
在定时部分,首先判断闹铃标志是否打开,只有当闹铃打开,小时、分钟都相等的情况下,蜂鸣器开始工作。
Beepflag作为标志用来使蜂鸣器在到时间响起时按下KEY1使其复位,停止在这一分钟内继续鸣响。
三、初始化模块
初始化的主要功能是指定定时器的工作方式,装载初值,打开中断。
该模块的工作流程如图8-4所示。
图8-4初始化流程
/**********************************************************************/
voidinit()
{TMOD=0x01;//T0初始化方式1,定时
TH0=(65536-2000)/256;//TH0,TL0装入定时2mS的初值
TL0=(65536-2000)%256;
TR0=1;//启动T0工作
ET0=1;//允许T0溢出中断
EA=1;//CPU开中断
}
/**********************************************************************/
中断时间和机器周期单位为微秒,机器周期是单片机振荡周期12倍,如果单片机的晶体振荡频率为
,则机器周期
由于执行定时器初始化相关语句和晶体振荡频率误差会影响定时器精度,实际预置数需要进行调试调整。
在本程序中,机器周期为1微秒,要求2000微秒即2毫秒中断1次,Timer0计数最大为0xffff,定时器预置数值按下面公式计算:
[预置数]16=[(2定时器位—中断时间/机器周期]10
三、显示模块
显示部分主要作用是把显示暂存区的内容传输到数码管上。
由于是6位数码管,因此必须要使用动态扫描的方式,动态扫描的方式有多种,在本例中是通过建立暂存区来实现,建立暂存区的目的就是使显示模块独立出来,如何显示内容在编程的其他部分不用过多考虑,只需要把显示数据放入在暂存区内,起到数据传递的作用,基本的结构如图8-5所示。
图8-5显示的基本模块
在本模块中,核心的语句就是:
P2=LEDBITDATA[LEDScanCount];//送出位选数据
P0=LEDDATA[LEDBuffer[LEDScanCount]];//送出段选数据
LEDScanCount++;//扫描指针加计数
if(LEDScanCount==6)LEDScanCount=0;//扫描完从头开始
语句中LEDBuffer[]就是所说的暂存区,实际是一个数组。
LEDScanCount相当于扫描计数器,从0到5循环。
LEDDATA[]为数码管的编码字符,LEDBITDATA[]是对应的数码管选中编码。
现以右端显示两位数为例进行说明,要显示的数据为“32”,把个位“2”放入LEDBuffer[0],把十位“3”放入LEDBuffer[1]。
假设LEDScanCount初始为0。
进入到该部分后,首先送位选数据,LEDScanCount=0,也就是LEDBITDATA[0],LEDBITDATA[0]意味着此数组中的第一个数即0xFE,也就是P2=0xFE=11111110,由电路结构可知,使用的是共阳型数码管通过反向器驱动,最后一位为“0”,取反后即可驱动数码管,因此最右边的数码管被选中,位选功能已经实现。
下面开始送段选数据,LEDScanCount=0,即LEDBuffer[0],LEDBuffer[0]=2,故P0=LEDDATA[2]=0xA4,就送出了段选数据。
此时
P2=0xFE=11111110
P0=LEDDATA[2]=0xA4
同理,LEDScanCount=1时
P2=LEDBITDATA[LEDScanCount];
=LEDBITDATA[1];
=0xFD=11111101;
P0=LEDDATA[LEDBuffer[LEDScanCount]];
=LEDDATA[LEDBuffer[1]];
=LEDDATA[3];
=0xB0
执行完后,LEDScanCount加一,当LEDScanCount为2时清零,通过对这几句的反复调用实现动态显示功能。
显示模块进入以后判定是否需要闪烁,然后送出位选数据,在送段选数据时需要结合当前的模式状态,也就是SETFlag的数值进行选择。
由于调整时间和调整定时需要闪烁的时分秒位置对应,故SETFlag的1、4相同,2、5相同,3、6相同,0时为正常显示。
当为1、4时,数码管的最左段两位需要闪烁,因此,当扫描到最左段两位即LEDScanCount>=4,P0送i和所送数据的按位取或,当i为11111111时,不管所送为何值,P0送出的都是11111111,也就是关闭了该位的数码管显示。
i隔0.5秒后变化为00000000,此时送出的就是原始数据,这样就实现了该位的闪烁功能。
其他各位情况相同,不再累述。
当扫描到其他不需要闪烁的位置时,执行else语句,正常的送出数据,不再和i去或。
每次进入display函数一次,点亮一位数码管,进入六次以后,也就是当扫描到最后一位时LEDScanCount清零复位。
该模块的主要流程如图8-6所示。
图8-6显示流程
此模块代码如下:
/**********************************************************************/
voiddisplay(void)//显示暂存区内容对应的代码显示
{unsignedcharLEDScanCount,i;//位选扫描计数器
P0=0xff;//适应仿真需要
if((UP_KEY==0)||(DOWN_KEY==0))i=0x00;//加减键有操作放弃闪烁
elsei=0xff*second_tick;//设定闪烁变量
P2=LEDBITDATA[LEDScanCount];//送出位选数据
switch(SETFlag)
{case0:
P0=LEDDATA[LEDBuffer[LEDScanCount]];break;//送出段选数据
case1:
if(LEDScanCount>=4)//判断出最高两位
P0=i|LEDDATA[LEDBuffer[LEDScanCount]];//使小时闪烁
elseP0=LEDDATA[LEDBuffer[LEDScanCount]];break;//低位正常显示
case2:
if((LEDScanCount==2)||(LEDScanCount==3))//判断出中间位
P0=i|LEDDATA[LEDBuffer[LEDScanCount]];//使分钟闪烁
elseP0=LEDDATA[LEDBuffer[LEDScanCount]];break;//其他位正常显示
case3:
if(LEDScanCount<=1)
P0=i|LEDDATA[LEDBuffer[LEDScanCount]];
elseP0=LEDDATA[LEDBuffer[LEDScanCount]];break;
case4:
if(LEDScanCount>=4)
P0=i|LEDDATA[LEDBuffer[LEDScanCount]];
elseP0=LEDDATA[LEDBuffer[LEDScanCount]];break;
case5:
if((LEDScanCount==2)||(LEDScanCount==3))
P0=i|LEDDATA[LEDBuffer[LEDScanCount]];
elseP0=LEDDATA[LEDBuffer[LEDScanCount]];break;
case6:
if(LEDScanCount<=1)
P0=i|LEDDATA[LEDBuffer[LEDScanCount]];
elseP0=LEDDATA[LEDBuffer[LEDScanCount]];break;
}
LEDScanCount++;//扫描指针加计数
if(LEDScanCount==6)LEDScanCount=0;//扫描完从头开始;
}
/**********************************************************************/
四、键盘扫描模块
键盘的具体定义前面已经给出,按照定义,可以规划出该模块的流程图,如图8-7所示。
程序进入此部分后循环对四个按键进行检测,如果有任意键被按下就执行该键的功能。
图8-7按键模块程序流程
当模式键被按下,在进行去抖检查后,模式标志增加,即SETFlag++。
由于调整时间和调整定时都有显示位置的相似性,因此,从显示暂存区LEDBuffer[]中取出当前调整的数值较为方便。
LEDBuffer中存储的都是1位十进制数据,需要进行合并,合并后进行加减操作,在更改数据后需要及时的把数据写回到对应变量,再由载入程序把新数据装载到暂存区,这样显示的数据就是已经更改过的新数据。
在加减时需要考虑合适返回值的问题,如小时加到“23”后应为“0”,减到“0”是应为“23”这样的问题。
闹铃键的功能相对比较独立,闹铃响时,按下此键更改闹铃标志,从而停止闹铃。
在正常显示时,按下闹铃键的功能是显示所定的闹铃时间,此键不需要去抖功能。
显示闹铃的方式就是直接把所定的闹铃时间装载到显示暂存区里面。
所定时间不包括“秒”,右边第二位进行灭灯处理,加载入0xFF。
最右边根据闹铃标志装载“F”或者“E”。
此部分的参考程序如下:
/**********************************************************************/
voidDelay(unsignedintt)//延时子程序
{while(t)t--;
}
voidkey()//键盘操作子程序
{unsignedchari;//缓冲数组位数标志
charNum;//临时数字,存储数组合并值
if(SET_KEY==0)//判断模式键是否按下
{Delay(500);//去按键抖动
if(SET_KEY==0)//再判断是否真得按下了
{SETFlag++;//状态改变
if(SETFlag==7)SETFlag=0;//返回正常模式
if(SETFlag==1)i=4;//调节读取显示数组的位数
if(SETFlag==2)i=2;
if(SETFlag==3)i=0;
if(SETFlag==4)i=4;
if(SETFlag==5)i=2;
if(SETFlag==6)i=0;
}
while(SET_KEY==0);//等按键释放
}
if((UP_KEY==0)&&(SETFlag!
=0))//判断加计数键是否按下
{
Delay(5000);//去按键抖动
if(UP_KEY==0)//再判断是否真得按下了
{Num=(LEDBuffer[i+1]*10+LEDBuffer[i]);
Num++;//时单元的数值加1
if(Num==60)Num=0;//加到60归0
if((Num==24)&&((SETFlag==1)||(SETFlag==4)))Num=0;//加到24归0
switch(SETFlag)//把修改值写回
{case0:
;break;
case1:
Hour=Num;break;
case2:
Minute=Num;break;
case3:
Second=Num;break;
case4:
Hourrom=Num;break;
case5:
Minuterom=Num;break;
case6:
ALMFlag=!
ALMFlag;break;
}
}
}
if((DOWN_KEY==0)&&(SETFlag!
=0))//判断减计数键是否按下
{
Delay(5000);//去按键抖动
if(DOWN_KEY==0)//再判断是否真得按下了
{Num=(LEDBuffer[i+1]*10+LEDBuffer[i]);
Num--;//时单元的数值减1
if((Num<0)&&((SETFlag==1)||(SETFlag==4)))Num=23;//到24归0
if((Num<0))Num=59;//到60归0
switch(SETFlag)//把修改值写回
{case0:
;break;
case1:
Hour=Num;break;
case2:
Minute=Num;break;
case3:
Second=Num;break;
case4:
Hourrom=Num;break;
case5:
Minuterom=Num;break;
case6:
ALMFlag=!
ALMFlag;break;
}
}
}
if(ALM_KEY==0)
{SETFlag=0;//状态返回
if(ALMFlag==0)LEDBuffer[0]=11;//根据闹铃状态显示F或者E
elseLEDBuffer[0]=12;//将时,分,秒单元内容送入暂存区
LEDBuffer[1]=10;//关闭该数码管显示
LEDBuffer[2]=Minuterom%10;
LEDBuffer[3]=Minuterom/10;
LEDBuffer[4]=Hourrom%10;
LEDBuffer[5]=Hourrom/10;
if(ALMFlag==1)
{Beep=1;
Beepflag=0;
}
}
}
/**********************************************************************/
五、自动返回操作
自动返回的作用是在调整过程中,如果10秒没有任何键被按下,那么就返回到正常的显示时间的状态。
基本原理是,设置一个变量名为Time的计时变量,此变量专门用于该模块。
进入该模块后,首先检查显示模式,如果在非正常显示模式即SETFlag不等于0,那么在10秒内模式键、加键、减键中的任意被按下,计时变量Time清零,如果没有被按下,当计时变量到10时,指定模式变回正常显示状态,即SETFlag为0。
该部分的源码如下:
/**********************************************************************/
if(SETFlag!
=0)//10秒不操作自动返回
{if((SET_KEY==0)||(UP_KEY==0)||(DOWN_KEY==0))Time=0;//任意键有操作放弃计时
if(Time>=10){SETFlag=0;Time=0;}
}
/**********************************************************************/
六、中断模块
中断模块的基本功能是要完成时间的计数。
根据初始化的设定,每2ms进入中断一次,对进入次数进行计数,500次就是1秒,当然这种计数存在较大的误差,需要进行修正,使用专用的时钟芯片进行计