LoginSignup
5

テンキーを改造して便利なガジェットを作成する

Last updated at Posted at 2021-04-26

はじめに

以前Mac用に購入したがあまり使っていなかったBUFFALOのテンキーボードBSTK08Mに、最近いろいろいじっているDiymoreのATmega32U4ボードを組み込んでオリジナルキーボードを作成することを思いついた。キーボードと言っても、ショートカットキーなどの特定のキーコードシーケンスを生成することで、PC操作で楽ができる便利なガジェットを作ることが目的である。

テンキーBSTK08Mを分解する

特殊なネジは使われていなかったので、普通に小型のプラスドライバーで良い。
IMG_6988.png IMG_6988.png
まず、ケース裏側の6箇所のネジを外す。上蓋ケースの内側に6箇所の爪(側面に4つ、手前に2つ)があって裏蓋を挟んでいるので丁寧に取り外す。
IMG_6988.png IMG_7033.png
特に手前2箇所の爪はそのままでは外せなかったので、上側が浮いた隙間から基板を固定している4箇所のネジを外し、スイッチフィルムとその下のキーボードを取り外してやっと分離できた。
IMG_7034.png

SONIX社のSN8F2267Fが使われている。
clearキーだけ打鍵感が違ったのは、ここだけタクトスイッチだったからだ。

キーマトリクスを調べる

BSTK08b.png

上の写真で右側1〜11の端子にキーマトリクスが来ているので、フィルムのパターンを追って調べたところ、下表の通りであった。

1 2 3 4 5 6 7 8 9 10 11
Row0 Col0 Col1 Col2 Col3 Row1 Row2 Row3 Row4 Row5 Col4
row\col Col0 Col1 Col2 Col3 Col4
Row0 = -
Row1 1 2 enter 3
Row2 7 8 + 9
Row3 4 5 000 6
Row4 0 .
Row5 clear / * delete

実際のキー配置と随分と違うマトリクスであるが、シート二つ折りでRow/Colの配線が横断しないパターンを設計した結果であろう。

この通りだと、デジタルピンが11本必要になるが、Col4にdeleteしか割り当てがないので、Col2に短絡させることでCol2:Row5にdeleteを移動することができる。この結果、デジタルピンを10本で済ませられる。

row\col Col0 Col1 Col2 Col3
Row0 = -
Row1 1 2 enter 3
Row2 7 8 + 9
Row3 4 5 000 6
Row4 0 .
Row5 clear / delete *

ATmega32U4ボードのデジタルピンに対応させる

このボードは、Reset/Vcc(5v, 3v3)/Gnd以外のポートはすべてデジタルピンとして使用が可能であるため、以下のように割り当てることにする。

1 2 3 4/11 5 6 7 8 9 10 LED - -
Row0 Col0 Col1 Col2/4 Col3 Row1 Row2 Row3 Row4 Row5 - - -
SCL SDA RX TX D11 D10 D9 A0 A1 A2 SCK MO MI
3 2 0 1 11 10 9 18 19 20 15 16 14

このテンキーにのLEDがあるので、D15ピンでプログラムからON/OFFできるようにする。

緑LEDの配線

USBケーブルの4本の線を切り取った上で、GNDをGNDへ、VCCをD15ピンへ配線することで、D15ピンのHIGH/LOWでこのLEDを点灯/消灯させることができる。
G-LED.jpg

ATmega32U4ボードのピンにどう配線するか

まず、赤線の通りSN8F2267Fへの配線をバッサリ遮断する。この端子は1mm間隔で11本並んでいるので、手元にあった0.26mmのポリウレタン銅線で引き出して、ATmega32U4ボードに結線することにする。
IMG_1-11.png
この基板と、ポリウレタン銅線、スイッチフィルムを3層構造で圧着固定する必要があるため、銅線11本をどう1mm間隔に整列させるかが悩みどころだった。
セロハンテープの粘着面を上にしておいて、先端の皮膜を剥いたポリウレタン銅線1本づつを1mm間隔で順番に貼り付けていく作戦でなんとかできた。
IMG_7037.png IMG_7038.png

USBケーブルの配線

テンキーについていたUSBケーブルを取り外し、ATmega32U4ボードのUSBオスに下図の通り結線する。USBケーブルのシールド線は結線しない。
usb_4.png

配線の仕上げと組み立て

ATmega32U4ボードごとテンキーケース内に収めるのだが、ピンヘッダを普通に使うと高さ(厚み)オーバーで蓋が閉まらない。このため、汎用基板を使わず銅線を直接ピンヘッダにハンダ付して、ピンヘッダを半分程度にカットすることでケース内に収める。USBケーブルもUSBメスにハンダ付けした。
今回は見送ったが、ATmega32U4ボードに直接ハンダ付する方が手っ取り早いはずだ。

IMG_7039.png IMG_7045.png IMG_7046_2.png
下記写真の赤破線で囲った部分の半円筒の下側も高さ方向に2〜3mmの余裕があるため、ATmega32U4ボードをなるべく上側に寄せ、ヘッダーピンを限界までカットしたところ無事にケース内に収めることができた。
IMG_7028-2.png IMG_7064-2.png
オンボードの赤LEDが中で光っているのがお分かりいただけるだろう。

以上でハードウェアは完成した。次は導通確認だ。

導通確認

Arduino IDEでテストプログラムを作成する
ここに紹介されているKeypadライブラリを使用する。テンキーを押したときにキートップ文字がシリアルモニターに出力されれば結線は正常。同時に緑LEDが点滅するはずだ。
Serial.printの代わりにKeyboard.printとかに変更すれば、一旦、元のテンキーの機能が復活する。

導通確認プログラム
#include <Keypad.h>

//digital pins
#define PIN_A2  (20)
#define PIN_A1  (19)
#define PIN_A0  (18)
#define PIN_D9   (9)
#define PIN_D10 (10)
#define PIN_D11 (11)
#define PIN_TX   (1)
#define PIN_RX   (0)
#define PIN_SDA  (2)
#define PIN_SCL  (3)
#define PIN_SCK (15)

#define LED_GREEN PIN_SCK

const byte ROWS = 6; // 6 rows
const byte COLS = 4; // 4 columns

// Define the Keymap
char keys[ROWS][COLS] = {
  {'\0', '=', '\0', '-'},
  {'1', '2', 'e', '3'}, // e : enter
  {'7', '8', '+', '9'},
  {'4', '5', 'O', '6'}, // O : 000
  {'\0', '0', '\0', '.'},
  {'c', '/', 'd', '*'}, // c : clear,  d : delete
};

byte rowPins[ROWS] = { PIN_A2, PIN_A1, PIN_A0, PIN_D9, PIN_D10, PIN_D11 };
byte colPins[COLS] = { PIN_SCL, PIN_SDA, PIN_RX, PIN_TX }; 

// Create the Keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); //onboard RED led
  pinMode(LED_GREEN, OUTPUT); // tenkey GREEN led
  Serial.begin(115200);
}

void loop() {
  char key = keypad.getKey();
  if (key) {
    Serial.print(millis());
    Serial.print("\t");
    Serial.println(key);
    ledSign();
  }
  delay(10);
}

void ledSign() {
  digitalWrite(LED_BUILTIN, HIGH);
  digitalWrite(LED_GREEN, HIGH);
  delay(50);  // → 80
  digitalWrite(LED_BUILTIN, LOW);
  digitalWrite(LED_GREEN, LOW);
}

ここで問題が発生した。オンボードの赤LEDは点滅するのだが、テンキー側の緑LEDが点滅しない。テンキーを再度分解して配線を見直すも結線に問題はない。なぜ光らない?30分ぐらい悪戦苦闘のすえ、1秒ブリンクいわゆるLチカのスケッチを試すとちゃんと点滅した。あれっ!どうやらD15ピンをHIGHにしてからLOWにする時間間隔に問題があるらしい。上記の50ミリ秒では短かすぎて点灯しないのだ。試験の結果、76ミリ秒が限界値と判明した。安全を見て80ミリ秒に変更して導通確認を完了した。キーパッドの読み取りは一発で成功。

余談だが、この緑LEDは点滅時にカチカチと音が鳴る。音の発生源をちゃんと調べていないが、元の基板の配線を流用していることに問題がある可能性がある。

独自キーボードの「仕様」を考える

元のテンキーの機能も残しつつ、独自のキーシーケンスも送出できるようにする。このため、テンキー左上のclearを「機能キー」と位置付け、モードの切り替えに使用するキーとし、
clearを押すたびに、モード1 モード2 モード3 モード1 と3つのモードが順に切り替わるようにする。また、clearを押したことで今がどのモードなのかを、緑LEDの点滅回数で知らせるようにする。各モードの意味を下表に示す。

モード 緑LED
点滅回数
意味(目的)
1 1 モード1としてプログラムしたキーシーケンスを送出する
2 2 モード2としてプログラムしたキーシーケンスを送出する
3 3 元々のテンキーのコードを送出する

モード1/2は、例えば、「Windows」用と「Mac」用とか、「プライベートPC」用と「会社PC」用とかの使い分けを想定した。

独自キーボードのプログラム

プログラムロジックは以下の通り。
変数modeで現在のモードを保持する。モードはプログラム上は0-Indexで管理するため、初期値をモード3を意味する2とする。
関数テーブルを使用することで、プログラムコードをスッキリさせる。

#include <Keypad.h>
#include <Keyboard.h>
#include "funcs.hpp"

//digital pins
#define PIN_A2  (20)
#define PIN_A1  (19)
#define PIN_A0  (18)
#define PIN_D9   (9)
#define PIN_D10 (10)
#define PIN_D11 (11)
#define PIN_TX   (1)
#define PIN_RX   (0)
#define PIN_SDA  (2)
#define PIN_SCL  (3)
#define PIN_SCK (15)

#define LED_GREEN PIN_SCK

const byte ROWS = 6; // 6 rows
const byte COLS = 4; // 4 columns
int mode; //0〜2

// Define the Keymap
byte keys[ROWS][COLS] = {
  { 0,  2,  0,  9},
  {14, 15, 20, 16},
  { 6,  7, 13,  8},
  {10, 11, 18, 12},
  { 0, 17,  0, 19},
  { 1,  3,  5,  4},
};

typedef void(* FP_Func)();
#define DummyFunc (FP_Func)0

//functions table
FP_Func funcs[3][21] = {
  { //mode1
    DummyFunc, 
    DummyFunc, funcM1F2,  funcM1F3,  funcM1F4,  funcM1F5,  
    funcM1F6,  funcM1F7,  funcM1F8,  funcM1F9,  funcM1F10,  
    funcM1F11, funcM1F12, funcM1F13, funcM1F14, funcM1F15,  
    funcM1F16, funcM1F17, funcM1F18, funcM1F19, funcM1F20,  
  },
  { //mode2,
    DummyFunc, 
    DummyFunc, funcM2F2,  funcM2F3,  funcM2F4,  funcM2F5,  
    funcM2F6,  funcM2F7,  funcM2F8,  funcM2F9,  funcM2F10,  
    funcM2F11, funcM2F12, funcM2F13, funcM2F14, funcM2F15,  
    funcM2F16, funcM2F17, funcM2F18, funcM2F19, funcM2F20,  
  },
  { //mode3,
    DummyFunc, 
    DummyFunc, funcM3F2,  funcM3F3,  funcM3F4,  funcM3F5,  
    funcM3F6,  funcM3F7,  funcM3F8,  funcM3F9,  funcM3F10,  
    funcM3F11, funcM3F12, funcM3F13, funcM3F14, funcM3F15,  
    funcM3F16, funcM3F17, funcM3F18, funcM3F19, funcM3F20,  
  },
};

byte rowPins[ROWS] = { PIN_SCL, PIN_D10, PIN_D9, PIN_A0, PIN_A1, PIN_A2 };
byte colPins[COLS] = { PIN_SDA, PIN_RX, PIN_TX, PIN_D11 }; 

// Create the Keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void setup() {
  pinMode(LED_GREEN, OUTPUT);
  Keyboard.begin();
  mode = 2; // mode3
}

void loop() {
  char key = keypad.getKey();
  if (key) {
    if (key == 1) {
      ++mode %= 3; //mode change
      ledSign(mode + 1);      
    } else {
      (funcs[mode][key])(); //call each function via functions table
      ledSign(1);
    }
  }
  delay(10);
}

void ledSign(int cnt) {
  for (int n = 0; n < cnt; n++) {
    digitalWrite(LED_GREEN, HIGH);
    delay(80);
    digitalWrite(LED_GREEN, LOW);
    delay(30);
  }
}

分かりやすいように、関数テーブルの添字はテンキーのキーの並び順としてある。
IMG_6988#1.png

funcs.hpp
#pragma once
//mode1
void funcM1F2() { /* 省略 */ };
void funcM1F3() { /* 省略 */ };
void funcM1F4() { /* 省略 */ };
void funcM1F5() { /* 省略 */ };
void funcM1F6() { /* 省略 */ };
void funcM1F7() { /* 省略 */ };
void funcM1F8() { /* 省略 */ };
void funcM1F9() { /* 省略 */ };
void funcM1F10() { /* 省略 */ };
void funcM1F11() { /* 省略 */ };
void funcM1F12() { /* 省略 */ };
void funcM1F13() { /* 省略 */ };
void funcM1F14() { /* 省略 */ };
void funcM1F15() { /* 省略 */ };
void funcM1F16() { /* 省略 */ };
void funcM1F17() { /* 省略 */ };
void funcM1F18() { /* 省略 */ };
void funcM1F19() { /* 省略 */ };
void funcM1F20() { /* 省略 */ };

//mode2
void funcM2F2() { /* 省略 */ };
void funcM2F3() { /* 省略 */ };
void funcM2F4() { /* 省略 */ };
void funcM2F5() { /* 省略 */ };
void funcM2F6() { /* 省略 */ };
void funcM2F7() { /* 省略 */ };
void funcM2F8() { /* 省略 */ };
void funcM2F9() { /* 省略 */ };
void funcM2F10() { /* 省略 */ };
void funcM2F11() { /* 省略 */ };
void funcM2F12() { /* 省略 */ };
void funcM2F13() { /* 省略 */ };
void funcM2F14() { /* 省略 */ };
void funcM2F15() { /* 省略 */ };
void funcM2F16() { /* 省略 */ };
void funcM2F17() { /* 省略 */ };
void funcM2F18() { /* 省略 */ };
void funcM2F19() { /* 省略 */ };
void funcM2F20() { /* 省略 */ };

//mode3  : original tenkey mode
void funcM3F2() { Keyboard.write(KEY_PAD_EQUALS); };
void funcM3F3() { Keyboard.write(KEYPAD_DIVIDE); };
void funcM3F4() { Keyboard.write(KEYPAD_MULTIPLY); };
void funcM3F5() { Keyboard.write(KEY_BACKSPACE); };
void funcM3F6() { Keyboard.write(KEYPAD_7); };
void funcM3F7() { Keyboard.write(KEYPAD_8); };
void funcM3F8() { Keyboard.write(KEYPAD_9); };
void funcM3F9() { Keyboard.write(KEYPAD_SUBTRACT); };
void funcM3F10() { Keyboard.write(KEYPAD_4); };
void funcM3F11() { Keyboard.write(KEYPAD_5); };
void funcM3F12() { Keyboard.write(KEYPAD_6); };
void funcM3F13() { Keyboard.write(KEYPAD_ADD); };
void funcM3F14() { Keyboard.write(KEYPAD_1); };
void funcM3F15() { Keyboard.write(KEYPAD_2); };
void funcM3F16() { Keyboard.write(KEYPAD_3); };
void funcM3F17() { Keyboard.write(KEYPAD_0); };
void funcM3F18() { Keyboard.print("000"); };
void funcM3F19() { Keyboard.write(KEYPAD_DOT); };
void funcM3F20() { Keyboard.write(KEY_ENTER); };

当然ながら、プログラム次第なので、もっとモードを増やすことも可能である。この辺は使ってみて様子を見ながら後で考えるとする。
以上で目的を達成できたので、この記事を閉じる。

興味のある方の参考になれば幸いである。以上

備忘録

久しぶりに、Arduino IDEで書き込みを行ったところ、エラーとなってうまくできない。色々試していたところ、次の事実が分かった。

書き込み装置に、AVR ISPを指定しなければならない。

忘れてしまった時のために、備忘録として追加しておく。
2023.12.28追加

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5