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

# はじめに

以前Mac用に購入したがあまり使っていなかったBUFFALOのテンキーボード[**BSTK08M**](https://www.buffalo.jp/product/detail/bstk08msv.html)に、最近いろいろいじっているDiymoreの[**ATmega32U4**](https://www.diymore.cc/products/diymore-beetle-badusb-micro-atmega32u4-au-development-expansion-module-board-for-arduino-leonardo-r3-5v-i2c)ボードを組み込んでオリジナルキーボードを作成することを思いついた。キーボードと言っても、ショートカットキーなどの特定のキーコードシーケンスを生成することで、PC操作で楽ができる便利なガジェットを作ることが目的である。
[<img alt="" width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/de2cdb7e-1566-788a-4fe2-70dead6fb7bd.jpeg">](https://www.amazon.co.jp/dp/B0053EB6HO/) [<img alt="" width="150" src="https://www.amazon.co.jp/images/I/610SxrdhPAL._AC_SL1000_.jpg">](https://www.amazon.co.jp/dp/B07MMB4HPF/)


# テンキーBSTK08Mを分解する

特殊なネジは使われていなかったので、普通に小型のプラスドライバーで良い。
<img alt="IMG_6988.png" width="190" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/00ca7ea2-3942-a17a-5f29-7d6603ab1332.jpeg"> <img alt="IMG_6988.png" width="240" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/eebe8c2a-92ad-9548-9332-5897bcc2cf04.png">
まず、ケース裏側の6箇所のネジを外す。上蓋ケースの内側に6箇所の爪（側面に4つ、手前に2つ）があって裏蓋を挟んでいるので丁寧に取り外す。
<img alt="IMG_6988.png" width="240" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/03e46f35-cf8a-356a-905b-94480675dc66.png"> <img alt="IMG_7033.png" width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/8733a4b8-8ba2-3478-ad40-34bcc16a0522.png">
特に手前2箇所の爪はそのままでは外せなかったので、上側が浮いた隙間から基板を固定している4箇所のネジを外し、スイッチフィルムとその下のキーボードを取り外してやっと分離できた。
<img alt="IMG_7034.png" width="240" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/98a7a0fc-f98c-f525-fdea-5d6ea8196d6c.png">

SONIX社の[SN8F2267F](http://www.sonix.com.tw/article-jp-3588-37891)が使われている。
`clear`キーだけ打鍵感が違ったのは、ここだけタクトスイッチだったからだ。


# キーマトリクスを調べる

<img alt="BSTK08b.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/ea945f38-ca25-e3f0-79d3-a47eb3e743f9.jpeg">

上の写真で右側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 | <font color=green>LED</font> | - | - |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 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 |

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

# <font color=green>緑LED</font>の配線

USBケーブルの4本の線を切り取った上で、<font color=red>GND</font>をGNDへ、<font color=red>VCC</font>をD15ピンへ配線することで、D15ピンのHIGH/LOWでこのLEDを点灯/消灯させることができる。
<img alt="G-LED.jpg" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/e40680d3-dc35-3908-67be-a017c4a5b0f8.jpeg">

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

まず、<font color=red>赤線</font>の通りSN8F2267Fへの配線をバッサリ遮断する。この端子は1mm間隔で11本並んでいるので、手元にあった0.26mmのポリウレタン銅線で引き出して、ATmega32U4ボードに結線することにする。
![IMG_1-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/61cb70f8-ed48-e41e-7ec6-0382aaf8333a.png)
この基板と、ポリウレタン銅線、スイッチフィルムを3層構造で圧着固定する必要があるため、銅線11本をどう1mm間隔に整列させるかが悩みどころだった。
セロハンテープの粘着面を上にしておいて、先端の皮膜を剥いたポリウレタン銅線1本づつを1mm間隔で順番に貼り付けていく作戦でなんとかできた。
<img alt="IMG_7037.png" width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/9e4e32e1-1b9f-722d-1da1-9953e6518c4c.png"> <img alt="IMG_7038.png" width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/ad90e576-b1db-0a85-e146-ea224c41b95b.png"> 




# USBケーブルの配線

テンキーについていたUSBケーブルを取り外し、ATmega32U4ボードのUSBオスに下図の通り結線する。USBケーブルのシールド線は結線しない。
<img alt="usb_4.png" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/0b493fd0-d870-1984-4bcc-62cca6a0c884.png">


# 配線の仕上げと組み立て

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

<img alt="IMG_7039.png" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/3d5a875c-c3a6-f598-ba54-3763709f9120.png"> <img alt="IMG_7045.png" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/8fe01739-42d4-4f4d-e6f4-c50299ca87c1.png"> <img alt="IMG_7046_2.png" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/aae56e8e-27ce-5d74-43c5-48cc006bbcca.jpeg">
下記写真の赤破線で囲った部分の半円筒の下側も高さ方向に2〜3mmの余裕があるため、ATmega32U4ボードをなるべく上側に寄せ、ヘッダーピンを限界までカットしたところ無事にケース内に収めることができた。
<img alt="IMG_7028-2.png" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/40ed1ff6-42ab-c9a1-25c4-e06cfae03868.png"> <img alt="IMG_7064-2.png" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/84b93c27-4f30-f5cf-91a5-49e90c41fbbc.png">
オンボードの<font color=red>赤LED</font>が中で光っているのがお分かりいただけるだろう。

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


# 導通確認

Arduino IDEでテストプログラムを作成する
[**ここに**](https://playground.arduino.cc/Code/Keypad/)紹介されている[Keypadライブラリ](https://github.com/Chris--A/Keypad)を使用する。テンキーを押したときにキートップ文字がシリアルモニターに出力されれば結線は正常。同時に<font color=green>緑LED</font>が点滅するはずだ。
`Serial.print`の代わりに`Keyboard.print`とかに変更すれば、一旦、元のテンキーの機能が復活する。

```cpp:導通確認プログラム
#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);
}
```

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

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

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

| モード | <font color=green>緑LED</font>の<br>点滅回数 | 意味（目的）|
|:-:|:-:|:-:|
| 1 | 1 | モード1としてプログラムしたキーシーケンスを送出する |
| 2 | 2 | モード2としてプログラムしたキーシーケンスを送出する  |
| 3 | 3 | 元々のテンキーのコードを送出する  |

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

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

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

```cpp
#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 alt="IMG_6988#1.png" width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/83f2240c-1a5a-f54c-017f-c5ff10e6055a.png">

```cpp: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で書き込みを行ったところ、エラーとなってうまくできない。色々試していたところ、次の事実が分かった。

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

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