LCD KEYPAD SHIELD試してみた


![P_20170724_215323.jpg](https://qiita-image-store.s3.amazonaws.com/0/61597/288759ba-0541-ac84-7545-1e5bafb6848a.jpeg "P_20170724_215323.jpg")

参考にしたページ

+ [LCD + キーパッドシールド](http://ht-deko.com/arduino/shield_lcd_keypad.html)
+ [1000円ではじめるArduino互換機入門](http://qiita.com/kou029w/items/e7ecf6cf5f95bfc19d36)

お手軽にいろんなことが試せそうだなと思って買ってみました。
すでにネット上で得られる情報で十分なのですが、いろいろ試してみたら意外とうまくいったので書いてみました。
何かのお役にたてば。

#こんなプログラムにしたい
このシールドはスイッチがA0に結線されていて、電圧を読んでどのスイッチか判別する必要があります。
多分使う時は、

+ A0を読んで -> 平滑化して -> どのキーか判別して -> キーに応じて何かする
+ ちょっと待つ
+ 最初に戻る

という流れになるかと思います。
なのでとりあえずloopをこんな風にしてみました。


```C++:
void loop() {
 // キーに応じて何かする <- どのキーか判別して <- 平滑化して <- A0を読んで
	actionByKey( 		 aKeyFromData( 	    smoothData( analogRead( A0 ) ) ) );
	delay( 1 );
}
```
 
まだ何にも決めていませんが、それっぽい名前をつけた関数を並べてみました。
これに合うように関数を作っていきたいと思います。

#関数をどうするか
smoothData,aKeyFromData,actionByKeyと三つの関数を作ります。

+ 引数は整数がひとつだけ。
+ 返り値は整数。

ということにすればうまくいきそうです。
関数を作るときには他にも引数が必要になることもあるので、そこは部分適用で対応することにします。
※ [補足記事:部分適用っておいしいの? on Arduino](http://qiita.com/ttatsf/items/8f092ac3eba5373042da)

まず、平滑化する関数。

```C++:
auto smoothF ( 
  []( const long RATE = 20 ){
    long previousValue ( 0 );
    return [=]( const long CURRENT_VALUE ) mutable -> long {       
      return previousValue = (RATE * CURRENT_VALUE + (100 - RATE) * previousValue  ) / 100;
    };
  } 
);
auto smoothData ( smoothF( 50 ) );
```

直前値と現在値の加重平均を返します。
ほかのスケッチで使用したものの使い回しです。
ただし、反応がのろい感じがしたので加重平均の比率を上げてみました。

キーを判別する関数。

```C++:
auto aKeyFromDataF (
  [](const int NO_KEY, const int NEITHER ){
      int previousValue ( NO_KEY );
      int confirmedValue ( NO_KEY );
      return [=]( const int DATA ) mutable -> int {
        const int CURRENT_VALUE ( whichKey( DATA ) );
        if( previousValue == NEITHER || CURRENT_VALUE == NEITHER ) {
          previousValue = CURRENT_VALUE;
          return confirmedValue;
        }
        if(  CURRENT_VALUE == previousValue ) return confirmedValue = CURRENT_VALUE;
        previousValue = CURRENT_VALUE;
        return confirmedValue;
      };
    }
 );
 auto aKeyFromData ( aKeyFromDataF( NO_KEY, NEITHER ) );
```
まだキーの値をどうするかとか決めてないのですが、とりあえず、なにか整数値で、

+ NO_KEYは何も押してない時の値
+ NEITHERはどのキーにもあてはまらない時の値

です。

whichKeyという関数を作って、データがどのキーにあたるか判別しています。
ただし、変動の途中でたまたまそのキーの値になってしまったということもあるので、

+ 直前値と現在値がともにNEITHER以外でかつ等しい場合、現在値を確定値に代入して返す
+ そうでなければ、現在値を直前値に代入し、確定値を返す

二つ同じキー値が続いたら新しいキー値として確定する、というようにしました。
whichKeyはこんな感じです。

```C++:
auto whichKeyF (
  []( const int NO_KEY, const int SELECT_KEY, const int LEFT_KEY
    , const int DOWN_KEY, const int UP_KEY, const int RIGHT_KEY, const int NEITHER
  ){
    
    return [=] ( const int DATA ) mutable -> int {
      if( isInRange(     NO_KEY, DATA ) )  return  NO_KEY;
      if( isInRange( SELECT_KEY, DATA ) )  return  SELECT_KEY;
      if( isInRange(   LEFT_KEY, DATA ) )  return  LEFT_KEY;
      if( isInRange(   DOWN_KEY, DATA ) )  return  DOWN_KEY;
      if( isInRange(     UP_KEY, DATA ) )  return  UP_KEY;
      if( isInRange(  RIGHT_KEY, DATA ) )  return  RIGHT_KEY;
      return NEITHER;
    };
  }
);
auto whichKey ( whichKeyF( NO_KEY, SELECT_KEY, LEFT_KEY
                         , DOWN_KEY, UP_KEY, RIGHT_KEY, NEITHER ) );
```

データがどのキーか判別します。どれでもなかったらNEITHERを返します。
isInRangeという関数を作って、キーの範囲にデータがあるか判別しています。
キーの値をどうするか決めてませんが、後述のactionByKeyと同じものを使っていれば大丈夫だろう、という作戦です。
isInRangeはこんな感じです。

```C++:
auto isInRangeF (
  [](const int KEY_RANGE ){
    return [=](const int KEY, const int DATA ) mutable -> bool {
      return DATA > KEY - KEY_RANGE && DATA < KEY + KEY_RANGE;
    };
  }
);
auto isInRange ( isInRangeF( KEY_RANGE ) );
```
( KEY - KEY_RANGE, KEY + KEY_RANGE ) の範囲内に DATA が入っているかを返します。

キーに応じて何かする関数。

```C++:
auto actionByKeyF (
  []( const int NO_KEY, const int SELECT_KEY, const int LEFT_KEY
    , const int DOWN_KEY, const int UP_KEY, const int RIGHT_KEY
  ){
    return [=]( const int KEY ) mutable -> int {
       //Serial.println( KEY );
      if( KEY == NO_KEY ) return KEY;
      if( KEY == SELECT_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "SELECT_KEY" );
        return KEY;
      }
      if( KEY == LEFT_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "LEFT_KEY  " );
        return KEY;
      }
      if( KEY == DOWN_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "DOWN_KEY  " );
        return KEY;
      }
      if( KEY == UP_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "UP_KEY    " );
        return KEY;
      }
      if( KEY == RIGHT_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "RIGHT_KEY " );
        return KEY;
      }
      return KEY;
    };
  }
);
auto actionByKey ( actionByKeyF( NO_KEY, SELECT_KEY, LEFT_KEY
                               , DOWN_KEY, UP_KEY, RIGHT_KEY ) );
```

[参考ページ](http://ht-deko.com/arduino/shield_lcd_keypad.html)と同様に、とりあえずキーの名前を表示してみました。何か他のことをさせたいときはここを書き換えればOK。
用途によってはまだ使うかも知れないので、一応、引数をそのまま返り値にしています。

デバッグ用の関数。

```C++:
auto sPrint( const int DATA ) -> int {
  Serial.print( DATA );
  Serial.print(",");
  return DATA;                 
};
auto sPrintln( const int DATA ) -> int {
  Serial.println(",");
  return DATA;                 
};
```

データの流れがどうなっているか見たかったので作ってみました。
引数をそのまま返しつつ、シリアルに出力します。
こんな風に見たいところに挟みこんで使います。便利。

```C++:
  sPrintln( actionByKey( sPrint( aKeyFromData( 
  	sPrint( smoothData( sPrint( analogRead( A0 ) 
  	) ) ) ) ) ) );

```
A0を読む -> シリアルに出力 -> 平滑化する -> シリアルに出力 -> キー値に変換 -> シリアルに出力 -> キーによって何かする -> シリアルを改行
という意味です。

プロッタに出してみたところ。
![スクリーンショット 2017-07-26 18.25.12.png](https://qiita-image-store.s3.amazonaws.com/0/61597/04a51010-5ed3-51e6-34fd-f0efda21b95c.png "スクリーンショット 2017-07-26 18.25.12.png")
青が生データ、赤が平滑化したもの、緑がキー値です。

キーの値は、0,1,2など別途IDを振ってもいいのですが、もろもろ簡単になるので 平滑化した値を実測したものにしました。IDイコール判断基準値ということです。
この流れでいくと、NEITHERは0から1023以外ならいいということになり、とりあえず-1ということにしました。

ここまで書いてきて、actionByKeyじゃないじゃん! actionByNewKeyじゃん!と気がつきました。
大概の場合、新しいキーが押された時だけ何かする、という方が使いでがあるようです。
使うこともあるかも知れないのでactionByKeyの方は処理の部分だけコメントアウトして残し、あらたにactionByNewKeyというのを加えました。といってもほとんど同じで、直前値と現在値が同じなら引数を返すだけで何もしない、というのが加わっただけです。


```C++:
auto actionByKeyF (
  []( const int NO_KEY, const int SELECT_KEY, const int LEFT_KEY
    , const int DOWN_KEY, const int UP_KEY, const int RIGHT_KEY
  ){
    return [=]( const int KEY ) mutable -> int {
      if( KEY == NO_KEY ) return KEY;
      if( KEY == SELECT_KEY ){
//        lcd.setCursor( 0,1 );
//        lcd.print( "SELECT_KEY" );
        return KEY;
      }
      if( KEY == LEFT_KEY ){
//        lcd.setCursor( 0,1 );
//        lcd.print( "LEFT_KEY  " );
        return KEY;
      }
      if( KEY == DOWN_KEY ){
//        lcd.setCursor( 0,1 );
//        lcd.print( "DOWN_KEY  " );
        return KEY;
      }
      if( KEY == UP_KEY ){
//        lcd.setCursor( 0,1 );
//        lcd.print( "UP_KEY    " );
        return KEY;
      }
      if( KEY == RIGHT_KEY ){
//        lcd.setCursor( 0,1 );
//        lcd.print( "RIGHT_KEY " );
        return KEY;
      }
      return KEY;
    };
  }
);
auto actionByKey ( actionByKeyF( NO_KEY, SELECT_KEY, LEFT_KEY
                               , DOWN_KEY, UP_KEY, RIGHT_KEY ) );
                               
auto actionByNewKeyF (
  []( const int NO_KEY, const int SELECT_KEY, const int LEFT_KEY
    , const int DOWN_KEY, const int UP_KEY, const int RIGHT_KEY
  ){
    int previousKey ( NO_KEY );
    return [=]( const int KEY ) mutable -> int {
      if( KEY == previousKey ) return KEY;
      if( KEY == NO_KEY ) return previousKey = KEY;
      if( KEY == SELECT_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "SELECT_KEY" );
        return previousKey = KEY;
      }
      if( KEY == LEFT_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "LEFT_KEY  " );
        return previousKey = KEY;
      }
      if( KEY == DOWN_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "DOWN_KEY  " );
        return previousKey = KEY;
      }
      if( KEY == UP_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "UP_KEY    " );
        return previousKey = KEY;
      }
      if( KEY == RIGHT_KEY ){
        lcd.setCursor( 0,1 );
        lcd.print( "RIGHT_KEY " );
        return previousKey = KEY;
      }
      return previousKey = KEY;
    };
  }
);
auto actionByNewKey ( actionByNewKeyF( NO_KEY, SELECT_KEY, LEFT_KEY
                               , DOWN_KEY, UP_KEY, RIGHT_KEY ) );

```

loopはこんな感じになりました。

```C++:
void loop() {
  sPrintln( actionByNewKey( actionByKey( sPrint( aKeyFromData( 
  	sPrint( smoothData( sPrint( analogRead( A0 ) 
  	) ) ) ) ) ) ) );
  delay( 1 );
}
```
最初のように言葉で書けば

+ A0を読む -> シリアルに出力 -> 平滑化する -> シリアルに出力 -> キー値に変換 -> シリアルに出力 -> キーによって何かする -> 新しいキーによって何かする -> シリアルを改行
+ ちょっと待つ
+ 最初に戻る

という流れです。

スケッチの全体は[こちら](https://gist.github.com/ttatsf/8e73e645a53ad35374fd1ba7950a6e38)からどうぞ。