51单片机程序设计——简易出租车计步器

写在开头:因为程序文件是先上传的,里面有些注释掉的内容是与程序完全无关,是本人原本写程序时无用的部分,可以不用理会,另外有些变量没有写注明具体的作用,只看程序比较难理解,我在写这篇文章时有所解释,可以再这里看一下。

简易出租车计步器的要求是:

(1)LED数码管或LCD显示路程和价格,价格单位为元,小数点后保留1位;路程单位为km,小数点后保留1位;
(2)起步价6.0元,3km;3km以后按1.2元/km计费;
(3)其他功能(创新部分),如显示时间、温湿度等;
(4)系统调试、分析、总结与功能实现。

这个程序我用的是郭天祥的51单片机,只用了那个开发板上的资源,没有连接任何外设,做出来的东西也相当简单,用到了单片机上的六位数码管。

一、设计思路

郭天祥的51单片机上P3.4到P3.7连接的是独立键盘s1到s4,这个程序只用到s1,数码管六位都用到了。数码管的前三位显示路程,后三位显示价格。路程的初始值为0.0,每按一次s1路程加0.5公里。价格和路程之间是一个分段函数的关系,路程<3.0时,价格始终等于6.0;路程>大于3.0时,价格始终等于(路程-3)*1.2+6.0。
做完程序用wps做了个流程图,如下:
在这里插入图片描述
下面是几个效果图,是从当时拍的视频里截的图,我现在身边没有郭天祥的51单片机只能用以前拍的了。
在这里插入图片描述
1、路程初始值
在这里插入图片描述
2、按了三次按键后,路程为1.5,价格仍为6
在这里插入图片描述
3、按了七次按键后,路程为3.5,价格为12.0(3+0.5 * 1.2=12.0)

二、程序作用简单描述,头文件,函数声明,段选、位选、按键使用I/O口定义,全局变量定义

首先使用宏定义:

	#define uchar unsigned char
	#define uint unsigned int

之后可减少编程负担,一般写单片机程序是都要在开头写上。
然后定义P3.4到P3.7分别为独立键盘s1到s4,其中只用到s1,其余三个按键想添加其他功能时可以酌情使用。

	sbit s1=P3^4;	 //定义P34口是s1
	sbit s2=P3^5;	 //定义P35口是s2
	sbit s3=P3^6;	 //定义P36口是s3
	sbit s4=P3^7;	 //定义P37口是s4

定义了三个数码管显示使用的数组:

uchar code table1[]={0x3f,0x06,0x5b,0x4f,
					 0x66,0x6d,0x7d,0x07,
				     0x7f,0x6f,0x77,0x7c,
			 	 	 0x39,0x5e,0x79,0x71};

第一个是显示从0到F的数组,当然只用到其中的0到9

	uchar code table2[]={0xbf,0x86,0xdb,0xcf,
						 0xe6,0xed,0xfd,0x87,
						 0xff,0xef,0xf7,0xfc,
						 0xb9,0xde,0xf9,0xf1};		

第二个也是显示0到F的数组,但是显示每个数时都会同时显示这个数码管右下角的小数点

	uchar code table3[]={0x7c,0x79,0x6e,0x3f,0x37,0x5e};

第三个算是个字母数组,总共6个元素,分别代表b,e,y,o,n,d,是用来在价格超过99.9,价格爆表时显示在整个六位数码管上的。
其余全局变量与函数声明的内容会在后面提到,这里先不说了

下面是主函数前包括程序作用简单描述,头文件,函数声明,段选、位选、按键使用I/O口定义和全局变量定义的内容的程序:

//按键一次加0.5公里,左三数码管显示路程,右三数码管显示对应的价格,
//每按一次独立按键k1加0.5公里,可更改
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
	
sbit s1=P3^4;	 //定义P34口是s1
sbit s2=P3^5;	 //定义P35口是s2
sbit s3=P3^6;	 //定义P36口是s3
sbit s4=P3^7;	 //定义P37口是s4

uchar code table1[]={0x3f,0x06,0x5b,0x4f,
					 0x66,0x6d,0x7d,0x07,
					 0x7f,0x6f,0x77,0x7c,
				 	 0x39,0x5e,0x79,0x71};
uchar code table2[]={0xbf,0x86,0xdb,0xcf,
					 0xe6,0xed,0xfd,0x87,
					 0xff,0xef,0xf7,0xfc,
					 0xb9,0xde,0xf9,0xf1};
uchar code table3[]={0x7c,0x79,0x6e,0x3f,0x37,0x5e};

sbit dula=P2^6;
sbit wela=P2^7;
										
uchar key;										
uint journey=0,cost=60;										
uint shi,ge,td;
uchar i,a=0xfe;
										
void delayms(uint);
void displayj(uint,uint,uint);
void displayc(uint,uint,uint);
void beyond();										
unsigned char keypros1();

三、程序主体内容

所有的数值都乘以了10,在数码管显示时将数字的百位当做十位,十位当做个位并加上小数点,个位当做十分位。如果不习惯这种用法,可以自己把程序里的数字除以10来使用。

void main()
{
	while(1)
	{
		if(keypros1())//检测是否按下s1
			journey+=5;					//每按一次按键,加5÷10=0.5公里,可更改,例,改为3,即为一次加0.3公里
		if(journey>30)
			cost=60+(journey-30)*12;
			
		if(journey<100)
			shi=0;
		else shi=journey/100;
		ge=journey/10%10;
		td=journey%10;
		displayj(shi,ge,td);
		
		if(cost<100)
			shi=0;
		else if(cost>999)
			beyond();
		else shi=cost/100;
		ge=cost/10%10;
		td=cost%10;		
		if(cost<100)
			shi=0;
		else shi=cost/100;		
		displayc(shi,ge,td);
		
		
	}
}

主函数主体为一个while函数,里面的内容无限循环,journey代表路程,为全局变量,初始值为0,keypros1()检测s1是否按下,若按下返回值1,反之,返回值0。

unsigned char keypros1()
{
  key=0;
	if(s1==0)		  //检测按键s1是否按下
	{	
		delayms(10);   //消除抖动 一般大约10ms
		if(s1==0)	 //再次判断按键是否按下
		{	
			key=1;		
		}
		while(!s1);	 //检测按键是否松开
	}
	if(key==1)
			return 1;
	else return 0;
}

由于本人在编程序时把所有的数字都乘以了10,也就是说,按键一次加0.5变成了加5,主函数循环检测是否按下按键,返回值为1,则journey加5。
全局变量cost,即价格的初始值为60,journey>30之前,不改变cost的值,journey>30之后,始终有cost=(journey-30)*12+60。
计算完journey和cost的值后,会将journey的百位赋给shi,十位赋给ge,个位赋给td。
shi代表十位,ge代表个位,td代表十分位,是英文tenths digit 的缩写。
然后使用函数displayj(shi,ge,td);这个函数是显示路程的函数:

void displayj(uint shi,uint ge,uint td)
{
	if(shi!=0)//shi为0时不显示
	{
		dula=1;
		P0=table1[shi];
		dula=0;
		P0=0xff;	
		wela=1;
		P0=0xfe;
		wela=0;
		delayms(1);
	}
	dula=1;
	P0=table2[ge];
	dula=0;
	P0=0xff;	
	wela=1;
	P0=0xfd;
	wela=0;
	delayms(1);
	
	dula=1;
	P0=table1[td];
	dula=0;
	P0=0xff;	
	wela=1;
	P0=0xfb;
	wela=0;
	delayms(1);	
}

当shi为0时不显示,如下图:
在这里插入图片描述
shi和td显示时使用数组table1,即没有小数点的数组;
ge显示时使用数组table2,即有小数点的数组;

之后再将cost的百位赋给shi,十位赋给ge,个位赋给td。然后使用displayc(uint shi,uint ge,uint td);与显示journey的函数大同小异:

void displayc(uint shi,uint ge,uint td)
{
	if(shi!=0)
	{
		dula=1;
		P0=table1[shi];
		dula=0;
		P0=0xff;	
		wela=1;
		P0=0xf7;
		wela=0;
		delayms(1);
	}

	dula=1;
	P0=table2[ge];
	dula=0;
	P0=0xff;
	wela=1;
	P0=0xef;
	wela=0;
	delayms(1);
	
	dula=1;
	P0=table1[td];
	dula=0;
	P0=0xff;	
	wela=1;
	P0=0xdf;
	wela=0;
	delayms(1);	
}

如果一直按s1直到cost>999,即显示值>99.9,则爆表。这时如果不加以限制,十位将会显示十六进制,个位仍是十进制,完全乱套。
所以,主函数中也会循环检测cost是否大于999,当cost>999时,会进入beyond()函数,函数如下:

void beyond()
{
	while(1)
	{
		for(i=0;i<=5;i++)
		{
			dula=1;
			P0=table3[i];
			dula=0;
			P0=0xff;

			wela=1;
			P0=a;						   //a初值为0xfe,第一个数码管亮
			wela=0;
			delayms(1);				   //延时1s
			a=_crol_(a,1);			       //循环左移
			if(a==0xbf)					   
				a=0xfe;
		}
	}
}	

进入这个函数后就会六位数码管无限循环显示beyond,不再返回主函数,效果如下:
在这里插入图片描述

四、仿真图

画仿真图时,因为连接数码管的是P0,所以必须外接上拉电阻,如果是其他I/O口则不必。
在这里插入图片描述
另外,程序中如果先段选后位选在实物上显示很正常,在proteus中显示的就有问题,这个程序也一样;反之,先位选后段选就都没有问题了。当然,如果有充足的I/O口,也可以使用两个8位I/O口分别段选和位选,这样proteus上显示绝对没有问题,只是有些画蛇添足了。

发布了3 篇原创文章 · 获赞 2 · 访问量 775

猜你喜欢

转载自blog.csdn.net/zh_j_wei/article/details/104213278