verylowfreq が 2024年06月16日22時07分59秒 に編集
初版
タイトルの変更
マクロパッド基板を作る/Arduinoシールド基板の設計
タグの変更
Arduino
CH32V
KiCad
メイン画像の変更
記事種類の変更
製作品
本文の変更
Arduino UNOサイズでマクロパッドが作りたくなったので、シールド基板を設計しました。Arduino LeonardoとSuzuduino UNO (自作のCH32Vボード) でコードを書きます。 この記事は動画の補足記事です。三峰スズはものづくりや電子工作を楽しむVTuberとして活動しています。 ぜひ動画のほうも見てね! !!YouTubeリンク ## 基板の設計 スイッチが4つ、ロータリーエンコーダーが2つ(押し込み操作あり)を接続するだけなので、回路としてはシンプルです。 ピン割り当ては本家Arduinoだけを考えるならば制約はないのですが、Suzuduino UNO / Suzuno32RVでの利用を想定して、複雑な事情を抱えるピンを避けた結果、このような割り当てになっています。 ![回路図](https://camo.elchika.com/cbd3ee9fdfc0a4b6360542d8cd71f8bedef76b82/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f64613530653234302d333138302d343035662d616563642d636633353762383930613262/) 基板はArduino UNOシールドサイズです。さほど広い面積ではないので、スイッチやノブ同士の隙間があまり確保できませんでした。 スイッチはCherry MX互換、ロータリーエンコーダーはEC11互換です。よく使われるものですね。 ![基板](https://camo.elchika.com/9a059983378132999531602e8f49c52ff3c43738/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f32626263626661322d653038302d343333362d626532392d663230353063333530303366/) ![できあがった基板](https://camo.elchika.com/7386069449e9ee72307daf5fb6172d7f25fb67bf/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376633303665652d656131352d343334662d626333342d6162343139336638333839392f64646266383039632d636462352d346539662d396461342d393335366166346561373634/) この規模のはんだづけは気楽にできていいですね。まったりと無心にはんだづけしたいときにも好適かもしれません。はんだづけの様子はぜひ動画にて! ## コード例 ### 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で公開しています !!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" ); } ```