verylowfreqのアイコン画像
verylowfreq 2024年06月16日作成 (2024年06月17日更新)
製作品 製作品 閲覧数 487
verylowfreq 2024年06月16日作成 (2024年06月17日更新) 製作品 製作品 閲覧数 487

マクロパッド基板を作る/Arduinoシールド基板の設計

マクロパッド基板を作る/Arduinoシールド基板の設計

Arduino UNOサイズでマクロパッドが作りたくなったので、シールド基板を設計しました。Arduino LeonardoとSuzuduino UNO (自作のCH32Vボード) でコードを書きます。
自作キーボードの文脈でのマクロパッドだとArduino Pro Microを利用するケースが多いと思いますが、個人的に最近Arduino UNOのフォームファクターにはまっているので、あえてのUNOシールド基板での製作にトライです。

この記事は動画の補足記事です。三峰スズはものづくりや電子工作を楽しむVTuberとして活動しています。
ぜひ動画のほうも見てね!

ここに動画が表示されます

コード全体はGitHubにも置いてあります。
https://github.com/verylowfreq/switchnobshield_samples

基板の設計

スイッチが4つ、ロータリーエンコーダーが2つ(押し込み操作あり)を接続するだけなので、回路としてはシンプルです。
ピン割り当ては本家Arduinoだけを考えるならば制約はないのですが、Suzuduino UNO / Suzuno32RVでの利用を想定して、複雑な事情を抱えるピンを避けた結果、このような割り当てになっています。
回路図

基板はArduino UNOシールドサイズです。さほど広い面積ではないので、スイッチやノブ同士の隙間があまり確保できませんでした。
スイッチはCherry MX互換、ロータリーエンコーダーはEC11互換です。よく使われるものですね。

基板

できあがった基板

この規模のはんだづけは気楽にできていいですね。まったりと無心にはんだづけしたいときにも好適かもしれません。はんだづけの様子はぜひ動画にて!

組み立て例

コード例

Arduino Leonardo (本家Arduino系)

Arduino UNOサイズのボードでUSB機能を持っているものは、Arduino LeonardoやArduino UNO R4があります。これらにはUSBキーボード・マウスとして振舞うための "Keyboard" / "Mouse" ライブラリがあるので、かんたんに実装できます。
ロータリーエンコーダーもライブラリがありますが割り込みを利用しないものを選定し、Suzuduino UNOでも利用できるようにしました。

// RotaryEncoder by Matthias Hertel #include <RotaryEncoder.h> #include <Keyboard.h> constexpr int PIN_SWITCH[6] = { 4,5,6,7,10,11 }; RotaryEncoder encoder1(A2, A3, RotaryEncoder::LatchMode::FOUR3); RotaryEncoder encoder2(A4, A5, RotaryEncoder::LatchMode::FOUR3); void setup() { for (int i = 0; i < 6; i++) { pinMode(PIN_SWITCH[i], INPUT_PULLUP); } Keyboard.begin(); } void loop() { encoder1.tick(); encoder2.tick(); int dir1 = (int)encoder1.getDirection(); if (dir1 > 0) { Keyboard.press('l'); Keyboard.release('l'); } else if (dir1 < 0) { Keyboard.press('r'); Keyboard.release('r'); } int dir2 = (int)encoder2.getDirection(); if (dir2 > 0) { Keyboard.press('L'); Keyboard.release('L'); } else if (dir2 < 0) { Keyboard.press('R'); Keyboard.release('R'); } if (digitalRead(PIN_SWITCH[0]) == LOW) { Keyboard.press('1'); } else { Keyboard.release('1'); } if (digitalRead(PIN_SWITCH[1]) == LOW) { Keyboard.press('2'); } else { Keyboard.release('2'); } if (digitalRead(PIN_SWITCH[2]) == LOW) { Keyboard.press('3'); } else { Keyboard.release('3'); } if (digitalRead(PIN_SWITCH[3]) == LOW) { Keyboard.press('4'); } else { Keyboard.release('4'); } if (digitalRead(PIN_SWITCH[4]) == LOW) { Keyboard.press('5'); } else { Keyboard.release('5'); } if (digitalRead(PIN_SWITCH[5]) == LOW) { Keyboard.press('6'); } else { Keyboard.release('6'); } }

Suzuduino UNO / Suzuno32RV (WCH CH32V CH32V203)

CH32VにはArduino環境で利用できるUSB機能のライブラリがまだありません。そのため、WCH提供のサンプルコードをもとに改変していきます。
このコードでは送信するHIDレポートを直接書き換えることで、入力を設定しています。

コード全体はGitHubで公開しています

constexpr int PIN_LED_BUILTIN = PA5; constexpr int PIN_BTN_0 = PA15; constexpr int PIN_BTN_1 = PB3; constexpr int PIN_BTN_2 = PB4; constexpr int PIN_BTN_3 = PB5; constexpr int PIN_BTN_4 = PA4; constexpr int PIN_BTN_5 = PA7; constexpr int PIN_RE1A = PA2; constexpr int PIN_RE1B = PA3; constexpr int PIN_RE2A = PB0; constexpr int PIN_RE2B= PB1; // RotaryEncoder by Matthias Hertel #include <RotaryEncoder.h> #include "src/USB-Driver/inc/usb_lib.h" #include "src/CONFIG/usb_desc.h" #include "src/CONFIG/usb_pwr.h" #include "src/CONFIG/usb_prop.h" #include "src/CONFIG/hw_config.h" // WORKAROUND: These function declaration exists in hw_config.h but no effect. extern "C" { void Set_USBConfig(void); void USB_Interrupts_Config(void); uint8_t USBD_ENDPx_DataUp( uint8_t endp, uint8_t *pbuf, uint16_t len ); void MCU_Sleep_Wakeup_Operate(void); } RotaryEncoder re1(PIN_RE1A, PIN_RE1B, RotaryEncoder::LatchMode::FOUR3); RotaryEncoder re2(PIN_RE2A, PIN_RE2B, RotaryEncoder::LatchMode::FOUR3); void setup() { Serial.begin(115200); Serial.println("Initializing..."); Serial.println("USB-HID composite, Keyboard and Consumer control"); Serial.printf("SystemCoreClock=%d\n", SystemCoreClock); Set_USBConfig(); USB_Init(); USB_Interrupts_Config(); Serial.println("Ready."); } void led_blink(unsigned int interval_ms) { static unsigned long timer = 0; static bool led_on = false; if (timer == 0) { pinMode(PIN_LED_BUILTIN, OUTPUT); } if (millis() - timer > interval_ms) { timer = millis(); digitalWrite(PIN_LED_BUILTIN, led_on ? LOW : HIGH); led_on = !led_on; } } // Keyboard's LED state (updated on USB callbacks) volatile uint8_t KB_LED_Cur_Status; uint8_t usbd_kbd_report[8]; uint8_t USBD_KBD_REPORT_BYTES = sizeof(usbd_kbd_report) / sizeof(usbd_kbd_report[0]); void usbd_kbd_update(void) { static unsigned long timer = 0; if (timer == 0) { pinMode(PIN_BTN_0, INPUT_PULLUP); pinMode(PIN_BTN_1, INPUT_PULLUP); pinMode(PIN_BTN_2, INPUT_PULLUP); pinMode(PIN_BTN_3, INPUT_PULLUP); } if (millis() - timer >= 10) { timer = millis(); bool btn_w = digitalRead(PIN_BTN_0) == LOW; bool btn_a = digitalRead(PIN_BTN_1) == LOW; bool btn_s = digitalRead(PIN_BTN_2) == LOW; bool btn_d = digitalRead(PIN_BTN_3) == LOW; if (btn_w) { usbd_kbd_report[3] = 0x1a; } if (btn_a) { usbd_kbd_report[4] = 0x04; } if (btn_s) { usbd_kbd_report[5] = 0x16; } if (btn_d) { usbd_kbd_report[6] = 0x07; } bool success = USBD_ENDPx_DataUp(1, usbd_kbd_report, USBD_KBD_REPORT_BYTES); if (success) { memset(usbd_kbd_report, 0x00, USBD_KBD_REPORT_BYTES); } } } /// HID Report for Mouse /// Layout: /// - Buttons: bitfield [ 0,0,0,0,0, Button3, Button2, Button1 ] /// - Cursor X: signed 1 byte integer /// - Cursor Y: signed 1 byte integer /// - Wheel vertical: signed 1 byte integer /// - Wheel horizontal: signed 1 byte integer uint8_t usbd_mouse_report[5]; uint8_t USBD_MOUSE_REPORT_BYTES = sizeof(usbd_mouse_report) / sizeof(usbd_mouse_report[0]); void usbd_mouse_update(void) { static unsigned long timer = 0; static long prevPosition = 0; if (timer == 0) { memset(usbd_mouse_report, 0x00, USBD_MOUSE_REPORT_BYTES); } unsigned long current_time = millis(); if (timer - current_time >= 10) { timer = current_time; bool btn_left = re1.getPosition() - prevPosition > 0; bool btn_right = re1.getPosition() - prevPosition < 0; prevPosition = re1.getPosition(); if (btn_left) { usbd_mouse_report[4] = (uint8_t)-1; } else if (btn_right) { usbd_mouse_report[4] = 1; } bool success = USBD_ENDPx_DataUp(2, usbd_mouse_report, USBD_MOUSE_REPORT_BYTES); if (success) { memset(usbd_mouse_report, 0x00, USBD_MOUSE_REPORT_BYTES); } } } uint8_t usbd_cc_report[1]; uint8_t USBD_CC_REPORT_BYTES = sizeof(usbd_cc_report) / sizeof(usbd_cc_report[0]); void usbd_cc_update(void) { static unsigned long timer = 0; static long prevPosition = 0; static bool muteButtonSent = false; if (timer == 0) { pinMode(PIN_BTN_5, INPUT_PULLUP); memset(usbd_cc_report, 0x00, USBD_CC_REPORT_BYTES); } if (millis() - timer >= 50) { timer = millis(); bool btn_down = (re2.getPosition() - prevPosition) < 0; bool btn_up = (re2.getPosition() - prevPosition) > 0; prevPosition = re2.getPosition(); bool btn_mute = false; if (digitalRead(PIN_BTN_5) == LOW) { if (!muteButtonSent) { btn_mute = true; muteButtonSent = true; } } else { muteButtonSent = false; } if (btn_mute) { usbd_cc_report[0] = 0x01; } else if (btn_down) { usbd_cc_report[0] = 0x04; } else if (btn_up) { usbd_cc_report[0] = 0x08; } else { usbd_cc_report[0] = 0; } bool success = USBD_ENDPx_DataUp(3, usbd_cc_report, USBD_CC_REPORT_BYTES); if (success) { memset(usbd_cc_report, 0x00, USBD_CC_REPORT_BYTES); } } } void loop() { if( bDeviceState == CONFIGURED ) { led_blink(500); re1.tick(); re2.tick(); usbd_kbd_update(); usbd_mouse_update(); usbd_cc_update(); } else { led_blink(1000); } } /** MCUをスリープ状態にする。復帰後の再初期化をする。 */ void MCU_Sleep_Wakeup_Operate(void) { Serial.printf( "Enter to Sleep\r\n" ); __disable_irq(); PWR_EnterSTOPMode(PWR_Regulator_LowPower,PWR_STOPEntry_WFE); SystemInit(); SystemCoreClockUpdate(); Set_USBConfig(); __enable_irq( ); Serial.printf( "Wake\r\n" ); }

雑記

USBデバイスを作るときに大切なこと:
USB機能を停止し、ファームウェア書き込み待機状態にできる方法を確保しておきましょう。Arduino Leonardoではリセットボタン2回押しで、書き込み待機になります。Suzuduino UNO / Suzuno32RV (CH32V)では、BOOTボタンを押しながらリセットで書き込み待機です。もし書き込み待機モードがないボードの場合は、特定のスイッチを押しながらリセットでUSB機能停止、などを仕込んでおくと便利です。
あるいは、動作テスト用のパソコンと開発・書き込み用のパソコンを分けるというのもアリですね。

基板むき出しでは取り扱いがこわいので、ケースを作りたいです。ただシールド基板側にネジ穴や固定のとっかかりがないので、どう固定するかはちょっと悩みそうです。

スイッチとノブはピンヘッダで支えているだけなので、長期的な耐久性は期待できません。あくまでも「かんたんに作るための基板」ということで。その点でも、シールド基板にネジ穴は用意したほうがよかったですね。

CH32VのUSBライブラリが欲しいです。記事執筆時点でTinyUSBの移植が進んでいるようなので、そちらにも期待ですが、Arduino環境から気軽に利用できるものもほしいです。(いずれ自分で書くことになると思います)

verylowfreqのアイコン画像
"verylowfreq" あるいは 「三峰スズ」(VTuber 2023年2月より) です。趣味で電子工作や3Dプリンターを楽しんでいます!
ログインしてコメントを投稿する