はじめに
以前Mac用に購入したがあまり使っていなかったBUFFALOのテンキーボードBSTK08Mに、最近いろいろいじっているDiymoreのATmega32U4ボードを組み込んでオリジナルキーボードを作成することを思いついた。キーボードと言っても、ショートカットキーなどの特定のキーコードシーケンスを生成することで、PC操作で楽ができる便利なガジェットを作ることが目的である。
テンキーBSTK08Mを分解する
特殊なネジは使われていなかったので、普通に小型のプラスドライバーで良い。
まず、ケース裏側の6箇所のネジを外す。上蓋ケースの内側に6箇所の爪(側面に4つ、手前に2つ)があって裏蓋を挟んでいるので丁寧に取り外す。
特に手前2箇所の爪はそのままでは外せなかったので、上側が浮いた隙間から基板を固定している4箇所のネジを外し、スイッチフィルムとその下のキーボードを取り外してやっと分離できた。
SONIX社のSN8F2267Fが使われている。
clear
キーだけ打鍵感が違ったのは、ここだけタクトスイッチだったからだ。
キーマトリクスを調べる
上の写真で右側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を点灯/消灯させることができる。
ATmega32U4ボードのピンにどう配線するか
まず、赤線の通りSN8F2267Fへの配線をバッサリ遮断する。この端子は1mm間隔で11本並んでいるので、手元にあった0.26mmのポリウレタン銅線で引き出して、ATmega32U4ボードに結線することにする。
この基板と、ポリウレタン銅線、スイッチフィルムを3層構造で圧着固定する必要があるため、銅線11本をどう1mm間隔に整列させるかが悩みどころだった。
セロハンテープの粘着面を上にしておいて、先端の皮膜を剥いたポリウレタン銅線1本づつを1mm間隔で順番に貼り付けていく作戦でなんとかできた。
USBケーブルの配線
テンキーについていたUSBケーブルを取り外し、ATmega32U4ボードのUSBオスに下図の通り結線する。USBケーブルのシールド線は結線しない。
配線の仕上げと組み立て
ATmega32U4ボードごとテンキーケース内に収めるのだが、ピンヘッダを普通に使うと高さ(厚み)オーバーで蓋が閉まらない。このため、汎用基板を使わず銅線を直接ピンヘッダにハンダ付して、ピンヘッダを半分程度にカットすることでケース内に収める。USBケーブルもUSBメスにハンダ付けした。
今回は見送ったが、ATmega32U4ボードに直接ハンダ付する方が手っ取り早いはずだ。
下記写真の赤破線で囲った部分の半円筒の下側も高さ方向に2〜3mmの余裕があるため、ATmega32U4ボードをなるべく上側に寄せ、ヘッダーピンを限界までカットしたところ無事にケース内に収めることができた。
オンボードの赤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);
}
}
分かりやすいように、関数テーブルの添字はテンキーのキーの並び順としてある。
#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追加