《单片机原理及其接口技术》
课程设计
物理与电子工程学院
《单片机原理及其接口技术》
课程设计报告
设计课题:基于MCS-51单片机的时钟设计
专业班级: 自动化0812 小组成员: 张巍(08118085) 指导教师: 阮海容 设计时间:二〇一三年三月三十日
一、设计任务与要求
1)设计任务
运用所学的单片机原理知识,在Keil C51和Protues软件平台上编写出24小时标准计时制的时钟电路程序,并在ZY15MCU12BD型综合单片机实验箱的硬件结构上进行调试完成设计。 2)设计要求:
1、除了可在ZY15MCU12BD型综合单片机实验箱的硬件结构上编写软件完成设计,也可在其它MCS—51单片机硬件板上完成,或自行设计硬件并制做完成。 2、程序的首地址应使目标机可直接运行,即从0000H开始;在主程序的开始部分必须设置一个合适的栈底;程序放置的地址必须连续且靠前,不要在中间留下大量的空闲地址,以使目标机可以使用较少的硬件资源。
3、6位LED数码管从左到右分别显示时、分、秒(各占用2位),采用24小时标准计时制。开始计时时,显示为00 00 00,到23 59 59后又变成00 00 00。 4、在键盘上选定3个键分别作为小时、分、秒的调校键。每按一次键,对应的显示值便加1。 分、秒加到59后再按键即变为00,小时加到23后再按键即变为00。在调校时均不向上一单位进位 (例如分位加到59后变为00,但小时位不发生改变)。
5、软件设计时必须使用MCS-51片内定时器,采用定时中断结构,不得使用软件延时法。
6、上机调试通过。 7、写出设计报告。 3)扩充功能
1、另设三个键,分别作小时、分、秒的减1调校。
2、在以上设计的基础上,修改程序制作一个电子秒表。分、秒各占用2位显示,1/10秒、1/100秒各占用1位显示。设定二个键分别作启动/停止、清零。 3、在做完(2)后,将时钟与秒表合二为一,并且在同时使用时互不影响。即可在时钟与秒表之间任意切换,而不影响走时、计秒。
二、 系统设计
1)硬件结构概述
本设计基于AT89C51单片机为核心完成的。
本时钟设计以AT89C51单片机为核心,外加少量其他辅助器件,硬件结构简单。电路中使用的另一个芯片为74HC573八位锁存器,它是一个普通的常用锁存器,在本电路中主要起增加驱动能力的作用。
由于本电路结构简单,单片机I/O口完全够用,故我们将6位8段LED直接和单片机相连。硬件结构中的6位8段LED显示器,采用动态方式驱动。即使一位LED显示器显示内容一段时间,然后下一位LED显示器显示内容一段时间……,周而复始。只要刷新频率不小于50Hz,就可以获得清晰稳定的显示效果。
为了增加和位选相连接的I/O口的驱动能力,直接接上上拉电阻。在本设计中,只使用了4个按键。分别是复位键、模式设定键、2个上下调节键。 2)程序流程图:
定时器中断程序:
设定定时器中断时间,当中断时间到达时,主程序转入执行该中断程序并保护现场,对定时器重送初值。判断是否到1s、60s、60min,若是则对相应的时间显示位进行处理。不是则恢复现场,中断返回。
三、程序清单:
#include "main.h" #include "LED.H"
#include "keyboard.h" #include "timer.h"
void system_initial(void) //系统初始化 {
TMOD=0x11; //定时器工作在模式1 ET0=1; //开定时器0中断 TR0=1; //启动定时器0 EA=1; //开总中断 year=10; month=12;
day=01; //日期初始化为2010年12月1日 }
void main(void) //主函数 {
system_initial(); //系统开机初始化 while(1) {
scan_keyboard(); //键盘扫描 timer(); //时钟功能 } }
void delayXms(unsigned int x) //延迟x毫秒函数 {
unsigned int i,j; for(i=0;i
code unsigned char LED_TAB[]= {
0x77, //0 0x42, //1 0x3B, //2 0x6B, //3 0x4E, //4 0x6D, //5 0x7D, //6 0x43, //7
0x6F, //9 0x5F, //A 0x7C, //B 0x35, //C 0x1A, //D 0x3D, //E 0x1D, //F
0x77|0X80, //带小数点显示的0 0x42|0X80, //带小数点显示的1 0x3B|0X80, //带小数点显示的2 0x6B|0X80, //带小数点显示的3 0x4E|0X80, //带小数点显示的4 0x6D|0X80, //带小数点显示的5 0x7D|0X80, //带小数点显示的6 0x43|0X80, //带小数点显示的7 0x7F|0X80, //带小数点显示的8 0x6F|0X80, //带小数点显示的9 0x5F|0X80, //带小数点显示的A 0x7C|0X80, //带小数点显示的B 0x35|0X80, //带小数点显示的C 0x1A|0X80, //带小数点显示的D 0x3D|0X80, //带小数点显示的E 0x1D|0X80, //带小数点显示的F 0x00 //灭 };
void display(unsigned char * disp_buff) //显示函数 {
unsigned char i;
unsigned char temp_bit; temp_bit=0x01;
for(i=0;i
LED_BIT=0xFF; //消隐 LED_SEGMENT=LED_TAB[disp_buff[i]]; //段码输出 LED_BIT=~temp_bit; //位选输出 temp_bit
LED_BIT=0xFF; //关灯 }
unsigned char key,key_old,key_new,key_pressed_time;
void scan_keyboard(void) //键盘扫描
if(key)return;
if((KEY_INPUT & KEY_PIN)==KEY_PIN) //按键释放 {
if(!key_old) //如果上次无按键按下 {
return; //返回 }
else //如果上次有按键按下 {
if(key_pressed_time>100) //按键时间超过一秒,长按键码 {
// key=key_old+0x80; //长按键码等于普通按键码加0x80 }
else if(key_pressed_time>1) {
key=key_old; //按键值 }
key_old=0; }
return; }
else //按键按下 {
key_new=0;
if(FUNCTION_KEY)key_new=KEY_FUNCTION; //MENU键按下
if(DOWN_KEY)key_new=KEY_DOWN; //DOWN键按下
if(UP_KEY)key_new=KEY_UP; //UP键按下
if(key_new==key_old) //如果按键按下长于10ms {
if(key_pressed_time>80) //如果按键按下超过1s {
key_pressed_time-=5; //则自动按键。相当于每隔50ms按一次
key=key_new; return; } else {
key_pressed_time++; //按键按下计时加一 } }
{
key_pressed_time=0; //如果本次按下跟上次按下不一样,则按键按下时间清零
}
key_old=key_new; //保存本次按键值 } }
unsigned char display_buffer[6]; //显示缓冲区
unsigned char second,minute,hour,day,month,year; //分别保存秒、分、时、天、月、年的变量
unsigned char n_50m_second; //保存多少个50毫秒的变量 unsigned char days_of_month; //保存该月多少天的变量
void timer0_isr(void) interrupt 1 using 1 //定时器0中断处理函数 {
TL0=(65536-50000)/256;
TH0=(65536-50000)%256; //定时器重装。定时50mS中断一次
n_50m_second++;
if(n_50m_second>=20) //1秒到 {
n_50m_second=0; //清50毫秒计数 second++; //秒加1
if(second==60) //如果秒到60 {
second=0; //秒清0 minute++; //分加1
if(minute==60) //如果分到60 {
minute=0; //分清0 hour++; //小时加1
if(hour==24) //如果小时到24 {
hour=0; //小时清零 day++; //天加1
if(day>days_of_month) //如果天大于本月最后一天 {
day=1; //天置1 month++; //月加1
if(month>12) //如果月大于12 {
month=1; //月置1 year++; //年加1
if(year>=100) //如果年大于100
year=0; //年清零 } } } } } } } }
#define NORMAL 0 #define SECOND_ADJ 1 #define MINUTE_ADJ 2 #define HOUR_ADJ 3 #define DAY_ADJ 4 #define MONTH_ADJ 5 #define YEAR_ADJ 6 #define STATUS_MIN 0 #define STATUS_MAX 6
unsigned char calculate_days(void) //计算本月有多少天 {
unsigned int temp; switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31; //1、3、5、7、8、10、12月为31天 break; case 4: case 6: case 9:
case 11: //4、6、9、11月为30天 return 30; break; case 2:
temp=2000+year; //因为year中保存的是年的最后两位,所以要加上2000 if((((temp%4)==0)&&((temp%100)!=0))||((temp%400)==0)) {
return 29; //闰年2月为29天 }
{
return 28; //平年2月为28天
}
}
}
unsigned char flicker_time; //闪烁频率
unsigned char display_on; //显示开关标志。用来产生闪烁效果
unsigned char status; //状态值
unsigned char display_date; //是否是显示日期
void timer(void)
{
if(key==KEY_FUNCTION) //如果功能键按下
{
if(status
else status=STATUS_MIN; //否则,状态值置最小状态值
key=0; //清键值
}
days_of_month=calculate_days(); //计算本月天数
switch (status)
{
case NORMAL: //平常状态
{
if(key==KEY_UP) //如果上翻键按下,则显示日期
{
display_date=100; //设置显示日期的时间
key=0;
}
if(key==KEY_DOWN) //如果下翻键按下,则显示时间
{
display_date=0;
key=0;
}
if(display_date) //如果是显示日期,则计算日期每位
{
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[2]=month/10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
display_date--; //经过一定时间后,自动显示时间
}
else //如果是显示时间,则计算时间每位
display_buffer[5]=second%10;
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
}
display(display_buffer); //显示结果
break;
}
case SECOND_ADJ: //秒调整状态
{
if(key==KEY_UP) //上翻键按下,秒增加1
{
display_on=1; //开显示,调整时能看清楚
second++;
if(second>=60)second=0; //如果秒到60,则清0
key=0;
}
if(key==KEY_DOWN) //下翻键按下,秒减1
{
display_on=1;
second--;
if(second>=60)second=59;
key=0;
}
display_buffer[5]=second%10; //计算每位值
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
if(!display_on) //如果关显示
{
flicker_time++; //闪烁计时加1
if(flicker_time>20){flicker_time=0;display_on=1;} //如果闪烁计时到20,开显示
display_buffer[5]=DISPLAY_OFF;
display_buffer[4]=DISPLAY_OFF;
}
else //如果开显示
{
flicker_time++; //闪烁计时加1
if(flicker_time>30){flicker_time=0;display_on=0;} //如果闪烁计时到20,关显示
display(display_buffer);
break;
}
case MINUTE_ADJ: //分调整状态
{
if(key==KEY_UP)
{
display_on=1;
minute++;
if(minute>=60)minute=0;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
minute--;
if(minute>=60)minute=59;
key=0;
}
display_buffer[5]=second%10;
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[3]=DISPLAY_OFF;
display_buffer[2]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case HOUR_ADJ: //小时调整状态
{
if(key==KEY_UP)
{
display_on=1;
if(hour>=24)hour=0;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
hour--;
if(hour>=24)hour=23;
key=0;
}
display_buffer[5]=second%10;
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[1]=DISPLAY_OFF;
display_buffer[0]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case DAY_ADJ: //天调整状态
{
if(key==KEY_UP)
{
display_on=1;
day++;
if(day>days_of_month)day=1;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
day--;
if(day==0)day=days_of_month;
}
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[2]=month/10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[5]=DISPLAY_OFF;
display_buffer[4]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case MONTH_ADJ:
{
if(key==KEY_UP)
{
display_on=1;
month++;
if(month>=13)month=1;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
month--;
if(month==0)month=12;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[3]=DISPLAY_OFF;
display_buffer[2]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case YEAR_ADJ: //年天调整状态
{
if(key==KEY_UP)
{
display_on=1;
year++;
if(year>=100)year=0;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
year--;
if(year>=100)year=99;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[2]=month/10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
if(!display_on)
{
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[1]=DISPLAY_OFF;
display_buffer[0]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
}
}
四、 系统调试
1)硬件调试:
Protus仿真时最初LED直接与单片机连接,输出I/O没有经过任何处理。观察到LED接口旁小信号灯暗淡,接上拉电阻及通过74HC573锁存器后,指示信号灯恢复正常。分析得出这是由于单片机I/O口驱动能力有限造成的结果。
2)软件调试:
初LED显示时钟时间与实际时间快慢不一致,通过调整定时器,改变给TLO、THO赋初值的大小,经过几次调试最终显示时间与实际时间相接近。秒表和时钟功能能经过几次调试,始终不能够完成切换,最终未能成功。
五、 心得体会
做了两周的课程设计,让人感受颇深,有关于单片机知识的,但更多是查阅资料和自学的能力以前一直以为硬件调试很麻烦,这次课设才发现原来软件调试更加费时费力。程序长了后,经常容易弄混,看一会就把许多变量、定义弄混。这需要我们在平时的写程序时养成了好的习惯。要注意条理清楚,多用写子程序,要用时使用函数调用。定义变量时注意名字好记又有他的特殊意义。
通过这次的课程设计作品的制作让我对单片机的理论有了更加深入的了解,同时在具体的实施过程中我们发现现在书本上的知识与实际的应用存在着不小的差距,书本上的知识很多都是理想化后的结论,忽略了很多实际的因素,或者涉及的不全面,可在实际的应用时这些是不能被忽略的,我们不得不考虑这方的问题,这让我们无法根据书上的理论就轻易得到预想中的结果,有时结果甚至很差别很
大。通过这次实践使我更深刻的体会到了理论联系实际的重要性,我们在今后的学习工作中会更加的注重实际。
很感谢学校和老师给我们安排了这次课程设计,让我真正感受到的是合作的重要,许多时候都是组员的讨论,老师的指导中的一句半句启发了我,就出现的让人欣喜的结果;理论知识同样很重要,有些问题都是由于基础知识掌握不好才出现的。
六、 参考文献
a) 单片机原理及其接口技术/胡汉才编著.——3版.——北京:清华大学出版
社,2010.5
b) 单片机系统设计与实践/王道辉主编.——北京:电子工业出版社,2006.5
《单片机原理及其接口技术》
课程设计
物理与电子工程学院
《单片机原理及其接口技术》
课程设计报告
设计课题:基于MCS-51单片机的时钟设计
专业班级: 自动化0812 小组成员: 张巍(08118085) 指导教师: 阮海容 设计时间:二〇一三年三月三十日
一、设计任务与要求
1)设计任务
运用所学的单片机原理知识,在Keil C51和Protues软件平台上编写出24小时标准计时制的时钟电路程序,并在ZY15MCU12BD型综合单片机实验箱的硬件结构上进行调试完成设计。 2)设计要求:
1、除了可在ZY15MCU12BD型综合单片机实验箱的硬件结构上编写软件完成设计,也可在其它MCS—51单片机硬件板上完成,或自行设计硬件并制做完成。 2、程序的首地址应使目标机可直接运行,即从0000H开始;在主程序的开始部分必须设置一个合适的栈底;程序放置的地址必须连续且靠前,不要在中间留下大量的空闲地址,以使目标机可以使用较少的硬件资源。
3、6位LED数码管从左到右分别显示时、分、秒(各占用2位),采用24小时标准计时制。开始计时时,显示为00 00 00,到23 59 59后又变成00 00 00。 4、在键盘上选定3个键分别作为小时、分、秒的调校键。每按一次键,对应的显示值便加1。 分、秒加到59后再按键即变为00,小时加到23后再按键即变为00。在调校时均不向上一单位进位 (例如分位加到59后变为00,但小时位不发生改变)。
5、软件设计时必须使用MCS-51片内定时器,采用定时中断结构,不得使用软件延时法。
6、上机调试通过。 7、写出设计报告。 3)扩充功能
1、另设三个键,分别作小时、分、秒的减1调校。
2、在以上设计的基础上,修改程序制作一个电子秒表。分、秒各占用2位显示,1/10秒、1/100秒各占用1位显示。设定二个键分别作启动/停止、清零。 3、在做完(2)后,将时钟与秒表合二为一,并且在同时使用时互不影响。即可在时钟与秒表之间任意切换,而不影响走时、计秒。
二、 系统设计
1)硬件结构概述
本设计基于AT89C51单片机为核心完成的。
本时钟设计以AT89C51单片机为核心,外加少量其他辅助器件,硬件结构简单。电路中使用的另一个芯片为74HC573八位锁存器,它是一个普通的常用锁存器,在本电路中主要起增加驱动能力的作用。
由于本电路结构简单,单片机I/O口完全够用,故我们将6位8段LED直接和单片机相连。硬件结构中的6位8段LED显示器,采用动态方式驱动。即使一位LED显示器显示内容一段时间,然后下一位LED显示器显示内容一段时间……,周而复始。只要刷新频率不小于50Hz,就可以获得清晰稳定的显示效果。
为了增加和位选相连接的I/O口的驱动能力,直接接上上拉电阻。在本设计中,只使用了4个按键。分别是复位键、模式设定键、2个上下调节键。 2)程序流程图:
定时器中断程序:
设定定时器中断时间,当中断时间到达时,主程序转入执行该中断程序并保护现场,对定时器重送初值。判断是否到1s、60s、60min,若是则对相应的时间显示位进行处理。不是则恢复现场,中断返回。
三、程序清单:
#include "main.h" #include "LED.H"
#include "keyboard.h" #include "timer.h"
void system_initial(void) //系统初始化 {
TMOD=0x11; //定时器工作在模式1 ET0=1; //开定时器0中断 TR0=1; //启动定时器0 EA=1; //开总中断 year=10; month=12;
day=01; //日期初始化为2010年12月1日 }
void main(void) //主函数 {
system_initial(); //系统开机初始化 while(1) {
scan_keyboard(); //键盘扫描 timer(); //时钟功能 } }
void delayXms(unsigned int x) //延迟x毫秒函数 {
unsigned int i,j; for(i=0;i
code unsigned char LED_TAB[]= {
0x77, //0 0x42, //1 0x3B, //2 0x6B, //3 0x4E, //4 0x6D, //5 0x7D, //6 0x43, //7
0x6F, //9 0x5F, //A 0x7C, //B 0x35, //C 0x1A, //D 0x3D, //E 0x1D, //F
0x77|0X80, //带小数点显示的0 0x42|0X80, //带小数点显示的1 0x3B|0X80, //带小数点显示的2 0x6B|0X80, //带小数点显示的3 0x4E|0X80, //带小数点显示的4 0x6D|0X80, //带小数点显示的5 0x7D|0X80, //带小数点显示的6 0x43|0X80, //带小数点显示的7 0x7F|0X80, //带小数点显示的8 0x6F|0X80, //带小数点显示的9 0x5F|0X80, //带小数点显示的A 0x7C|0X80, //带小数点显示的B 0x35|0X80, //带小数点显示的C 0x1A|0X80, //带小数点显示的D 0x3D|0X80, //带小数点显示的E 0x1D|0X80, //带小数点显示的F 0x00 //灭 };
void display(unsigned char * disp_buff) //显示函数 {
unsigned char i;
unsigned char temp_bit; temp_bit=0x01;
for(i=0;i
LED_BIT=0xFF; //消隐 LED_SEGMENT=LED_TAB[disp_buff[i]]; //段码输出 LED_BIT=~temp_bit; //位选输出 temp_bit
LED_BIT=0xFF; //关灯 }
unsigned char key,key_old,key_new,key_pressed_time;
void scan_keyboard(void) //键盘扫描
if(key)return;
if((KEY_INPUT & KEY_PIN)==KEY_PIN) //按键释放 {
if(!key_old) //如果上次无按键按下 {
return; //返回 }
else //如果上次有按键按下 {
if(key_pressed_time>100) //按键时间超过一秒,长按键码 {
// key=key_old+0x80; //长按键码等于普通按键码加0x80 }
else if(key_pressed_time>1) {
key=key_old; //按键值 }
key_old=0; }
return; }
else //按键按下 {
key_new=0;
if(FUNCTION_KEY)key_new=KEY_FUNCTION; //MENU键按下
if(DOWN_KEY)key_new=KEY_DOWN; //DOWN键按下
if(UP_KEY)key_new=KEY_UP; //UP键按下
if(key_new==key_old) //如果按键按下长于10ms {
if(key_pressed_time>80) //如果按键按下超过1s {
key_pressed_time-=5; //则自动按键。相当于每隔50ms按一次
key=key_new; return; } else {
key_pressed_time++; //按键按下计时加一 } }
{
key_pressed_time=0; //如果本次按下跟上次按下不一样,则按键按下时间清零
}
key_old=key_new; //保存本次按键值 } }
unsigned char display_buffer[6]; //显示缓冲区
unsigned char second,minute,hour,day,month,year; //分别保存秒、分、时、天、月、年的变量
unsigned char n_50m_second; //保存多少个50毫秒的变量 unsigned char days_of_month; //保存该月多少天的变量
void timer0_isr(void) interrupt 1 using 1 //定时器0中断处理函数 {
TL0=(65536-50000)/256;
TH0=(65536-50000)%256; //定时器重装。定时50mS中断一次
n_50m_second++;
if(n_50m_second>=20) //1秒到 {
n_50m_second=0; //清50毫秒计数 second++; //秒加1
if(second==60) //如果秒到60 {
second=0; //秒清0 minute++; //分加1
if(minute==60) //如果分到60 {
minute=0; //分清0 hour++; //小时加1
if(hour==24) //如果小时到24 {
hour=0; //小时清零 day++; //天加1
if(day>days_of_month) //如果天大于本月最后一天 {
day=1; //天置1 month++; //月加1
if(month>12) //如果月大于12 {
month=1; //月置1 year++; //年加1
if(year>=100) //如果年大于100
year=0; //年清零 } } } } } } } }
#define NORMAL 0 #define SECOND_ADJ 1 #define MINUTE_ADJ 2 #define HOUR_ADJ 3 #define DAY_ADJ 4 #define MONTH_ADJ 5 #define YEAR_ADJ 6 #define STATUS_MIN 0 #define STATUS_MAX 6
unsigned char calculate_days(void) //计算本月有多少天 {
unsigned int temp; switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31; //1、3、5、7、8、10、12月为31天 break; case 4: case 6: case 9:
case 11: //4、6、9、11月为30天 return 30; break; case 2:
temp=2000+year; //因为year中保存的是年的最后两位,所以要加上2000 if((((temp%4)==0)&&((temp%100)!=0))||((temp%400)==0)) {
return 29; //闰年2月为29天 }
{
return 28; //平年2月为28天
}
}
}
unsigned char flicker_time; //闪烁频率
unsigned char display_on; //显示开关标志。用来产生闪烁效果
unsigned char status; //状态值
unsigned char display_date; //是否是显示日期
void timer(void)
{
if(key==KEY_FUNCTION) //如果功能键按下
{
if(status
else status=STATUS_MIN; //否则,状态值置最小状态值
key=0; //清键值
}
days_of_month=calculate_days(); //计算本月天数
switch (status)
{
case NORMAL: //平常状态
{
if(key==KEY_UP) //如果上翻键按下,则显示日期
{
display_date=100; //设置显示日期的时间
key=0;
}
if(key==KEY_DOWN) //如果下翻键按下,则显示时间
{
display_date=0;
key=0;
}
if(display_date) //如果是显示日期,则计算日期每位
{
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[2]=month/10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
display_date--; //经过一定时间后,自动显示时间
}
else //如果是显示时间,则计算时间每位
display_buffer[5]=second%10;
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
}
display(display_buffer); //显示结果
break;
}
case SECOND_ADJ: //秒调整状态
{
if(key==KEY_UP) //上翻键按下,秒增加1
{
display_on=1; //开显示,调整时能看清楚
second++;
if(second>=60)second=0; //如果秒到60,则清0
key=0;
}
if(key==KEY_DOWN) //下翻键按下,秒减1
{
display_on=1;
second--;
if(second>=60)second=59;
key=0;
}
display_buffer[5]=second%10; //计算每位值
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
if(!display_on) //如果关显示
{
flicker_time++; //闪烁计时加1
if(flicker_time>20){flicker_time=0;display_on=1;} //如果闪烁计时到20,开显示
display_buffer[5]=DISPLAY_OFF;
display_buffer[4]=DISPLAY_OFF;
}
else //如果开显示
{
flicker_time++; //闪烁计时加1
if(flicker_time>30){flicker_time=0;display_on=0;} //如果闪烁计时到20,关显示
display(display_buffer);
break;
}
case MINUTE_ADJ: //分调整状态
{
if(key==KEY_UP)
{
display_on=1;
minute++;
if(minute>=60)minute=0;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
minute--;
if(minute>=60)minute=59;
key=0;
}
display_buffer[5]=second%10;
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[3]=DISPLAY_OFF;
display_buffer[2]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case HOUR_ADJ: //小时调整状态
{
if(key==KEY_UP)
{
display_on=1;
if(hour>=24)hour=0;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
hour--;
if(hour>=24)hour=23;
key=0;
}
display_buffer[5]=second%10;
display_buffer[4]=second/10;
display_buffer[3]=16+minute%10;
display_buffer[2]=minute/10;
display_buffer[1]=16+hour%10;
display_buffer[0]=hour/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[1]=DISPLAY_OFF;
display_buffer[0]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case DAY_ADJ: //天调整状态
{
if(key==KEY_UP)
{
display_on=1;
day++;
if(day>days_of_month)day=1;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
day--;
if(day==0)day=days_of_month;
}
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[2]=month/10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[5]=DISPLAY_OFF;
display_buffer[4]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case MONTH_ADJ:
{
if(key==KEY_UP)
{
display_on=1;
month++;
if(month>=13)month=1;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
month--;
if(month==0)month=12;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
if(!display_on)
{
flicker_time++;
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[3]=DISPLAY_OFF;
display_buffer[2]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
case YEAR_ADJ: //年天调整状态
{
if(key==KEY_UP)
{
display_on=1;
year++;
if(year>=100)year=0;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
if(key==KEY_DOWN)
{
display_on=1;
year--;
if(year>=100)year=99;
days_of_month=calculate_days();
if(day>days_of_month)day=days_of_month;
key=0;
}
display_buffer[5]=16+day%10;
display_buffer[4]=day/10;
display_buffer[3]=16+month%10;
display_buffer[2]=month/10;
display_buffer[1]=16+year%10;
display_buffer[0]=year/10;
if(!display_on)
{
if(flicker_time>20){flicker_time=0;display_on=1;}
display_buffer[1]=DISPLAY_OFF;
display_buffer[0]=DISPLAY_OFF;
}
else
{
flicker_time++;
if(flicker_time>30){flicker_time=0;display_on=0;}
}
display(display_buffer);
break;
}
}
}
四、 系统调试
1)硬件调试:
Protus仿真时最初LED直接与单片机连接,输出I/O没有经过任何处理。观察到LED接口旁小信号灯暗淡,接上拉电阻及通过74HC573锁存器后,指示信号灯恢复正常。分析得出这是由于单片机I/O口驱动能力有限造成的结果。
2)软件调试:
初LED显示时钟时间与实际时间快慢不一致,通过调整定时器,改变给TLO、THO赋初值的大小,经过几次调试最终显示时间与实际时间相接近。秒表和时钟功能能经过几次调试,始终不能够完成切换,最终未能成功。
五、 心得体会
做了两周的课程设计,让人感受颇深,有关于单片机知识的,但更多是查阅资料和自学的能力以前一直以为硬件调试很麻烦,这次课设才发现原来软件调试更加费时费力。程序长了后,经常容易弄混,看一会就把许多变量、定义弄混。这需要我们在平时的写程序时养成了好的习惯。要注意条理清楚,多用写子程序,要用时使用函数调用。定义变量时注意名字好记又有他的特殊意义。
通过这次的课程设计作品的制作让我对单片机的理论有了更加深入的了解,同时在具体的实施过程中我们发现现在书本上的知识与实际的应用存在着不小的差距,书本上的知识很多都是理想化后的结论,忽略了很多实际的因素,或者涉及的不全面,可在实际的应用时这些是不能被忽略的,我们不得不考虑这方的问题,这让我们无法根据书上的理论就轻易得到预想中的结果,有时结果甚至很差别很
大。通过这次实践使我更深刻的体会到了理论联系实际的重要性,我们在今后的学习工作中会更加的注重实际。
很感谢学校和老师给我们安排了这次课程设计,让我真正感受到的是合作的重要,许多时候都是组员的讨论,老师的指导中的一句半句启发了我,就出现的让人欣喜的结果;理论知识同样很重要,有些问题都是由于基础知识掌握不好才出现的。
六、 参考文献
a) 单片机原理及其接口技术/胡汉才编著.——3版.——北京:清华大学出版
社,2010.5
b) 单片机系统设计与实践/王道辉主编.——北京:电子工业出版社,2006.5