Release 2018.4.18

マルチファンクションスイッチ 小型の3方向スイッチ

aitendoさんで購入した小型のマルチファンクションスイッチ。これを扱うコードを書いてみました。

マルチファンクションスイッチ

「右/前」と「左/後」、そして「押す」の3動作を認識するスイッチです。平たく言えば、1次元上のジョイスティックっていう感じです。ただ、以前扱ったのとは違い、単純に倒れたかどうかのON・OFFしか識別しないので、繊細な操作をするのには向きません。カーソル移動や数値の増減操作なんかには都合がよさそうで、似たモノをビデオカメラなんかの家電機器系で良く見る気がします。

何より、パーツ自体が指にスッポリ収まるくらいのサイズなので、Arduinoを使った製作物で、「何かしらの操作」用パーツを組み込もうとした時、都合が良いかなと思います。

aitendoさんでは、パーツそのものから2.54mmピッチの変換基板付きまで、いくつか種類違いがあるんですが、ここでは変換基板付きのものを扱っていきます。

スイッチの構造

パーツ右横にあるコネクタ部分が「共通」となっていて、動作によってそれぞれが「共通」とショートする3点切替のスイッチ、という構造になってます(左横の結線がどうもよく分からなかったので、no connectionとしてます)。

 押した時 左へ傾けた時 右へ傾けた時 パーツ裏面

「右か左か」は見る角度によって違うって話もありますが(苦)、ここでは写真の様に見たときでの左右という事でお願いします。

サンプルスケッチ

接点のON・OFFで動作する単純なものと、接点が繋がっている長さで振る舞いを変えるもの2種類のサンプルコードを書いてみました。勿論、今までに倣い、チャタリング対策にゲージ判定を使っています。

回路図

シリアルモニタに数値等の返しがくるので、ボーレートを38400で開き、挙動を確認してください。

1. 3方向別で動作を分ける

単純に、「左右の傾き」で特定の数値(value)が増減し、「押す」と数値(value)がリセットされるスケッチです。

#define PINR 4
#define PINP 5
#define PINL 6
#define PIN_SIZE 3
const byte sw_num[PIN_SIZE] = {PINR, PINP, PINL};
byte sw_status[PIN_SIZE];

#define PUSH_SHORT  1500
#define PUSH_LONG   300000
#define STAT_SHORT  B00000001
#define STAT_RELES  B00001000

long value, temp_gauge;


void setup() {
  for (byte i = 0 ; i < PIN_SIZE ; i++) pinMode(sw_num[i], INPUT_PULLUP);
  Serial.begin(38400);
  SPRI(0);
}

void loop() {
  for (byte pin = 0 ; pin < PIN_SIZE ; pin++)
  {
    if (SW_GAUGE(pin))
    {
      if (sw_status[pin] & STAT_RELES)
      {
        if (pin == 1) value = 0;    // button "push", reset the value
        else if (pin == 0) value++; // button Right
        else if (pin == 2) value--; // button Left

        SPRI(pin);
        RESET_STAT(pin, 0);
      }
    }
  }
}

void SPRI(byte pin_number) {
  Serial.print("value:");
  Serial.print(value);
  Serial.print("  ");

  char pin_txt[3][5] = {"RGHT", "PUSH", "LEFT"} ;
  Serial.print("  (");
  Serial.print(pin_txt[pin_number]);
  Serial.print(")");
  Serial.print(temp_gauge);

  Serial.println();
}


void RESET_STAT(byte layer, byte mask) {
  if (sw_status[layer] & STAT_RELES) mask &= !STAT_RELES;
  sw_status[layer] &= mask;
}

boolean SW_GAUGE(byte pin_num) {
  static unsigned long sw_gauge[PIN_SIZE];
  int read_repeat = 1;

  for (int i = 0 ; i < read_repeat ; i++)
  {
    if (PIN_READ(pin_num))
    {
      // change reading times by pin status
      read_repeat = PUSH_SHORT;
      // gauging and limit the value
      sw_gauge[pin_num] = min(sw_gauge[pin_num]++, PUSH_LONG + 1);

      // status change for "PUSH SHORT"
      if (sw_gauge[pin_num] >= PUSH_SHORT)
      {
        sw_status[pin_num] |= STAT_SHORT;
        // set current gauge value to temporary value for check
        temp_gauge = sw_gauge[pin_num];
      }

    } else {
      // set "RELEASE" for digital pin "0" after "pushed"
      if (sw_gauge[pin_num] >= PUSH_SHORT) sw_status[pin_num] |= STAT_RELES;
      // reset gauge
      sw_gauge[pin_num] = 0;
    }
  }
  return sw_status[pin_num] & STAT_SHORT;
}

bool PIN_READ(byte pin_num) {
  return ((PIND & _BV(PINR + pin_num)) ? 0 : 1);
}

ざっくり説明すると、SW_GAUGE(sw_numの順番)関数でピン状態を読みに行き、PUSH_SHORTの閾値を越えるとTrueを返してきます。そこでsw_status(sw_numの順番)の状態を見て、「接点が離れた後(STAT_RELES)」だと識別すれば、任意のアクションをし、最後にステータスをリセット、という流れになっています。

おなじみのゲージ判定でチャタリング対策していますが、ピンを読みに行った時「押されている時だけ、読み取りの繰り返しを増やす」よう工夫を入れてみました。こうすれば、押さない限り他のタスクを邪魔しないかな、と。

temp_gauge変数に押した時のゲージ量が入るようにしているので、閾値設定の参考にしてください。

2. 押し時間で動作を変える

スイッチがショートしている時間の長さで数値の増減具合が変わります。左右の傾き時間が短いと+-1、中ぐらいの長さだと連続して+-1、長いと+-10で増減します。

また、数値をA,B(values)と2種類用意し、「押す」と増減先が変更、「長押し」すると選択している数値がリセットされるようになっています。

#define LED  13
#define PINR 4
#define PINP 5
#define PINL 6
#define PIN_SIZE 3
#define PUSH_SHORT  1500
#define PUSH_MIDDLE 70000
#define PUSH_LONG   300000

const byte sw_num[PIN_SIZE] = {PINR, PINP, PINL};
byte sw_status[PIN_SIZE];

#define STAT_SHORT  B00000001
#define STAT_MID    B00000010
#define STAT_LONG   B00000100
#define STAT_RELES  B00001000
#define STAT_PICKED B00010000
#define STAT_P_KEEP STAT_SHORT + STAT_MID + STAT_LONG

long values[2];
boolean a_b, led_status;
unsigned long temp_gauge;


void setup() {
  for (byte i = 0 ; i < PIN_SIZE ; i++) {
    pinMode(sw_num[i], INPUT_PULLUP);
  }
  pinMode(LED, OUTPUT);
  Serial.begin(38400);
  SPRI(0);
}

void loop() {
  for (byte pin = 0 ; pin < PIN_SIZE ; pin++)
  {
    byte status_ret = SW_READ(pin);

    if (status_ret)
    {
      bool spri_on = false;
      byte reset_mask = STAT_P_KEEP + STAT_PICKED;

      // LED behavior
      if ((status_ret % 10) == 3)
      {
        if (LED_TASK(100)) led_status = !led_status;
      }
      if ((status_ret % 10) == 2 || status_ret == 1)
      {
        led_status = false;
        LED_TASK(250);
      }
      if (status_ret >= 100) led_status = false;

      // button "push"
      if (pin == 1)
      {
        // action after "released"
        if (status_ret >= 101)
        {
          // reset the value
          if ((status_ret % 10) == 3) values[a_b] = 0;
          // invert destination of value
          else a_b = !a_b;

          spri_on    = true;  // display to serial monitor
          reset_mask = 0;     // reset all status
        }


        // button "Right & Left"
      } else {

        // for "one shot push" or "middle push"
        int add_val = (status_ret == 1 || (status_ret % 10) >= 2) ? 1 : 0;
        // for "long push"
        if ((status_ret % 10) == 3) add_val = 10;

        // reset status when the switch is released
        if (status_ret > 100) reset_mask = 0;

        long tmp_val = values[a_b];  // keep previous value
        //increase or decrease value
        if (pin == 2) values[a_b] += add_val;  // button Right
        if (pin == 0) values[a_b] -= add_val;  // button Left
        if (values[a_b] != tmp_val) spri_on = true;  // display to serial monitor
      }
      if (spri_on) SPRI(pin);
      RESET_STAT(pin, reset_mask);
    }
  }
  LED_TASK(0);
}

boolean LED_TASK(int intval) {
  static unsigned long timer;
  bool led_to_go, ret;

  if (timer <= millis())
  {
    if (intval != 0) timer = millis() + intval;
    led_to_go = led_status;
    ret = true;
  } else {
    led_to_go = !led_status;
  }
  digitalWrite(LED, led_to_go);
  return ret;
}


void SPRI(byte pin_number) {
  for (byte i = 0 ; i < 2 ; i++)
  {
    if (i == a_b) Serial.print(">");
    else Serial.print(" ");
    Serial.print((char)('A' + i));
    Serial.print(':');
    Serial.print(values[i]);
    Serial.print("  ");
  }

  // sw_status & gauge display
  char pin_txt[3][5] = {"RGHT", "PUSH", "LEFT"};
  Serial.print("   b");
  Serial.print(sw_status[pin_number] + 128, BIN);
  Serial.print("  (");
  Serial.print(pin_txt[pin_number]);
  Serial.print(")");
  Serial.print(temp_gauge);

  Serial.println();
}


byte SW_READ(byte pin_num) {
  byte ret_val = 0;

  for (byte i = 0 ; i < PIN_SIZE ; i++)
  {
    if (SW_GAUGE(i) && pin_num == i)
    {
      if (sw_status[i] & STAT_SHORT)  ret_val++;
      if (sw_status[i] & STAT_MID)    ret_val++;
      if (sw_status[i] & STAT_LONG)   ret_val++;
      if (sw_status[i] & STAT_PICKED) ret_val += 10;
      if (sw_status[i] & STAT_RELES)  ret_val += 100;

      sw_status[i] |= STAT_PICKED;
    }
  }
  return ret_val;
}

void RESET_STAT(byte layer, byte mask) {
  if (sw_status[layer] & STAT_RELES) mask &= !STAT_PICKED;
  sw_status[layer] &= mask;
}

boolean SW_GAUGE(byte pin_num) {
  static unsigned long sw_gauge[PIN_SIZE];
  int read_repeat = 1;

  for (int i = 0 ; i < read_repeat ; i++)
  {
    if (PIN_READ(pin_num))
    {
      // change reading times by pin status
      read_repeat = PUSH_SHORT;
      // gauging and limit the value
      sw_gauge[pin_num] = min(sw_gauge[pin_num]++, PUSH_LONG + 1);

      // status change for "PUSH SHORT"
      if (sw_gauge[pin_num] >= PUSH_SHORT)
      {
        sw_status[pin_num] |= STAT_SHORT;
        // set current gauge value to temporary value for check
        temp_gauge = sw_gauge[pin_num];


        // status change for "PUSH MIDDLE"
        if (sw_gauge[pin_num] >= PUSH_MIDDLE)
        {
          sw_status[pin_num] |= STAT_MID;

          // status change for "PUSH LONG"
          if (sw_gauge[pin_num] >= PUSH_LONG) sw_status[pin_num] |= STAT_LONG;
        }
      }

    } else {
      // set "RELEASE" for digital pin "0" after "pushed"
      if (sw_gauge[pin_num] >= PUSH_SHORT) sw_status[pin_num] |= STAT_RELES;
      // reset gauge
      sw_gauge[pin_num] = 0;
    }
  }
  return sw_status[pin_num] & STAT_SHORT;
}

bool PIN_READ(byte pin_num) {
  return ((PIND & _BV(PINR + pin_num)) ? 0 : 1);
}

D13ピンにLEDをつけて、挙動によって点滅するようにもしてます(少々、ごちゃごちゃした書き方になってしまいましたが…)。Arduino本体でも視認出来ますが、余力があれば独自にLEDをつけてみてください。

今度はSW_READ()という関数を作り、状態を数字で返すようにしました。sw_statusのビット値をそのまま拾っても良かったんですが、こうした方がif分岐を書くとき混乱しないかな、と。

SW_READ(sw_numの順番)関数は以下のような返り値にしています。

  初回確認 1回確認済み スイッチ離した スイッチ離した
(1回確認済み)
PUSH_SHORT 1 11 101 111
PUSH_MID 2 12 102 112
PUSH_LONG 3 13 103 113

上記を参考にすれば、if分岐で振る舞いを分けたスケッチが構築できるかと思います。

もう一つ付け加えると、結局これらは単純にスイッチに対するスケッチなんで、普通に「スイッチでの数値操作」用としても使えます。別に普通のタクトスイッチ3つでもいいし、冒頭のスイッチ定義周りをちょっといじれば、個数も好きに増やせたり。(ただし、digitalReadを使っていないのと、内部プルアップの負論理も反転している事に注意)。

まあ、役立つようなら、書き換えてご活用ください。

参照リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください