Arduinoを USB/HIDデバイス(仮想キーボード)として活用する(第六回）ロギングとプロット②


##本記事について
　Arduinoを USB/HIDデバイスとして活用する第六回②です。[第六回①はここ]
(https://qiita.com/pronechia/items/5054b9567454df6b9edd)。①で実現した制御結果をロギングするところを紹介します。
##第六回）「赤外線によるエアコンのON/OFFと結果のロギングとプロット②」
 ①で紹介した、赤外線によるエアコンのON/OFFと温度調整制御を行いながら、周期毎のセンサー値や制御結果をEEPROMにロギングします。
　ロギングは、周期的に発生するデータ一式を繰り返し回数分保持し、実行後にデータを指定して時系列順に取り出すなどして利用します。個々のデータのことをチャンネル（ch）と呼びます。
　実現方法として特筆すべきは、アプリとデータの独立性を高めデータの追加／変更を容易とするため、EEPROM上のデータを読み書きする仕組みとして、INFOという考え方を導入したことです。これは、さまざまなアプリに適用することができます。
　データの発生頻度が高くなければ、より大量にデータを保存できる方法として、ファイルをSDカードに保存する方法もあります。が、今回はエアコンのON/OFF制御のロギングということで、１日程度、データを保管する仕組みとしてEEPROMを使って実現しました。

##機能概要
　　下図にあるように、初期化時に蓄積するデータにラベル名（ch名）を付与してEEPROMアドレスと紐付けをします。これをINFO情報と呼びます。データは個別データと周期データを格納することができます。個別データはアプリ間のデータ共有として利用します。周期データはロギング／ビューワーで利用します。
　制御実行時には、ロガーにより、ch名を指定して（EEPROMのアドレスを指定することなく）EEPROMに書き込みます。制御実行後動かすビューアーでは、 ch名と周期を指定してデータを読み出すことができます。また、アクセスをサポートするライブラリーも用意しました。ライブラリーでは、INFO情報を使ってchを読み書きすることができます。
　この仕組みを使えば、データの追加／変更が簡単にでき、複数のアプリが共通のINFOを介してEEPROM上のデータにアクセスすることもできます。

・データの追加／変更が簡単にでき、EEPROMの物理アドレスを気にすることなくアプリ実装ができる。
・今回はマイコン１つだが、マイコンが複数であってもEEPROMを共有する仕組みが実現できる。
・INFOの提供元としてSDカードがあれば、ファイル渡しもできる。

＜INFO を使ってEEPROMをアクセスするイメージ＞
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/b5100619-fdc0-eb2e-3dea-e64a2cd26564.png)

##info情報

```ruby:info情報の変数定義
//info 情報定義構造体
typedef struct _INFO {
   char label[8];       //ch名
   uint16_t  address;   //EEPROM保存時のアドレス
   uint16_t  len;       //1,2,4byte
} INFO;
//個別データ
#define INFOMAX 5
int tableInfoCount = INFOMAX;
INFO tableInfo[INFOMAX];
//周期データ（ヘッダー）
#define HEADERMAX 20
INFO tableHeaderInfo[HEADERMAX];
//周期データ（データ）
#define LOGRMAX 8
INFO tableLogInfo[LOGRMAX];
```

```ruby:info情報
//初期化にて、設定する情報の例
//BME280で取得した温度、湿度、気圧を共有する
#define MAPDT     0x0040
#define MAPDH     0x0042
#define MAPDP     0x0044
#define MAPDC     0x0046
#define MAPDY     0x0047
//周期データは、温度、湿度、制御値
#define MAPLOGK     0x0000
#define MAPLOGL     0x0002
#define MAPLOGC     0x0003
//個別データのchごとの情報
  strcpy(tableInfo[0].label , "Dtemp");
  tableInfo[0].address = MAPDT;
  tableInfo[0].len = 2;
  strcpy(tableInfo[1].label , "Dhum");
  tableInfo[1].address = MAPDH;
  tableInfo[1].len = 2;
  strcpy(tableInfo[2].label , "Dpress");
  tableInfo[2].address = MAPDP;
  tableInfo[2].len = 2;
  strcpy(tableInfo[3].label , "Dctl");
  tableInfo[3].address = MAPDC;
  tableInfo[3].len = 1;
  strcpy(tableInfo[4].label , "Dyobi");
  tableInfo[4].address = MAPDY;
  tableInfo[4].len = 1;
  tableInfoCount = 5;
//周期データのchごとの情報
  strcpy(tableLogInfo[0].label , "LK");
  tableLogInfo[0].address = MAPLOGK;
  tableLogInfo[0].len = 2;
  strcpy(tableLogInfo[1].label , "LL");
  tableLogInfo[1].address = MAPLOGL;
  tableLogInfo[1].len = 1;
  strcpy(tableLogInfo[2].label , "LC");  //controll値　エアコン on/off
  tableLogInfo[2].address = MAPLOGC;
  tableLogInfo[2].len = 1;  // int8_t とする 
  tableLogInfoCount = 4
//周期データの管理情報
  tableLogRepeat[0] = 2048; //最大個数
  tableLogRepeat[1] = 2048;
  tableLogRepeat[2] = ２０４８;
  tableLogStartAddr[0] = 0x4000; //先頭アドレス
  tableLogStartAddr[1] = 0x8000;
  tableLogStartAddr[2] = 0xc000;
```

INFO構造体が、ch名とEEPROM物理アドレスを変換する主要な情報となる。
この例では、個別データを５つ、周期データを４つ定義している。周期データの最大個数を2048 としている。これだけのことを初期化で行っておけば、実際のEEPROM読み書き時は、ch名でアクセスできる。

##提供されるライブラリ

```ruby:ログヘッダーの読み書き
//Logヘッダーを読む  
bool sub_EEPROM512_ReadLogHeader(int8_t p_lognum, unsigned long* p_s, unsigned long* p_e, int16_t* p_c, int16_t* p_n)
//
//Logヘッダーを書く 
void sub_EEPROM512_WriteLogHeader(char p_se)
```
ロギングされたデータを管理するため、ログヘッダーの読み書き関数を提供する。

```ruby:ラベルを指定してログを読み書き
//chデータを書く
  int16_t p_t;  //温度、 25.00度の場合は2500とする
  char* p = (char*)&p_t;
  sub_SetLogVal(gCount, "LK", p); //温度
//chデータを読む
　　　　char p[8];
  int16_t* fp;
  sub_GetLogVal(pCount, "LK", p);
  fp = (int16_t*)(p);
```
周期データの読み書き関数を提供する。

##実行時のログ蓄積

・個別データ用の例
　"Dtemp"というch名のデータを制御で書き、別アプリで読むイメージの図です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/69f2bfb6-8d6b-7512-7e64-8c685b6cdd20.png)
・周期データ用の例 
"LK"というch名の周期データをロガーで書き、ビューアーで読み出すイメージ図です。何番名のデータかを意味する、パラメータ=cがあることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/34990769-b383-4bc2-4e87-3dcede9d26b1.png)

##ログ管理機能
３セットのログを保持できるようにした。ログヘッダには、開始、終了、実行周期、データ点数を保持している。３セットのログのうち、次回書き込む番号（つまりログを残す番号）、次回読み取る番号（つまりプロットする番号）を指定することができる。
 LLISTの例） LPLOT 0 / LNEXT 2 を実行した後の状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592438/5a20b43f-36f6-a7a5-f42d-4878d2534d6b.png)
ログに関する命令は以下の３種類
・LLIST           一覧表示
・LNEXT n       次回使用するログの指定　（書込番号)
・LPLOT n       PLOTログの指示　（読込番号）

　使い方手順例）
１。LLIST でログの状況を確認する。次回のロギングはログ番号２を使うと決める
２。LNEXT ２　で書込番号を２に設定する
３。ロギング実行
４。ロギング終了後、LLISTでログが書き込まれたことを確認する（時間、点数など）
５。LPLOT ２　で読込番号を２に設定する
６。シリアルモニター画面に切り替えて、１を入力すると、1ページ目が描画される。
　　点数が５００点以上であれば、２を入力して、２ページ目を描画する、、、

##まとめ
　実装上の工夫として、アプリとデータの独立性を高めデータの追加／変更を容易とする、INFOという考え方を実現できました。開発中にEEPROMを違うサイズに取り替えることが発生しましたが、アドレス依存部分が局所的で簡単に対応することができました。アプリの仕様変更にも柔軟に対応できると考えます。また、各種アプリで利用するために、ライブラリーとして提供することも考えられます。
　③では、ロギングしたデータを取り出してIDEのシリアルプロッターにプロットする仕組みを紹介します。