寫在開頭:因爲程序文件是先上傳的,裏面有些註釋掉的內容是與程序完全無關,是本人原本寫程序時無用的部分,可以不用理會,另外有些變量沒有寫註明具體的作用,只看程序比較難理解,我在寫這篇文章時有所解釋,可以再這裏看一下。
簡易出租車計步器的要求是:
(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上顯示絕對沒有問題,只是有些畫蛇添足了。