Arduinoでページ切り替えスイッチを作りたい話。

#概要
コントローラー(というか、フィジカルサーフェイス)を作るときに、ページ切り替えボタンが欲しいと思ったので、試行錯誤。ありがちな感じだけど、調べてもイマイチ出て来ないので考えたものの、再考の余地ありという感じです。
コードの書き間違いが多いので適宜直してください。

#回路1
##方針
例えば4つのフェーダー (可変抵抗、スラィドポテンショメーター)と1つのボタン(タクトスイッチ)を使った回路を用意する。スイッチを押すとページが切り替わり、フェーダーを格納する場所が変わる。
つまり、ページ1の時はフェーダーはフェーダー1〜4、ページ2の時はフェーダーはフェーダー5〜8、ページ3の時はフェーダーはフェーダー9〜12、というように、少ないフェーダーで多くの処理をできるようにしたい。

##コード
```
//ピンの接続
int const FADER[4] = {A0,A1,A2,A3}; //フェーダーの接続
int const BUTTON = 3; //ボタンの接続

//変数の宣言
int valFader[4][3]; //値を格納(フェーダー番号、ページ番号)
int page; //ページは0,1,2の3ページ。

void setup() {

  for(int i=0; i<4; i++){
    pinMode(FADER[i],INPUT);
  }

    pinMode(BUTTON,INPUT);
}

void loop() {
  
  //ボタンが押された場合、ページを切り替える
  if(digitalRead(BUTTON) == 1){
    switch(page){
      case 2:
        page = 0;
        break;
      default:
        page++;    
        break;  
    }
  }

  //値を格納
  for(int i=0; i<4; i++){
    valFader[i][page] = analogRead(FADER[i]);
  }
}
```

プルアップを使う場合は適宜書き換えという形で。今回は4つのフェーダーと3ページのページ数ということで、4とか3とか書いてるけども、変えたい場合はdefineして変えられるようにする方がいいかも。とりあえず、スタートはここからという感じ。
実際に動かそうとしても全く思ったようには動かないので、ひとつひとつ直していかないといけない。

##問題点と解決策1
```
  if(digitalRead(BUTTON) == 1){
    switch(page){
      case 2:
        page = 0;
        break;
      default:
        page++;
        break;      
    }
  }
```
問題点のひとつ目はページの切り替え。
ボタンを押している間にloopがサイクルすると、ページがひたすら切り替わる。
(SerialPrintすると0,1,2がぐるぐる回る。)
これを解決するために、押したらページを一度だけ切り替えるようにしないといけない。チャタリングを考えない場合、以下のように書き換えてみる。

```
  //変数の宣言に追加
 byte valButton; // =0;

  //古い値の格納
//  valButton = valButton & B00000011;
  valButton = valButton<<1;
  valButton += digitalRead(BUTTON);

  //ページ切り替え判定
  if(valButton == 1){
    switch(page){
      case 2:
        page = 0;
        break;
      default:
        page++;      
        break;
    }
  }
```

digitalReadで読み込んだ値を格納する変数を宣言する。初期値は0。```valButton = digitalRead(BUTTON);```で読み込むことになり、ボタンを押したら、ページが切り替わる。押しっぱなしにしているとloopの2サイクル目は```valButton = B00000011```となり、if文に引っかからなくなるのでページが切り替わることはない。
正直下位2ビットだけで判断して押しっぱなしを判定するので、下位3ビット目より上は0でも1でもどっちでもいい。最大8周で```B11111111```になる。が、逆にボタンを離したあとに```B00000000```に戻るのにも8周サイクルしないといけないので、loopが長いとボタンの反応が悪くなる可能性がある。
その場合は、```valButton = valButton & B00000011;```を使って上位ビットを捨てると反応は早くなる。
あるいは、if文を使ってちゃっちゃと0にもどしてもいい。

```
if(digitalRead(BUTTON) == 1){
  valButton = valButton<<1;
  valButton += digitalRead(BUTTON);
}else{
  valButton = 0;
}
```
あと、チャタリング対策をしている場合は、digitalReadの代わりにチャタリング対策したコードに書き換えればよいかと。

##問題点と解決策2
```
  for(int i=0; i<4; i++){
    valFader[i][page] = analogRead(FADER[i]);
  }
```
loop内を毎サイクルごとにフェーダーの値を読む方法は、ページ切り替えをしていない場合にはまったく問題ないが、ページ切り替え機能をつけた時には問題がある。別ページでフェーダーを動かしていると、ページを切り替えた時にもともとあった値とフェーダーの位置が違うということになり、ページを切り替えた瞬間に意図しない値に飛ぶことになってしまう。
そのため、ページを切り替えてもフェーダーの値は読みとらず、フェーダーを動かした時に初めて値を読むという方法をとらないといけない。そのため、「1.ページを切り替えたこと」「2.(ページを切り替えたのちに)初めてフェーダーを動かしたということ」「3.もともとあった値と実際のフェーダーの位置の比較」を認識させる必要がある。

「1.ページを切り替えたこと」、というのは先述と同様に古い情報と比較させることでできる。ただし、ページ番号はスイッチのようなゼロイチではないので、シフトアップで処理できないので配列を用いて新しい領域をつくってやる必要がありそう。

```
//変数の宣言に追加
 　 int page[2]; //古い情報を格納するために2つの値を格納できる配列を作る。

/* //ページ切り替えの処理内のpageを配列化。
 　if(digitalRead(BUTTON) == 1){
  　 valButton = valButton<<1;
  　 valButton = digitalRead(BUTTON);
 　}else{
 　  valButton = 0;
 　}

 　if(valButton == 1){
 　  switch(page[0]){ 
   　  case 2:
     　  page[0] = 0;
     　  break;
    　 default:
    　   page[0]++;      
   　    break;
  　}
  }
*/

//ページが切り替わったかどうかの判定
  if(page[0] != page[1]){

 　 //情報を更新
 　 page[1] = page[0];

   //ページが切り替わった時の処理

  }else{

   //ページが切り替わっていない時の処理

  }
```

ページが変わっていないときには```page[1] == page[0]```であり、ボタンを押してページが切り替わったサイクルタイミングで```page[0] != page[1]```となり、if文が真となり、処理がおこなわれる。そのタイミングで古い情報を捨てるために```page[1] = page[0]```により情報を更新する。
これで「1.ページが切り替わったこと」を認識できるようになった。

次に、「2.(ページを切り替えたのちに)初めてフェーダーを動かしたということ」「3.もともとあった値と実際のフェーダーの位置の比較」については、digitalReadした値をすぐに対応する番号に格納せずに置いておくという方法を取れば良い。

```
//変数の宣言に追加
 　 int valFaderReal[4]; //実際のフェーダーの位置を格納する変数

/*
 ...(ページ切り替えの処理)
 ...(ページ切り替え判定の処理)
*/

//ページが切り替わったかどうかの判定
  if(page[0] != page[1]){
 　 page[1] = page[0];

   //ページが切り替わった時の処理
   　for(int i=0; i<4; i++){
　　   valFaderReal[i] = analogRead(FADER[i]);   //実際のフェーダーの位置を読み取る。
    }
  }else{
   //ページが切り替わっていない時の処理
  }

//フェーダーが動いたかどうかの判定
   for(int i=0; i<4; i++){
      if(analogRead(Fader[i]) != valFaderReal[i]){
         valFader[i][page[0]] = analogRead(Fader[i]);　//フェーダーが動いたので値を読む。
//         valFaderReal[i] = analogRead(Fader[i]);
      }
   }
```

ページを切り替えたタイミングで一度フェーダーの位置を読み取る。ここで初めに書いたコード読み取った値をすぐにvalFaderに格納してしまうとページを切り替えた瞬間に意図しない動作が起きてしまうから問題だった。
なので、valFaderRealという新しい変数を用意し、ページを切り替えたタイミングで一度そこに値を格納する。こうすることで、ページを切り替えたタイミングでもともとあったvalFaderとフェーダーの位置がずれていても、すぐに値を変える処理を行わずにすむ。
つぎに、```if(analogRead(Fader[i]) != valFaderReal[i])```という条件をつくることで、ページを切り替えた後にフェーダーが動いたということを認識させ、初めて動かしたタイミングでvalFaderに値を格納する。
これでページが切り替わってフェーダーの位置がずれていても、初めてフェーダーを触ったタイミングから読むことができた。

さらにページを切り替えたタイミングでフラグを立てればもう少し複雑なことができるようになる気がする。僕的にはしっくりくる。

```
//変数の宣言に追加
 　 int valFaderReal[4]; //実際のフェーダーの位置を格納する変数
 　 int flagFader[4]; //フラグ on 1, off 0 

/*
 ...(ページ切り替えの処理)
 ...(ページ切り替え判定の処理)
*/

//ページが切り替わったかどうかの判定
  if(page[0] != page[1]){
 　 page[1] = page[0];

   //ページが切り替わった時の処理
   　for(int i=0; i<4; i++){
　　   valFaderReal[i] = analogRead(FADER[i]);   //実際のフェーダーの位置を読み取る。
      flagFader[i] = 1; フラグを立てる。
    }
  }else{
   //ページが切り替わっていない時の処理
  }

   for(int i=0; i<4; i++){
      if(flagFader[i] = 1){ //フラグが立っている
         if(analogRead(Fader[i]) != valFaderReal[i]){
            flagFader[i] = 0;
         }
      }else{ //フラグが降りている
         valFader[i][page[0]] = analogRead(FADER[i]);
      }
   }
```

このコードの場合、動かした瞬間にその値に飛ぶことになる。例えばもともとあった値が100で、フェーダーの位置が0だった状態でページが切り替わるような場合、ページを切り替えても0にはならないものの、フェーダーを動かした瞬間に100から1に飛んでしまう。もう少し改良を加えて、0にあるフェーダーが100を超えるまでは動かしても値を変えないようにし、101を超えるとフェーダーの値を読むようにしたい。

```
//変数の宣言に追加
   int valFaderReal[4]; //実際のフェーダーの位置を格納する変数
   int flagFader[4]; //フラグ on 1, off 0 
   int holFader[4]; // High 2, Low 1 or equal 0;

/*
 ...(ページ切り替えの処理)
 ...(ページ切り替え判定の処理)
*/

//ページが切り替わったかどうかの判定
  if(page[0] != page[1]){
 　 page[1] = page[0];

   //ページが切り替わった時の処理
   　for(int i=0; i<4; i++){
　　   valFaderReal[i] = analogRead(FADER[i]);   //実際のフェーダーの位置を読み取る。
      flagFader[i] = 1; フラグを立てる。
      if(valFaderReal[i] > valFader[i][page[0]){
         holFader[i] = 2;
      }else if(valFaderReal[i] < valFader[i][page[0]){
         holFader[i] = 1;
      }else{
         holFader[i] = 0;
      }
    }
  }else{
   //ページが切り替わっていない時の処理
  }

   for(int i=0; i<4; i++){
      if(flagFader[i] = 1){ //フラグが立っている
      if(((analogRead(FADER[i]) <= valFader[i][page[0]])&&(holFader[i] == 2))||((analogRead(FADER[i]) >= valFader[i][page[0]])&&(holFader[i] == 1))||(holFader[i] == 0)){
            flagFader[i] = 0;
         }
      }else{ //フラグが降りている
         valFader[i][page[0]] = analogRead(FADER[i]);
      }
   }
```

フェーダーがあるラインを超えると読み込むようにするためには、もともとの値より実際の値が大きい場合と小さい場合、加えて同じ値の場合がある。そこでHigh or Lowのチェックをした後に、それが逆転するとフラグを下ろすという操作を行うことで、期待した動作を行えるようになったと思う。

#回路2
##方針
概ね完成には近づいてきたものの、１つ改良したい点が出てきた。ページ切り替えのボタンが１つしかない点。一気にページを切り替えたい場合、連打する必要がある。今回は3ページしかないので問題ないものの、これが8ページとか16ページとかになると使い勝手が悪くなる。
となると、ページ分だけボタンを用意し、ボタンを押せば対応するページに切り替えるようにすれば良い。
ボタンの判定だけ変更すれば、後半はほとんどいじるものはないはず。


##コード
```
//ボタンの数を8つに増やして8ページまで対応させる。
int const BUTTON[8] = {3,4,5,6,7,8,9,10}

//変数の宣言を配列化
byte valButton[8];

for(int i=0; i<8; i+*){
   //ボタンを押したかの判定
   if(digitalRead(BUTTON[i]) == 1){
      valButton[i] = valButton[i]<<1;
      valButton[i] += digitalRead(BUTTON[i]);
   }else{
      valButton[i] = 0;
   }

  //ページ切り替え判定
  if(valButton[i] == 1){
    page[0] = i
  }
}
```

##問題点と解決策1
回路1の時のコードを少し改良。ボタンが8つに増えたので格納する変数を配列化した。書いてないけどもpinModeも8つに増える。
ボタンが増えた分、2つ以上を同時押しすることができるようになるのでその対策が必要になると思うが、上述のコードで十分対策されている。バイナリで多重認識を避けた方法をとると、ひとつのボタンの対策だけでなく全部のボタンの多重認識を避けることができた。ただし、loopのサイクルより早い速度でボタンを連打したらおかしくなるけども、人力じゃ無理だろうなぁと思う。
例えばボタン1を押してページ1に切り替える際、ボタン1を押し続けている状態でボタン2を押しても、キチンとページ2に切り替わりその後ページ1にもどることはない。もちろんページ番号が大きいところから小さいところに変えても同様だし、3つ以上のボタンを押しても最後に押したボタンのページに移るようになってる。


#まとめ
##コード

```
///ピンの接続
int const BUTTON[8] = {3,4,5,6,7,8,9,10};
int const FADER[4] = {A0,A1,A2,A3};

///値の格納場所
int page[2] = {0,0}; //ページ番号、オールド
byte valButton[8]; //ボタン番号
int valFader[4][8]; //フェーダー番号、ページ番号

///処理のための変数
int valFaderReal[4]; //実際のフェーダーの位置を格納する変数
byte flagFader[4]; //フラグ on 1, off 0 
int holFader[4]; // High 2, Low 1 or equal 0;

void setup(){
  for(int i=0; i<8; i++){
    pinMode(BUTTON[i],INPUT);
  }
  for(int i=0; i<4; i++){
    pinMode(FADER[i],INPUT);
  }
}

void loop(){
//ボタンを押したかの判定
  for(int i=0; i<8; i++){
    if(digitalRead(BUTTON[i]) == 1){
      valButton[i] = valButton[i]<<1;
      valButton[i] += digitalRead(BUTTON[i]);
    }else{
      valButton[i] = 0;
    }
  
//ボタンを押していた場合ページ切り替え
    if(valButton[i] == 1){
      page[0] = i;
    }
  }

//ページの切り替え判定
  if(page[0] != page[1]){
    page[1] = page[0];

    for(int i=0; i<4; i++){
      valFaderReal[i] = analogRead(FADER[i]); //実際のフェーダーの位置を読み取る。
      flagFader[i] = 1;
      
      if(valFaderReal[i] > valFader[i][page[0]]){
        holFader[i] = 2;
      }else if(valFaderReal[i] < valFader[i][page[0]]){
        holFader[i] = 1;
      }else{
        holFader[i] = 0;
      }
    }
  }

//フェーダーの値を読み取る。
  for(int i=0; i<4; i++){
    if(flagFader[i] = 1){ //フラグが立っている
      if(((analogRead(FADER[i]) <= valFader[i][page[0]])&&(holFader[i] == 2))||((analogRead(FADER[i]) >= valFader[i][page[0]])&&(holFader[i] == 1))||(holFader[i] == 0)){
        flagFader[i] = 0;
      }else{ //フラグが降りている
        valFader[i][page[0]] = analogRead(FADER[i]);
      }
    }
  }
}
```

##まとめについて
とりあえず、最終形としてまとめたもの。
あとは各々の処理を関数化したり、別ファイル化したり、ページ数ボタン数フェーダー数をdefineして変えられるようにしたり、arduino高速化したりすればいいのかな。
コードを書いたものの、間違いだらけだと思うので適宜直していたたげれば。