mixiグループアドベントカレンダー 9日目を担当させていただきます。
普段はSNSミクシィのAndroidアプリを開発しています。Android全く触れない内容ですが、どうぞよろしくお願いします。
概要
新しいMacBookProが発表され、ファンクションキーの代わりにTouchBarになりました。
Vimを使っている私としてはESCは残してほしかったので完全に\(^o^)/状態でした。
多分、設定でESCは残せるだろうし、Ctrl-[
を使えばいいのですが、ESCをまず数回叩かないと気持ち悪いレベルになっている自分としては非常に寂しい。
とにかくESCの打鍵感が欲しいので、ESC専用キーボードは売っているのだろうかと軽くググったらなかった。なら作ってみようという話です。
正直なところ、ATmega32u4チップが搭載されたArduino LeonardoかArudiono Microを使うとUSBデバイスとして認識してくれるので、ライブラリを使えば簡単にできると思います。
しかし、ここでは多くの人がもっているであろうであるArudino Uno R3(以下R3) をターゲットにして、DFUによるもう一つのチップを書き込むなどしてR3をキーボードデバイスにします。
R3のチップ構成について
R3はざっくり2つのAtmelチップがついています。
チップ | 概要 | 仕様 |
---|---|---|
ATmega 16u2 |
usb serialとして使われ328pに書き込むために使われ、USBポート付近にあるやつ。R2からAtmelチップになったのでDFUができるようになった。ちなみにR2は ATmega 8u2
|
link |
ATmeaga 328p |
sketchなどで書き込まれるMCU。一般的にはこちらがメイン | link |
Arudinoを開発する場合、作成したSketchを公式IDEなどからR3に書き込みエルチカなどします。IDEを使って書き込んでいるチップは ATmeaga 328p
です。今回はR3をUSBキーボードデバイスとして認識させるために ATmega 16u2
も書き換えます。
やること
- R3をUSBキーボードデバイスとして認識させてみる (DFUで
ATmega 16u2
に書き込み) - R3をスケッチから書き込みできるようにUSBシリアルに戻す (DFUで
ATmega 16u2
に書き込み) - ESCキーコードを送るプログラム作成 (sketch)
- sketchを
ATmeaga 328p
に書き込み - 再度R3をUSBキーボードデバイスとして認識させる (DFUで
ATmega 16u2
に書き込み)
やってみる
R3をUSBキーボードとして認識させてみる
R3をUSBキーボードとして認識させてみます。
そのためには、DFUで ATmega 16u2
に対してキーボードHexファイルを書き込みます。
DFU(Device Firmware Update)とは、ざっくりとUSBプロトコルを使用してプログラム(hexファイル)をチップに書き込むことです。
hexファイルの取得
Hexファイルは、Atmelチップ開発環境を構築すればビルドして作成できます。macならCrossPack、linxuならtoolchainでクロスコンパイルするための avr-gcc
を導入する必要があります。
LUFAライブラリを使うと様々なUSBデバイスになるHexファイルを作成することができます。2つのHexファイルもLUFAライブラリを使っています。
今回必要なHexファイルは2つです。
UNO-dfu_and_usbserial_combined.hex
最初から書き込まれているデフォルトのHexファイル。ATmeaga 328p
にsketchプログラムを書き込めたりします。GitHubから取得できます。公式IDEをインストール済みなら /Applications/Arduino.app/Contents/Java/hardware/arduino/avr/firmwares/atmegaxxu2/
にあります。
Arduino-keyboard-0.3.hex
USBkeyboardデバイスとして認識するためのHexファイル。ここから入手できますが、現在リンク切れなので今回のプロジェクトリポジトリに追加ておきました。
dfu-programmerのインストール
dfu-programmer
という便利なコマンドラインプログラマがあります。
これはISP対応のUSBブートローダを備えたAtmelチップに対してコマンドラインで書き込むことができます。
brew
を使ってインストールします。(UbuntuとかのDebian系なら apt
でインストール)
$ brew update
$ brew install dfu-programmer
# versionを確認してみる
$ dfu-programmer --version
dfu-programmer 0.7.2
DFUプログラムをする
dfuプログラムは以下の手順で行います。
- dfuモードにする
-
dfu-programmer
による書き込み(削除・書き込み・リセット)
dfuモードにする
以下のようなアナログ操作です。
- USBから抜く
- リセットスイッチの前のピン2つをジャンパーピンで接続
- USB接続する
- ジャンパーピンを抜く
dfuモードになったかどうかはシステムレポートのUSBの状態を見て判断できます。
- 左上のアップルマーク -> このMacについて -> システムレポート -> USB で
複合装置
になっていればOKです。
normal | dfu mode |
---|---|
dfu-progmrammer
による書き込み
削除・書き込み・リセットを一連の手順とします。
# 削除する
$ dfu-programmer atmega16u2 erase
Checking memory from 0x0 to 0x2FFF... Not blank at 0x1.
Erasing flash... Success
# 書き込む (ここではUSBKeyboard Hexファイル)
$ dfu-programmer atmega16u2 flash ./hex/Arduino-keyboard-0.3.hex
Checking memory from 0x0 to 0xFFF... Empty.
0% 100% Programming 0x1000 bytes...
[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] Success
0% 100% Reading 0x3000 bytes...
[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] Success
Validating... Success
0x1000 bytes written into 0x3000 bytes memory (33.33%).
# reset
$ dfu-programmer atmega16u2 reset
USBを抜き差しするとキーボードとして認識されるはずです。システムレポートで確認してみます。
R3をUSBシリアルとして認識させてみる (元に戻す)
USBキーボードとして認識できましたが、 GPIOからのインプットをESCキーとして送信するためのプログラムは本丸である ATmeaga 328p
に書き込む必要があります。
このままでは通常のSketchを書き込むことができないので、先程の手順でHexファイルを書き込み、もとに戻してみます。
- 前述の方法で、DFUモードにしてください
-
dfu-programmer
でUSBシリアルHexファイルを書き込みます
# 削除
$ dfu-programmer atmega16u2 erase
:
# 書き込み
$ dfu-programmer atmega16u2 flash /Applications/Arduino.app/Contents/Java/hardware/arduino/avr/firmwares/atmegaxxu2/UNO-dfu_and_usbserial_combined.hex
:
# リセット
$ dfu-programmer atmega16u2 reset
:
次はESCキーコードを送るSketchプログラムを作成します。
ESCキーコードを送るプログラム作成 (sketch)
ESCキーコードを送るプログラムを作成します。このプログラムの書き込み先は ATmeaga 328p
なのでArduinoの一般的な開発と同じです。
USBプロトコルと送信するキーコードについて
- uno R3 はKeyboardライブラリが使えないのでSerialで書き込みます。
- Serialで送信するのは基本的には8バイトのデータで、プロトコルは以下の通りとなります。
-
USB仕様HID編の
8.3 Report Format for Array Items
に詳細が記載されています。
Byte | Contents |
---|---|
0 : Bit0 | Left CTRL |
0 : Bit1 | Left SHIFT |
0 : Bit2 | Left ALT |
0 : Bit3 | Left GUI |
0 : Bit4 | Right CTRL |
0 : Bit5 | Right SHIFT |
0 : Bit6 | Right ALT |
0 : Bit7 | Right GUI |
1 | 今回使わない |
2~7 | HID active key codes |
キーコードについては 仕様の 10 Keyboard/Keypad Page (0x07)
にあるリストから探してください。
送信したいのは ESC
キーでなのでリストから 0x29
となります。
回路作成
誤操作を防ぐためにプルダウン回路にします。
ビルド環境作成
コマンドラインで開発したいので、platformIOを使います。IDEでSketchをプログラムする場合は飛ばしてください。
- virtualenv上で、platformIOをインストールします
# workon virtualenv
$ mkvirtualenv --no-site-package arduino
# install platformio
$ pip install platformio
$ pip install --egg scons
- プロジェクト作成
$ mkdir esc_keyboard; cd ./esc_keyboard;
$ platformio init --board=uno
$ echo ".pioenvs" > .gitignore
$ git init; git add -A; git commit -a -m "init";
- R3のデバイスを調べてコンフィグに設定します (下のスクリプトだと
peco
が必要)
$ uno=`platformio serialports list |grep usb | peco`; echo "upload_port="$uno >> platformio.ini
$ git commit -a -m "add upload port"
- makefileも作成
all: build install monitor
build:
platformio run
# ターゲットボードにアップロードします
install:
platformio run --target=upload
# シリアルをモニタします
# デバイス指定したい場合はオプションを追加してください (-p /dev/xxx)
# モニタを止めたい場合 ctrl+[
monitor:
platformio serialports monitor
clean:
platformio run --target=clean
.PHONY: all build install clean monitor
プログラム作成して書き込む
- プログラムは愚直なコードですが以下のとおりです。
#define LED_PIN 13
#define PIN_ESC 2
#define KEY_RESET 0x00
#define KEY_ESC 0x29
uint8_t keyData[8] = { 0, 0, 0, 0, 0, 0, 0 };
void handler(int pin, uint8_t keycode) {
if (digitalRead(pin) == 1) {
digitalWrite(LED_PIN, HIGH);
keyData[2] = keycode;
Serial.write(keyData, 8); // send keypress
keyData[2] = KEY_RESET;
Serial.write(keyData, 8); // release key
while( digitalRead(pin) == 1) {
delay(100);
}
digitalWrite(LED_PIN, LOW);
}
}
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(PIN_ESC, INPUT);
Serial.begin(9600);
delay(2000);
}
void loop() {
handler(PIN_ESC, KEY_ESC);
}
- buildして書き込む
# build (platformio run と同じ)
$ make build
# 書き込み (platformio run --target=upload と同じ)
$ make install
# シリアル出力を確認 (platformio serialports monitor と同じ)
$ make monitor
スイッチ押下でR3に組み込まれているLEDが点灯し、よくわからない文字列がシリアル出力されていればプログラムはOKです。
USBプロトコルに従ったシリアルデータを送るプログラムの書き込みができました。最後に再度USBキーボードとして認識させるために ATmega 16u2
にHexファイルを書き込みます。
R3をUSBキーボードとして認識させる
USBキーボードとして認識させるために、再度DFUモードにしてHexファイルを書き換えます。
- 前述の方法で、DFUモードにしてください
-
dfu-programmer
でUSBシリアルHexファイルを書き込みます
# 削除
$ dfu-programmer atmega16u2 erase
:
# 書き込み
$ dfu-programmer atmega16u2 flash ./hex/Arduino-keyboard-0.3.hex
:
# リセット
$ dfu-programmer atmega16u2 reset
:
どうでしょうか、R3がキーボードとして認識して、かつスイッチ押下すると、、、ESCキー押したことになっていると思います。ESCキーボードの完成!無理やりDashButtonの箱にR3入れてみました。
まとめ
DFUも含めた開発の流れとしては面倒ですが以下のような感じです。
- DFUモードでusbシリアルHexファイルを
ATmega 16u2
に書き込む - sketchプログラム作成
- IDEなどで
ATmeaga 328p
に書き込む - DFUモードでKeyboardデバイスHexファイルを
ATmega 16u2
に書き込む - 確認してsketchプログラムを修正したいのならば 最初へ戻る
今回は単純な構造なのでシンプルなプログラムで実装しましたが、フルスペックキーボードの場合、もっと効率的なプログラムが必要かと思います。
正直 Arudiono Micro
の方が小さくて簡単にキーボード認識させることができるので実用的だと思いますが、DFUというもう少し踏み込んだ開発は楽しいですね。
USBキーボードだけでなく、Hexファイルを変えればマウスにも、MIDI(Moco For LUFA)にもなります。
USB仕様を把握してLUFAライブラリを使って自分で好きなデバイスにもできると思います。サンプルも充実しているので是非チェックしてみてください。
今回のソースはGithubにあります。