フォト
2017年12月
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
無料ブログはココログ

« キーボードの作成 その1 | トップページ | ベットに寝ているタマちゃん »

2017年9月 4日 (月)

キーボードの作成 その2

前回の続きです。

64キーを次のような感じに割り付けました。
64キーだと足りないため、Fnキーを押すことで、ファンクションキー等の水色キーが
入力できます。

01

押したボタン入力は、PS/2キーボード I/Fの信号に変換します。
とりあえず、IchigoJamにて利用出来ることを確認しました。

Dscn6955

PS/2 キーボードのエミュレーションは ps2devライブラリを利用しました。
・PS2 mouse interface for Arduino
  http://playground.arduino.cc/ComponentLib/Ps2mouse

スケッチは次のような感じです。
Fnキーの処理とキーの長押し時のリピート処理がちょっと面倒でした。

(2017/9/6 スケッチを修正しました)

#include <Arduino.h>
#include <ps2dev.h>

#define KB_CLK      9  // PS/2 CLK  IchigoJamのKBD1に接続
#define KB_DATA     10 // PS/2 DATA IchigoJamのKBD2に接続

#define SOUT_PIN   2 // シリアルアウト
#define CLK_PIN    3 // クロック
#define LATCH_PIN  4 // ラッチ
#define SCN1_PIN   5 // ライン1
#define SCN2_PIN   6 // ライン2
#define SCN3_PIN   7 // ライン3
#define SCN4_PIN   8 // ライン4

#define KEY_NUM 64 // キー数
// キーリピートの定義
#define REPEATTIME      5   // キーを押し続けて、REP_INTERVALxREPEATTIMEmsec後にリピート開始
#define EMPTY           0   // リピート管理テーブルが空状態
#define MAXKEYENTRY     64  // リピート管理テーブルサイズ
#define REP_INTERVAL    10  // リピート間隔 100msec

PS2dev keyboard(KB_CLK, KB_DATA); // PS/2デバイス

// スキャンコードテーブル
const uint8_t scan_table[][4] =  {
 {  0x76,0x00  ,  0x05,0x00  ,  },  // 0 ESC
 {  0x16,0x00  ,  0x06,0x00  ,  },  // 1 1
 {  0x1E,0x00  ,  0x04,0x00  ,  },  // 2 2
 {  0x26,0x00  ,  0x0C,0x00  ,  },  // 3 3
 {  0x25,0x00  ,  0x03,0x00  ,  },  // 4 4
 {  0x2E,0x00  ,  0x0B,0x00  ,  },  // 5 5
 {  0x36,0x00  ,  0x83,0x00  ,  },  // 6 6
 {  0x3D,0x00  ,  0x0A,0x00  ,  },  // 7 7
 {  0x3E,0x00  ,  0x01,0x00  ,  },  // 8 8
 {  0x46,0x00  ,  0x09,0x00  ,  },  // 9 9
 {  0x45,0x00  ,  0x78,0x00  ,  },  // 10 0
 {  0x4E,0x00  ,  0x07,0x00  ,  },  // 11 -
 {  0x55,0x00  ,  0x00,0x00  ,  },  // 12 ^
 {  0x6A,0x00  ,  0x00,0x00  ,  },  // 13 ¥
 {  0xE0,0x6C  ,  0xE0,0x70  ,  },  // 14 Home
 {  0x66,0x00  ,  0xE0,0x71  ,  },  // 15 BS
 {  0x29,0x00  ,  0x0D,0x00  ,  },  // 16 Space
 {  0x15,0x00  ,  0x6C,0x00  ,  },  // 17 q
 {  0x1D,0x00  ,  0x75,0x00  ,  },  // 18 w
 {  0x24,0x00  ,  0x7D,0x00  ,  },  // 19 e
 {  0x2D,0x00  ,  0x7B,0x00  ,  },  // 20 r
 {  0x2C,0x00  ,  0xE0,0x4A  ,  },  // 21 t
 {  0x35,0x00  ,  0x00,0x00  ,  },  // 22 y
 {  0x3C,0x00  ,  0x00,0x00  ,  },  // 23 u
 {  0x43,0x00  ,  0x00,0x00  ,  },  // 24 i
 {  0x44,0x00  ,  0x00,0x00  ,  },  // 25 o
 {  0x4D,0x00  ,  0x00,0x00  ,  },  // 26 p
 {  0x54,0x00  ,  0x00,0x00  ,  },  // 27 @
 {  0x5B,0x00  ,  0x00,0x00  ,  },  // 28 [
 {  0x00,0x00  ,  0x00,0x00  ,  },  // 29 Fn
 {  0xE0,0x69  ,  0x00,0x00  ,  },  // 30 End
 {  0x59,0x00  ,  0x00,0x00  ,  },  // 31 RghtShift
 {  0x12,0x00  ,  0x58,0x00  ,  },  // 32 LeftShift
 {  0x1C,0x00  ,  0x6B,0x00  ,  },  // 33 a
 {  0x1B,0x00  ,  0x73,0x00  ,  },  // 34 s
 {  0x23,0x00  ,  0x74,0x00  ,  },  // 35 d
 {  0x2B,0x00  ,  0x79,0x00  ,  },  // 36 f
 {  0x34,0x00  ,  0x7C,0x00  ,  },  // 37 g
 {  0x33,0x00  ,  0x00,0x00  ,  },  // 38 h
 {  0x3B,0x00  ,  0x00,0x00  ,  },  // 39 j
 {  0x42,0x00  ,  0x00,0x00  ,  },  // 40 k
 {  0x4B,0x00  ,  0x00,0x00  ,  },  // 41 l
 {  0x4C,0x00  ,  0x00,0x00  ,  },  // 42 ;
 {  0x52,0x00  ,  0x00,0x00  ,  },  // 43 :
 {  0x5D,0x00  ,  0x00,0x00  ,  },  // 44 ]
 {  0xE0,0x71  ,  0x00,0x00  ,  },  // 45 PageUp
 {  0xE0,0x75  ,  0x00,0x00  ,  },  // 46 ↑
 {  0xE0,0x7A  ,  0x00,0x00  ,  },  // 47 PageDown
 {  0x14,0x00  ,  0x13,0x00  ,  },  // 48 LeftCtrl
 {  0x1A,0x00  ,  0x69,0x00  ,  },  // 49 z
 {  0x22,0x00  ,  0x72,0x00  ,  },  // 50 x
 {  0x21,0x00  ,  0x7A,0x00  ,  },  // 51 c
 {  0x2A,0x00  ,  0x70,0x00  ,  },  // 52 v
 {  0x32,0x00  ,  0x71,0x00  ,  },  // 53 b
 {  0x31,0x00  ,  0x00,0x00  ,  },  // 54 n
 {  0x3A,0x00  ,  0x00,0x00  ,  },  // 55 m
 {  0x41,0x00  ,  0x00,0x00  ,  },  // 56 ,
 {  0x49,0x00  ,  0x00,0x00  ,  },  // 57 .
 {  0x4A,0x00  ,  0x00,0x00  ,  },  // 58 /
 {  0x51,0x00  ,  0x00,0x00  ,  },  // 59 ¥
 {  0x5A,0x00  ,  0x00,0x00  ,  },  // 60 Enter
 {  0xE0,0x6B  ,  0xE0,0x2F  ,  },  // 61 ←
 {  0xE0,0x72  ,  0xE0,0x1F  ,  },  // 62 ↓
 {  0xE0,0x74  ,  0x11,0x00  ,  },  // 63 →
};            

uint8_t lines[] = { SCN1_PIN, SCN2_PIN, SCN3_PIN, SCN4_PIN }; 
uint16_t scn[4];        // キーボード状態
uint16_t prev_scn[4];   // 前回のキーボード状態
uint8_t enabled =0;               // PS/2 ホスト送信可能状態
uint8_t keyentry[MAXKEYENTRY];    // リピート管理テーブル
uint8_t repeatWait[MAXKEYENTRY];  // リピート開始待ち管理テーブル

// PS/2 ホストにack送信
void ack() {
  while(keyboard.write(0xFA));
}

// PS/2 ホストから送信されるコマンドの処理
int keyboardcommand(int command) {
  unsigned char val;
  uint32_t tm;
  switch (command) {
  case 0xFF:  ack();// Reset: キーボードリセットコマンド。正しく受け取った場合ACKを返す。
    //keyboard.write(0xAA);
    break;
  case 0xFE: // 再送要求
    ack();
    break;
  case 0xF6: // 起動時の状態へ戻す
    //enter stream mode
    ack();
    break;
  case 0xF5: //起動時の状態へ戻し、キースキャンを停止する
    //FM
    enabled = 0;
    ack();
    break;
  case 0xF4: //キースキャンを開始する
    //FM
    enabled = 1;
    ack();
    break;
  case 0xF3: //set typematic rate/delay : 
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  case 0xF2: //get device id : 
    ack();
    keyboard.write(0xAB);
    keyboard.write(0x83);
    break;
  case 0xF0: //set scan code set
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  case 0xEE: //echo :キーボードが接続されている場合、キーボードはパソコンへ応答(ECHO Responce)を返す。
    //ack();
    keyboard.write(0xEE);
    break;
  case 0xED: //set/reset LEDs :キーボードのLEDの点灯/消灯要求。これに続くオプションバイトでLEDを指定する。 
    ack();
    keyboard.read(&val); //do nothing with the rate
    ack();
    break;
  }
}

//
// PS/2 makeコード送信
// 引数 index    : キーライン   0 ~ 3
//      no       : ライン内番号 0 ~ 15
//      flgFnKey : Fnキーの状態 0 or 1
//
void sendKeyMake(uint8_t index, uint8_t no, uint8_t flgFnkey) {
  uint8_t code1,code2;
  
  // キーコードの取得
  if (flgFnkey) {
    code1 = scan_table[index*16+no][2];
    code2 = scan_table[index*16+no][3];
  } else {
    code1 = scan_table[index*16+no][0];
    code2 = scan_table[index*16+no][1];
  }

  // PS/2キーの発行
  if (code2 == 0) {
    keyboard.write(code1);
  } else {
    keyboard.write(code1);
    keyboard.write(code2);    
  }
}

//
// PS/2 breakコード送信
// 引数 index    : キーライン   0 ~ 3
//      no       : ライン内番号 0 ~ 15
//      flgFnKey : Fnキーの状態 0 or 1
//
void sendKeyBreak(uint8_t index, uint8_t no, uint8_t flgFnkey) {
  uint8_t code1,code2;

  // キーコードの取得
  if (flgFnkey) {
    code1 = scan_table[index*16+no][2];
    code2 = scan_table[index*16+no][3];
  } else {
    code1 = scan_table[index*16+no][0];
    code2 = scan_table[index*16+no][1];
  }

  // PS/2キーの発行
  if (code2 == 0) {
    keyboard.write(0xF0);
    keyboard.write(code1);
  } else {
    keyboard.write(code1);
    keyboard.write(0xF0);
    keyboard.write(code2);    
  }
}

// リピート処理(タイマー割り込み処理から呼ばれる)
void sendRepeat() {
  uint8_t index,no,flgFnKey;
  uint8_t key;
  
  for (uint8_t i=0; i < MAXKEYENTRY; i++) {
    if (keyentry[i] != EMPTY) {
      if (repeatWait[i] == 0) {
        key = keyentry[i]; 
        flgFnKey = key & 0x80 ? 1:0;
        no = key % 16;
        index = (key & 0x7F)/16;      
        sendKeyMake(index,no,flgFnKey);
    } else {
        repeatWait[i]--;          
      }
    }
  }
}

// キーボード状態の取得
void getKeyCode() {
  uint16_t clm = 0x0001;  
  memset(scn, 0, 8);
  for (uint8_t i=0; i < 16; i++) {
    digitalWrite(LATCH_PIN, LOW);
    shiftOut(SOUT_PIN, CLK_PIN, LSBFIRST, ~clm & 0xff );
    shiftOut(SOUT_PIN, CLK_PIN, LSBFIRST, ~(clm>>8) & 0xff ); 
    digitalWrite(LATCH_PIN, HIGH);     
    for (uint8_t j=0; j <4; j++) {
        scn[j]<<=1;
        scn[j] |= digitalRead(lines[j]);
     }
     clm<<=1;  
  }
}

// スキャンコードの送信
void sendScanCode() {
  uint8_t scn_bit;      // 現在の状態
  uint8_t prev_scn_bit; // 前回の状態
  uint8_t flgFnKey;     // Fnキーの状態
  uint8_t prevflgFnKey; // Fnキーの状態

  // Fnキーのチェック
  flgFnKey = scn[1]&(0x8000>>13) ? 0:1; 
  prevflgFnKey = prev_scn[1]&(0x8000>>13) ? 0:1; 
  
  for (uint8_t i=0; i < 4; i++) {
    for (uint8_t j=0; j < 16; j++) {     
      scn_bit = scn[i]&(0x8000>>j) ? 0:1;
      prev_scn_bit = prev_scn[i]&(0x8000>>j) ? 0:1;

      // Fnキーの場合
      if (i==1 && j==13) {
        continue;
      }

      // 前回から状態が変化しているキーの処理 
      if (scn_bit != prev_scn_bit) {
        if (scn_bit) {
          // キーが押された
          sendKeyMake(i, j, flgFnKey);
          if ( !(i == 2 && j == 0) &&  !(i == 1 && j == 15)) {
            addKey(i, j, flgFnKey);
          }       
        } else {
          // キーが離された
          sendKeyBreak(i, j, flgFnKey);
          if ( !(i == 2 && j == 0) && !(i == 1 && j == 15)) {
            delKey(i, j, flgFnKey);
          }
        }
      } else {
        if (scn_bit && flgFnKey != prevflgFnKey){
          // キーを押している状態でFnキーが切り替わった
          sendKeyBreak(i, j, prevflgFnKey);
          if ( !(i == 2 && j == 0) && !(i == 1 && j == 15)) {
            delKey(i, j, prevflgFnKey);
            sendKeyMake(i, j, flgFnKey);
            addKey(i, j, flgFnKey);
          }
        }
      }
    }
  }
}

// リピート管理テーブルのクリア
void claerKeyEntry() {
 for (uint8_t i=0; i <MAXKEYENTRY; i++)
    keyentry[i] = EMPTY;
}

// リピート管理テーブルにキーを追加
void addKey(uint8_t index, uint8_t no, uint8_t flgFnkey) {
 uint8_t key = index*16+no | (flgFnkey ? 0x80:0);
 for (uint8_t i=0; i <MAXKEYENTRY; i++) {
  if (keyentry[i] == EMPTY) {
    keyentry[i] = key;  
    repeatWait[i] = REPEATTIME;
    break;
  }
 }
}

// リピート管理テーブルからキーを削除
void delKey(uint8_t index, uint8_t no, uint8_t flgFnkey) {
 uint8_t key = index*16+no | (flgFnkey ? 0x80:0);
 for (uint8_t i=0; i <MAXKEYENTRY; i++) {
  if (keyentry[i] == key) {
    keyentry[i] = EMPTY;
    //break;
  }
 }  
}

void setup() {
  Serial.begin(115200);
  pinMode(SOUT_PIN,  OUTPUT);
  pinMode(CLK_PIN,   OUTPUT);
  pinMode(LATCH_PIN, OUTPUT);  
  pinMode(SCN1_PIN,  INPUT_PULLUP);  
  pinMode(SCN2_PIN,  INPUT_PULLUP);  
  pinMode(SCN3_PIN,  INPUT_PULLUP);  
  pinMode(SCN4_PIN,  INPUT_PULLUP); 

  while(keyboard.write(0xAA)!=0);  
  while(keyboard.write(0x00)!=0);
  
  claerKeyEntry();
  memset(scn, 0xff, 8);
  Serial.println(F("Start."));
}

void loop() {
  static uint32_t tm = 0;
  unsigned char c;  // ホストからの送信データ
  if( (digitalRead(KB_CLK)==LOW) || (digitalRead(KB_DATA) == LOW)) {
    while(keyboard.read(&c)) ;
    keyboardcommand(c);
  } 

  for (uint8_t i=0; i <4; i++)  prev_scn[i] = scn[i];    
  getKeyCode();
  sendScanCode();

  //キーリピート処理
  if (millis() >= tm+100) {
    sendRepeat();
    tm = millis();
  }
}

一点、問題が発覚。
なぜか、同じライン(横一列)の同時入力が出来ません。
シフトキーとの同時押しが出来ないのはちょっと問題です。
ハードウェア的には可能なはずなのですが.. ソフト的な問題?
原因調査中です。

2017/09/05 追記
スイッチを2つにて単純化して検証。
結線して確認しました(左側の回路図)。

K1のボタンを押すとLEDが点灯しますが、K2を同時に押すとVCC→K2→K1→GNDの経路で、

電流が流れてしまいショートしてしまいます。
これはちょっとまずいですね。

右の回路のようにダイオードを入れて対応するしかなさそうです。
このタクトスイッチモジュール、そのまま使うには問題ありですね。
安いですが、あまりお手軽に利用出来ないようです。

回路図

配線もちょっとめんどくさくなりますね。

全体をダイオードを追加する回路図に修正しましました。

01_3

端子を基板に乗せられるものに変更し、実装し直しました。

Dscn6964

再度、IchigoJamに接続して動作確認

Dscn6968

同時押しも出来るようになりました。

Dscn6967

このサイズのキーボード、意外と使い心地良さそうです。
現時点ではArduinoを使っていますが、Atmega328マイコンに置き換えて、
基板実装すれば、小型化は出来ると思います。

キーの刻印を何としたいですね。方法をちょっと考えてみます。

2017/09/09追記

結局、タクトスイッチを基板に並べ5行、12列の60キーで実装することにしました。
キーの刻印は、基板にシールを貼る形にします。

手持ちのタクトスイッチでは足りないため、手ごろな製品をAliexpressにて見つけて発注しました。

100個 $0.79の激安タクトスイッチ 試作に使うにはこれで良いでしょう

07

2ピンタイプのタクトスイッチ 100個 $1.57 はんだ付け楽になるが強度不足になるかも

05

2ピンタイプで通常の1/2幅 100個 $1.50 空いたスーペースにキーの刻印表記出来そう

06

ボタン部分の素材がシリコン 100個 $4.90 (多く買うと割引) 押し心地がよさそう

08

キーが5行×12列の場合、キーの状態をスキャンする端子は17本となります。
これとは別に、PS/2 IF に2本、シリアル通信 2本が必要で合計で21本が必要。

ATmega328をArduinoとして利用する場合、最大22本をデジタル入出力で利用出来ます。
そうすると、シフトレジスタ無しで直接制御も出来そうですね。
結線もさらに簡略化出来ると思います。
1本余るので状況に応じて、1列または1行分キーの追加も出来ます。






« キーボードの作成 その1 | トップページ | ベットに寝ているタマちゃん »

arduino」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/571408/65751530

この記事へのトラックバック一覧です: キーボードの作成 その2:

« キーボードの作成 その1 | トップページ | ベットに寝ているタマちゃん »