目次 [ Contents ]
aitendoさんで購入した小型のマルチファンクションスイッチ。これを扱うコードを書いてみました。
マルチファンクションスイッチ
「右/前」と「左/後」、そして「押す」の3動作を認識するスイッチです。平たく言えば、1次元上のジョイスティックっていう感じです。ただ、以前扱ったのとは違い、単純に倒れたかどうかのON・OFFしか識別しないので、繊細な操作をするのには向きません。カーソル移動や数値の増減操作なんかには都合がよさそうで、似たモノをビデオカメラなんかの家電機器系で良く見る気がします。
何より、パーツ自体が指にスッポリ収まるくらいのサイズなので、Arduinoを使った製作物で、「何かしらの操作」用パーツを組み込もうとした時、都合が良いかなと思います。
aitendoさんでは、パーツそのものから2.54mmピッチの変換基板付きまで、いくつか種類違いがあるんですが、ここでは変換基板付きのものを扱っていきます。
サンプルスケッチ
接点のON・OFFで動作する単純なものと、接点が繋がっている長さで振る舞いを変えるもの2種類のサンプルコードを書いてみました。勿論、今までに倣い、チャタリング対策にゲージ判定を使っています。
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を使っていないのと、内部プルアップの負論理も反転している事に注意)。
まあ、役立つようなら、書き換えてご活用ください。
参照リンク





