獨立按鍵和矩陣按鍵
我們和微控制器之間進行資訊互動,主要包含兩大類,輸入裝置和輸出裝置。前邊講的LED小燈、數碼管、點陣都是輸出裝置,這節課我們學習一下最常用的輸入裝置——按鍵。在本節課的學習過程中我們還會穿插介紹一點硬體設計的基礎知識。
8.1 微控制器最小系統電路解析
8.1.1 電源
我們在學習過程中,很多指標都是直接用的概念指標,比如我們說+5V代表1,GND代表0等等這些。但在實際電路中是沒有這麼精準的,那這些指標允許範圍是什麼呢?隨著我們所學的內容不斷增多,大家要慢慢培養一種閱讀手冊的能力。
比如我們使用STC89C52RC微控制器的時候,我們找到他的手冊的11頁,第二個選項,工作電壓:
現在我們再順便多瞭解一點,大家開啟74HC138的資料手冊,會發現
8.1.2 晶振
晶振通常分為無源晶振和有源晶振兩種型別,無源晶振一般稱之為crystal(晶體),而有源晶振則叫做oscillator(振盪器)。
有源晶振是一個完整的諧振振盪器,他是利用石英晶體的壓電效應來起振,所以有源晶振需要供電,當我們把有源晶振電路做好後,不需要外接電路,它就可以主動產生振盪頻率,並且可以提供高精度的頻率基準,訊號質量比無源訊號好。
而無源晶振自身無法振盪起來,它需要晶片內部的振盪電路一起工作才能振盪,它允許不同的電壓,但是訊號質量和精度較有源晶振差一些。相對價格來說,無源晶振要比有源晶振價格便宜很多。無源晶振兩側通常都會有兩個電容,一般其容值都選在10pF~40pF之間,如果手冊中有具體電容大小的要求則要根據要求來選電容,如果手冊沒有要求,我們用20pF就是比較好的選擇,這是一個長久以來的經驗值,具有極其普遍的適用性。
我們來認識下比較常用的兩種晶振的樣貌,如圖8-1和圖8-2所示。
圖8-1 27Mhz有源晶振 圖8-2 11.0592M無源晶振
有源晶振通常有4個引腳,VCC,GND,晶振輸出引腳和一個沒有用到的懸空引腳。無源晶振有2個或3個引腳,如果是3個引腳的話,中間引腳是晶振的外殼,使用時要接到GND,兩側的引腳就是晶體的2個引出腳了,這兩個引腳作用是等同的,就像是電阻的2個引腳一樣,沒有正負之分。對於無源晶振,就是用我們的微控制器上的兩個晶振引腳接上去即可,而有源晶振,只接到微控制器的晶振的輸入引腳上,輸出引腳上不需要接,如圖8-3和圖8-4
圖8-3 無源晶振接法 圖8-4 有源晶振接法
8.1.3 復位電路
我們先來分析一下我們的復位電路,如圖8-5所示。
圖8-5 微控制器復位電路
當這個電路處於穩態時,電容起到隔離直流的作用,隔離了+5V,而左側的復位按鍵是彈起狀態,下邊部分電路就沒有電壓差的產生,所以按鍵和電容C11以下部分的電位都是和GND相等的,也就是0V電壓。我們這個微控制器是高電平復位,低電平正常工作,所以正常工作的電壓是0V電壓,完全OK,沒有問題。
我們再來分析從沒有電到上電的瞬間,電容C11上方是5V電壓,下方是0V電壓,根據我們初中所學的知識,這個時候電容C11要進行充電,正離子從上往下充電,負電子從GND往上充電,這個時候電容對電路來說相當於一根導線,全部電壓都加在了R31這個電阻上,那麼RST埠位置是+5V電壓,隨著電容充電越來越多,即將充滿的時候,電流會越來越小,那RST埠上的電壓值等於電流乘以R31的阻值,也就會越來越小,一直到電容完全充滿後,線路上不再有電流,這個時候RST和GND的電位就相等了也就是0V了。
從這個過程上來看,我們加上這個電路,微控制器系統上電後,RST引腳會先保持一小段時間的高電平而後變成低電平,這個過程就是上電覆位的過程。那這個“一小段時間”到底是多少才合適呢?每種微控制器不完全一樣,51微控制器手冊裡寫的是持續時間不少於2個機器週期的時間。復位電壓值,每種微控制器不完全一樣,我們按照通常值0.7Vcc作為復位電壓值,復位時間的計算過程比較複雜,我這裡只給大家一個結論,時間t=1.2RC,我們用的R是4700,C是0.0000001,那計算得知t是564us,遠遠大於2個機器週期(2us),在電路設計的時候一般留夠餘量就行。
按鍵復位(即手動復位)有2個過程,按下按鍵之前,RST的電壓值是0V,當按下按鍵後電路導通,同時電容也會在瞬間進行放電,RST電壓值變化為4700Vcc/(4700+18),會處於高電平復位狀態。當鬆開按鍵後就和上電覆位類似了,先是電容充電,後電流逐漸減小直到RST電壓變0V的過程。我們按下按鍵的時間通常都會有上百毫秒,這個時間足夠復位了。按下按鍵的瞬間,電容兩端的5V電壓(注意不是電源的5V和GND之間)會被直接接通,此刻會有一個瞬間的大電流衝擊,會在區域性範圍內產生電磁干擾,為了抑制這個大電流所引起的干擾,我們這裡在電容放電迴路中串入一個18歐的電阻來限流。
如果有的同學已經開始DIY設計自己的電路板的時候,那微控制器最小系統的設計現在已經有了足夠的理論依據了,可以考慮嘗試了。如在製作過程有有問題可到:微控制器論壇http://www.51hei.com/bbs/ 求助作者會不定期回覆的,基礎比較薄弱的同學先不要著急,繼續跟著往下學,把課程都學完了再動手操作也不遲,磨刀不誤砍柴工。
8.2 函式的呼叫
隨著我們程式設計的程式量的增多,如果把所有的語句都寫到main函式中,一方面程式會寫的比較亂,另外一個方面,當我們一個功能需要多次執行的時候,我們就得不斷重複寫語句,這個時候,就引入了函式呼叫的概念。
一個程式一般由若干個子程式模組組成,一個模組實現一個特定的功能,在C語言中,這個模組就用函式來表示。一個C程式一般由一個主函式和若干個其他函式構成。主函式可以呼叫其他函式,其他函式也可以相互呼叫,但其它函式不能呼叫主函式。在我們的51微控制器程式中,還有中斷服務函式,是當相應的中斷到來後自動呼叫執行的,不需要也不能由其他函式呼叫。
函式呼叫的一般形式是:
函式名(實參列表)
函式名就是需要呼叫的函式的名稱,實參列表就是根據實際呼叫函式要傳遞給被呼叫函式的引數列表,不需要傳遞引數的只加括號就可以,傳遞多個引數時要用逗號隔開。在這裡我以上節課的點陣I❤U的縱向移動的程式改動一下,大家先了解一下基本的函式呼叫。另外,大家不要偷懶,一定把這個程式抄下來做一下實驗加深一下自己的印象。
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code graph[] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3,0xFF,
0x99,0x00,0x00,0x00,0x81,0xC3,0xE7,0xFF,
0x99,0x99,0x99,0x99,0x99,0x81,0xC3,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
unsigned char index = 0; //圖片重新整理索引
void refresh(); //函式宣告
void main()
{
P0 = 0xFF; //P0口初始化
ADDR3 = 0; //選擇LED點陣
ENLED = 0; //LED顯示總使能
TMOD = 0x01; //設定定時器0為模式1
TH0 = 0xFC; //定時器初值,定時1ms
TL0 = 0x67;
TR0 = 1; //開啟定時器0
ET0 = 1; //使能定時器0中斷
EA = 1; //開啟總中斷開關
while(1);
}
void refresh()
{
static unsigned char j = 0;
P0 = 0xFF; //LED點陣動態重新整理
switch (j)
{
case 0: ADDR0=0; ADDR1=0; ADDR2=0; break;
case 1: ADDR0=1; ADDR1=0; ADDR2=0; break;
case 2: ADDR0=0; ADDR1=1; ADDR2=0; break;
case 3: ADDR0=1; ADDR1=1; ADDR2=0; break;
case 4:ADDR0=0; ADDR1=0; ADDR2=1; break;
case 5:ADDR0=1; ADDR1=0; ADDR2=1; break;
case 6:ADDR0=0; ADDR1=1; ADDR2=1; break;
case 7:ADDR0=1; ADDR1=1; ADDR2=1; break;
default: break;
}
P0 = graph[index+j];
j++;
if (j >= 8)
{
j = 0;
}
}
void InterruptTimer0() interrupt 1
{
static unsigned char tmr = 0;
TH0 = 0xFC; //溢位後進入中斷重新賦值
TL0 = 0x67;
refresh(); //函式呼叫
tmr++; //圖片重新整理頻率控制
if (tmr >= 250) //每隔250ms重新整理一幀
{
tmr = 0;
index++;
if (index >= 32)
{
index = 0;
}
}
}
這個程式是對函式的簡單呼叫,但是有以下三個細節需要大家注意一下:
1、函式呼叫的時候,不需要加函式型別。在中斷函式內呼叫重新整理函式的時候我們只寫了refresh(); 而沒有加void。
2、呼叫函式與被呼叫函式的位置關係,C語言規定:函式在被呼叫之前,必須先被定時或宣告。意思就是說:在一個檔案中,一個函式應該先定義,然後才能被呼叫,也就是呼叫函式應位於被呼叫函式的下方。但是作為一種通常的程式設計規範,我們推薦main函式寫在最前面(因為它起到提綱挈領的作用),其後再定義各個子函式,而中斷函式則寫在檔案的最後。這時候,我們就在檔案開頭,所有函式定義之前,開闢一塊區域,叫做函式宣告區,用來把被呼叫的子函式宣告一下,如此,該函式就可以被隨意呼叫了。如上述例程所示。
3、函式宣告的時候必須加函式型別,函式的形式引數,最後加上一個分號表示結束。這點請尤其注意,因為函式定義時最後是不能有分號的,初學者很容易因粗心大意搞錯,導致程式編譯不過。
4、函式自身的型別、宣告的型別以及呼叫的型別必須一致。我們這個例子裡refresh函式的型別是void。
8.3 函式的形式引數和實際引數
上一個程式在進行函式呼叫的時候,我們不需要任何引數傳遞,所以函式定義和呼叫時refresh()括號裡是空的,但是更多的時候我們呼叫函式,主調函式和被呼叫函式之間是要有引數傳遞關係的。在呼叫一個有引數的函式時,函式名後邊括號裡中的引數叫做實際引數,簡稱實參。而被呼叫的函式在進行定義的時候,括號裡的引數就叫做形式引數,簡稱形參,我們找個簡單程式例子做說明。
unsigned char add(unsigned char x, unsigned char y);
void main()
{
unsigned char a = 1;
unsigned char b = 2;
unsigned char c = 0;
c = add(a,b); //呼叫時,a和b就是實參,把函式的返回值賦給c
//運算完後,c的值就是3
while(1);
}
unsigned char add(unsigned char x, unsigned char y) //x和y就是形參
{
unsigned char z = 0;
z = x + y;
return z; //返回值z的型別就是函式add的型別
}
這個演示程式雖然很簡單,但是形參和實參以及函式返回值等全部內容都囊括在內了。主調函式main和被調函式add之間的資料通過形參和實參發生了傳遞關係,而函式運算完了也把值傳遞給了變數c,函式只要不是void型別的函式,都會有返回值,返回值型別就是函式的型別。關於形參和實參,還有以下幾點需要注意。
1、函式定義中指定的形參,在未發生函式呼叫時不佔記憶體,只有函式呼叫時,函式add中的形參才被分配記憶體單元。在呼叫結束後,形參所佔的記憶體單元也被釋放,這個前邊講過了,形參是區域性變數。