写在开头:因为程序文件是先上传的,里面有些注释掉的内容是与程序完全无关,是本人原本写程序时无用的部分,可以不用理会,另外有些变量没有写注明具体的作用,只看程序比较难理解,我在写这篇文章时有所解释,可以再这里看一下。
简易出租车计步器的要求是:
(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上显示绝对没有问题,只是有些画蛇添足了。