M5SticC と MLX90614/MLX90615 で非接触温度計を作った

コロナ禍で非接触体温計があちこちで見かけることもあり、遅ればせながら、M5StickCを使って非接触温度計を作ろうと思った。（体温計にはならないが額の表面温度はわかる）

##NCIR Hat が無い
手っ取り早いのが、NCIR HAT (MLX90614)を購入すれば良いのだが、国内の販売代理店はもとより、本家 M5Stick ショップにも在庫が無い状態だった。（以下のサイトのとおり）
https://m5stack.com/collections/m5-hat

## センサーの手持ちが有った
手持ちの部品を調べたら、GY-906 というのが2つ出てきた。以前、Arduino nano互換機で使っていたもので、調べてみると MLX90614 と同じようで、5vだけでなく、3.3v でも動作するようだった。これは使える！
（1月17日追記）MLX90165も有ったので、そちらでも使えるようにしたいと思った。

##配線の情報とサンプルプログラムを見つける

本家のサイトに詳しく載っていた。
https://docs.m5stack.com/#/en/hat/hat-ncir

###配線情報
HHAT 用の8ピンと、センサーの接続は次のとおり
M5StickC  --->  MLX90614

* GND  --->  GND
* 3V3  --->  VIN
* G0   --->  SDA
* G26  --->  SCL

###サンプルプログラム
　https://github.com/m5stack/M5StickC/tree/master/examples/Hat/NCIR_HAT
から、NCIR_HAT.ino を Arduino IDE のスケッチにコピペするだけと簡単！

##動かない！
なんと、コピペしただけのスケッチでは M5StickC の画面に何も表示されないのだった！
原因は7行目の Serial.begin(115200); のようで、この行を削除したら動作した。
動作はしたものの、表示される温度は１つだけ。（対象物の温度のようだ）

##環境温度と対象物の温度を出すには

MLX90614 のデータシート
https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/hat/MLX90614-Datasheet-Melexis_en.pdf
を見てみると、17ページの「8.3.4 RAM」の表に

* TA: 0x06
* TOBJ1: 0x07

 とある。（TOBJ2 って何だろう？）
 やはりサンプルは 0x07 を読んでいるので、対象物の温度を読み出していたことがわかる。
 その部分を改造して両方を読み出すようにすれば良いというわけだ。
 （後で知ったが、Adafruit_MLX90614.h ライブラリーを使うと、もっと簡単に「readAmbientTempC()」で呼び出せる）  --> 参考にしたサイトを参照

##動作の様子
冷めたコーヒーカップ（笑）の温度
ジャンパーピンに直接半田付けがみっともないのですが...
![IMG_6194X.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16491/71952f18-0027-43ea-8fa1-c17eb0a8073d.jpeg)

## HATも自作
本家の Proto HAT の基盤を使って自前のHAT（勝手HATともいうらしい）も作った。
筐体は Fusion360 でモデリングして 3Dプリントした。
![IMG_6214.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16491/b7b12b25-f0e3-e036-b59e-d7024f721ca5.jpeg)
 
## 出来上がったソース
ファイル名が「Temp.cpp」となっていますが、cpp としているのは文脈をカラーで表示させたいためで、実際には「好きな名前.ino」としてください。スケッチにソースをコピペするのでファイル名は関係ないか（笑）
（1月17日追記）小数点以下が常に0で表示されるバグも見つかり、ついでに MLX90164 と MLX90165 のどちらを繋いでも起動時に識別して動作するように改良しました。

``` Temp.cpp
/*    No contact IR temperature measurements.
      used to measure the surface temperature of a human body or other object.
      For MLX90615 or MLX90614(Infra Red thermometer)
*/
      
#include <M5StickC.h>

// =================== for MIX90614
#define I2C_Addr_90614 0x5A                 // MIX90614 I2C Addr = 0x5A
#define AMBIENT_90614  0x06;                 // Select data for MLX90614 to Ambient Temp.
#define OBJECT_90614   0x07;                 // Select data for MLX90614 to Object Temp.

// ===================  for MIX90615
#define I2C_Addr_90615 0x5B;                 // MIX90615 I2C Addr = 0x5B
#define AMBIENT_90615  0x26;                 // Select data for MLX90615 to Ambient Temp.
#define OBJECT_90615   0x27;                 // Select data for MLX90615 to Object Temp.

// ================= set default senser to 90614

uint8_t Addr = I2C_Addr_90614;
uint8_t Amb = AMBIENT_90614;
uint8_t Obj = OBJECT_90614;

float maxtemp=0.0;
float mintemp=100.0;
float hold=0.0;
float TempO=0.0;
float TempA=0.0;

void setup() {
  M5.begin();
  Wire.begin(0,26);                    // I2C begin,  SDA: Pin0,  SCL: Pin26
  M5.Lcd.setRotation(0);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0,0);
  M5.Lcd.setTextSize(2);

//------------- Sensor detection
  Addr = I2C_Addr_90614;
  Amb = AMBIENT_90614;
  Obj = OBJECT_90614;
  if(I2CreadTest()) {
    M5.Lcd.printf("MIX\n\n 90614\n\n Sensor");
  } else {
    Addr = I2C_Addr_90615;
    Amb = AMBIENT_90615;
    Obj = OBJECT_90615;
    if(I2CreadTest()) {
      M5.Lcd.printf("MIX\n 90615\n\n Sensor\n\n");
    } else {
      M5.Lcd.setTextSize(4);
      M5.Lcd.setTextColor(RED);
      M5.Lcd.printf("NO SENSOR!\n");
    }
  } 
  delay(1000);
}

bool I2CreadTest() {             // I2C read test function for Sensor detection
  Wire.beginTransmission(Addr);                         // Send Initial Signal and I2C Bus Address
  Wire.write(Amb);                                      // set target to Ambient or Object
  Wire.endTransmission(false);                          // Stop signal
  size_t ReturnSize = Wire.requestFrom(Addr, 3);        // read 3 consecutive data from I2C
  if(ReturnSize > 0) {
    for(int i = ReturnSize; i <= 0; i--) Wire.read();
    return true;
  }
  return false;
}


 float ReadTemp(uint8_t Target) {      // read Temp Function
  uint16_t temp;
  Wire.beginTransmission(Addr);          // Send Initial Signal and I2C Bus Address
  Wire.write(Target);                    // set target to Ambient or Object
  Wire.endTransmission(false);           // Stop signal
  Wire.requestFrom(Addr, 3);             // read 3 consecutive data from I2C
  temp = Wire.read();                    // read data (low 8 bit)
  temp |= Wire.read() << 8;              // read data (high 8 bit)
  Wire.read();                           // read and discard PEC (packet error code)
  return temp * 0.02 - 273.15;           // convet temperature to degree Celsius
}


void loop() {
  for(int i = 0; i <= 100; i++) {
    M5.update();
    if ( M5.BtnA.wasPressed() ) {
      hold = ReadTemp(Obj);
    }
    if ( M5.BtnB.wasPressed() ) {
      esp_restart();
    }
    if (i >= 100) {                        // Read temp and display interval (1 second.)
      TempA = ReadTemp(Amb);
      TempO = ReadTemp(Obj); 
      if (mintemp>TempO) mintemp = TempO;
      if (maxtemp<TempO) maxtemp = TempO;
      M5.Lcd.fillScreen(BLACK);
      M5.Lcd.setCursor(0,0);
      M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(GREEN); 
      M5.Lcd.printf("Ambient temp\n");
      M5.Lcd.setTextSize(3);M5.Lcd.setTextColor(WHITE);   
      M5.Lcd.printf("%3.1f\n",TempA);
      M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(GREEN); 
      M5.Lcd.printf("\Object  temp\n");
      M5.Lcd.setTextSize(3);M5.Lcd.setTextColor(WHITE);   
      M5.Lcd.printf("%3.1f\n",TempO);
      M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(CYAN);  
      M5.Lcd.printf("\nBtnB=Restart\n");
      M5.Lcd.setTextColor(GREEN); 
      M5.Lcd.printf("Battery:%3.1fV\n", M5.Axp.GetBatVoltage());
      M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(GREEN);  
      M5.Lcd.printf("\n min   max\n"); 
      M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(WHITE);   
      M5.Lcd.printf(" %3.1f  %3.1f\n",mintemp,maxtemp);
      M5.Lcd.setTextSize(1);M5.Lcd.setTextColor(CYAN);   
      M5.Lcd.printf("\nBtnA = hold\n");
      M5.Lcd.setTextSize(3);M5.Lcd.setTextColor(WHITE);     
      M5.Lcd.printf("%3.1f\n",hold);
    }
  delay(10);
  }
}
```
 
 M5StickCライブラリーのみを使用してできたソースです。
 （M5StickC Plusでもライブラリーを M5StidkC_Plusに変えるだけで使えるはず）
 
 
## 上記以外に参考にしたサイト
（1）画面表示のレイアウトを参考にさせていただいた
 「M5StickC MLX90615 赤外線温度センサー で 非接触温度計 を作ってみました。」
 　http://gijin77.blog.jp/archives/23823203.html
 
（2）Adafruit_MLX90614.h ライブラリーの情報が参考になります
「M5StickCにNCIR(MLX90614)をつなぐ(2)」
 　https://edycube.blog.fc2.com/blog-entry-1106.html

## あとがき

配線とセンサーが剥き出しで格好悪いので、後で HAT風のケースを 3D プリンターで作るつもり。
 
## 追記
2020/1/11 ソースを修正：MLX90614 だけでなく MLX90165 にも対応できるよう修正した。（コードの一部をコメントで修正）

2020/1/17 ソースを修正
・起動時に MLX90164/MLX90615 のどちらのセンサーが接続されているかを判断して動作する。
・小数点以下が常にゼロで表示されるバグの対策

2020/1/17（その2） ソースを修正
・センサーの判別部分を改良