2020年5月23日 星期六

四.7 4 X 4 鍵盤實習


所謂的4X4鍵盤就是把16個按鍵以4行4列的方式排列,然後將此4行4列按鍵的接腳以垂直和水平的方向並接在一起,構成對外只有8根接腳的一種結構這樣一來只需8根I/O Pin接腳就可以偵測到16個按鍵的輸入,當然要付出的代價就是軟體程式會變得比較複雜。一般的4X4鍵盤在使用時並沒有規定那些腳要當成輸出那些腳要當作輸入,全看使用者自己的規畫;不過一般都是4根掃瞄輸出腳配4個掃瞄輸入腳,否則會無法全部偵測到

由於ESP8266可使用的I/O Pin腳只有9隻當有需要用到4X4鍵盤這種輸入裝置時如果採用前述的傳統接法可能就會面臨沒有腳位可用的窘境;因此在這個範例中將會教導讀者如何只使用一隻額外的類比輸入腳(A0)就可以實現一個有16個按鍵的4X4鍵盤輸入電路而且重要的是不必使用到什麼特殊或額外的零件只要加上幾個(7個)一般的電阻就可以了!


功能與動作說明

1、 使用ESP8266的A0類比輸入腳及7顆電阻設計一個16按鍵的4X4鍵盤輸入電路

2、 當按鍵被按下時ESP8266模組板上的Pin2內建LED會閃爍一下而且對應的按鍵值會顯示在Arduino IDE的序列監控視窗中


電路圖

圖415 16個按鍵的4X4鍵盤輸入電路麵包板接線與電路圖


圖四15電路圖的右邊讀者們可以發現,整個4X4鍵盤電路只使用了ESP8266一根接腳即”Analog_In”,也就是ESP8266模組板的A0輸入腳;由於原始的ESP8266的A0輸入電壓範圍為0~1V,不過為了配合ESP8266工作電壓是3.3V,所以目前可見到的模組板如NodeMCU、ESP-202或是WeMos等;都會在前面加上一個電阻分壓電路,讓輸入範圍可以到3.3V,在本範例中也是以這個電壓為準。

所以可以只使用一根類比的輸入腳去完成這個4X4鍵盤電路,主要是用了7個電阻構成分壓電路以產生不同按鍵的分壓電壓,這樣我們便可以由所量測到的電壓值去判斷按鍵的位置與內容了。在右圖中4X4鍵盤的下方有4個10K電阻(RK1~RK4)組成分壓電路,分別接到4X4鍵盤四組垂直共同點上,而水平共同點則直接或是透過RK5~RK7這3個電阻接到V3V3。

這個電路的工作原理其實很簡單,以4X4鍵盤最上面一排按鍵(即按鍵’1’~’3’與’A’)來說水平共同接腳的右端是接在V3V3上,所以當這四個按鍵分別被按下時,則透過RK1~RK4這四個電組所組成的分壓電路,在A0輸入腳所得到的電壓分別為:


按鍵‘1’ 🡺 V3V3   (V3V3直接接在RK1上)

  按鍵‘2’ 🡺 V3V3/2  (RK1與RK2兩個10K電阻分壓)

按鍵‘3’ 🡺 V3V3/3  (RK1~RK3三個10K電阻分壓)

按鍵‘A’ 🡺 V3V3/4  (RK1~RK4四個10K電阻分壓)


而上面第二排按鍵(即按鍵’4’~’6’與’B’)的水平共同接腳則是經過一40K的電阻(即R7)接在V3V3上,所以當這四個按鍵被按下時,會再經過RK1~RK4這四個電組所組成的分壓電路,這時在A0輸入腳所得到的電壓分別為:


按鍵‘4’ 🡺 V3V3/5  (R7與RK1兩個電阻分壓)

按鍵‘5’ 🡺 V3V3/6  (R7與RK1+RK2三個電阻分壓)

按鍵‘6’ 🡺 V3V3/7  (R7與RK1~RK3四個電阻分壓)

按鍵‘B’ 🡺 V3V3/8  (R7與RK1~RK4五個電阻分壓)


經過上述方式的推演,我們可得到表三.2的結果,也就是4X4鍵盤的16個按鍵被按下時ESP8266模組板的A0類比輸入上所得到的電壓值,而其中最重要的一點,就是這16個按鍵電壓值都是唯一且沒有相同的!如此一來我們便可以透過測量A0這根類比輸入腳的輸入電壓值來決定是那一個按鍵被按下,這樣我們就可以只使用一隻類比輸入腳來完成我們的4X4鍵盤輸入電路。


按鍵值

分壓電壓

V3V3

1/2V3V3

1/3 V3V3

1/4 V3V3

按鍵值

分壓電壓

1/5 V3V3

1/6 V3V3

1/7 V3V3

1/8 V3V3

按鍵值

分壓電壓

2/3 V3V3

2/5 V3V3

2/7 V3V3

2/9 V3V3

按鍵值

分壓電壓

4/5 V3V3

4/9 V3V3

4/13 V3V3

4/17 V3V3

表四-1 4X4鍵盤按鍵值與分壓電壓之對照


目前市售的4X4鍵盤外型與種類很多,圖4-16中是一些常見的4X4鍵盤外觀,一般來說這些鍵盤的對外8根接腳大都是位在下方;在照片5中的左上方有焊接上電阻的白色鍵盤便是筆者這次所專題製作使用的,上方的電阻即為圖4-15中的7個分壓電組它的接腳之所以會在上方,主要是筆者為了方便使用而自行修改的。不管是那一種接法或是哪一種的按鍵排列位置,本上我們都可以使用,只是到時候程式中的參數資料修改一下就可以了,這部份位會在後面的範例中為各位說明。

圖416 常見的4X4鍵盤外觀


至於圖4-17則是現在很流行的薄膜式4X4鍵盤外觀與內部構造,外表看起來很美觀,只是由於厚度很薄,在固定與操作使用時較不方便,所以這次沒有拿來使用,讀者們可依自己的喜好去選擇採用那一種。



圖417 薄膜式4X4鍵盤外觀與內部構造


程式說明與列表

在這個實習範例中並不需要引用到任何的函式庫但是必須參考前面的表三.2之”4X4鍵盤按鍵值與分壓電壓對照表在程式的2~5行主要是訂義兩個和鍵盤轉換有關的常數陣列第2行的”keyValue[]”是存放16個按鍵的輸入電壓轉換成數位後的結果在ESP8266內部的數位至類比電路(ADC)使用了10個位元所以轉換的結果介於0~1023要注意的是由於實際鍵盤上的數字排列和接線的差異這個常數陣列其中的元數值的順序必須經過實測才能確定在”keyValue[]” 這個陣列中所定義的變數值是由大而小依序排列過去這是因為程式碼是以比較大小的方式去決定可能的按鍵值陣列中所有的值都是經過實際測試並且加以調適過的基本上讀者們只要電路跟零件都按照文中的建議應該都可以直接使用沒有問題

在”keyValue[]”這個陣列中存放的定義的按鍵輸入電壓傳換值都比實際值要來的低一些,兩者之間有差異主要是為了要有一些容許的誤差之所以要有這些容許誤差存在,是因為我們實際所使用的分壓電阻本來就有誤差而且不是所有的電阻值在市面上都找的到例如電路中的RK7(40K)就不是一個常見的電阻值所以在此是改用阻值為39K的電阻這樣當然就會有誤差了! 


  1. // 不同按鍵之類比輸入轉換值陣列:

  2. //   按鍵對應值 :  1   E  7  2  0  8  3  F  9  A  D  C  4  5  6  B

  3. const  int  keyValue[]={950,800,670,520,460,410,350,315,280,255,245,230,210,170,140,110};

  4. // 類比輸入轉換值與實際鍵盤值對應轉換陣列

  5. const byte keyNumbers[]={1,0x0e,7,2,0,8,3,0x0f,9,0x0a,0x0d,0x0c,4,5,6,0x0b};


由於實際上鍵盤按鍵所標示的值只是一個符號而已,當我們測試到它按下去時,所得到的電壓轉換值並非如所看見的數字一樣成比例的關係,例如”0、1、2、3、4”這幾個按鍵的轉換對應值分別為”460、950、511、350、210”,看起來並沒有按照鍵盤數值的大小依序出現;所以這時我們就需要一個陣列來將轉換值對應到鍵盤上所看到的按鍵值,而這就是程式第5行的”keyNumbers[]”這個陣列的用途。在”keyNumbers[]”這個陣列中的內容,其實就是”keyValue[]”這個陣列中所定義的變數值對應到鍵盤按鍵標示的符號值,例如在”keyNumbers[]”這個陣列中的第一個元素值為”950”,第二個元素值為”800”,而”keyValue[]”這個陣列中第一個元素值為’1’, 第二個元素值為’0E’(即按鍵”E/*”),也就是實際對應到的鍵盤按鍵標示符號;其他的內容依此類推,讀者們可自行測試看看每一個按鍵的轉換結果看是不是都符合所言?

接著讓我們再回頭來看一下程式部分,其中11~17行是初始化設定(void setup())區,主要是設定GPIO2腳為輸出腳,而這根腳也就是ESP8266模組板內建的LED輸出腳;此外還初始化序列埠工作的傳輸速率為115200 bps,這個序列通訊埠一定要經過初始化的過程才能真正開始使用。

再來的16~31行程式是我們的主程式(void loop())區,在說明主程式的內容之前,必須要先介紹兩個副程式,即39~50行的byte findKeyNumber(),和53~64行的boolean keyPress()首先我們看一下keyPress()這個副程式,它是用來偵測4X4鍵盤的16個按鍵是否有任一個被按下?其原理可參考圖四.7,在此電路圖中,當這16個按鍵沒有任何一個被按下時, 因為ESP8266模組板的A0類比輸入腳是接到RK1這個電阻的上端,而RK1這個電阻的下端是接地,當所有的按鍵都是開路狀態時,A0的輸入電壓將會是等於地電位的0伏,但為了考慮到電源穩定與雜訊的問題,所以我們還是得保留一點寬容度;由於ESP8266模組板的ADC轉換器解析度是10個位元(即轉換值為0~1023),而從圖四.7中我們可以看出,按鍵[B]的電壓分壓值會是最小(=1/8V3v3),而程式第2行的”keyValue[]”這個陣列變數所存放的最小也是最後一個的設定值為110,這就是按鍵’B’的電壓轉換判斷值;所以筆者設定40為門檻,也就是說如果A0輸入電壓的轉換值大於40,則視為有鍵被按下,此時副程式keyPress()會傳回”true”的結果,反之如果沒有任何鍵被按下則傳回”false”,所以我們便可以由副程式keyPress()傳回來的結果得知是否有鍵被按下。

至於findKeyNumber()這個副程式,則是由鍵盤按鍵輸入電壓的轉換值以比較大小的方式找出實際對應的按鍵數值;比較與搜尋動作主要是由44~49行的for迴圈程式所完成,由於程式第2行的”keyValue[]”這個陣列變數所存放的,是16個按鍵的分壓電壓所轉換出來的對照值,且是由大到小排列,而第4行的”keyNumbers[]”陣列中元素的內容,則是依”keyValue[]”這個陣列變數的內容所對應的實際按鍵輸值來建立的,因此我們可以依鍵盤按鍵輸入電壓的轉換值(KeyIn),去跟”keyValue[]”這個陣列變數中的元素比較之後,得到相對的位置值(即for迴圈程式中的迴圈值i),然後再由這個位置值從第二個”keyNumbers[]”陣列中,找出該鍵盤按鍵輸入電壓轉換值所對應到的實際按鍵值了!例如按鍵[7]的電壓轉換值為698,因為:


800(keyValue[1]) > 698 > 670(keyValue[2])


所以這個鍵盤按鍵輸入電壓的轉換值的位置參考值便會是2,而在”keyNumbers[]”這個陣列中第3個元素是7(keyNumbers[2]=7),因此在呼叫findKeyNumber()這個副程式之後,會傳回’7’這個結果,也就是該按鍵所看到的數值了。

在主程式(即loop())中,一開始會先呼叫keyPress()這個副程式,以測試是否有任一鍵被被按下(第21行),若有則先讓指示用LED(即ESP8266模組板的GPIO2腳)閃爍一下,以提醒使用者有鍵被按下(第23~25行); 然後再呼叫findKeyNumber()副程式找出對應的按鍵值出來,並顯示在串列監控視窗中(第26~29行)。後面的第30行程式則以”while”流程的方式再次的呼叫keyPress()這個副程式,不過這次不是測是否有鍵被按下,反而是用來測試使用者是否已經放開按鍵,因為如果不加上這個動作,被按下的按鍵值會連續不斷的顯示在Arduino IDE的序列監控視窗中,如果這個按鍵是用來控制一些動作的話,這個動作就會被連續執行,因而造成一些不可預期的結果,這在實務上是一定要考慮跟避免的。當keyPress()這個副程式傳回”false”的結果時,代表按鍵已經被放開,程式會結束第32、33行”while”這個無窮迴圈並且在延遲30ms等按鍵的彈跳現象結束後重新開始主迴圈程式。


以下是完整的程式列表:



 

// 不同按鍵之類比輸入轉換值陣列:

//    按鍵對應值 : 1  E  7  2  0  8  3  F  9  A  D  C  4  5  6  B

const  int  keyValue[]={950,800,670,520,460,410,350,315,280,255,245,230,210,170,140,110};

// 類比輸入轉換值與實際鍵盤值對應轉換陣列

const  byte  keyNumbers[]={1,0x0e,7,2,0,8,3,0x0f,9,0x0a,0x0d,0x0c,4,5,6,0x0b};

// 數字與ASCII碼字元轉換陣列

const  char keyChar[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

byte  keyNumber; // 鍵盤按鍵輸入對應值

int  KeyIn; // 按鍵類比電壓輸入轉換值

//

void  setup()

{

    pinMode(2,OUTPUT);         // 設定ESP8266的第2腳為輸出

    digitalWrite(2,0);            // 設定第2腳的輸出除為0

    Serial.begin(115200); //初始化串列監控視窗並設定傳輸速率為115200Bps

    Serial.println("Start...");      // 在串列監控視窗中顯示開始提示訊息

}

//

void  loop()

{

  if(keyPress())                      // 測試是否有任一鍵被被按下

  {   // 有鍵被被按下將按鍵轉換結果顯示在串列監控視窗中

    digitalWrite(2,HIGH);         // 將內建在Arduino Uno地13腳的LED點亮

    delay(50);

    digitalWrite(2,LOW);       

    keyNumber=findKeyNumber(); // 將按鍵輸入轉換值轉換成實際按鍵對應值

    Serial.println(keyNumber);    // 將按鍵轉換對應值顯示在串列監控視窗

    Serial.print("Key => ");

    Serial.println(keyChar[keyNumber]); // 這次以字元的形式將按鍵轉換對應值顯示在串列監控視窗中

  }

  while(keyPress())                   // 等待使用者將按鍵放開

  {}

  delay(30);

}  

 

// 4 X 4鍵盤按鍵轉換副程式 : 依偵測到的輸入電壓判斷並轉換成實際對應的鍵盤按鍵值

byte findKeyNumber(){

    delay(10);

    KeyIn=analogRead(A0);       // 讀取類比輸入腳A0的電壓轉換值

    Serial.println(KeyIn);       // 將類比輸入腳A0的電壓轉換值顯示在串列監控視窗中

    for(byte i=0;i<16;i++)      // 以預設的16個按鍵轉換範圍陣列去判斷可能的按鍵值

    {

      if(KeyIn>keyValue[i])

        return(keyNumbers[i]) ; // 將測試的結果回傳給呼叫者

    }

}

 

// 按鍵被按下偵測副程式 : 若有任一鍵被被按下會傳回true

boolean keyPress(){

  KeyIn=analogRead(A0);       // 讀取類比輸入腳A0的電壓轉換值

  if(KeyIn>40)                // 測試電壓輸入值,若有任一鍵被按下其電壓轉換值必定會在40以上

  {

    delay(50);                // 等待50mS好讓輸入腳狀態穩定

    KeyIn=analogRead(A0);     // 再次讀取電壓轉換值

    return(true);             // 若有任一鍵被被按下傳回true

  }

  else

    return(false);            // 若沒有鍵被被按下則傳回false

}    


程式名稱:Key4X4_1.ino


沒有留言:

張貼留言