用戶與單片機之間的信息交互需要依賴于兩類設備:輸入設備和輸出設備。前邊講的LED小燈、數碼管、點陣都是輸出設備,本章我們就來學習一下最常用的輸入設備——按鍵,同時還會學到一些硬件電路的基礎知識與C語言函數的一些進階知識。
8.1 單片機最小系統解析
8.1.1 電源
我們在學習過程中,很多指標都是直接用的概念指標,比如我們說+5V代表1,GND代表0等等。但在實際電路中的電壓值并不是完全精準的,那這些指標允許范圍是什么呢?隨著我們所學的內容不斷增多,大家要慢慢培養一種閱讀數據手冊的能力。
比如,我們要使用STC89C52RC單片機的時候,找到它的數據手冊第11頁,看第二項——工作電壓:5.5V~3.4V(5V單片機),這個地方就說明這個單片機正常的工作電壓是個范圍值,只要電源VCC在5.5V~3.4V之間都可以正常工作,電壓超過5.5V是絕對不允許的,會燒壞單片機,電壓如果低于3.4V,單片機不會損壞,但是也不能正常工作。而在這個范圍內,最典型、最常用的電壓值就是5V,這就是后面括號里“5V單片機”這個名稱的由來。除此之外,還有一種常用的工作電壓范圍是2.7V~3.6V、典型值是3.3V的單片機,也就是所謂的“3.3V單片機”。日后隨著大家接觸更多的器件,對這點會有更深刻的理解。
現在我們再順便多了解一點,大家打開74HC138的數據手冊,會發現74HC138手冊的第二頁也有一個表格,上邊寫了74HC138的工作電壓范圍,最小值是4.75V,額定值是5V,最大值是5.25V,可以得知它的工作電壓范圍是4.75V~5.25V。這個地方講這些目的是讓大家清楚的了解,我們獲取器件工作參數的一個最重要、也是最權威的途徑,就是查閱該器件的數據手冊。
8.1.2 晶振
晶振通常分為無源晶振和有源晶振兩種類型,無源晶振一般稱之為crystal(晶體),而有源晶振則叫做oscillator(振蕩器)。
有源晶振是一個完整的諧振振蕩器,它是利用石英晶體的壓電效應來起振,所以有源晶振需要供電,當我們把有源晶振電路做好后,不需要外接其它器件,只要給它供電,它就可以主動產生振蕩頻率,并且可以提供高精度的頻率基準,信號質量也比無源信號要好。
無源晶振自身無法振蕩起來,它需要芯片內部的振蕩電路一起工作才能振蕩,它允許不同的電壓,但是信號質量和精度較有源晶振差一些。相對價格來說,無源晶振要比有源晶振價格便宜很多。無源晶振兩側通常都會有個電容,一般其容值都選在10pF~40pF之間,如果手冊中有具體電容大小的要求則要根據要求來選電容,如果手冊沒有要求,我們用20pF就是比較好的選擇,這是一個長久以來的經驗值,具有極其普遍的適用性。
有源晶振通常有4個引腳,VCC,GND,晶振輸出引腳和一個沒有用到的懸空引腳(有些晶振也把該引腳作為使能引腳)。無源晶振有2個或3個引腳,如果是3個引腳的話,中間引腳接是晶振的外殼,使用時要接到GND,兩側的引腳就是晶體的2個引出腳了,這兩個引腳作用是等同的,就像是電阻的2個引腳一樣,沒有正負之分。對于無源晶振,用我們的單片機上的兩個晶振引腳接上去即可,而有源晶振,只接到單片機的晶振的輸入引腳上,輸出引腳上不需要接,如圖8-3和圖8-4所示。
圖8-3 無源晶振接法
圖8-4 有源晶振接法
8.1.3 復位電路
我們先來分析一下KST-51開發板上的復位電路,如圖8-5所示。
圖8-5 單片機復位電路
當這個電路處于穩態時,電容起到隔離直流的作用,隔離了+5V,而左側的復位按鍵是彈起狀態,下邊部分電路就沒有電壓差的產生,所以按鍵和電容C11以下部分的電位都是和GND相等的,也就是0V。我們這個單片機是高電平復位,低電平正常工作,所以正常工作的電壓是0V,沒有問題。
我們再來分析從沒有電到上電的瞬間,電容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就是0.000564秒,即564us,遠遠大于2個機器周期(2us),在電路設計的時候一般留夠余量就行。
按鍵復位(即手動復位)有2個過程,按下按鍵之前,RST的電壓是0V,當按下按鍵后電路導通,同時電容也會在瞬間進行放電,RST電壓值變化為4700VCC/(4700+18),會處于高電平復位狀態。當松開按鍵后就和上電復位類似了,先是電容充電,后電流逐漸減小直到RST電壓變0V的過程。我們按下按鍵的時間通常都會有幾百毫秒,這個時間足夠復位了。按下按鍵的瞬間,電容兩端的5V電壓(注意不是電源的5V和GND之間)會被直接接通,此刻會有一個瞬間的大電流沖擊,會在局部范圍內產生電磁干擾,為了抑制這個大電流所引起的干擾,我們這里在電容放電回路中串入一個18歐的電阻來限流。
如果有的同學已經想開始DIY設計自己的電路板,那單片機最小系統的設計現在已經有了足夠的理論依據了,可以考慮嘗試了。基礎比較薄弱的同學先不要著急,繼續跟著往下學,把課程都學完了再動手操作也不遲,磨刀不誤砍柴工。
8.2 函數的調用
在一個程序的編寫過程中,隨著代碼量的增加,如果把所有的語句都寫到main函數中,一方面程序會顯得的比較亂,另外一個方面,當同一個功能需要在不同地方執行時,我們就得再重復寫一遍相同的語句。此時,如果把一些零碎的功能單獨寫成一個函數,在需要它們時只需進行一些簡單的函數調用,這樣既有助于程序結構的清晰條理,又可以避免大塊的代碼重復。
在實際工程項目中,一個程序通常都是由很多個子程序模塊組成的,一個模塊實現一個特定的功能,在C語言中,這個模塊就用函數來表示。一個C程序一般由一個主函數和若干個其他函數構成。主函數可以調用其它函數,其它函數也可以相互調用,但其它函數不能調用主函數。在我們的51單片機程序中,還有中斷服務函數,是當相應的中斷到來后自動調用的,不需要也不能由其它函數來調用。
函數調用的一般形式是:
函數名 (實參列表)
函數名就是需要調用的函數的名稱,實參列表就是根據實際需求調用函數要傳遞給被調用函數的參數列表,不需要傳遞參數時只保留括號就可以了,傳遞多個參數時參數之間要用逗號隔開。
那么我先舉例看一下函數調用使程序結構更加條理清晰方面的作用。回顧一下圖6-1所示的程序流程圖和為實現它而編寫的程序代碼,相對來說這個主函數的結構就比較復雜了,很難一眼看清楚它的執行流程。那么如果我們把其中最重要的兩件事——秒計數和數碼管動態掃描功能都用單獨的函數來實現會怎樣呢?來看程序。
看一下,主函數的結構是不是清晰的多了——每隔1ms就去干兩件事,至于這兩件事是什么交由各自的函數去實現。還請大家注意一點:原來程序中的i、cnt、sec這三個變量在放到單獨的函數中后,都加了static關鍵字而變成了靜態變量。因為原來的main()永遠不會結束所以它們的值也總是得到保持的,但現在它們在各自的功能函數內,如不加static修飾那么每次函數被調用時它們的值就都成了初值了,借此也把靜態變量再加深一下理解吧。
當然,這是我們刻意把程序功能做了這樣的劃分,主要目的還是來講解函數的調用,對于這個程序即使你不劃分函數也復雜不到哪里去,但繼續學下去你就能領會到劃分功能函數的必要了。現在我們還是把注意力放在學習函數調用上,有以下幾點需要大家注意:
1、函數調用的時候,不需要加函數類型。我們在主函數內調用SecondCount()和LedRefresh()時都沒有加void。
2、調用函數與被調用函數的位置關系,C語言規定:函數在被調用之前,必須先被定義或聲明。意思就是說:在一個文件中,一個函數應該先定義,然后才能被調用,也就是調用函數應位于被調用函數的下方。但是作為一種通常的編程規范,我們推薦main函數寫在最前面(因為它起到提綱挈領的作用),其后再定義各個功能函數,而中斷函數則寫在文件的最后。那么主函數要調用定義在它之后的函數怎么辦呢?我們就在文件開頭,所有函數定義之前,開辟一塊區域,叫做函數聲明區,用來把被調用的函數聲明一下,如此,該函數就可以被隨意調用了。如上述例程所示。
3、函數聲明的時候必須加函數類型,函數的形式參數,最后加上一個分號表示結束。函數聲明行與函數定義行的唯一區別就是最后的分號,其它的都必須保持一致。這點請尤其注意,初學者很容易因粗心大意而搞錯分號或是修改了定義行中的形參卻忘了修改聲明行中的形參,導致程序編譯不過。
8.3 函數的形式參數和實際參數
上一個例程中在進行函數調用的時候,不需要任何參數傳遞,所以函數定義和調用時括號內都是空的,但是更多的時候我們需要在主調函數和被調用函數之間傳遞參數。在調用一個有參數的函數時,函數名后邊括號中的參數叫做實際參數,簡稱實參。而被調用的函數在進行定義時,括號里的參數叫做形式參數,簡稱形參。我們用個簡單程序例子做說明。
這個演示程序雖然很簡單,但是函數調用的全部內容都囊括在內了。主調函數main和被調用函數add之間的數據通過形參和實參發生了傳遞關系,而函數運算完后把值傳遞給了變量c,函數只要不是void類型,就都會有返回值,返回值類型就是函數的類型。關于形參和實參,還有以下幾點需要注意。
1、函數定義中指定的形參,在未發生函數調用時不占內存,只有函數調用時,函數add中的形參才被分配內存單元。在調用結束后,形參所占的內存單元也被釋放,這個前邊講過了,形參是局部變量。
2、實參可以是常量,也可以是簡單或者復雜的表達式,但是要求他們必須有確定的值,在調用發生時將實參的值傳遞給形參。如上邊這個程序也可以寫成:c = add(1, a+b);
3、形參必須要指定數據類型,和定義變量一樣,因為它本來就是局部變量。
4、實參和形參的數據類型應該相同或者賦值兼容。和變量賦值一樣,當形參和實參出現不同類型時,則按照不同類型數值的賦值規則進行轉換。
5、主調函數在調用函數之前,應對被調函數做原型聲明。
6、實參向形參的數據傳遞是單向傳遞,不能由形參再回傳給實參。也就是說,實參值傳遞給形參后,調用結束,形參單元被釋放,而實參單元仍保留并且維持原值。
8.4 按鍵
8.4.1 獨立按鍵
常用的按鍵電路有兩種形式,獨立式按鍵和矩陣式按鍵,獨立式按鍵比較簡單,它們各自與獨立的輸入線相連接,如圖8-6所示。
圖8-6 獨立式按鍵原理圖
4條輸入線接到單片機的IO口上,當按鍵K1按下時,+5V通過電阻R1然后再通過按鍵K1最終進入GND形成一條通路,那么這條線路的全部電壓都加到了R1這個電阻上,KeyIn1這個引腳就是個低電平。當松開按鍵后,線路斷開,就不會有電流通過,那么KeyIn1和+5V就應該是等電位,是一個高電平。我們就可以通過KeyIn1這個IO口的高低電平來判斷是否有按鍵按下。
這個電路中按鍵的原理我們清楚了,但是實際上在我們的單片機IO口內部,也有一個上拉電阻的存在。我們的按鍵是接到了P2口上,P2口上電默認是準雙向IO口,我們來簡單了解一下這個準雙向IO口的電路,如圖8-7所示。
圖8-7 準雙向IO口結構圖
首先說明一點,就是我們現在絕大多數單片機的IO口都是使用MOS管而非三極管,但用在這里的MOS管其原理和三極管是一樣的,因此在這里我用三極管替代它來進行原理講解,把前面講過的三極管的知識搬過來,一切都是適用的,有助于理解。
圖8-7方框內的電路都是指單片機內部部分,方框外的就是我們外接的上拉電阻和按鍵。這個地方大家要注意一下,就是當我們要讀取外部按鍵信號的時候,單片機必須先給該引腳寫“1”,也就是高電平,這樣我們才能正確讀取到外部按鍵信號,我們來分析一下緣由。
當內部輸出是高電平,經過一個反向器變成低電平,NPN三極管不會導通,那么單片機IO口從內部來看,由于上拉電阻R的存在,所以是一個高電平。當外部沒有按鍵按下將電平拉低的話,VCC也是+5V,它們之間雖然有2個電阻,但是沒有壓差,就不會有電流,線上所有的位置都是高電平,這個時候我們就可以正常讀取到按鍵的狀態了。
當內部輸出是個低電平,經過一個反相器變成高電平,NPN三極管導通,那么單片機的內部IO口就是個低電平,這個時候,外部雖然也有上拉電阻的存在,但是兩個電阻是并聯關系,不管按鍵是否按下,單片機的IO口上輸入到單片機內部的狀態都是低電平,我們就無法正常讀取到按鍵的狀態了。
這個和水流其實很類似的,內部和外部,只要有一邊是低電位,那么電流就會順流而下,由于只有上拉電阻,下邊沒有電阻分壓,直接到GND上了,所以不管另外一邊是高還是低,那電平肯定就是低電平了。
從上面的分析就可以得出一個結論,這種具有上拉的準雙向IO口,如果要正常讀取外部信號的狀態,必須首先得保證自己內部輸出的是1,如果內部輸出0,則無論外部信號是1還是0,這個引腳讀進來的都是0。
8.4.2 矩陣按鍵
在某一個系統設計中,如果需要使用很多的按鍵時,做成獨立按鍵會大量占用IO口,因此我們引入了矩陣按鍵的設計。如圖8-8所示,是我們的KST-51開發板上的矩陣按鍵電路原理圖,使用8個IO口來實現了16個按鍵。
圖8-8 矩陣按鍵原理圖
如果獨立按鍵理解了,矩陣按鍵也不難理解,那么我們一起來分析一下。圖8-8中,一共有4組按鍵,我們只看其中一組,如圖8-9所示。大家認真看一下,如果KeyOut1輸出一個低電平,KeyOut1就相當于是GND,是否相當于4個獨立按鍵呢。當然這時候KeyOut2、KeyOut3、KeyOut4都必須輸出高電平,它們都輸出高電平才能保證與它們相連的三路按鍵不會對這一路產生干擾,大家可以對照兩張原理圖分析一下。
圖8-9 矩陣按鍵變獨立按鍵示意圖
8.4.3 獨立按鍵的掃描
原理搞清楚了,那么下面我們就先編寫一個獨立按鍵的程序,把最基本的功能驗證一下。
本程序固定在KeyOut1上輸出低電平,而KeyOut2~4保持高電平,就相當于是把矩陣按鍵的第一行,即K1~K4作為4個獨立按鍵來處理,然后把這4個按鍵的狀態直接送給LED9~6這4個LED小燈,那么當按鍵按下時,對應按鍵的輸入引腳是0,對應小燈控制信號也是0,于是燈就亮了,這說明上述關于按鍵檢測的理論都是可實現的。
絕大多數情況下,按鍵是不會一直按住的,所以我們通常檢測按鍵的動作并不是檢測一個固定的電平值,而是檢測電平值的變化,即按鍵在按下和彈起這兩種狀態之間的變化,只要發生了這種變化就說明現在按鍵產生動作了。
程序上,我們可以把每次掃描到的按鍵狀態都保存起來,當一次按鍵狀態掃描進來的時候,與前一次的狀態做比較,如果發現這兩次按鍵狀態不一致,就說明按鍵產生動作了。當上一次的狀態是未按下而現在是按下,此時按鍵的動作就是“按下”;當上一次的狀態是按下而現在是未按下,此時按鍵的動作就是“彈起”。顯然,每次按鍵動作都會包含一次“按下”和一次“彈起”,我們可以任選其一來執行程序,或者兩個都用,以執行不同的程序也是可以的。下面就用程序來實現這個功能,程序只取按鍵K4為例。
先來介紹出現在程序中的一個新知識點,就是變量類型——bit,這個在標準C語言里邊是沒有的。51單片機有一種特殊的變量類型就是bit型。比如unsigned char型是定義了一個無符號的8位的數據,它占用一個字節(Byte)的內存,而bit型是1位數據,只占用1個位(bit)的內存,用法和標準C中其他的基本數據類型是一致的。它的優點就是節省內存空間,8個bit型變量才相當于1個char型變量所占用的空間。雖然它只有0和1兩個值,但也已經可以表示很多東西了,比如:按鍵的按下和彈起、LED燈的亮和滅、三極管的導通與關斷等等,聯想一下已經學過的內容,它是不是能用最小的內存代價來完成很多工作呢?
在這個程序中,我們以K4為例,按一次按鍵,就會產生“按下”和“彈起”兩個動態的動作,我們選擇在“彈起”時對數碼管進行加1操作。理論是如此,大家可以在板子上用K4按鍵做做實驗試試,多按幾次,是不是會發生這樣一種現象:有的時候我明明只按了一下按鍵,但數字卻加了不止1,而是2或者更多?但是我們的程序并沒有任何邏輯上的錯誤,這是怎么回事呢?于是我們就得來說說按鍵抖動和消抖的問題了。
8.4.4 按鍵消抖
通常按鍵所用的開關都是機械彈性開關,當機械觸點斷開、閉合時,由于機械觸點的彈性作用,一個按鍵開關在閉合時不會馬上就穩定的接通,在斷開時也不會一下子徹底斷開,而是在閉合和斷開的瞬間伴隨了一連串的抖動,如圖8-10所示。
圖8-10 按鍵抖動狀態圖
按鍵穩定閉合時間長短是由操作人員決定的,通常都會在100ms以上,刻意快速按的話能達到40-50ms左右,很難再低了。抖動時間是由按鍵的機械特性決定的,一般都會在10ms以內,為了確保程序對按鍵的一次閉合或者一次斷開只響應一次,必須進行按鍵的消抖處理。當檢測到按鍵狀態變化時,不是立即去響應動作,而是先等待閉合或斷開穩定后再進行處理。按鍵消抖可分為硬件消抖和軟件消抖。
硬件消抖就是在按鍵上并聯一個電容,如圖8-11所示,利用電容的充放電特性來對抖動過程中產生的電壓毛刺進行平滑處理,從而實現消抖。但實際應用中,這種方式的效果往往不是很好,而且還增加了成本和電路復雜度,所以實際中使用的并不多。
圖8-11 硬件電容消抖
在絕大多數情況下,我們是用軟件即程序來實現消抖的。最簡單的消抖原理,就是當檢測到按鍵狀態變化后,先等待一個10ms左右的延時時間,讓抖動消失后再進行一次按鍵狀態檢測,如果與剛才檢測到的狀態相同,就可以確認按鍵已經穩定的動作了。將上一個的程序稍加改動,得到新的帶消抖功能的程序如下。
大家把這個程序下載到板子上再進行試驗試試,按一下按鍵而數字加了多次的問題是不是就這樣解決了?把問題解決掉的感覺是不是很爽呢?
這個程序用了一個簡單的算法實現了按鍵的消抖。作為這種很簡單的演示程序,我們可以這樣來寫,但是實際做項目開發的時候,程序量往往很大,各種狀態值也很多,while(1)這個主循環要不停的掃描各種狀態值是否有發生變化,及時的進行任務調度,如果程序中間加了這種delay延時操作后,很可能某一事件發生了,但是我們程序還在進行delay延時操作中,當這個事件發生完了,程序還在delay操作中,當我們delay完事再去檢查的時候,已經晚了,已經檢測不到那個事件了。為了避免這種情況的發生,我們要盡量縮短while(1)循環一次所用的時間,而需要進行長時間延時的操作,必須想其它的辦法來處理。
那么消抖操作所需要的延時該怎么處理呢?其實除了這種簡單的延時,我們還有更優異的方法來處理按鍵抖動問題。舉個例子:我們啟用一個定時中斷,每2ms進一次中斷,掃描一次按鍵狀態并且存儲起來,連續掃描8次后,看看這連續8次的按鍵狀態是否是一致的。8次按鍵的時間大概是16ms,這16ms內如果按鍵狀態一直保持一致,那就可以確定現在按鍵處于穩定的階段,而非處于抖動的階段,如圖8-12。
圖8-12 按鍵連續掃描判斷
假如左邊時間是起始0時刻,每經過2ms左移一次,每移動一次,判斷當前連續的8次按鍵狀態是不是全1或者全0,如果是全1則判定為彈起,如果是全0則判定為按下,如果0和1交錯,就認為是抖動,不做任何判定。想一下,這樣是不是比簡單的延時更加可靠?
利用這種方法,就可以避免通過延時消抖占用單片機執行時間,而是轉化成了一種按鍵狀態判定而非按鍵過程判定,我們只對當前按鍵的連續16ms的8次狀態進行判斷,而不再關心它在這16ms內都做了什么事情,那么下面就按照這種思路用程序實現出來,同樣只以K4為例。
這個算法是我們在實際工程中經常使用按鍵所總結的一個比較好的方法,介紹給大家,今后都可以用這種方法消抖了。
-
單片機
+關注
關注
6037文章
44558瀏覽量
635230 -
振蕩器
+關注
關注
28文章
3832瀏覽量
139081 -
C語言
+關注
關注
180文章
7604瀏覽量
136813 -
STC89C52RC
+關注
關注
15文章
64瀏覽量
38639 -
GND
+關注
關注
2文章
539瀏覽量
38712
發布評論請先 登錄
相關推薦
評論