キーボードのマトリクス方式を試してみる

2024年1月8日作ってみたRaspberry Pi Pico,自作キーボード

概要

以前からCtrl+C等を登録したプログラマブルキーボードを作ってみたいと思っていました。同時押しにも対応したキーボードマトリクスも合わせて勉強してみたいと思って部品を発注したのですが、ずっと放置していました。。。
年末ということで時間を作って完成させたので記事にします。

今回使用した基板はLEDと電流制御用の抵抗を取り付けられる仕様になっています。キーキャップを透明にしてLEDを取り付ければ、イルミネーションみたいな感じで光らせることも出来ます。(今回はやりません)

キースイッチ

キースイッチはCherryMXシリーズの青軸を使いました。
カチカチッと良い音がするんですが普段使いのキーボードではうるさすぎて採用しなかったので、こっちで使ってみました。

このキースイッチは底に位置決め用のでっぱりがあり、ピンも100milの倍数ではないのでユニバーサル基板には実装できません。
プリント基板を作るのは面倒だったのでAmazonで変換基板を買ってきました。

現物のパターンを確認したところ、SparkFanで売られている変換基板のコピー品のようです。

ダイオード

ダイオードは小信号用の安いものでOKです。電流は1~0.1mA程度しか流さないので、かなり小さいものでも余裕で使えます。

Amazonだと1N4148辺りが安くて入手しやすいと思います。

ダイオード 1N4148

キーボードマトリクスの原理

こちらのブログを参考に設計しました。

ダイオードを取り付けているのは、複数のキーを同時に押したときに電流が回り込んで上手く認識できなくなることを防止するためです。ただ、文章だけだとダイオードで同時押しが回避できるイメージがしづらいので、回路シミュレータで確認しました。
IN1とIN2はわかりやすいように電圧計ではなくLEDにしています。

例えば、「OUT1をLow(スイッチON)にした時にIN1がLow(LED消灯)」なら左上のスイッチが押されたと認識できます。同様に「OUT1をLow(スイッチON)にした時にIN2がLow(LED消灯)」なら左下のスイッチが押されたと認識できます。

この辺りを直感的に理解するのは実際に触っているのが一番だと思いますので、実際に使ったシミュレーションファイルを用意しました。↓このリンクで「Circuit Simulator Applet」という回路シミュレータが起動するので試しに触ってみてください。

Circuit Simulator Applet – キーマトリクス

使い方は↓ここで参考サイトを紹介しています。

回路図

まずはSparkFanの回路図をKiCADに読み込んで説明を翻訳しました。(DNPは部品無しという意味です)
今回は同時押し対応にするのでJP2をカッターで削ってオープン(繋がっていない状態)にし、ダイオードD1を追加しています。同時押し対応が不要であればダイオード無し・カッターでの追加工なしでOKです。
LEDをチカチカさせたいときはJP1をオープンにしてR1とD2を実装します。

変換基板9つは↓このように配線しました。
ラベル名は私好みの名前に変えているので参考サイトと一致していません。

配線

変換基板には下のように配線します。逆流防止のダイオードも忘れずに全ての基板に取り付けます。
+、-の端子はLED制御用なので今回は使いません。

プログラミング

Arduino IDEを使ってコーディングしました。ライブラリがあるので記述は楽ですね。
キーの判定は100us感覚にしています。勘で決めたので、いつか検証しないと。

連射機能も実装しています。Ctrl+C等のショートカットキーには必要ありませんが、もしかしたら普通のキー操作もするかもなので。
1秒以上キーを押したままにすると0.1秒間隔で連射するよう設定しています。
動作確認のため、キー入力は全て数字にしています。

#include <Keyboard.h>

/* GPIO */
const int PIN_OUT1 = 0;
const int PIN_OUT2 = 1;
const int PIN_OUT3 = 2;
const int PIN_SENSE1 = 3;
const int PIN_SENSE2 = 4;
const int PIN_SENSE3 = 5;

/* パラメータ */
const int KEY_ROW = 3;
const int KEY_COLUMN = 3;
const int KEY_NUM = 9;
const int KEY_WAIT_1 = 10000;
const int KEY_WAIT_2 = 1000;

/* 変数 キーボード */
bool key_on[KEY_NUM];
int key_on_cnt[KEY_NUM] = {0};


/* 変数 タイマー */
struct repeating_timer st_timer;
bool timer_flag = false;

/* -------------------------------------------------- */

void setup() {
  /* GPIO */
  pinMode(PIN_OUT1, OUTPUT);
  pinMode(PIN_OUT2, OUTPUT);
  pinMode(PIN_OUT3, OUTPUT);
  pinMode(PIN_SENSE1, INPUT_PULLUP);
  pinMode(PIN_SENSE2, INPUT_PULLUP);
  pinMode(PIN_SENSE3, INPUT_PULLUP);
  digitalWrite(PIN_OUT1, HIGH);
  digitalWrite(PIN_OUT2, HIGH);
  digitalWrite(PIN_OUT3, HIGH);

  /* タイマー */
  add_repeating_timer_us(100, Timer, NULL, &st_timer);

  /* キーボード */
  Keyboard.begin();
}

/* -------------------------------------------------- */

/* タイマー割込み */
bool Timer(struct repeating_timer *t) {
  timer_flag = true;
  return true;
}

/* -------------------------------------------------- */

void loop() {
  if (timer_flag) {
    for (int i = 0; i < KEY_NUM; i++) {
      key_on[i] = false;
    }

    digitalWrite(PIN_OUT1, LOW);
    if (!digitalRead(PIN_SENSE1))key_on[0] = true;
    if (!digitalRead(PIN_SENSE2))key_on[1] = true;
    if (!digitalRead(PIN_SENSE3))key_on[2] = true;
    digitalWrite(PIN_OUT1, HIGH);
    delayMicroseconds(10);

    digitalWrite(PIN_OUT2, LOW);
    if (!digitalRead(PIN_SENSE1))key_on[3] = true;
    if (!digitalRead(PIN_SENSE2))key_on[4] = true;
    if (!digitalRead(PIN_SENSE3))key_on[5] = true;
    digitalWrite(PIN_OUT2, HIGH);
    delayMicroseconds(10);

    digitalWrite(PIN_OUT3, LOW);
    if (!digitalRead(PIN_SENSE1))key_on[6] = true;
    if (!digitalRead(PIN_SENSE2))key_on[7] = true;
    if (!digitalRead(PIN_SENSE3))key_on[8] = true;
    digitalWrite(PIN_OUT3, HIGH);
    delayMicroseconds(10);

    for (int i = 0; i < KEY_NUM; i++) {
      if (key_on[i] == true) {
        if (key_on_cnt[i] == 0) {
          KeyboardSend(i);
          key_on_cnt[i]++;
        } else {
          key_on_cnt[i]++;
          if (key_on_cnt[i] > KEY_WAIT_1) {
            KeyboardSend(i);
            key_on_cnt[i] -= KEY_WAIT_2;
          }
        }
      } else {
        key_on_cnt[i] = 0;
      }
    }
    timer_flag = false;
  }
}

/* -------------------------------------------------- */

void KeyboardSend(int num) {
  if(num == 0){
    Keyboard.print("1");
  }else if(num == 1){
    Keyboard.print("2");
  }else if(num == 2){
    Keyboard.print("3");
  }else if(num == 3){
    Keyboard.print("4");
  }else if(num == 4){
    Keyboard.print("5");
  }else if(num == 5){
    Keyboard.print("6");
  }else if(num == 6){
    Keyboard.print("7");
  }else if(num == 7){
    Keyboard.print("8");
  }else if(num == 8){
    Keyboard.print("9");
  }
}

ケース

ケースは3Dプリンターで土台だけ作りました。(手抜き)
基板はM2のタッピングビスで固定しています。ネジ穴はプリント後に1.5mmのドリルで整えています。

雑に作ったデータですが、もしかしたら欲しい人がいるかもしれないので公開しておきます。
OneDrive – ケース.stl

動作確認

今のところチャタリングはないですね。ほとんど対策していないので出ると思ったんですが、CherryMXは優秀ですね。
念のためオシロスコープで波形を確認します。いつの日か気が向いたときに。