NefryBTのディスプレイに折れ線グラフを表示する

Nefry Advent Calendar2018の10日目担当させていただきます。よろしくお願いします。
NefryBTにつけられるディスプレイは、デバッグにちょうど良いサイズです。これにグラフを表示してみてはいかがでしょうか？という話です。
今回は折れ線グラフについてまとめてみました。

※[Ambient](https://ambidata.io/)という、データを可視化する(グラフにする)サービスもあります。Ambientの方が当然機能が充実しています。ディスプレイに表示するメリットとしましてはすぐに表示されて結果が分かる・オフラインでも見られる点かと思います。
※NefryBT R2、バージョン1.4.1で動かしています。
※プログラムソースは[こちら](https://github.com/NearMugi/NefrySample/tree/master/DispGraph_Line)になります。ダウンロードしてNefryBTに書き込めばサンプルが表示されます。
# ディスプレイと描画に使う関数
　　<img src="https://qiita-image-store.s3.amazonaws.com/0/237144/1bb8a5d5-fcb2-1398-ca2c-728da0521666.png" width="400">
ディスプレイの解像度は128×64ドットとなります。
左上が(0,0)である点に注意が必要です。折れ線グラフは左下を(0,0)としたほうが見やすいので、グラフを描く際にひと手間必要になります。
![display2.png](https://qiita-image-store.s3.amazonaws.com/0/237144/5e0b64c2-3664-324c-3ca0-b1d2e7490916.png)

またグラフの描画はえむにわさん([@m2wasabi](https://qiita.com/m2wasabi))の[Nefryのディスプレイを使おう](https://qiita.com/m2wasabi/items/257469f0f9853b164c6e)を参考にしました。
>NefryDisplay.setPixel(X, Y)
>点を描画します。
>NefryDisplay.drawLine(X0, Y0, X1, Y1)
>直線を描画します。
>NefryDisplay.drawRect(X, Y, WIDTH, HEIGHT)
>四角形を描画します。
>NefryDisplay.fillRect(X, Y, WIDTH, HEIGHT)
>四角形を塗りつぶします。
>NefryDisplay.drawCircle(X, Y, RADIUS)
>円形を描画します。
>NefryDisplay.fillCircle(X, Y, RADIUS)
>円形を塗りつぶします。
>NefryDisplay.drawHorizontalLine(X, Y, LENGTH)
>画面に水平に線を描画します。
>NefryDisplay.drawVerticalLine(X, Y, LENGTH)
>画面に垂直に線を描画します。




# グラフの仕様
見やすさと使い勝手を考えて、いくつか自由に設定できるようにしています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/237144/b4beef00-2661-6dfe-a505-1f60867421fe.png" width="400">

+ 描画領域を自由に設定できる
 + グラフ以外にも情報を表示したい場合、領域を小さくすることで両立できます。

+ 最大3つのアナログデータを同時にプロットできる
 + 例えば3軸の加速度センサー(x,y,z)を同時に表示したい！などにも対応できます。
+ プロットした点の形状を"丸"・”四角”に変えられる
 + ディスプレイは単色のため、点の形状を変えることで違いを出せます。
+ 特定の値(最大値や平均値など)を表示できる
+ プロットする点の数を指定できる　
 + 数を増やすと、表示し続ける時間が長くなります。細かくて見づらくなることもあるので、用途により適宜設定が必要です。
![dpp.png](https://qiita-image-store.s3.amazonaws.com/0/237144/83dec509-5598-5fa1-dbf9-66ab47f30031.png)

+ 1秒ごとに区切り線を引いている
 + 見やすさのため引いています。

# 描画の考え方
以下の仕組みでグラフを描画しています。

1. 表示するデータの保存場所を確保する。
 + プロットする点の数だけ配列(箱)を用意します。
1. 箱の中身を更新する。
 + 最も古いデータを捨てて、箱の中身を一つずつずらしていきます。
 + 最後の箱に新しいデータを追加します。
1. 箱の中身をグラフにする。
 + 保存しているデータをディスプレイの描画領域の値に変換します。
 + 変換した点と点を結んで折れ線グラフにします。
1. 箱の中身の更新→グラフにするを繰り返す。

図にすると以下の通りです。10個の点をプロットする場合の例です。
<img src="https://qiita-image-store.s3.amazonaws.com/0/237144/655405c3-f77b-f764-35e7-e8897e2a4ad0.png" width="500">
<img src="https://qiita-image-store.s3.amazonaws.com/0/237144/8c295d4b-3331-6290-6a59-e844b882c69b.png" width="500">
<img src="https://qiita-image-store.s3.amazonaws.com/0/237144/201bc869-3a49-bb9d-0872-8f0322ebb859.png" width="500">
<img src="https://qiita-image-store.s3.amazonaws.com/0/237144/8fd2b0fd-2d2b-b58e-0872-a9b651d6103d.png" width="500">


# プログラム
サンプルプログラムはDispGraph_Line.inoとdispGraphLine.hになります。
基本的にDispGraph_Line.inoを編集すれば良いようにしています。

## 補足  

グラフの表示領域、アナログデータの範囲設定はこちらです。

```
//グラフの表示領域
#define GRAPH_LINE_POS_X 27
#define GRAPH_LINE_POS_Y 10
#define GRAPH_LINE_LEN_X 100
#define GRAPH_LINE_LEN_Y 50
#define GRAPH_LEN_DPP 10 //点をプロットする間隔(1なら1ドットにつき1点、2なら2ドットにつき1点)

//アナログデータの最大値・最小値
#define VALUE_LINE_MIN 0
#define VALUE_LINE_MAX 4098 //esp32は分解能12bit
```
プロットするアナログデータの数を減らしたい場合、こちらの配列を削除します。

```
int v1[LINE_PLOT_SIZE]; //アナログデータ1の値
int v2[LINE_PLOT_SIZE]; //アナログデータ2の値
int v3[LINE_PLOT_SIZE]; //アナログデータ3の値
```
グラフの設定を初期化する関数はこちらです。

```
//初期化
void dispGraphLine_init() {
  //時間の箱を初期化
  grline.initGraphTime();
  
  //各グラフの箱を初期化
  //  インデックス[0～2]
  //  アナログデータを保存する箱
  //  頂点のタイプ[VERTEX_NONE(頂点そのまま), VERTEX_CIR(丸), VERTEX_SQU(四角)]
  //  特定の値(最大値や平均値など)の表示有無[true:表示する,false:表示しない]
  grline.setGraph(GR_1, &v1[0], VERTEX_CIR, true);
  grline.setGraph(GR_2, &v2[0], VERTEX_NONE, false);
  grline.setGraph(GR_3, &v3[0], VERTEX_NONE, false);
}
```

グラフを描画する関数はこちらです。

```
//描画
void dispGraphLine_update() {
  grline.dispArea();
  grline.updateGraph();
}
```

ループ処理内で一定時間ごとにデータの追加、グラフの描画を行っています。
「一定時間」はcho45さん([@cho45](https://twitter.com/cho45))の[Arduino で一定時間ごとに何かをする interval クラス](https://lowreal.net/2016/02/24/3)を使わせていただいています。すごく便利です。

```
void loop() {

   ～中略～

  //データ・グラフ更新
  interval<LOOPTIME_GRAPH>::run([] {
    if (!isStopGraph) {
      
      ～中略～
      
      //データを追加
      grline.addGraphData(GR_1, (int)sampleData[0]);
      grline.addGraphData(GR_2, (int)sampleData[1]);
      grline.addGraphData(GR_3, (int)sampleData[2]);

      ～中略～

      grline.setValue(GR_1, _tmpMax);
      
      //時間を更新
      grline.updateGraphTime();
    }

    //描画する
    NefryDisplay.clear();
    dispGraphLine_update();
    NefryDisplay.display();

  });
}
```

## その他

1. メインの処理が遅延しない程度に利用しましょう。
 + グラフの更新によりマイコンへ負荷がかかっています。「グラフを描画しているからループ処理が遅延する！」とならないよう、適度な利用をおすすめします。
1. dispGraphLine.hを編集するともっとカスタマイズできます。
 + 1秒ごとの区切り線の設定を変える
 + 秒数を表示しない
 + 文字の大きさを変える

　　などなど。自由にいじっちゃってください。


# まとめ
ディスプレイにグラフを表示するとアナログデータの変化をチェックするのに大変便利です。
細かい設定をつらつらと書きましたが、まずはサンプルを動かしてもらえると良さを分かってもらえるかと思います。