Arduinoで実験 (テンキー)

2013/04/26

テンキーがあると、たとえ見えないところに実装されていてもマイコンが存在を主張します。 アナログの回路でもテンキーを使用することはありますが、キーボードがあってこそのコンピュータ。

テンキーやキーボードは、スイッチの集まりで、極端な所、タクトスイッチをズラリと配線すればキーボードになります。

ただ、ひとつのスイッチにひとつのポートを割り振ると 3 x 3 のテンキーでも 9 個のポートを占有します。 実際、数値の入力装置ではエンターキーやエスケープの相当するキーをつけると 4 x 4 の 16 は必要となりさらにポートを占有します。

一般にキーマトリックスと呼ばれる格子状の回路で、縦または横の列を出力、逆を入力とし、各列を順に走査することで接点の位置を検出する方法がとられる。(下図)

上記は 4 x 4 のキーマトリックスで P1 ~ P4 を出力、 P5 ~ P8 を入力としています。 以下はスキャン手順です。

    P1 P2 P3 P4 P5 P6 P7 P8
1 S = HIGH なら sw1 = ON H L L L S - - -
2 S = HIGH なら sw2 = ON L H L L S - - -
3 S = HIGH なら sw3 = ON L L H L S - - -
4 S = HIGH なら sw4 = ON L L L H S - - -
5 S = HIGH なら sw5 = ON H L L L - S - -
6 S = HIGH なら sw6 = ON L H L L - S - -
7 S = HIGH なら sw7 = ON L L H L - S - -
8 S = HIGH なら sw8 = ON L L L H - S - -
9 S = HIGH なら sw9 = ON H L L L - - S -
10 S = HIGH なら sw10 = ON L H L L - - S -
11 S = HIGH なら sw11 = ON L L H L - - S -
12 S = HIGH なら sw12 = ON L L L H - - S -
13 S = HIGH なら sw13 = ON H L L L - - - S
14 S = HIGH なら sw14 = ON L H L L - - - S
15 S = HIGH なら sw15 = ON L L H L - - - S
16 S = HIGH なら sw16 = ON L L L H - - - S

出力側を HIGH にして速攻で入力側を読み取っても HIGH に立ち上がりきっていない場合が考えられるので、完全に出力がが HIGH になるまでほんの少し待機時間を設けても良いかも。

マウスを一回クリックしたにもかかわらずダブルクリックと判断されて勝手にアプリケーションが実行された経験がある人も多いと思う。 このような現象をチャタリングといい、振動等で一時的に非常に短い間隔でオン/オフを繰り返す現象で、瞬間的に起きる場合や持続的に起こる場合がある。 ある程度はプログラムでチャタリングを判断し除去することができる。


◆ キーを読み取る

実際にプログラムを組んでみることにする。 テンキーは自分で作っても良し、製品を買ってきてもよし、ジャンクのテンキーをばらしてみても良いでしょう。

種別 定格・品番 個数 備考
マイコン Arduino UNO 1  
テンキー 4x4 マトリックス 1 8端子

本来は各端子が解放状態にあるのでプルダウンとかするべきなのだろうが実験なので大がかりなことはしない。 入力ピンを内部プルアップして実験する。 まずはチャタリングを考慮しない。 D0 と D1 はシリアル通信で使用するので配線はしない。

//
// 08-01 キ-マトリックス実験スケッチ
//

          //  P1 P2 P3 P4 P5 P6 P7 P8
byte pins[] ={ 2, 3, 4, 5, 6, 7, 8, 9};

void setup() {
  Serial.begin(9600);
  Serial.println("START");

  for (int i=0; i<4; i++) {
    pinMode(pins[i], OUTPUT);          // 出力ピンを設定
    digitalWrite(pins[i], HIGH);       // 初期状態を設定
    pinMode(pins[i+4], INPUT_PULLUP);  // 入力ピンをプルアップで設定
  }
}

void loop() {
  for (int i=0; i<4; i++) {
    digitalWrite(pins[i], LOW);       // 出力ピンを LOW にする
    for (int j=4; j<8; j++) {
      Serial.print( digitalRead(pins[j]) );  // ピンの状態を検出する
    }
    Serial.println();
    digitalWrite(pins[i], HIGH);      // 出力ピンを初期状態へ戻す
  }
  Serial.println("------");
  delay(500);
}

上記スケッチを実行しキーを押したら、キーを押した位置が 0 になる。 もし、位置が違っているのなら pins[] の定義を修正する。


◆ キー関連関数を作る

いくつかキーボード関連の関数を作る。

キーを読み取る関数を作る。 チャタリング対策で 100 回読み取り 95回押している状態を検出したら、キーが押されていると判断する。

キーボードの状態を検出する関数を作成する。 sw1 ~ sw4 あるいは sw5 ~ sw8 等の同列キーの同時押しは検出できないが、異なる列のキーは検出できるため、キーが押されている状態を検出したら検出をやめて押されているキーの番号を返す。 当然スキャン順位の若いキーの優先順位が高くなる。

//
// 08-02 キ-状態検出
//

void loop() {
  Serial.write( keyStatus() );
  Serial.println();
  delay(500);
}

char keyStatus(void) {
  int count;
  char kmap[4][4] = {'1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'};

  for (int i=0; i<4; i++) {
    digitalWrite(pins[i], LOW);
    for (int j=0; j<4; j++) {
      count = 0;
      for (int k=0; k<100; k++) {              // 100回ループ
        if (!digitalRead(pins[j+4])) count++;  // キーが押されていたら 1 加算
      }
      if (count > 94) {                        // カウンタが 95回以上ならキーが押されていると判断
        digitalWrite(pins[i], HIGH);
        return (kmap[i][j]);                   // 押されていたキーを戻り値で返す 
      }
    }
    digitalWrite(pins[i], HIGH);
  }
  return (0);                                  // 何も押されていなければ 0 を返す
}

上記スケッチの loop() 内を書き換え、keyStatus() 関数を追加して実行すると、押されたキーの文字が返される。

delay() 関数を用いて表示しているが、実際はこんなタイミングでキーの取得を調べることはない。

次に一文字入力関数を作る。 一文字入力関数は呼び出されたらキーの状態を調べキーが押されていない状態まで待機し、その後キーが押されるのを待つ。 何かしらのキーが押されるまで関数内をループする。

//
// 08-03 一文字入力関数
//

// loop() は書き換え
void loop() {
  Serial.write(tenkey());
}

// 以下を追加
char tenkey(void) {
  char ret;
  while(keyStatus());

  do {
    ret = keyStatus();
  } while(!ret);

  return (ret);
}

上記の使い方だけではただのスイッチでしかない為、プログラムで数値入力関数やキーの長押し判定関数等を作る必要がある。