linclipのアイコン画像
linclip 2021年02月15日作成 (2022年01月23日更新) © MIT
製作品 製作品 閲覧数 2923
linclip 2021年02月15日作成 (2022年01月23日更新) © MIT 製作品 製作品 閲覧数 2923

USB端子に直接挿せる小さなボリュームコントローラー

USB端子に直接挿せる小さなボリュームコントローラー

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

はじめに

ケーブル不要、直接USB端子に挿せるミニマムなPC用ボリューム・コントローラー。PCのボタンで操作するよりも直感的かつスピーディに音量を調整できます。

これまでATmega32U4でいくつか作ってきたのですが、直接PCのUSB端子に挿せるDigisparkというとても小さなボードがあることを知り、Digispark(互換機)で新たに作り直しました。

用意したもの

  • Digispark
  • ロータリーエンコーダー(スイッチ付き)
  • ノブ
  • ピンソケット
  • ピンヘッダー
  • 抵抗(10kΩ)
  • ハサミで切れる!薄型ユニバーサル基板

音量をコントロールするだけなら、普通のロータリーエンコーダーでいいのですが、モード切替でほかの操作をすることも考えて、スイッチ付きを選んでいます。

今回は音量のほか、スクロールもできるようにしてみました(ボタンで切替)。

Digisparkには今回使用したタイプのほかに、MicroUSB端子が出ているものもあります。ケーブルで接続したい場合はそちらを選択してください。

配線

Digisparkのボードに直接はんだ付けしたのはピンソケットのみ。ユニバーサル基板にピンヘッダーとロータリーエンコーダー、抵抗をはんだ付けします。

以下のように配線します。雑な図の左がロータリーエンコーダー、右がDigispark。ロータリーエンコーダーから、3本の端子が出ている上側が回転を検出するためのもの。2本の端子が出ている下側が、スイッチです。

P0とP2 はプログラムでプルアップできますが、P1はプルアップできないので、外部プルアップに抵抗を使っています(プルアップできないのに気づかず、無駄に時間がかかってしまいました)。

配線

はんだ付け後の写真です。
表
裏

プログラム

DigisparkのプログラミングはArduino IDEが使用できます。

Adfruitが紹介しているTrinklet用プログラムを参考にしました。ロータリーエンコーダーまわりはほぼそのまま。スイッチによる切替やLEDの処理などいろいろ試してみました。
https://learn.adafruit.com/trinket-usb-volume-knob

USB端子に挿して実際にコントローラーとして動作するまでには数秒かかります。準備ができたことを示すため、Digispark搭載のLEDを点滅させてみました。

使用したライブラリ

Adafruit-Trinket-USB
https://github.com/adafruit/Adafruit-Trinket-USB/

コード

#include "TrinketHidCombo.h"

#define PIN_ENCODER_A 2
#define PIN_ENCODER_B 0
#define TRINKET_PINx  PINB

static uint8_t enc_prev_pos = 0;
static uint8_t enc_flags    = 0;

#define PIN_LED 1     // LEDピン
#define PIN_BUTTON 1  // ボタンピン
int button_state = 0;      // ボタンの状態
int button_state_old = 0;  // ボタンの状態 前の値
int mode = 0;              // モード

void setup()
{
  pinMode(PIN_BUTTON, INPUT);
  pinMode(PIN_ENCODER_A, INPUT_PULLUP);
  pinMode(PIN_ENCODER_B, INPUT_PULLUP);

  TrinketHidCombo.begin(); // start the USB device engine and enumerate

  // 準備ができたら点滅
  pinMode(PIN_LED, OUTPUT);
  for (int i=0; i < 5 ; i++){
    digitalWrite(PIN_LED, HIGH);
    delay(60);  
    digitalWrite(PIN_LED, LOW);  
    delay(60);  
  }

  if (digitalRead(PIN_ENCODER_A) == LOW) {
    enc_prev_pos |= (1 << 0);
  }
  if (digitalRead(PIN_ENCODER_B) == LOW) {
    enc_prev_pos |= (1 << 1);
  }
}

void loop()
{
  int8_t enc_action = 0; // 1 or -1 if moved, sign is direction
  uint8_t enc_cur_pos = 0;

  if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_A)) {
    enc_cur_pos |= (1 << 0);
  }
  if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_B)) {
    enc_cur_pos |= (1 << 1);
  }

  if (enc_cur_pos != enc_prev_pos)
  {
    if (enc_prev_pos == 0x00)
    {
      if (enc_cur_pos == 0x01) {
        enc_flags |= (1 << 0);
      }
      else if (enc_cur_pos == 0x02) {
        enc_flags |= (1 << 1);
      }
    }

    if (enc_cur_pos == 0x03)
    {
      enc_flags |= (1 << 4);
    }
    else if (enc_cur_pos == 0x00)
    {
      if (enc_prev_pos == 0x02) {
        enc_flags |= (1 << 2);
      }
      else if (enc_prev_pos == 0x01) {
        enc_flags |= (1 << 3);
      }

      if (bit_is_set(enc_flags, 0) && (bit_is_set(enc_flags, 2) || bit_is_set(enc_flags, 4))) {
        enc_action = 1;
      }
      else if (bit_is_set(enc_flags, 2) && (bit_is_set(enc_flags, 0) || bit_is_set(enc_flags, 4))) {
        enc_action = 1;
      }
      else if (bit_is_set(enc_flags, 1) && (bit_is_set(enc_flags, 3) || bit_is_set(enc_flags, 4))) {
        enc_action = -1;
      }
      else if (bit_is_set(enc_flags, 3) && (bit_is_set(enc_flags, 1) || bit_is_set(enc_flags, 4))) {
        enc_action = -1;
      }
      enc_flags = 0; // reset for next time
    }
  }

  enc_prev_pos = enc_cur_pos;

  if (enc_action > 0) {  // RIGHT
    switch(mode){
      case 0:
      default:
        TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_UP);
        TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_UP);
        break;
      case 1:
        TrinketHidCombo.pressKey(0, KEYCODE_PAGE_DOWN);  //PAGE DOWN
        TrinketHidCombo.pressKey(0, 0);
        break;
    }
  }
  else if (enc_action < 0) {   // LEFT
    switch(mode){
      case 0:
      default:
        TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_DOWN);
        TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_DOWN);
        break;
      case 1:
        TrinketHidCombo.pressKey(0, KEYCODE_PAGE_UP);  //PAGE UP
        TrinketHidCombo.pressKey(0, 0);
        break;
    }
  }
  else {
    TrinketHidCombo.poll(); // do nothing, check if USB needs anything done
  }

  // button
  button_state = digitalRead(PIN_BUTTON);
  if(button_state != button_state_old){
    if(button_state == HIGH){   //button ON
        mode ++;
        if(mode > 1)
        {
          mode = 0;
        }
        delay(200);
    } else {  //button OFF
        delay(200);
    }
    button_state_old = button_state;
  }
}

注意点

今回使用したDigisparkはそのままUSB端子に挿せるのですが、PCのUSB端子のバネの具合によっては、基板面が水平にならず、ノートPCのディスプレイをたたむ際にノブがぶつかってしまいます。そのため、スペーサーとなるものが必要になります。そこで、「おゆまる」というお湯でやわらかくなるプラスチックねんどを挟み込んでいます。もっとスマートな方法もあると思いますが、ひとまず手持ちのもので済ませました。

また、スイッチを活用しようとしたため、予期せぬ負荷が端子にかかることもありそうです。PCの形状に合わせて下側にもなんらかのスペーサーを付けたほうがいいと思われます。不安ならケーブル接続タイプを。

おわりに

スクロールは、アプリケーションによって動作が異なり、けっこういろいろな使い方ができます。画像処理ソフトなら拡大・縮小、動画編集ならコマ送り・コマ戻しに使えるので、思いのほか便利です。

もちろん、今回の音量やスクロールだけでなく、さまざまな機能を割り当てることができます。よく使用するアプリケーションに合わせてカスタムコントローラーを作るのはとても楽しいです。

1
ログインしてコメントを投稿する