電子工作
USB

USBキーボードを実装してキーボードとPCの間で通信を行う(Windows編)

USBキーボードとして振る舞う機器をマイコンで自作する話はたまに目にしますが,マルチバイト文字の入力や,PCからキーボードへのデータの送信など一筋縄でいかない部分も多いのでまとめてみました.

2010年頃に職場のエンジニアLTで発表したネタの焼き直しですが,忘備録を兼ねて&もっとマシなやり方あるよ,というコメントあったら嬉しい.

TL;DR

  • USBでキーボードの振りしてスクリプトを入力したりするやつ実装してみたよ
  • キーボードデバイスだってLEDの状態通知イベントで双方向通信できるよ
  • LED等が高速に点滅してたら何か通信してるかもなので要注意

多くの人にとって役に立たないので,ここで読み終えて問題ありません.

USBメモリサイズのキーボード(っぽい何か)を作る

手間もお金もかけたくないので,AVRとV-USBを使ってお手軽に実装しました.(※8年近く前の話なので,2018年の未来技術を使えばもっと簡単なはずですが)

AVR

Atmelの8ビットマイコン.最近目にするところだと,Arduinoに載ってるのもAVRです.

同じ用途だとPICの方が有名かもしれませんが,プログラム書くとSAN値が下がるPICの変態アーキテクチャと比べて,AVRの方が圧倒的にまともです.個人的に好きなマイクロプロセッサ・ベスト3くらいに入ります.

あと,百円~数百円で買えます.

V-USB (旧:AVR-USB)

V-USBはソフトウェアで実装されたUSB(Universal Serial Bus)デバイス向けファームウェアです.

AtmelのAVRや,AVRが載っているArduinoなどでも動作します.ソフトウェアで実装されているため,USBコントローラを持たないAVRで最小限の外付け部品でUSBデバイスを作成出来ます.ただし,low-speedデバイスしかサポートされないので,通信速度は最大1.5Mbpsです.

GPLのOSSに使う限りは無料です.

外観

どう見てもUSBメモリには見えませんが,サイズは少し大きめのUSBメモリサイズになりました.

usb01.jpg

写真右側の凸部分が簡易のUSBコネクタになっていてUSBポートに刺さります.

左側に2つタクトスイッチ(ボタン)付いているのはデバッグ用です.
制作費,500円くらいです.

回路

雑な説明です.

 PC <--- USB ---> 抵抗とか色々 <---> ATMEGA88 <---> デバッグ用ボタン

基本,V-USB任せなので,ほぼUSBに直結です.

ファームウェア

LTの発表用に突貫で作ったものなので役に立たない気がしますが,一応GitHubのURL.
https://github.com/binzume/lt201005-usbkeyboard

やってみたこと

エディタ(notepad.exe)を起動

Windowsであれば,Win + R キーで「ファイル名を指定して実行」を開き,notepad.exe と入力して起動できます.

これはWindowsでしか使えません.実行環境ごとに動作を変えるのは結構面倒そうだったので試してないです.
(USBのデバイス認識時のメッセージの内容や送信タイミングの微妙な差や,エラーから復帰するときの挙動でWindowsとMacOSくらいは区別できそうだと思いました.V-USBは全部ソフトウェア実装なのでUSB的に不正な信号を生成したり,微妙なパケット送受信タイミングの差を計測したりすることは容易です)

WSHスクリプトを入力&保存&実行

notepadが起動できたなら大抵のWindowsマシンで実行可能なWSHスクリプトをjsかvbsで入力できます.
WSHからは自由にHTTPリクエスト投げたり出来るので便利ですね.

WSHスクリプトの例: https://github.com/binzume/lt201005-usbkeyboard/blob/master/main.c#L165

prog_char pagedata_0[] PROGMEM = "\x05r\anotepad\n\a" "\04of\t\t5\n\a"
    "var f='test01.js -',args=WScript.Arguments,ws=new ActiveXObject('WScript.Shell');\n"
    "if(args.count()==0){"
    "ws.SendKeys('{CAPSLOCK}');\n"
    "var ie=new ActiveXObject('InternetExplorer.Application');\n"
    "ie.Navigate('about:blank');while(ie.Busy)WScript.Sleep(5);\n"
    "var iew=ie.Document.parentWindow;\n"
    "function cbget(){try{return iew.clipboardData.getData('text')}catch(e){}}\n"
    "function cbset(s){try{iew.clipboardData.setData('text',s)}catch(e){}}\n"
    "ws.run('wscript.exe \"'+WScript.ScriptFullName+'\" a');\n"

    "cbset('*');WScript.Sleep(500);ws.SendKeys('%{tab}');WScript.Sleep(100);ws.AppActivate(f);ws.SendKeys('{CAPSLOCK}');\n"
    "for(;;){WScript.Sleep(50);s = cbget();if(s=='end' || s=='')break;if(s=='*') continue;\n"
    "cbset(decodeURI(s));ws.AppActivate(f);ws.SendKeys('^a^v');WScript.Sleep(10);cbset('*');}ie.Quit();\n"
    "}else{for(i=0;i<500;i++){WScript.Sleep(9);if(ws.AppActivate('Internet Explorer')){ws.SendKeys('a');}}}\n"

    "\x03s\a\a%TEMP%\\test01.js\n\ay\a"
    "\x05r\a%TEMP%\\test01.js\n"

    "\a\a"

    "\03a ready.\03a\03c";

元のソースコード見つからなかったのでCのコードをそのまま貼りました.どんなスクリプトなのかはフィーリングで察して下さい.(気力があったら書き直します).入力用の \x03 とか \x05 のような謎の文字が入ってますが,一定時間待つとか,CTRLやALTを押す処理を実行するコードです(その辺の処理もmain.c内にあります).

PCからキーボードへの情報伝達

キーボードからPCへの伝達はキー入力のイベントを送信すれば良いのですが,逆向きの通信は難易度が高めです.HIDのAPI経由で直接データを書き込むことは可能ですが,特別な権限が不要な範囲で実装したいですね.

とりあえず使えそうなものを探すと,キーボードにいくつかあるLEDが目につきます.

USBキーボードのLEDはホスト(PC)側で状態を管理し,キーボードに状態を提供することになっています.なのでPCに複数のキーボードをつないで1つのキーボードでCapsLockを操作すると他のキーボードのCapsLockのLEDの状態も変化するはずです.

CapsLockの状態はwshのSendKeysで変更可能なので,これを使ってキーボードとの間のシリアル通信を行うことが出来ます.(上のwshスクリプトの例でも処理終了時の同期を取るためにCAPSLOCKをON/OFFしてます)
CapsLockでなくとも,ScrollLockとかの方が他に影響しなくて良さそうですが何でも良いと思います.

CapsLock等の状態が複数のキーボード間で共有されることを利用すると,キーボードとキーボードの間の通信も特別なソフトウェアなしで実現することが出来ます(真っ当な用途があるとは思えませんが)

日本語入力

意外と苦戦したのが日本語入力.PCのUSBポートに接続すると,自動的に発表用プレゼン資料を入力するプレゼンをしようと思ったのですが,マルチバイト文字の入力の方法に迷いました.

とりあえずWindowsなら,

  • WSHスクリプトを自動入力&実行して文字コードから文字への変換等を行う(上に貼ったスクリプト)
  • Alt押しながら文字コードを入力する(入力先のアプリケーション依存)
  • もっと良い方法あるかも...

セキュリティ的な話

怪しげなふるまいをするUSBデバイスは簡単に作れてしまうものなので,信頼できないデバイスを接続するときは要注意です.OSレベルでUSBのリムーバブルデバイス接続を制限されているような環境でも,PCからUSBデバイスへデータ転送する手段は普通にあるので要注意かもです.