ATOM Matrixに心拍センサを繋ぐ

[ATOM Matrixのファーストタッチ](https://qiita.com/jksoft/items/fa92281f90bcbc32929c)で動作の確認ができたので、次はGroveモジュールを繋いでみました。
温度や湿度、気圧、空気の状態がわかるBME680を繋ごうとしたのですが、ATOM MatrixのGrove互換コネクタの電源出力は5Vだったので、容易には繋げられなかった（BME680の電源は1.8V～3.6V）ので、Groveの心拍センサを繋いでみました。

[GROVE - 心拍センサ](https://www.switch-science.com/catalog/2526/)
家にあったのは上記ですが、M5Stack用のもあるようです。
[M5Stack用心拍センサユニット](https://www.switch-science.com/catalog/5695/)（こちらは血中酸素濃度もわかる）

本記事では、[GROVE - 心拍センサ](https://www.switch-science.com/catalog/2526/)を繋ぎます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/0d64ede7-79bc-e3e4-f631-0609cd111746.png)

ATOM Matrixがあまりに小さいので、心拍センサの回路ユニットの方が大きいぐらい
クリップを指先か耳たぶに挟むことで、心拍を計測できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/23a6c49b-4ec8-691c-4c1a-86f414e1e9ec.png)


# ハートマークを作る
心拍のドキドキをハートマークで表現したかったので、ATOM Matrixの5x5のフルカラーLEDにハートマークを表示させるためのデータを作ります。

[M5Atomのライブラリ](https://github.com/m5stack/M5Atom)にデータを作ってくれる[アプリのリンク](https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/software/AtomPixTool.exe)(Windows版のみ)があったのでこれを使います。
単純なアプリでダウンロードして、起動するだけです。

赤い四角で囲んだ部分をクリックするとカラーパレットから色を選択することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/18937c09-edfa-929c-b2ca-2edd73e16671.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/9339ad04-7058-5de4-f8d0-0c7a9adc2e97.png)

色をそれっぽい色にしてハートマークを作りました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/a52bf06a-3247-9847-daf6-536bb59f75a6.png)

ドキドキを表現するのにパレットのサイズの`W`を10に変更して、小さいハートも用意しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/fe37ea62-c7ca-77e7-ce49-ec5a2750e16d.png)

`TOOLS`から`SAVE`を選ぶことで、ソースコードとして保存できます。

# プログラム
[GROVE - 心拍センサ](https://www.switch-science.com/catalog/2526/)は、GitHubに[サンプル](https://github.com/Seeed-Studio/Grove_Ear_Clip_Heart_Rate_Sensor/blob/master/examples/MeasureHeartRate/MeasureHeartRate.ino)が用意されているので、ほぼほぼコピペで作りました。

心拍センサの出力はATOM MatrixのGrove互換ポートのG32と書いてあるピンに繋がるようになっているので、そのピンを指定して、入力割込みを設定しています。

入力割込みの処理で心拍の周期を記録し、一定時間内の周期から心拍数を計算するようになってます。
loop()の中では、心拍数からハートマークを鼓動させる周期を計算して、表示を切り替えてるだけです。

```cpp
#include "M5Atom.h"

unsigned char counter;
unsigned long temp[21];
unsigned long sub;
bool data_effect = true;
unsigned int heart_rate = 60;

const int max_heartpluse_duty = 2000;

// ツールが出力した画像データ部分のソースコードをコピペ
const unsigned char image_tmp[152] =
{
  /* width  010 */ 0x0a,
  /* height 005 */ 0x05,
  /* Line   000 */ 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
  /* Line   001 */ 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, //
  /* Line   002 */ 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0xff, 0x00, 0x7f, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, //
  /* Line   003 */ 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
  /* Line   004 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};

void setup()
{
  arrayInit();
  M5.begin(true, false, true);
  Serial.begin(115200);

  // 心拍センサの入力ピン設定
  pinMode(32, INPUT_PULLUP);              // Grove G32 pin
  // 心拍センサの入力割込み設定
  attachInterrupt(32, interrupt, RISING); // Grove G32 pin
}

void loop()
{
  // 心拍からハートマークを鼓動するウェイト時間を計算
  unsigned long wait = (unsigned long)(500.0 * (60.0 / (float)heart_rate));
  M5.dis.displaybuff((uint8_t*)image_tmp, 0, 0);
  delay(wait);
  M5.dis.displaybuff((uint8_t*)image_tmp, 5, 0);
  delay(wait);
}

/*Function: calculate the heart rate*/
void sum()
{
  if (data_effect) {
    heart_rate = 1200000 / (temp[20] - temp[0]); //60*20*1000/20_total_time
    Serial.print("Heart_rate_is:\t");
    Serial.println(heart_rate);
  }
  data_effect = 1; //sign bit
}

/*Function: Interrupt service routine.Get the sigal from the external interrupt*/
void interrupt()
{
  temp[counter] = millis();
  Serial.println(counter, DEC);
  Serial.println(temp[counter]);

  switch (counter) {
    case 0:
      sub = temp[counter] - temp[20];
      Serial.println(sub);
      break;
    default:
      sub = temp[counter] - temp[counter - 1];
      Serial.println(sub);
      break;
  }
  //set 2 seconds as max heart pluse duty
  if (sub > max_heartpluse_duty) {
    data_effect = 0; //sign bit
    counter = 0;
    Serial.println("Heart rate measure error,test will restart!" );
    arrayInit();
  }
  if (counter == 20 && data_effect) {
    counter = 0;
    sum();
  } else if (counter != 20 && data_effect) {
    counter++;
  } else {
    counter = 0;
    data_effect = 1;
  }
}

/*Function: Initialization for the array(temp)*/
void arrayInit()
{
  for (unsigned char i = 0; i < 20; i ++) {
    temp[i] = 0;
  }
  temp[20] = millis();
}
```

# 動作確認
プログラムを書き込んで、クリップを指先に挟んで、しばらくすると実際の心拍に沿って、ハートマークが鼓動しました。

心拍65ぐらい
![20200415-025307.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/4f8731b3-7252-c606-0287-1d53c0146b6b.gif)

心拍100ぐらい（ちょっと運動したとき）
![20200415-025113.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109992/51e2e22d-f5d1-8a8f-c26d-235e11e46b3e.gif)

特に引っかかることもなく、思った通りに動きました。

# あとがき
たいしたプログラムでも無いですが、ツールや入力割込みの使い方の参考にしてもらえればと思います。
本当は、Wi-Fiを繋いで、[Ambient](https://ambidata.io/)に心拍をアップロードしてブラウザでグラフ表示までやりたかったのですが、アップロードするところで、リセットがかかってしまい、うまくいきませんでした。原因を解決して、次の記事で書きたいと思います。
Wi-Fiに繋ぐことは成功したので、参考にソースコードを載せておきます。Wi-Fiに繋がるArduino互換ボードならおなじみのコードだと思います。

```cpp
#include "M5Atom.h"
#include <WiFi.h>

const char* ssid = "ssid";
const char* password = "pass";

void setup()
{
  M5.begin(true, false, true);
  Serial.begin(115200);

  //  Wi-Fi APに接続
  WiFi.begin(ssid, password);
  //  Wi-Fi AP接続待ち
  while (WiFi.status() != WL_CONNECTED) {  
      delay(500);
      Serial.print(".");
  }
  Serial.print("WiFi connected\r\nIP address: ");
  Serial.println(WiFi.localIP());
}
```