C51单片机学习笔记——秒表

前言

不知不觉我又被自己的惰性拖住了小一个月,今天在宿舍窗边吸烟时候,看着楼下人来人往的道路不由自主的感到一丝惭愧,手里的小视频也被我刷出来一条鸡汤,在这儿我要写下来记录给将来又在颓废的我:

  • 这个世界最恐怖的不是有人比你优秀,而是那些比你优秀的人比你还努力!!!

OK,食归大肠,水入膀胱,咱们言归正传。今儿做的是时钟升级版实验——秒表。

任务介绍

同样利用上次的时钟原理图,改写成为可记录多人的秒表。( ヾ(TДT;))))…!脑子里不知不觉想到在部队生活时的夺命追魂表。。。)
要求:

  • 显示格式为X-XX-X,例如1-56-7表示1分56.7秒(或1点56分7钞),即以1/10秒的速度运行
  • 使用两个按键 K1、K2 实现 开始/结束记录/查询 的功能

实验目的

  • 1、掌握数码管动态扫描显示的原理及过程。
  • 2、掌握单片机定时/计数中断程序的设计方法。
  • 3、掌握单片机定时计数器去按键抖动的原理及编程方法。

程序构想

为了提升自己的能力,从本次开始程序构想统统以流程图来呈现。(画的不好还请高人多多指教,上图),别问我为啥不用 ProcessOn 、Mindmanage 来画,因为我没钱
C51单片机学习笔记——秒表_第1张图片
K1按键对应P3.2 , K2按键对应P3.3 ,(表达不好截图来搞,整体如下图所示)
C51单片机学习笔记——秒表_第2张图片

码代码开始

先把框架码起来,根据流程图可以看出主要需要以下几个动作:

void init(void)/*初始化程序*/
void SaveTime(u8 nsave)	  /*储存当前时间  */
void Output(u8 x)/*显示第x个成员的时间成绩*/
void main()//主函数(必不可少)
void K1() interrupt 0	//INT0外部中断  K1 开始/结束/清零 计时
void K2() interrupt 2	//INT1外部中断  K2  记录时间/切换显示内容
void TimePulse() interrupt 1  //T0定时器 产生时间脉冲
void LedShow() interrupt 3	 //T1定时器 动态显示 按键中断  4ms进入一次

框架打好,慢慢填写内容:

  1. 产生一个1/10秒的信号驱动:
    这里我用T0定时器的方式二产生,因为是自动TL0是被TH0自动重置的,所以可以提高一定的精准度。这里用的单片机晶振频率为12MHz,通过公式 机械周期 = 12 / 12M = 1us ,为了达到100ms的周期需要两个循环(因为定时器方式二最大计数值为256),具体如下:
void TimePulse() interrupt 1  //T0定时器 产生时间脉冲
{
	static u8 i=0,ii=0;
	i++;  //每0.2ms进入一次中断  500次为0.1s
	if(i>=50)
	{
		i=0;
		ii++;
		if(ii==10)
		{
			(*ss)++;	//ss加1	   *ss为显示的最后一位
			ii=0;
			i=0;
		}
	}
}
  1. 动态显示
    这个没什么好说的大家都会就不啰嗦了,利用定时器T1动态扫描显示,同时解决按键防抖动
void LedShow() interrupt 3	 //T1定时器 动态显示 按键中断  4ms进入一次
{
	static u8 w=0;
	P2 = 0;
	TH1 =  TScan/256;		 //
	TL1 =  TScan%256;
	
	/* 将时间赋值给 数组led_show[] */
	if(*ss>=10)
	{
		*ss=0;
		(*sl)++;
	}else{}
	if(*sl>=10)
	{
		*sl=0;
		(*sh)++;
	}else{}

	if(*sh>=6)
	{
		*sh=0;
		(*m)++;		
	}else{}
	if(*m>=10)
	{
		TR0=0;	 //计时到9分59秒自动停止计时
		*ss=9;
		*sl=9;
		*sh=5;
		*m=9;			
	}else{}
	
	/* 显示led_show[] */
	P1 = led_scan[w];
	P2 = led_cc[ led_show[w] ];		//  
	w++;
	if(w==8)
	{
		w=0;
	}else{}

		
	/* 防抖70*4ms = 280ms */
	if(as>0)
	{
		as++;
		if(as==71)
		{
			as=0;
			IE0=0;	//清空外部中断0中断源
			EX0=1;  //打开外部中断0
			IE1=0;	//清空外部中断1中断源
			EX1=1;  //打开外部中断1
		}else{}
	}else{}
}
  1. K1 、K2分别对应外部中断0 、 外部中断1
    根据流程图描写构建对应的功能,K1负责 开始/结束 K2负责 记录/切换。这里我主要通过对TR0状态的判断决定按下按键后进行的动作,我们结合来看:
void K1() interrupt 0	//INT0外部中断  K1 开始/结束/清零 计时
{
	as=1;  //标记进入防抖
	EX0 = 0;
	k1f++; //k1f:K1状态标识    0初始化准备开始    1计时状态    2停止计时状态
	if(k1f==1) //计时开始
	{
		TR0 = 1;
	}
	if(k1f==2) //全部结束计时
	{
		TR0 = 0;
		SaveTime((*num));  //记录当前显示的时间
		Output((*num));	   //输出当前显示的时间
	}
	if(k1f>=3) //清零 重新加载
	{
		k1f=0;
		init();
	}
}

void K2() interrupt 2	//INT1外部中断  K2  记录时间/切换显示内容
{
	as=1;  //标记进入防抖
	EX1 = 0;
	if(TR0 == 1)  //计时状态K2动作
	{
		SaveTime((*num));
		(*num)++;
	}
	if((*num)>=10)	  //9人记录全部完成,停止并显示
	{
		TR0 = 0;
		k1f = 2;
		(*num)=(*num)-1;
		Output((*num));
		return ;   // 这个return退出很重要
	}
	if(TR0==0&&k1f!=0)	//停止计时状态下查询各成员的用时成绩
	{
		(*num)=(*num)+1;  //向下切换成员序号  最多9个成员
		if((*num)>=10)
		{
			(*num)=1;	
		}
		Output((*num));  // 显示第(*num)个成绩
	}
}

4. 存储当前时间,显示以储存的时间
原本憨憨的我是用了一个9*8的二维数组,来强行储存存显示内容,就像这样

u8 player[9][8]; //定义9人成员成绩 二维数组 用于存储9个人的成绩

结果编译器狠狠的Duang了一声,成这样了“*** ERROR L107: ADDRESS SPACE OVERFLOW”,有道老师告诉我这是地址空间溢出了。后来,我想过用xdata来修饰一下,但是我发现这个片外RAM访问需要间接寻址,通过翻书看资料智商欠费的我在桌子前徘徊了三趟,终究还是没有掌握这个知识点(给我点儿时间等我搞懂了就回来专门写一篇)。可是问题它不管你会还是不会,它始终就在那不离不弃。于是只能另辟蹊径,别说还真让我想到了PlanB。挠掉三根头发后滚轮让我看见定时器T1的赋初值的方法,于是“储存”和“显示”这两个函数就成了这样:

void SaveTime(u8 nsave)	  /*储存当前时间  */
{
	static u8 n;
	n = nsave-1;
	if(n<9)
	{
		player[n][0] = nsave;	//成员序号
		player[n][1] = (*m)*10+(*ss);	//储存 分钟 和 100ms  对应的数值
		player[n][2] = (*sh)*10+(*sl);	//储存 秒 对应的两个数值
	}
}
void Output(u8 x)/*显示第x个成员的时间成绩*/
{
	static u8 n;
	n = x-1;
	if(n<9&&player[n][0]!=0)
	{
		*num = player[n][0];		//读取 成员序号
		*m   = player[n][1]/10;		//读取 分钟值
		*ss  = player[n][1]%10;		//读取 100ms值
		*sh  = player[n][2]/10;		//读取 秒高位
		*sl  = player[n][2]%10;		//读取 秒低位
	}else  //如果当前没有存入成绩,显示第1名成绩
	{
		*num = player[0][0];
		*m   = player[0][1]/10;
		*ss  = player[0][1]%10;
		*sh  = player[0][2]/10;
		*sl  = player[0][2]%10;	
	}
}  
/* 等会结合最后的完整程序,你应该就能看懂我这蹩脚的编程流程了 */

5. 最后根据上述的思路流程对单片机进行初始化就可以了
下面是程序的全部面貌,为了让各位看官能方便使用,源码也会上传,portues工程文件和上次时钟的一样,等会儿网络通畅了我全部给上传,有需要的同学请进入我的 “主页” >> “资源” ,进行下载。

C程序: test5_StopWatch.c
protues工程: timer.pdsprj

最后,再次感谢您的浏览。

#include 
#define u8 unsigned char
#define u16 unsigned int
#define TScan (65536-4000)//(65536-4000)   动态扫描4ms移位一次

//共阴数码管  0     1     2     3      4    5     6     7     8     9     -    _   “空”
u8 led_cc[]={0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40,0x08,0x00};
//数码管从左往右,第1	 2    3    4    5    6    7    8个
u8 led_scan[8] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
//数码管初始显示内容:1_0-00-0
u8 led_show[]={1,11,0,10,0,0,10,0};	
//(个人喜好)方便清楚识别和操作
u8 *m=&led_show[2];	  //分
u8 *sh=&led_show[4];  //秒高位
u8 *sl=&led_show[5];  //秒低位
u8 *ss=&led_show[7];  //(*ss)* 100毫秒
u8 *num=&led_show[0]; //成员序号

u8 player[9][3]; //定义9人成员成绩 二维数组 用于存储9个人的成绩
u8 as=0; //防抖标识
u8 itemp,jtemp;	//用作循环语句的中间变量
u8 k1f;	//K1键标识
void init(void)/*初始化程序*/	  
{
	EA = 1;	//打开中断总开关
	IE0 = 0;//清除外部中断0 中断源
	EX0 = 1;//打开外部中断0
	IT0 = 1;//下降沿触发
	IE1 = 0;//清除外部中断1 中断源
	EX1 = 1;//打开外部中断1
	IT1 = 1;//下降沿触发

	TMOD = 0x12;//定时器1 方式1工作 ;定时器0 方式2工作
	ET0 = 1;//打开定时器0中断
	TR0 = 0;//关闭定时器0
	TH0 = (256-200);   //200*1us=0.2ms     机械周期:12/f = 1us
	TL0 = (256-200);
	ET1 = 1;//打开定时器1中断
	TR1 = 1;//启动定时器1
	TH1 =  TScan/256;
	TL1 =  TScan%256;

	/*初始成员数组*/
	led_show[0]=1;
	led_show[2]=0;
	led_show[4]=0;
	led_show[5]=0;
	led_show[7]=0;
	for(itemp=0;itemp<9;itemp++)
	{
		for(jtemp=0;jtemp<2;jtemp++)
		{
			player[itemp][jtemp]=0;
		}
	}
	player[0][0]=1;
	/*完成初始成员数组*/
	k1f = 0; //K1状态 标志符
	
}

void SaveTime(u8 nsave)	  /*储存当前时间  */
{
	static u8 n;
	n = nsave-1;
	if(n<9)
	{
		player[n][0] = nsave;
		player[n][1] = (*m)*10+(*ss);
		player[n][2] = (*sh)*10+(*sl);
	}
}

void Output(u8 x)/*显示第x个成员的时间成绩*/
{
	static u8 n;
	n = x-1;
	if(n<9&&player[n][0]!=0)
	{
		*num = player[n][0];
		*m   = player[n][1]/10;
		*ss  = player[n][1]%10;
		*sh  = player[n][2]/10;
		*sl  = player[n][2]%10;
	}else  //如果当前没有存入成绩,显示第1名成绩
	{
		*num = player[0][0];
		*m   = player[0][1]/10;
		*ss  = player[0][1]%10;
		*sh  = player[0][2]/10;
		*sl  = player[0][2]%10;	
	}
}

void main()//主函数(必不可少)
{
	init();
	while(1);
}

void K1() interrupt 0	//INT0外部中断  K1 开始/结束/清零 计时
{

	as=1;  //标记进入防抖
	EX0 = 0;
	k1f++;
	if(k1f==1) //计时开始
	{
		TR0 = 1;
	}
	if(k1f==2) //全部结束计时
	{
		TR0 = 0;
		SaveTime((*num));
		Output((*num));	
	}
	if(k1f>=3) //清零 重新加载
	{
		k1f=0;
		init();
	}
}

void K2() interrupt 2	//INT1外部中断  K2  记录时间/切换显示内容
{
	as=1;  //标记进入防抖
	EX1 = 0;
	if(TR0 == 1)
	{
		SaveTime((*num));
		(*num)++;
	}
	if((*num)>=10)	  //
	{

		TR0 = 0;
		k1f = 2;
		(*num)=(*num)-1;
		Output((*num));
		return ;   // 这个return退出很重要
	}
	if(TR0==0&&k1f!=0)
	{
		(*num)=(*num)+1;  //向下切换成员序号  最多9个成员
		if((*num)>=10)
		{
			(*num)=1;	
		}
		Output((*num));  // 显示第(*num)个成绩
	}
}

void TimePulse() interrupt 1  //T0定时器 产生时间脉冲
{
	static u8 i=0,ii=0;
	i++;  //每0.2ms进入一次中断  500次为0.1s
	if(i>=50)
	{
		i=0;
		ii++;
		if(ii==10)
		{
			(*ss)++;	//ss加1	   *ss为显示的最后一位
			ii=0;
			i=0;
		}
	}
}

void LedShow() interrupt 3	 //T1定时器 动态显示 按键中断  4ms进入一次
{
	static u8 w=0;
	P2 = 0;
	TH1 =  TScan/256;		 //
	TL1 =  TScan%256;
	
	/* 将时间赋值给 数组led_show[] */
	if(*ss>=10)
	{
		*ss=0;
		(*sl)++;
	}else{}
	if(*sl>=10)
	{
		*sl=0;
		(*sh)++;
	}else{}

	if(*sh>=6)
	{
		*sh=0;
		(*m)++;		
	}else{}
	if(*m>=10)
	{
		TR0=0;	 //计时到9分59秒自动停止计时
		*ss=9;
		*sl=9;
		*sh=5;
		*m=9;			
	}else{}
	
	/* 显示led_show[] */
	P1 = led_scan[w];
	P2 = led_cc[ led_show[w] ];		//  
	w++;
	if(w==8)
	{
		w=0;
	}else{}

		
	/* 防抖70*4ms = 280ms */
	if(as>0)
	{
		as++;
		if(as==71)
		{
			as=0;
			IE0=0;	//清空外部中断0中断源
			EX0=1;  //打开外部中断0
			IE1=0;	//清空外部中断1中断源
			EX1=1;  //打开外部中断1
		}else{}
	}else{}
}

END

o(︶︿︶)o 唉!今晚还要码MATLAB的作业,
去窗边吹吹风抽根烟,为了生活为了她,继续努力吧骚年。

你可能感兴趣的:(学习51单片机)