Grove 赤外線サーマルカメラ IRアレイ MLX90640 110度 を M5Stack で動かしたメモ
Grove 赤外線サーマルカメラ IRアレイ MLX90640 110度 を M5Stack で動かしたメモです。この記事は 2021年 ゆるくすすめる ( ワンフットシーバス ) GWアドベントカレンダー の7日目の記事でもあります。
Grove 赤外線サーマルカメラ/ IRアレイ MLX90640 110度

Grove 赤外線サーマルカメラ/ IRアレイ MLX90640 110度 - Grove Thermal Imaging Camera / IR Array - Seeed Studio は、以前別件で比較的扱いやすそうな MLX90640 のセンサーを見つけていて、なんと Grove でも使えるということで、ちょっと実験や仕事面でもやってみたいことがあり、購入したものです。
https://twitter.com/1ft_seabass/status/1367958076494475267
使い方の情報
Grove だといつも通り公式の Wiki が分かりやすいです。
Grove - Thermal Imaging Camera IR-Array MLX90641 - Seeed Wiki
Seeed_Arduino_MLX9064x のライブラリをダウンロードしてインストールします。
Arduino UNO ではメモリ不足で動かない
MLX90640 は結構パワーを使うセンサーのようで、いつも最初に試す Arduino UNO では以下のエラーに。
data section exceeds available space in board
最大32256バイトのフラッシュメモリのうち、スケッチが16452バイト(51%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が14552バイト(710%)を使っていて、ローカル変数で-12504バイト使うことができます。
スケッチが使用するメモリが足りません。メモリを節約する方法については、以下のURLのページを参照してください。http://www.arduino.cc/en/Guide/Troubleshooting#size
ボードArduino Unoに対するコンパイル時にエラーが発生しました。
実際、先ほどの購入ページにも書いてあります。
このモジュールは、I2Cインターフェイス経由でMCUを接続しますが、カメラを駆動するには、20000バイトを超えるRAMを備えたMCUが必要ですので、Arduino Unoのような計算能力が低い開発ボードはこのカメラでは使用できません。IRカメラからの複雑なデータを処理できて、優れたパフォーマンスがあるArch Mixをお勧めします。
ということで、いきなり M5Stack
こうなると、M5Stackで、ぶつけ本番です。(ほんとはこういう状況からスタートするとハマりやすいので避けたい)

ライブラリと一緒に使える BasicReading がちゃんと M5Stack Basic で動いたのでホッとしました。
Wiki にある Software Code 1 というのは、いくつかある判定で動作が相性良くないのか、すんなり動かなかったので BasicReading を元に、Software Code 1 の 32 x 24 = 768 マスの情報取得の部分だけ抽出して、合体させたところ、うまく動きました。
購入ページにも説明されてますが、結構なデータ量ですよね。32 x 24。
このカメラのFOV(視野)は55°x35°で、温度測定範囲は-40℃〜300℃です。32 x 24配列の熱センサー(MLX90640)が搭載されて、±1.5℃の精度で離れた場所から物体の温度を検出できます。 熱画像を簡単に取得するために、I2Cプロトコルを使用してカメラから低解像度画像を取得します。
その動作したコードが以下です。
#include <M5Stack.h>
/*
Read the temperature pixels from the MLX90640 IR array
*/
#include <Wire.h>
#include "MLX90640_API.h"
#include "MLX9064X_I2C_Driver.h"
#if defined(ARDUINO_ARCH_AVR)
#define debug Serial
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)
#define debug Serial
#else
#define debug Serial
#endif
const byte MLX90640_address = 0x33; //Default 7-bit unshifted address of the MLX90640
#define TA_SHIFT 8 //Default shift for MLX90640 in open air
static float mlx90640To[768];
paramsMLX90640 mlx90640;
void setup() {
Wire.begin();
Wire.setClock(400000); //Increase I2C clock speed to 400kHz
M5.begin();
// debug.begin(9600);
debug.begin(115200);
// START
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
// M5.Lcd.printf("START");
M5.Lcd.println("<<<< MLX90640 Sensor >>>>");
while (!debug); //Wait for user to open terminal
debug.println("MLX90640 IR Array Example");
if (isConnected() == false) {
debug.println("MLX90640 not detected at default I2C address. Please check wiring. Freezing.");
while (1);
}
debug.println("MLX90640 online!");
M5.Lcd.println("MLX90640 online!");
//Get device parameters - We only have to do this once
int status;
uint16_t eeMLX90640[832];
status = MLX90640_DumpEE(MLX90640_address, eeMLX90640);
if (status != 0) {
debug.println("Failed to load system parameters");
}
status = MLX90640_ExtractParameters(eeMLX90640, &mlx90640);
if (status != 0) {
debug.println("Parameter extraction failed");
}
//Once params are extracted, we can release eeMLX90640 array
}
void loop() {
M5.update();
// Wiki のソース取得
long startTime = millis();
for (byte x = 0 ; x < 2 ; x++) {
uint16_t mlx90640Frame[834];
int status = MLX90640_GetFrameData(MLX90640_address, mlx90640Frame);
float vdd = MLX90640_GetVdd(mlx90640Frame, &mlx90640);
float Ta = MLX90640_GetTa(mlx90640Frame, &mlx90640);
float tr = Ta - TA_SHIFT; //Reflected temperature based on the sensor ambient temperature
float emissivity = 0.95;
MLX90640_CalculateTo(mlx90640Frame, &mlx90640, emissivity, tr, mlx90640To);
}
long stopTime = millis();
int x;
for (x = 0 ; x < 768 ; x++) {
if(x % 8 == 0) debug.println();
debug.print(mlx90640To[x], 2);
debug.print(",");
}
debug.println("");
}
//Returns true if the MLX90640 is detected on the I2C bus
boolean isConnected() {
Wire.beginTransmission((uint8_t)MLX90640_address);
if (Wire.endTransmission() != 0) {
return (false); //Sensor did not ACK
}
return (true);
}
ちょっと、不必要なところや冗長なところがあるかもしれませんが、ひとまず動作出来てよかったです。

1つ1つのマス目の温度が表示されました。これだけでも楽しい。実際、手をセンサー前で動かすと 35.6 とか 33.4 みたいな温度も出ていたのでちゃんと反応しているようです。
これを、さらにオンライン上にデータを送ったり、ヒートマップに流し込んでみます!