(WIP)Seeeduino XIAOとSP-34でフットスイッチコントローラーを作る

目的

(下書きでずーっと保持してるのもアレなので雑すぎる情報だけど発行)

2020年10月にゲーム配信を始めてもうすぐ2年.配信で英語の練習のために英語もまれにしゃべる中,字幕の翻訳入力言語を切り替える必要があった*1

ゲーム中は,もちろん両手が塞がっていて,あまっているのは足! 既存のフットスイッチはなんか弱々しいので,もう作っちゃお! そういうこと.

本記事で紹介するおもな環境は下表のとおり:

名称 規格 製造会社 備考
フットスイッチ
(3本ペダル)
SP-34 CASIO 購入時5,733JPY,
Amazon.co.jpのURL
マイコン Seeeduino XIAO Seeed Studio 購入時580JPY,
秋月電子通販コードM-15178
フットスイッチ用コネクター
PS/2ネクター
JPCB-PS/2-2P aitendo 購入時39JPY,
aitendoのURL
炭素皮膜抵抗 2.2kΩ 不明

また,GitHubリポジトリーは以下: github.com

結果

動くものは,現状,以下の感じ(このツイートを追っていくと製作までの流れが見える).

要求・要件

  1. 足の操作で,マウスクリック相当をしたい
    1. ON/OFFの2値とする

設計(書きかけ)

実装(書きかけ)

ハードウェア

Seeeduino XIAOとPS/2ネクターの結線

ブレッドボード上で運用してて,接触悪くなってきたのでユニバーサル基板へ実装!

表面 裏面
表面
裏面(ハンダづけ下手になってない?)

一応,絶縁的なサムシングを施して以下が現状2022/07/18最終形とした:

透明収縮チューブで絶縁じゃあ

ソフトウェア

#include "HID-Project.h"
#include <TimerTC3.h>

#define SERIAL_BAUDRATE (115200)
#define TC3_INT_COUNT   (10 * 1000)  // microsecond
#define AD_RESOLUTION   (10)
#define HORIZ_POS_ADJ   (1920)
#define VERT_POS_ADJ    (1760)
#define BTN_POS_DIFF    (720)
#define MOUSE_MOVE_ITR  (5)
#define DAMPER_PEDAL_UNPUSHED     (642)
#define DAMPER_PEDAL_PUSHED       (374)
#define SOSTENUTO_PEDAL_UNPUSHED  (1023)
#define SOSTENUTO_PEDAL_PUSHED    (180)
#define SOFT_PEDAL_UNPUSHED       (1023)
#define SOFT_PEDAL_PUSHED         (160)
#define PIN_IN_DAMPER_PEDAL     (1)
#define PIN_IN_SOSTENUTO_PEDAL  (2)
#define PIN_IN_SOFT_PEDAL       (3)
#define NUM_OF_EDGE             (1)
#define NUM_OF_DEBOUNCE         (3)
#define NUM_OF_POLLING          (NUM_OF_EDGE + NUM_OF_DEBOUNCE)

// buttons name on Yukarinette Connector Voice Recognition Window
typedef enum TAG_BUTTON_NAME
{
  LANG_1ST,   // 1st language: Japanese (default)
  LANG_2ND,   // 2nd language: English US (default)
  LANG_3RD,   // 3rd language: Chinese Mainland (default)
  LANG_MAX
}button_number_t;

typedef enum TAG_PEDAL_NUMBER
{
  PEDAL_DAMPER,     // right pedal
  PEDAL_SOSTENUTO,  // middle pedal
  PEDAL_SOFT,       // left pedal
  PEDAL_MAX
}pedal_number_t;

typedef enum TAG_PEDAL_COND
{
  PEDAL_COND_DONT_CARE,     // unpushed or continuing pushed
  PEDAL_COND_PUSHED_EDGE,   // detect pushed edge
  PEDAL_COND_PUSHED,        // pushed
  PEDAL_COND_MAX
}pedal_cond_t;

typedef struct TAG_ANALOG_PEDAL
{
  uint32_t ulPinIndicator;        // indicator led output: ON when pedal pushed
  uint32_t ulPinPedal;            // pedal analog input
  uint16_t usThresholdPushed;     // pedal threshold when pedal pushed
  uint16_t usThresholdUnpushed;   // pedal threshold when pedal unpushed
  bool bIsPushedPoll[NUM_OF_POLLING];  // debounce polling pattern: true when pedal pushed
  pedal_cond_t kPedalCond;        // pedal condition
  uint8_t ucPushedCount;          // number of pushed
  uint8_t ucPollCount;            // polling counter: 0 to NUM_OF_POLLING -1
}analog_pedal_t;

analog_pedal_t kPedal[PEDAL_MAX] = 
{
  { // damper pedal (right pedal) setting
    PIN_LED,
    PIN_IN_DAMPER_PEDAL,
    DAMPER_PEDAL_PUSHED,
    DAMPER_PEDAL_UNPUSHED,
    {false, false, false, true},  // 4th element prevents from PUSHED detection when started with pedal pushing
    PEDAL_COND_DONT_CARE,
    0,
    0
  },
  { // sostenuto pedal (middle pedal) setting
    PIN_LED2,
    PIN_IN_SOSTENUTO_PEDAL,
    SOSTENUTO_PEDAL_PUSHED,
    SOSTENUTO_PEDAL_UNPUSHED,
    {false, false, false, true},  // 4th element prevents from PUSHED detection when started with pedal pushing
    PEDAL_COND_DONT_CARE,
    0,
    0
  },
  { // soft pedal (left pedal) setting
    PIN_LED3,
    PIN_IN_SOFT_PEDAL,
    SOFT_PEDAL_PUSHED,
    SOFT_PEDAL_UNPUSHED,
    {false, false, false, true},  // 4th element prevents from PUSHED detection when started with pedal pushing
    PEDAL_COND_DONT_CARE,
    0,
    0
  }
};

void MouseMoveAndClick(button_number_t);
void vPedalDiscriminant(pedal_number_t, button_number_t);
void vInputPoll(void);

void setup()
{
  SerialUSB.begin(SERIAL_BAUDRATE, SERIAL_8N1); // default SERIAL_8N1
  while(!SerialUSB)
  {
    ; // wait for SerialUSB enabled
  }
  SerialUSB.print("serial com opened as ");
  SerialUSB.print("baudrate: ");
  SerialUSB.print(SERIAL_BAUDRATE);
  SerialUSB.println(", data bit: 8, parity bit: none, stop bit: 1");

  pinMode(kPedal[PEDAL_DAMPER].ulPinIndicator, OUTPUT);
  SerialUSB.print("set damper pedal indicator OUTPUT #");
  SerialUSB.println(kPedal[PEDAL_DAMPER].ulPinIndicator);
  pinMode(kPedal[PEDAL_SOSTENUTO].ulPinIndicator, OUTPUT);
  SerialUSB.print("set sostenuto pedal indicator OUTPUT #");
  SerialUSB.println(kPedal[PEDAL_SOSTENUTO].ulPinIndicator);
  pinMode(kPedal[PEDAL_SOFT].ulPinIndicator, OUTPUT);
  SerialUSB.print("set soft pedal indicator OUTPUT #");
  SerialUSB.println(kPedal[PEDAL_SOFT].ulPinIndicator);

  pinMode(kPedal[PEDAL_DAMPER].ulPinPedal, INPUT);
  SerialUSB.print("set damper pedal analog INPUT #");
  SerialUSB.println(kPedal[PEDAL_DAMPER].ulPinPedal);
  pinMode(kPedal[PEDAL_SOSTENUTO].ulPinPedal, INPUT);
  SerialUSB.print("sostenuto pedal analog INPUT #");
  SerialUSB.println(kPedal[PEDAL_SOSTENUTO].ulPinPedal);
  pinMode(kPedal[PEDAL_SOFT].ulPinPedal, INPUT);
  SerialUSB.print("soft pedal analog INPUT #");
  SerialUSB.println(kPedal[PEDAL_SOFT].ulPinPedal);

  analogReadResolution(AD_RESOLUTION);  // default 10-bit on Seeeduino XIAO
  SerialUSB.print("set A/D converter resolution: ");
  SerialUSB.print(AD_RESOLUTION);
  SerialUSB.println(" bits");

  AbsoluteMouse.begin();
  SerialUSB.println("absolute positioning mouse started");
  
  Mouse.begin();
  SerialUSB.println("relative positioning mouse started");

  TimerTc3.initialize(TC3_INT_COUNT);
  TimerTc3.attachInterrupt(vInputPoll);
}

void loop()
{
  ;
}

void MouseMoveAndClick(button_number_t kButtonNumber)
{
  AbsoluteMouse.moveTo(SHRT_MAX - HORIZ_POS_ADJ,
                        VERT_POS_ADJ + kButtonNumber * BTN_POS_DIFF);
  for(int i = 0; i < MOUSE_MOVE_ITR; i++)
  {
    Mouse.move(SCHAR_MAX, 0);
  }
  Mouse.click();

  return;
}

void vPedalDiscriminant(pedal_number_t pedal_idx, button_number_t button_idx)
{
  uint8_t ucPollCountPrev = (kPedal[pedal_idx].ucPollCount + NUM_OF_POLLING - 1) % NUM_OF_POLLING;
  uint32_t ulPedalVal = analogRead(kPedal[pedal_idx].ulPinPedal);

  kPedal[pedal_idx].bIsPushedPoll[kPedal[pedal_idx].ucPollCount]
    = (ulPedalVal < kPedal[pedal_idx].usThresholdPushed) ? true : false;

  switch (kPedal[pedal_idx].kPedalCond)
  {
    case PEDAL_COND_DONT_CARE:
      if ((!kPedal[pedal_idx].bIsPushedPoll[ucPollCountPrev])
        && (kPedal[pedal_idx].bIsPushedPoll[kPedal[pedal_idx].ucPollCount]))
      {
        kPedal[pedal_idx].kPedalCond = PEDAL_COND_PUSHED_EDGE;
        kPedal[pedal_idx].ucPushedCount = 1;
      }
      break;
    case PEDAL_COND_PUSHED_EDGE:
      if (kPedal[pedal_idx].bIsPushedPoll[kPedal[pedal_idx].ucPollCount])
      {
        if (NUM_OF_DEBOUNCE == ++kPedal[pedal_idx].ucPushedCount)
        {
          kPedal[pedal_idx].kPedalCond = PEDAL_COND_PUSHED;
        }
      }
      else
      {
        kPedal[pedal_idx].kPedalCond = PEDAL_COND_DONT_CARE;
      }
      break;
    case PEDAL_COND_PUSHED:
      MouseMoveAndClick(button_idx);
    default:
      kPedal[pedal_idx].kPedalCond = PEDAL_COND_DONT_CARE;
      kPedal[pedal_idx].ucPushedCount = 0;
      break;
  }

  kPedal[pedal_idx].ucPollCount = (kPedal[pedal_idx].ucPollCount + 1) % NUM_OF_POLLING;

  return;
}

void vInputPoll(void)
{
  vPedalDiscriminant(PEDAL_DAMPER, LANG_1ST);
  vPedalDiscriminant(PEDAL_SOSTENUTO, LANG_2ND);
  vPedalDiscriminant(PEDAL_SOFT, LANG_3RD);

  return;
}

改訂履歴

# 日付 内容
1 2022/07/18 ユニバーサル基板での実装写真を掲載

参考URL

  1. Seeed Seeeduino XIAO を使ってみた(USBデバイス編): K.H.WEBLOG
  2. フットスイッチコントローラの作り方 - Qiita
  3. マウスポインタを絶対画面座標に移動するためのArduinoLeonardo / Micro用ライブラリ。 - wenyanet
  4. Seeeduino XIAO(Arduinoと互換)の開発環境を作る | スマートライフを目指すエンジニア
  5. Seeeduino Xiao - Anyone got the part? - parts help - fritzing forum

*1:当たり前(?)ながら,日本語の翻訳入力で英語をしゃべると正しく翻訳されないため.