いやぁ1月~3月までずっとスキーしてました。パソコンやマイコンなんてほっておいてました。カービングは奥が深いよ!みんな冬は外へ出ましょう!
この投稿をInstagramで見る
ほっときすぎたせいで、Mac Mini起動してないのに壊れるとか…どゆこと?。では、完全に忘れていた98とUSBマウス・キーボードの続きですw。
前までにUSB Host ShieldのセットアップとUSB HostLibrary 2.0のセットアップ・サンプルコードの動作まで終了しました。
カスタムレポートパーサー作成の準備
USBHIDBootKbdAndMouse.inoをベースにして改造していきます。キーボード、マウスのデータの処理は用意されたレポートパーサーというクラスで行っています。
キーボードはKeyboardReportParserというクラスを継承して作ったサブクラスにカスタムの処理を拡張して利用します。以前利用したライブラリにあったサンプルでは押されたキーをASCIIで表示したりシフトなどのキーの押す離すを表示したりする簡易なものとなっています。以下の部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
class KbdRptParser : public KeyboardReportParser { void PrintKey(uint8_t mod, uint8_t key); protected: void OnControlKeysChanged(uint8_t before, uint8_t after); void OnKeyDown (uint8_t mod, uint8_t key); void OnKeyUp (uint8_t mod, uint8_t key); void OnKeyPressed(uint8_t key); }; void KbdRptParser::PrintKey(uint8_t m, uint8_t key) { MODIFIERKEYS mod; *((uint8_t*)&mod) = m; Serial.print((mod.bmLeftCtrl == 1) ? "C" : " "); Serial.print((mod.bmLeftShift == 1) ? "S" : " "); Serial.print((mod.bmLeftAlt == 1) ? "A" : " "); Serial.print((mod.bmLeftGUI == 1) ? "G" : " "); Serial.print(" >"); PrintHex<uint8_t>(key, 0x80); Serial.print("< "); Serial.print((mod.bmRightCtrl == 1) ? "C" : " "); Serial.print((mod.bmRightShift == 1) ? "S" : " "); Serial.print((mod.bmRightAlt == 1) ? "A" : " "); Serial.println((mod.bmRightGUI == 1) ? "G" : " "); }; void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key) { Serial.print("DN "); PrintKey(mod, key); uint8_t c = OemToAscii(mod, key); if (c) OnKeyPressed(c); } void KbdRptParser::OnControlKeysChanged(uint8_t before, uint8_t after) { MODIFIERKEYS beforeMod; *((uint8_t*)&beforeMod) = before; MODIFIERKEYS afterMod; *((uint8_t*)&afterMod) = after; if (beforeMod.bmLeftCtrl != afterMod.bmLeftCtrl) { Serial.println("LeftCtrl changed"); } if (beforeMod.bmLeftShift != afterMod.bmLeftShift) { Serial.println("LeftShift changed"); } if (beforeMod.bmLeftAlt != afterMod.bmLeftAlt) { Serial.println("LeftAlt changed"); } if (beforeMod.bmLeftGUI != afterMod.bmLeftGUI) { Serial.println("LeftGUI changed"); } if (beforeMod.bmRightCtrl != afterMod.bmRightCtrl) { Serial.println("RightCtrl changed"); } if (beforeMod.bmRightShift != afterMod.bmRightShift) { Serial.println("RightShift changed"); } if (beforeMod.bmRightAlt != afterMod.bmRightAlt) { Serial.println("RightAlt changed"); } if (beforeMod.bmRightGUI != afterMod.bmRightGUI) { Serial.println("RightGUI changed"); } } void KbdRptParser::OnKeyUp(uint8_t mod, uint8_t key) { Serial.print("UP "); PrintKey(mod, key); } void KbdRptParser::OnKeyPressed(uint8_t key) { Serial.print("ASCII: "); Serial.println((char)key); }; |
マウスレポートパーサーも同様にMouseReportParserを継承して作られています。MouseReportParserでマウスイベントを発生させ継承したサブクラス(MouseRptParser)で情報の表示を行っています。宣言と実装部は以下です
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class MouseRptParser : public MouseReportParser { protected: void OnMouseMove(MOUSEINFO *mi); void OnLeftButtonUp(MOUSEINFO *mi); void OnLeftButtonDown(MOUSEINFO *mi); void OnRightButtonUp(MOUSEINFO *mi); void OnRightButtonDown(MOUSEINFO *mi); void OnMiddleButtonUp(MOUSEINFO *mi); void OnMiddleButtonDown(MOUSEINFO *mi); }; void MouseRptParser::OnMouseMove(MOUSEINFO *mi) { Serial.print("dx="); Serial.print(mi->dX, DEC); Serial.print(" dy="); Serial.println(mi->dY, DEC); }; void MouseRptParser::OnLeftButtonUp (MOUSEINFO *mi) { Serial.println("L Butt Up"); }; void MouseRptParser::OnLeftButtonDown (MOUSEINFO *mi) { Serial.println("L Butt Dn"); }; void MouseRptParser::OnRightButtonUp (MOUSEINFO *mi) { Serial.println("R Butt Up"); }; void MouseRptParser::OnRightButtonDown (MOUSEINFO *mi) { Serial.println("R Butt Dn"); }; void MouseRptParser::OnMiddleButtonUp (MOUSEINFO *mi) { Serial.println("M Butt Up"); }; void MouseRptParser::OnMiddleButtonDown (MOUSEINFO *mi) { Serial.println("M Butt Dn"); }; |
この2つのサブクラスがinoにあると、とても見にくいので.hファイルに別途記述していくようにします。僕はめんどいので.cppファイルを作らずに.hファイルに実装も記述していきます。
98用キーボードレポートパーサーの作成
とりあえず全コードをGithubに上げます。
ライブラリのサンプルでも使われているKeyboardReportParserクラスが使いやすく98のキー処理もこのクラスを継承して作っています。98特有の処理、不足している処理は継承して作ったサブクラス(KbdRptParser.hのKbdRptParser)に実装していきます
大まかに下の機能を追加しました。
- USBキーボードから98キーボードへスキャンコード変換
- 98本体とキーボードのコマンドやり取り
- キーリピートの処理
- CAPS,NumLock,カナのLED制御
- PC-98の特殊起動(セットアップメニューなど)処理
全部説明すると大変なので、コードを見ていただくとおおむねわかると思うのですがkeyconst.hでスキャンコード変換テーブル、特殊起動時のコマンド定義などを行っています。
コマンドのやり取り、LEDの制御、キーリピート処理は常時監視処理が必要なためloopにてtask()を処理することで実現しています。おおむねPS/2キーボードの変換器を作るときと変わりはないのですが、PS/2のスキャンコードとUSBのそれは違いますし、キーリピートもキーボード側ではなく変換器で実現していますのでコードが修正してあります。
CAPS,NumLock,カナのLEDも変換器側でつけてあり制御しています。NumLockについては変換器側でテンキー部のキーコードを変換しています。LEDは抵抗(10kΩ程度)をかませて定義されているのピンで接続しています。
PC-98の特殊起動の実現
セットアップメニューなどの特殊モードでの起動は、以前PS2キーボード変換器制作時に解説した通り、起動直後でコマンドを送らないとなりません。ESP32ではUSB Host Shieldの初期化をしキーボードを認識するまでには時間がかかりすぎてUSBキーボードからは起動することができないため、変換器にボタンを設置してそれを押して起動することで実現しています。
1 2 3 4 5 6 |
static uint8_t BOOTMODE_SETUP_MENU[2] = {0x3F,0xBF}; static uint8_t BOOTMODE_CRT_24[4] = {0x01,0x73,0x81,0x01}; static uint8_t BOOTMODE_CRT_31[4] = {0x02,0x73,0x82,0x02}; static uint8_t BOOTMODE_BIOS_REV[5] = {0x01,0x3F,0x00,0x80,0x00}; static uint8_t BOOTMODE_CPU_MSG[5] = {0x73,0x74,0x72,0x71,0xF4}; static uint8_t BOOTMODE_NO_MEMCHK[3] = {0x60,0xE0,0x60}; |
上の定義はkeyconst.hの抜粋ですが6つの起動モードを定義してあります。右辺値が起動時に必要なコマンドとなります。6つのボタンを変換機に組み込みますが、ピン数節約のためボタンアレイ(分圧回路)を接続し1pinのみで制御しています。ESP32販売元のEspressifの以下の資料を参考にしています。
98用マウスレポートパーサーの作成
マウスレポートパーサーはスクロール機能やボタンを多く持つマウス、複合デバイスでの特殊なマウスなど色々なパターンがあり、ライブラリ標準のMouseReportParserだと、機能不足な面もありましたので、色々なHIDデバイスのレポートパーサーの継承元であるHIDReportParserを継承して98用に作ってみました。
1 2 3 4 5 6 7 8 9 10 11 |
~ 略 ~ USB Usb; USBHub Hub(&Usb); HIDBoot < USB_HID_PROTOCOL_KEYBOARD | USB_HID_PROTOCOL_MOUSE > HidComposite(&Usb,false); KbdRptParser KbdPrs; BskBw120aMParser MousePrs; ~ 略 ~ |
HidComposite(&Usb,false);をHidComposite(&Usb,true);にしてあげればいいのですが、このサンプルでtrueに変えると受け取るデータの構造が変わりますのBskBw120aMParserでは処理できませんのでマウスは当然動かなくなります。OUTPUT_MOUSE_DATA=trueにすれば生データを見れますので、スクロール情報も見てみたい方はどうぞ…。
話を戻します。Pc98MouseReportParserは
- USBマウスデータから98マウスデータへの変換・送信
これのみを担当しています。スクロールアップ、ダウンなどの定義の作りこみがまだ甘い(実装できるかな?)ですが、カーソルやボタンの押し離しは問題なくできます。
ライブラリ標準のMouseReportParserではデータをMOUSEINFO構造体にキャストして使っていますが、このクラスのMOUSEINFO_EX構造体は、あくまで箱として利用してParseMouseDataのオーバーロード関数で参照渡しされた引数MOUSEINFO_EX &pmiにセットしていく形としています。
そのうち他のマウスのサンプルも少し上げていきたいと思います。
その他の説明
inoファイルは基本的にloop()でUsb.Task();を処理します。KbdPrs.Task();は98側とキーボードデータのやり取りを常時行っています。最初はloop()で両方の処理を実施していたのですが、Usb.Task()が重い処理(特に初期化時)で回らなくなってキーボードのデータ処理が追い付いていないことが判明しました。というわけでESP32のデュアルコアを生かし、Core0(通常Wifiで利用)にてキーボードデータ処理をマルチタスクで実装しています。
1 2 3 4 5 6 7 8 9 10 11 12 |
void loop() { Usb.Task(); //KbdPrs.task(); } void Core0_KbdTask(void *args) { while (1) { delay(1); KbdPrs.task(); } } |
ESP32との接続
ピンアサインはコード中に書いていますが
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//98側キーボード(miniDin8)ピン定義 #define RST 4 //リセット要求 #define RDY 34 //送信可能 #define RXD 22 //データ送信 #define RTY 39 //再送要求 //ボタンアレイ接続ピン定義 #define BUTTON_ARRAY_ADC_PIN 35 //LED接続ピン定義 #define KANA_LED 12 #define CAPS_LED 13 #define NUM_LED 15 //98側マウス(miniDin9)ピン定義 #define XA 27 #define XB 14 #define YA 32 #define YB 33 #define LB 25 #define RB 26 |
となっています。MiniDin側(98と接続する側、RST,RDY,RXD,RTY,XA,XB,YA,YB,LB,RB)はロジック電圧が5vですので、ロジック変換機をかませて3.3vにしてESP32と接続してください。直結すると破損しますよ?コネクタのピンアサインはPS/2変換器の時の記事を参考にしてください。
ボタンアレイは適当なボタンと抵抗を用意して上のボタンアレイ回路図どおりに接続してください。ボタンを押してみて正常に分岐されるのを確認してください。
LEDは10kΩ程度の抵抗を挟んで各々のpinに接続します。
USBホストアダプタは前記事の接続と一緒です
ESP32の開発ボードには5v->3.3vのレギュレータが載ってますのでESP32には5vを供給してOKですが、開発ボードでないESP32を使う場合は別途レギュレータや書き込み回路が必要となりますので、そちらは別サイトで確認してください。
結線が大変ですが、難しくはありませんので時間をかけて確認しながら進めます。読んでる人なら不要と思いますが、いずれ回路図(といってもただの結線図)を作る気持ちはありますw
全面にはCAPS,NumLock,カナのLEDをつけて、上には各種起動モードを割り当てたボタンを配置しています。USBには無線のドングルをさしてあります。書き足りないこともありますが、いつまでたっても記事化できないので・・・気づいたら追記します。
最後と言いましたが、Bluetooth(BLE)キーボード、マウスもやってみました。USBシールドを使わない分簡単で安くできます