Posted at

Arduino Leonardo(Pro Micro)のHID(キーボード)機能を使う(ショートカットキー実行,コマンド実行)

More than 1 year has passed since last update.


はじめに

Arduino LeonardoはPCなどとUSB接続したとき,自身をHIDとして認識させる機能が標準で備わっている.HID(ヒューマン・インターフェイス・デバイス)とはUSB接続のキーボードやマウス,ゲームパッドのような入出力装置だと思っておけば差し支えない.

つまりArduinoによって任意の文字列をPCに打ち込んだり,マウスのスクロール操作を行ったりということが可能になる.

この記事では特にキーボードとして動作させる.

参考リンク:

Arduino Mouse and Keyboard libraries : https://www.arduino.cc/en/Reference/MouseKeyboard

DEKOのアヤシいお部屋。>Arduino Leonardo : http://ht-deko.com/arduino/leonardo.html


マイコンボードを選ぶ際の注意点

HID機能はArduinoの中でも32u4マイコンを使用したLeonardoやMicro,ProMicro,他にDue,Zeroなど限られたボードでしか動作しない.UNOやMegaでは動作せず,ファームウェアの特別な書換えによって対応する必要があるので,初心者の方は無理せず対応するボードを入手することをお勧めする.


この記事でやること

Arduinoにつないだボタンを押すと


  • 任意の文字列を入力

  • ショートカットキーを操作

  • コマンドプロンプトを起動して任意のコマンドを実行

など.基本的にLeonardoでできること・使い方の紹介の記事としたいので,他にどう使うかは読者の想像力におまかせする.

記事においてコマンドの内容はWindowsでの利用を前提とするが,他OSでも考え方は基本的に同じである.


使うもの


  • Arduino Pro Micro

  • ブレッドボード

  • ジャンプワイヤ

  • タクトスイッチ(5個)

  • Arduino IDE 1.6.6

今回はLeonardoの代わりにProMicroを使用した.スケッチの内容や配線はいずれも同じままで動作する.IDEから書き込む際はマイコンボードの種類は「Arduino Leonardo」を選択すれば(今回の動作範囲では)問題ない.

それからキーボード入力を受け付けるPCと,それらを接続するUSBケーブルが必要.


回路

デジタルIOピンにタクトスイッチを5個接続するだけなので回路図は省略.

D5~D9をpinMode(Dn,INPUT_PULLUP);してタクトスイッチを介してGNDに接続.キー入力イベントを起こすためだけに使用する.

マイコンの電源はPCから供給する.


スケッチ

ArduinoIDEにサンプルスケッチがいくつかあるので,基本的な使い方はそれを読んだり手を加えることで理解できるかと思う.

ここではサンプルスケッチよりも単純化したもので動作を紹介することにする.


文字・文字列の入力

キーボードとして文字や文字列を入力する.


test_keyboard1.ino

#include "Keyboard.h"


#define Button5 9

void setup() {
Keyboard.begin();
pinMode(Button5, INPUT_PULLUP);
}

void loop() {
if(digitalRead(Button5) == LOW){
Keyboard.print("Arduino operating time ");
Keyboard.write(','); //
Keyboard.print(millis());
Keyboard.write('\n');
delay(100);

while(digitalRead(Button5) == LOW);
}

delay(100);
}


ボタンが押されるとキー入力し,文字列とマイコンの動作時間を押す度に入力する.

キー入力イベントの最後でwhile(digitalRead(Button5) == LOW);としているのは,押し下げられている間はloop処理に戻らずその場に留まることで同じ処理を意図せず何度も実行しないようにするためである.

KeyboardKeyboard_クラスのインスタンスでKeyboard.cppに定義されている.

公式リファレンスでは処理を始める前にKeyboard.begin();によって初期化しなければならないとされているが,ライブラリのメンバ関数を辿ってみると空の関数となっていて意味のない処理となっている.Keyboard.end();も同様に空の関数として定義されており,呼び出す必要がない.将来的な事を考えて予約しているのかもしれない.


文字入力処理におけるエラー

そのまま使おうとすると思いがけないエラーがいくつかあったので紹介しておく.


println()による改行処理

自分の環境だとprintln()がうまく改行動作をしないのでwrite('\n')で代用した.println()は通常,print()の引数文字列の末尾に"\r\n"を付加している.


記号文字の入力

記号を入力しようとすると意図したものと違う記号が入力されることがある.これはJPキーボードとUSキーボードなどキー配列の違いによる.

自分の環境ではArduinoは自身を英語配列キーボードであるものとして出力し,Windowsは日本語配列キーボードが接続されていると認識して入力を受け付けるようで,例えばスケッチで「@」を扱おうとすると英語配列において「@」に対応するキーが出力され,日本語配列において対応する記号の「”」が入力される.この現象はスケッチ中に記号を直接入力するのではなくアスキーコードの値を入力しても対処することができない.一方でそれを先読みするのであれば,例えば「:」を入力しようとすると「+」が入力されてしまうため,「:」を入力するためにスケッチでは「’」(シングルクォーテーション)を入力するように記述することになる.

半兵衛の城大手門>JP106からUS101キーボード対応図 : http://www.h4.dion.ne.jp/~hanbei/keyb.htm

汎用性の高いコードを書こうと思うなら想定するキー配列に共通した記号を使うなどの配慮が必要になる.


非ASCIIキーの入力

普段使うキーボードに文字以外のキーがあるように,Arduinoも文字キー以外の入力動作ができる.

複数のキーを同時押しするショートカットキーを登録しておけば.省力化やキー入力だけで特別な動作をすることができる.また,別の言い方をするとGUI環境で普段マウスを併用する動作を割り当てておくとカーソルを合わせる手間や注意力を省くことができ,複雑なキー操作を素早く間違いなく実行できる.


test_keyboard2.ino

#include "Keyboard.h"


#define Button1 5
#define Button2 6
#define Button3 7
#define Button4 8
#define Button5 9

void setup() {
Keyboard.begin();
pinMode(Button1, INPUT_PULLUP);
pinMode(Button2, INPUT_PULLUP);
pinMode(Button3, INPUT_PULLUP);
pinMode(Button4, INPUT_PULLUP);
pinMode(Button5, INPUT_PULLUP);
}

void loop() {
if(digitalRead(Button1) == LOW){ // タスクマネージャの起動
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press(KEY_ESC);
delay(100);
Keyboard.releaseAll();

while(digitalRead(Button1) == LOW);
}

if(digitalRead(Button2) == LOW){ // ウィンドウを閉じる
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(KEY_F4);
delay(100);
Keyboard.releaseAll();

while(digitalRead(Button2) == LOW);
}

if(digitalRead(Button3) == LOW){ // デスクトップの表示
Keyboard.press(KEY_LEFT_GUI);
Keyboard.press('d');
delay(100);
Keyboard.releaseAll();

while(digitalRead(Button3) == LOW);
}

if(digitalRead(Button4) == LOW){ // Arduinoスケッチの書き込み
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('u');
delay(100);
Keyboard.releaseAll();

while(digitalRead(Button4) == LOW);
}

if(digitalRead(Button5) == LOW){
while(digitalRead(Button5) == LOW);
}

delay(100);
}


各ショートカットキーの説明は省略する.Button3でデスクトップ表示する際に使っている「KEY_EFT_GUI」は左の「Win」キーの事を意味している.

各ボタンイベントで入力した後にdelay(100)で100ms処理を停止しているのは,入力を受けるPC側が瞬間的なビジー状態により入力を受けられない状況を回避するための処理である.

Keyboard.releaseAll();全てのキーを離した状態にしている.キーの同時押しの場合は押してすぐに離すKeyboard.write()ではなくKeyboard.press()によって押下状態を継続させながら押下ボタンを増やす必要があるが,目的のキーを全て押した後にまとめて離す処理を担っている.

意外と便利なのはArduinoIDEのスケッチの書き込みである.「u」キーはキーボードの中央付近にあってCtrlとの同時押しが面倒なのと,他の場面で使うショートカットではないので慣れないというのもあり小気味がいい.Arduino自身もまさか自分の制御プログラムの書き換えトリガーが自分自身であるとは夢にも思うまい.

スケッチにおける非ASCIIキーの入力はKeyboard.hで定義されている以下の対応文字列を利用できる.


Keyboard.h(抜粋)

#define KEY_LEFT_CTRL   0x80

#define KEY_LEFT_SHIFT 0x81
#define KEY_LEFT_ALT 0x82
#define KEY_LEFT_GUI 0x83
#define KEY_RIGHT_CTRL 0x84
#define KEY_RIGHT_SHIFT 0x85
#define KEY_RIGHT_ALT 0x86
#define KEY_RIGHT_GUI 0x87

#define KEY_UP_ARROW 0xDA
#define KEY_DOWN_ARROW 0xD9
#define KEY_LEFT_ARROW 0xD8
#define KEY_RIGHT_ARROW 0xD7
#define KEY_BACKSPACE 0xB2
#define KEY_TAB 0xB3
#define KEY_RETURN 0xB0
#define KEY_ESC 0xB1
#define KEY_INSERT 0xD1
#define KEY_DELETE 0xD4
#define KEY_PAGE_UP 0xD3
#define KEY_PAGE_DOWN 0xD6
#define KEY_HOME 0xD2
#define KEY_END 0xD5
#define KEY_CAPS_LOCK 0xC1
#define KEY_F1 0xC2
#define KEY_F2 0xC3
#define KEY_F3 0xC4
#define KEY_F4 0xC5
#define KEY_F5 0xC6
#define KEY_F6 0xC7
#define KEY_F7 0xC8
#define KEY_F8 0xC9
#define KEY_F9 0xCA
#define KEY_F10 0xCB
#define KEY_F11 0xCC
#define KEY_F12 0xCD


キー入力の連続処理

Arduinoのスケッチを利用している以上,もっと複雑な処理ができて然るべきである.

Windowsでは「ファイル名を指定して実行」「Win」+「R」のキー操作だけで呼び出すことができる.「ファイル名を指定して実行」は実行ファイルを起動したりファイルを開くだけでなく,いくつかのコマンド操作に対応しているので,今回はそれを利用する.


test_keyboard3.ino

#include "Keyboard.h"


#define Button1 5
#define Button2 6
#define Button3 7
#define Button4 8
#define Button5 9

void setup() {
Keyboard.begin();
pinMode(Button1, INPUT_PULLUP);
pinMode(Button2, INPUT_PULLUP);
pinMode(Button3, INPUT_PULLUP);
pinMode(Button4, INPUT_PULLUP);
pinMode(Button5, INPUT_PULLUP);
}

void loop() {
if(digitalRead(Button1) == LOW){
while(digitalRead(Button1) == LOW);
}

if(digitalRead(Button2) == LOW){
Keyboard.write(0xCE); // PrintScreen
delay(100);

Keyboard.press(KEY_LEFT_GUI); // ファイル名を指定して実行の起動
Keyboard.press('r');
Keyboard.releaseAll();
delay(100);

Keyboard.print("mspaint"); // ペイント起動
Keyboard.write(KEY_RETURN);
delay(300);

Keyboard.press(KEY_LEFT_CTRL); // クリップボードのデータを貼り付け
Keyboard.press('v');
Keyboard.releaseAll();

delay(100);
Keyboard.releaseAll();

while(digitalRead(Button2) == LOW);
}

if(digitalRead(Button3) == LOW){
Keyboard.press(KEY_LEFT_GUI); // ファイル名を指定して実行の起動
Keyboard.press('r');
Keyboard.releaseAll();
delay(100);

Keyboard.print("notepad"); // メモ帳起動
Keyboard.write(KEY_RETURN);
delay(100);
Keyboard.releaseAll();

while(digitalRead(Button3) == LOW);
}

if(digitalRead(Button4) == LOW){
Keyboard.press(KEY_LEFT_CTRL); // Arduinoスケッチの書き込み
Keyboard.press('u');
delay(100);
Keyboard.releaseAll();

while(digitalRead(Button4) == LOW);
}

if(digitalRead(Button5) == LOW){
while(digitalRead(Button5) == LOW);
}

delay(100);
}


スケッチにおいてペイントの起動後300ms処理を停止しているのは,ペイント自身の起動に少し時間がかかるためである.起動が終了する前に貼り付け処理を実行しても,準備が整っていないため意図した通りに動作しない結果となる.この辺は入力を想定するPCのスペックや実行ファイルの起動時間などを勘案して調整する.例えばラズベリーパイのような低スペックPCでの利用を想定するならば1000msでは足りないかもしれないし,裏で別プロセスが動作している可能性があるならばもっと余裕を見るべきかもしれない.

「ファイル名を指定して実行」の他のコマンドはリンク先などを参照.Arduinoを使わずとも思いのほか日常的に便利と感じるかもしれない.

カモメのリズム>Windows の「ファイル名を指定して実行」を使いこなす! ~ 知ってるとはかどるよく使う機能の呼び出し方を紹介します。 【Windows10 対応】 : http://www.kamomer.com/entry/windows10-run-filename

コマンド実行の他にも例えばフルパスを入力してテキストファイルを開き,データログを残すというやり方も考えられる.


コマンドプロンプトの起動・入力

ここで説明する内容は使い方によって大きな危険を引き起こすことがあるため,悪ふざけや悪意を持った開発のために利用してはいけません.

キー入力の可能性を更に拡げるにはどうすればいいか.GUIがコンピュータ世界の標準のような時代ではあるが,やはりCUIの事を忘れるわけにはいかない.

Windowsではキー入力だけでコマンドプロンプトを起動し,コマンドを実行することができる.

ファイル名を指定して実行を起動,コマンドプロンプト起動「cmd」→「Enter」とする.あとは通常のコマンドプロンプトにおける操作となるので,いつものキー入力をArduinoにさせるだけとなる.


test_keyboard4.ino

#include "Keyboard.h"

#define Button1 5
#define Button2 6
#define Button3 7
#define Button4 8
#define Button5 9

void setup() {
Keyboard.begin();
pinMode(Button1, INPUT_PULLUP);
pinMode(Button2, INPUT_PULLUP);
pinMode(Button3, INPUT_PULLUP);
pinMode(Button4, INPUT_PULLUP);
pinMode(Button5, INPUT_PULLUP);
}

void loop() {
if(digitalRead(Button1) == LOW){
Keyboard.press(KEY_LEFT_GUI); // ファイル名を指定して実行の起動
Keyboard.press('r');
Keyboard.releaseAll();
delay(100);

Keyboard.print("cmd"); // コマンドプロンプト起動
Keyboard.write(KEY_RETURN);
delay(300);

Keyboard.print("echo %date%%time%"); // 日付時刻の表示
Keyboard.write(KEY_RETURN);

delay(100);
Keyboard.releaseAll();

while(digitalRead(Button1) == LOW);
}

if(digitalRead(Button2) == LOW){
while(digitalRead(Button2) == LOW);
}

if(digitalRead(Button3) == LOW){
while(digitalRead(Button3) == LOW);
}

if(digitalRead(Button4) == LOW){
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('u');
delay(100);
Keyboard.releaseAll();

while(digitalRead(Button4) == LOW);
}

if(digitalRead(Button5) == LOW){
while(digitalRead(Button5) == LOW);
}

delay(100);
}


重ねて言うが,入力するPCに大きな危険をもたらす可能性があるので,デバイスの特性を理解しないまま安易に利用するのは控えた方がよいと考える.

例えば同じコマンドでもOSの違いにより動作が異なっていたり,他のキーボードデバイスから文字入力の割り込みがあったり,Arduinoの動作が不安定になり正しく通信できないなど,スケッチ自体が正しく安全に記述されていても実用上危険が生じる可能性は考えられる.


おわりに

このように,普通ならPC向けのアプリケーションを用意したりバッチファイルを使ったりして実現する処理をArduinoの接続によって置き換えることができる.マイコン開発全般に言えることだが,潜在的に高い機能を持っていても,開発する側の技量によって便利アイテムにもガラクタにもなり得る.もしかすると記事にしたものよりもっと便利な使い方があるかもしれない.