eucalyのアイコン画像

キーボード:QMKコンフィグから、Arduinoプログラミング、他

eucaly 2021年11月17日に作成  (2021年11月17日に更新)

キーボード:QMKコンフィグから、Arduinoプログラミング、他

自作キーボード ≒ Arduino

自作キーボード界隈でもてはやされている「ATMEGA32U4」と、実装マイコンボードである「Pro Micro」は、まあArduino系なハードウェアだったりします。
マイコンはともかく、Pro Microは、「Arduino Micro」から、ISPだのリセットボタンだのを取っ払っただけ、ですしね。
ですが、ソフトウェアとしてArduinoは、あまり使われません。
まあガッツリソース書く必要あるし、オーバーヘッドもアレだし、キーボード作りには足りなかったり過剰だったりしますしね。

でもまあ、「Arduino使える」なら、やってみたくなるわけで。
いっちょ、Arduinoでキーボード制御に挑戦してみました。

回路図を読まずにQMKコンフィグを読む

さて、キーボード界隈でよー使われる「QMK」というファームウェアがあります。
https://github.com/qmk/qmk_firmware

これは、定義ファイルを作ることで、ATMEGA32U4ベースの様々なキーボードにフィッティングが可能です。
公式にアップロードされている定義も、たくさんあります。
https://qmk.fm/keyboards/

今回のEUTである「exmp」は公式には載っておりませんが、作者さんがQMK用の定義を公開してくれています。
https://abitkeys.com/exmp/build_base.html
の、「exmp_rev1_via.zip」ですね。

QMKの定義を見ると、キーボードマトリクス回路の定義は「config.h」で行っている様です。
以下、exmpのconfig.hの抜粋です。

config.h

#define MATRIX_ROW_PINS { F6, F7, B0, B1, B2, B3, B4, B5, B6, B7, C6, C7, D0, D1, D2, D3, D4, D5 } #define MATRIX_COL_PINS { F0, F1, F4, F5 } #define UNUSED_PINS /* COL2ROW, ROW2COL*/ #define DIODE_DIRECTION COL2ROW

ま、意味はそのままで。
キーボードマトリクスのCOLとROWに使われているピン、及びCOLとROWの使いざま(どっちが入力でどっちが出力か)が定義されています。

このままではArduinoのデジタルピン定義では無いですが。
これはATMEGA32U4のピングループ表記なので、Arduino公式のピンアウトで対照すればOKです。
キャプションを入力できます
オレンジ色の「Microcontroller's port」と、斜めストライプの「Digital Pin」で対照を取ればOKです。
例えば、「F6」はピンアウト「PF6」、つまりArduinoデジタルピン的には「19」です。
Arduinoではデジタルピンとして定義されていない「B0」「D5」 「C7」を使う場合はポートレジスタを使うなどの工夫が必要かも。

キーボードマトリクスの使い方

まあ回路的なお話はさておき。
検知のやりざまは

  • 検知側をプルアップ、出力側をHi-Zにする
  • 出力側を1ピンずつLowに引っ張る
  • 都度、検知側のHi/Lowを全ピン見る
    と、各ボタン未入力時「1」、入力時「0」なビット配列を入手できます。
    同時押ししていても、各ボタンの状態が検知可能な優れもの。

キャプションを入力できます

ま、上手くいかなかったら、ROWとCOLをひっくり返してみて下さいな。
「COL2ROW」の場合、COLが検知側、ROWが出力側です。

Arduino keyboard周りライブラリ改造

HIDキーコードハンドリング

Arduinoには最初から、USBキーボードライブラリが入っていますが。
内部的にHIDキーボード番号を、ASCII文字列に読み替えて使っていたりします。
そのままだと使い勝手が悪いので、HIDキーボードコードをそのまま送信できるよう改造します。
以下URLあたりが参考になりますよと。
https://qiita.com/Hiroki_Kawakami/items/8dc1e33e4ed41ca8809f

POWERキー実装

Windows系の「Power」キー、まあ昔キーボードにくっついてて蛇蝎の如く嫌われてたアレもくっつけてみましょうか。

コレね。
キャプションを入力できます

コレ、実はWindowsと他(MacやUnix)では実装が異なり、Windowsの場合、ここの3つのボタンは、「キーボードじゃない別デバイス」だったりします。

なので、USBのディスクリプタにシステムコントロール周りを追加します。
あとついでに送信最大値を254に拡張。

_hidReportDescriptor.cpp

static const uint8_t _hidReportDescriptor[] PROGMEM = { // Keyboard 0x05, 0x01, // USAGE_PAGE (Generic Desktop) // 47 0x09, 0x06, // USAGE (Keyboard) 0xa1, 0x01, // COLLECTION (Application) 0x85, 0x02, // REPORT_ID (2) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x08, // REPORT_COUNT (8) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0xFE, // LOGICAL_MAXIMUM (115) / 254 modify 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x73, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0xc0, // END_COLLECTION // System Control Descriptor 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x80, // Usage (System Control) 0xa1, 0x01, // Collection (Application) 0x85, 0x03, // Report ID 0x03 [SYSTEM CTRL] 0x19, 0x81, // Usage minimum (System Power) 0x29, 0x83, // Usage maximum (System Wake up) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x02, // REPORT_COUNT (2) 0x81, 0x06, // INPUT (Data,Var,Rel) // -------------------- padding bits 0x95, 0x06, // REPORT_COUNT (6) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0xc0, // END_COLLECTION };

・・・その後の検討で、このへんの改造、結果的に意味が無かったんですけれども・・・。

改造後ソース

こんな感じになりました。

Keyboard.cpp

/* Keyboard.cpp Copyright (c) 2015, Arduino LLC Original code (pre-library): Copyright (c) 2011, Peter Barrett This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "Keyboard.h" #if defined(_USING_HID) //================================================================================ //================================================================================ // Keyboard static const uint8_t _hidReportDescriptor[] PROGMEM = { // Keyboard 0x05, 0x01, // USAGE_PAGE (Generic Desktop) // 47 0x09, 0x06, // USAGE (Keyboard) 0xa1, 0x01, // COLLECTION (Application) 0x85, 0x02, // REPORT_ID (2) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x08, // REPORT_COUNT (8) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0xFE, // LOGICAL_MAXIMUM (115) / 254 modify 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x73, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0xc0, // END_COLLECTION // System Control Descriptor 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x80, // Usage (System Control) 0xa1, 0x01, // Collection (Application) 0x85, 0x03, // Report ID 0x03 [SYSTEM CTRL] 0x19, 0x81, // Usage minimum (System Power) 0x29, 0x83, // Usage maximum (System Wake up) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x02, // REPORT_COUNT (2) 0x81, 0x06, // INPUT (Data,Var,Rel) // -------------------- padding bits 0x95, 0x06, // REPORT_COUNT (6) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0xc0, // END_COLLECTION }; Keyboard_::Keyboard_(void) { static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor)); HID().AppendDescriptor(&node); } void Keyboard_::begin(void) { } void Keyboard_::end(void) { } void Keyboard_::sendReport(KeyReport* keys) { HID().SendReport(2,keys,sizeof(KeyReport)); } void Keyboard_::pressSystemPower(void) { HID().SendReport(3,1,1); } void Keyboard_::pressSystemSleep(void) { HID().SendReport(3,2,1); } void Keyboard_::releaseSystem(void) { HID().SendReport(3,0,1); } extern const uint8_t _asciimap[128] PROGMEM; #define SHIFT 0x80 const uint8_t _asciimap[128] = { 0x00, // NUL 0x00, // SOH 0x00, // STX 0x00, // ETX 0x00, // EOT 0x00, // ENQ 0x00, // ACK 0x00, // BEL 0x2a, // BS Backspace 0x2b, // TAB Tab 0x28, // LF Enter 0x00, // VT 0x00, // FF 0x00, // CR 0x00, // SO 0x00, // SI 0x00, // DEL 0x00, // DC1 0x00, // DC2 0x00, // DC3 0x00, // DC4 0x00, // NAK 0x00, // SYN 0x00, // ETB 0x00, // CAN 0x00, // EM 0x00, // SUB 0x00, // ESC 0x00, // FS 0x00, // GS 0x00, // RS 0x00, // US 0x2c, // ' ' 0x1e|SHIFT, // ! 0x34|SHIFT, // " 0x20|SHIFT, // # 0x21|SHIFT, // $ 0x22|SHIFT, // % 0x24|SHIFT, // & 0x34, // ' 0x26|SHIFT, // ( 0x27|SHIFT, // ) 0x25|SHIFT, // * 0x2e|SHIFT, // + 0x36, // , 0x2d, // - 0x37, // . 0x38, // / 0x27, // 0 0x1e, // 1 0x1f, // 2 0x20, // 3 0x21, // 4 0x22, // 5 0x23, // 6 0x24, // 7 0x25, // 8 0x26, // 9 0x33|SHIFT, // : 0x33, // ; 0x36|SHIFT, // < 0x2e, // = 0x37|SHIFT, // > 0x38|SHIFT, // ? 0x1f|SHIFT, // @ 0x04|SHIFT, // A 0x05|SHIFT, // B 0x06|SHIFT, // C 0x07|SHIFT, // D 0x08|SHIFT, // E 0x09|SHIFT, // F 0x0a|SHIFT, // G 0x0b|SHIFT, // H 0x0c|SHIFT, // I 0x0d|SHIFT, // J 0x0e|SHIFT, // K 0x0f|SHIFT, // L 0x10|SHIFT, // M 0x11|SHIFT, // N 0x12|SHIFT, // O 0x13|SHIFT, // P 0x14|SHIFT, // Q 0x15|SHIFT, // R 0x16|SHIFT, // S 0x17|SHIFT, // T 0x18|SHIFT, // U 0x19|SHIFT, // V 0x1a|SHIFT, // W 0x1b|SHIFT, // X 0x1c|SHIFT, // Y 0x1d|SHIFT, // Z 0x2f, // [ 0x31, // bslash 0x30, // ] 0x23|SHIFT, // ^ 0x2d|SHIFT, // _ 0x35, // ` 0x04, // a 0x05, // b 0x06, // c 0x07, // d 0x08, // e 0x09, // f 0x0a, // g 0x0b, // h 0x0c, // i 0x0d, // j 0x0e, // k 0x0f, // l 0x10, // m 0x11, // n 0x12, // o 0x13, // p 0x14, // q 0x15, // r 0x16, // s 0x17, // t 0x18, // u 0x19, // v 0x1a, // w 0x1b, // x 0x1c, // y 0x1d, // z 0x2f|SHIFT, // { 0x31|SHIFT, // | 0x30|SHIFT, // } 0x35|SHIFT, // ~ 0 // DEL }; uint8_t USBPutChar(uint8_t c); // press() adds the specified key (printing, non-printing, or modifier) // to the persistent key report and sends the report. Because of the way // USB HID works, the host acts like the key remains pressed until we // call release(), releaseAll(), or otherwise clear the report and resend. size_t Keyboard_::press(uint8_t k) { uint8_t i; if (k >= 136) { // it's a non-printing key (not a modifier) k = k - 136; } else if (k >= 128) { // it's a modifier key _keyReport.modifiers |= (1<<(k-128)); k = 0; } else { // it's a printing key k = pgm_read_byte(_asciimap + k); if (!k) { setWriteError(); return 0; } if (k & 0x80) { // it's a capital letter or other character reached with shift _keyReport.modifiers |= 0x02; // the left shift modifier k &= 0x7F; } } // Add k to the key report only if it's not already present // and if there is an empty slot. if (_keyReport.keys[0] != k && _keyReport.keys[1] != k && _keyReport.keys[2] != k && _keyReport.keys[3] != k && _keyReport.keys[4] != k && _keyReport.keys[5] != k) { for (i=0; i<6; i++) { if (_keyReport.keys[i] == 0x00) { _keyReport.keys[i] = k; break; } } if (i == 6) { setWriteError(); return 0; } } sendReport(&_keyReport); return 1; } // release() takes the specified key out of the persistent key report and // sends the report. This tells the OS the key is no longer pressed and that // it shouldn't be repeated any more. size_t Keyboard_::release(uint8_t k) { uint8_t i; if (k >= 136) { // it's a non-printing key (not a modifier) k = k - 136; } else if (k >= 128) { // it's a modifier key _keyReport.modifiers &= ~(1<<(k-128)); k = 0; } else { // it's a printing key k = pgm_read_byte(_asciimap + k); if (!k) { return 0; } if (k & 0x80) { // it's a capital letter or other character reached with shift _keyReport.modifiers &= ~(0x02); // the left shift modifier k &= 0x7F; } } // Test the key report to see if k is present. Clear it if it exists. // Check all positions in case the key is present more than once (which it shouldn't be) for (i=0; i<6; i++) { if (0 != k && _keyReport.keys[i] == k) { _keyReport.keys[i] = 0x00; } } sendReport(&_keyReport); return 1; } size_t Keyboard_::pressRaw(uint8_t k, uint8_t m) { uint8_t i; // Add k to the key report only if it's not already present // and if there is an empty slot. if (_keyReport.keys[0] != k && _keyReport.keys[1] != k && _keyReport.keys[2] != k && _keyReport.keys[3] != k && _keyReport.keys[4] != k && _keyReport.keys[5] != k) { for (i=0; i<6; i++) { if (_keyReport.keys[i] == 0x00) { _keyReport.keys[i] = k; break; } } if (i == 6) { setWriteError(); return 0; } } _keyReport.modifiers |= m; sendReport(&_keyReport); return 1; } size_t Keyboard_::releaseRaw(uint8_t k, uint8_t m) { uint8_t i; // Test the key report to see if k is present. Clear it if it exists. // Check all positions in case the key is present more than once (which it shouldn't be) for (i=0; i<6; i++) { if (0 != k && _keyReport.keys[i] == k) { _keyReport.keys[i] = 0x00; } } _keyReport.modifiers &= ~m; sendReport(&_keyReport); return 1; } void Keyboard_::releaseAll(void) { _keyReport.keys[0] = 0; _keyReport.keys[1] = 0; _keyReport.keys[2] = 0; _keyReport.keys[3] = 0; _keyReport.keys[4] = 0; _keyReport.keys[5] = 0; _keyReport.modifiers = 0; sendReport(&_keyReport); } size_t Keyboard_::write(uint8_t c) { uint8_t p = press(c); // Keydown release(c); // Keyup return p; // just return the result of press() since release() almost always returns 1 } size_t Keyboard_::write(const uint8_t *buffer, size_t size) { size_t n = 0; while (size--) { if (*buffer != '\r') { if (write(*buffer)) { n++; } else { break; } } buffer++; } return n; } Keyboard_ Keyboard; #endif

Keyboard.h

/* Keyboard.h Copyright (c) 2015, Arduino LLC Original code (pre-library): Copyright (c) 2011, Peter Barrett This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KEYBOARD_h #define KEYBOARD_h #include "HID.h" #if !defined(_USING_HID) #warning "Using legacy HID core (non pluggable)" #else //================================================================================ //================================================================================ // Keyboard #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 #define KEY_F13 0xF0 #define KEY_F14 0xF1 #define KEY_F15 0xF2 #define KEY_F16 0xF3 #define KEY_F17 0xF4 #define KEY_F18 0xF5 #define KEY_F19 0xF6 #define KEY_F20 0xF7 #define KEY_F21 0xF8 #define KEY_F22 0xF9 #define KEY_F23 0xFA #define KEY_F24 0xFB #define MODIFIER_LEFT_CTRL 0b00000001 #define MODIFIER_LEFT_SHIFT 0b00000010 #define MODIFIER_LEFT_ALT 0b00000100 #define MODIFIER_LEFT_GUI 0b00001000 #define MODIFIER_RIGHT_CTRL 0b00010000 #define MODIFIER_RIGHT_SHIFT 0b00100000 #define MODIFIER_RIGHT_ALT 0b01000000 #define MODIFIER_RIGHT_GUI 0b10000000 // Low level key report: up to 6 keys and shift, ctrl etc at once typedef struct { uint8_t modifiers; uint8_t reserved; uint8_t keys[6]; } KeyReport; class Keyboard_ : public Print { private: KeyReport _keyReport; void sendReport(KeyReport* keys); public: Keyboard_(void); void begin(void); void end(void); size_t write(uint8_t k); size_t write(const uint8_t *buffer, size_t size); size_t press(uint8_t k); size_t release(uint8_t k); size_t pressRaw(uint8_t k, uint8_t m = 0); size_t releaseRaw(uint8_t k, uint8_t m = 0); void releaseAll(void); void pressSystemPower(void); void pressSystemSleep(void); void releaseSystem(void); }; extern Keyboard_ Keyboard; #endif #endif

Arduinoで実装だ!

ここまで来たら、まああとは、やりたいことをプログラミングしていくだけです。
今回は8キーなので、以下な感じにデザインしました。

キー チョン押し 長押し 時間
0 無反応 Alt+F4 100ms
1 Delete Caps-lock 1000ms
2 Win Win+D 500ms
3 PrintScreen Alt+printScreen 500ms
4 F2 - -
5 F3 - -
6 F5 Ctrl+F5 500ms
7 F12 - -

実際に組んだプログラムは、以下な感じ。

sketch_kbd_exmp.ino

#include "Keyboard.h" #include "HID.h" int row_pins[] = {19,18}; int col_pins[] = {23,22,21,20}; int i,j; int keyvalue; int keystatus[8]; int keycount; unsigned long keytime[8]; unsigned long keypushtime; unsigned long nowtime; void setup() { // Serial.begin(9600); // Serial.println("Hello Arduino!"); Keyboard.begin(); matrix_init(); key_init(); } void matrix_init() { for (i = 0; i < (sizeof(col_pins)/sizeof(int)); i++) { pinMode(col_pins[i], INPUT_PULLUP); } for (i = 0; i < (sizeof(row_pins)/sizeof(int)); i++) { pinMode(row_pins[i], INPUT); } } void key_init() { for (i = 0; i < (sizeof(keystatus)/sizeof(int)); i++) { keystatus[i] = 1; } } void matrix_read() { keycount = 0; for (i = 0; i < (sizeof(row_pins)/sizeof(int)); i++) { pinMode(row_pins[i], OUTPUT); digitalWrite(row_pins[i], LOW); for (j = 0; j < (sizeof(col_pins)/sizeof(int)); j++) { keyvalue = digitalRead(col_pins[j]); key_send(keyvalue); // Serial.print(keyvalue); keycount++; } pinMode(row_pins[i], INPUT); } // Serial.println(""); } void key_send(int keyvalue) { // long-push release if (keystatus[keycount] == 2) { if (keyvalue == 1) { keystatus[keycount] = 1; } return; } if ((keyvalue == 0)&&(keystatus[keycount] == 1)) { keytime[keycount] = millis(); } // Serial.print(keystatus[keycount]); if ((keyvalue == 1)&&((keystatus[keycount] == 0)||(keystatus[keycount] == 3))) { nowtime = millis(); keypushtime = nowtime - keytime[keycount]; if (keypushtime < 10 ) { // anti chattering return; } if (keypushtime > 100000 ) { // abnormal reset keystatus[keycount] = 1; return; } if (keycount == 0) { if (keypushtime > 100) { // Keyboard.pressSystemPower(); // Windows ( system-power ) // Keyboard.pressRaw( 0x66 ); // Mac/Unix ( keyboard-power ) Keyboard.pressRaw( 0x3D, MODIFIER_LEFT_ALT ); // alt + f4 delay(100); // Keyboard.releaseSystem(); // Keyboard.releaseRaw( 0x66 ); Keyboard.releaseRaw( 0x3D, MODIFIER_LEFT_ALT ); } } if (keycount == 1) { if (keypushtime > 1000) { Keyboard.pressRaw( 0x39 ); // caps-lock delay(100); Keyboard.releaseRaw( 0x39 ); } else { Keyboard.pressRaw( 0x4C ); // del delay(100); Keyboard.releaseRaw( 0x4C ); } } if (keycount == 2) { if (keypushtime > 500) { Keyboard.pressRaw( 0x07, MODIFIER_LEFT_GUI ); // win + d delay(100); Keyboard.releaseRaw( 0x07, MODIFIER_LEFT_GUI ); // win + d } else { Keyboard.press(KEY_LEFT_GUI); delay(100); Keyboard.release(KEY_LEFT_GUI); } } if (keycount == 3) { if (keypushtime > 500) { Keyboard.pressRaw( 0x46, MODIFIER_LEFT_ALT ); // alt + printscreen delay(100); Keyboard.releaseRaw( 0x46, MODIFIER_LEFT_ALT ); } else { Keyboard.pressRaw( 0x46 ); // printscreen delay(100); Keyboard.releaseRaw( 0x46 ); } } if (keycount == 4) { Keyboard.releaseRaw( 0x3B ); // f2 } if (keycount == 5) { Keyboard.releaseRaw( 0x3C ); // f3 } if (keycount == 6) { if (keypushtime > 500) { Keyboard.pressRaw( 0x3E , MODIFIER_LEFT_CTRL); // ctrl + f5 delay(100); Keyboard.releaseRaw( 0x3E , MODIFIER_LEFT_CTRL); } else { Keyboard.pressRaw( 0x3E ); // f5 delay(100); Keyboard.releaseRaw( 0x3E ); } } if (keycount == 7) { Keyboard.releaseRaw( 0x45 ); // f12 } keystatus[keycount] = 2; return; } if ((keyvalue == 0)&&(keystatus[keycount] != 3)) { keystatus[keycount] = 0; nowtime = millis(); keypushtime = nowtime - keytime[keycount]; if (keypushtime < 10 ) { // anti chattering return; } if (keycount == 4) { Keyboard.pressRaw( 0x3B ); // f2 keystatus[keycount] = 3; } if (keycount == 5) { Keyboard.pressRaw( 0x3C ); // f3 keystatus[keycount] = 3; } if (keycount == 7) { Keyboard.pressRaw( 0x45 ); // f12 keystatus[keycount] = 3; } } } void loop() { matrix_read(); delay(5); }

電源ボタン悲喜こもごも、そして挫折

今回の目標の一つに、「キーボードによる電源ボタンの実装」がありました。
結果的に無理!だったんですが、まあ顛末をば。

WindowsとMac/Unixのキーボード電源ボタンの違い

HIDキーボードには、予め「POWER」キーが定められています。
キーコード的には「0x66」。
MacやUnix系の場合、このコードを送れば電源ボタンとして動作します。

しかしWindowsは違います。
多分歴史的経緯から、Windowsは「電源、スリープ、ウェーク専用デバイス」を別にキーボードの中に実装している形態を取っています。
それが上記にあるシステムディスクリプタの拡張、にあたります。
定義後のUSB送信周りはこんな感じです。

Sendreport.cpp

void Keyboard_::sendReport(KeyReport* keys) { HID().SendReport(2,keys,sizeof(KeyReport)); } void Keyboard_::pressSystemPower(void) { HID().SendReport(3,1,1); } void Keyboard_::pressSystemSleep(void) { HID().SendReport(3,2,1); } void Keyboard_::releaseSystem(void) { HID().SendReport(3,0,1); }

キーボードとしては2番目のデバイス定義として、電源ボタンとかは3番目のデバイス定義として送信、な感じです。

最近のBIOSの制限

UEFI全盛期、こっそり削除されたファンクションがあるらしく。
「USBキーボードによる電源投入」が、今使っているマザーボードのBIOSだと出来ません。
PS/2だと出来るあたりが嫌らしいです、、、ぶう。

多分Windowsバグってます

で、じゃあ、電源キーあればせめて電源は切れるよね、と。
そうは問屋が卸しません。

このキーボード電源キー、現状のWindows11だと、非常に残念な動きをします。

  • 画面が消える
  • システムは「Sleep」状態になる
  • この状態で、本体側の電源スイッチを押すと、起動する
  • が、即「シャットダウンしています」→本格的に電源が切れる

・・・どーやらSleepとシャットダウンが同時に走るみたいで、上記のような妙な挙動になります。
嫌がらせかっ!。

日本語の足かせ

じゃあキーコンビネーションでどうにかなるのか!?と検討。
https://windowsreport.com/windows-11-shutdown-shortcut/
「Win+X」→「U」→「U」でイケるっ!。

って、日本語Windows11だと、「U」のショートカットがありません、、、しょぼーん。
キャプションを入力できます

・・・というわけで、結局、「電源キー」には「Alt+F4」という、まあありふれたアレを実装することにしましたよ。

作り終わって

なんか、かなり遠回りしたような気もしますが。

結果的には、色々知見も広まったし、ステキな試行錯誤だったかな、と。

これから愛用していきます!。

1
eucalyのアイコン画像
いつも、てきとうです
ログインしてコメントを投稿する