【M5Stack_IoT入門】いろいろ遊んでみた♪

今回は、M5Stackのセンサー類で遊んでみたので、備忘録としてまとめます。
※将来別記事にするかもだけど、Arduino MKR WiFi 1010は、系統違いますが、センサー利用含めここへ記載します
今回、M5Stack M5Atomをたようしているので、そのピン配置を貼っておきます。
M5Atom
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/Atom?src=hash&amp;ref_src=twsrc%5Etfw">#Atom</a> liteピン <a href="https://t.co/y62q2pzHhg">pic.twitter.com/y62q2pzHhg</a></p>&mdash; ウワン (@MuAuan) <a href="https://twitter.com/MuAuan/status/1428948154657906689?ref_src=twsrc%5Etfw">August 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
M5Stack
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">M5Stack ピン <a href="https://t.co/7WBo73TOgB">pic.twitter.com/7WBo73TOgB</a></p>&mdash; ウワン (@MuAuan) <a href="https://twitter.com/MuAuan/status/1428951599393562630?ref_src=twsrc%5Etfw">August 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

###やったこと
・超音波距離センサー
・ToF距離センサー
・PIRセンサー
・IRセンサー
・ENV.III温度・湿度・気圧センサー
・THERMAL_MLX9064
・Arduino　MKR　１０１０ボード with M5CameraX
###・超音波距離センサー
ここは、参考①のとおりで動きました。
なお、参考②のM5Stackのサンプルの取得関数はちょっと誤差が大きいような気がします。
【参考】
①[M5Stack 超音波測距ユニット(U098)](https://lang-ship.com/blog/work/m5stack-ultrasonic-unit-u098/)
②[M5-ProductExampleCodes/Unit/ULTRA/Arduino/ULTRA/ULTRA.ino](https://github.com/m5stack/M5-ProductExampleCodes/blob/master/Unit/ULTRA/Arduino/ULTRA/ULTRA.ino)
M5Atomも以下で動きます。
delayをいろいろ変えてみましたが、取得時delay(10)でも割と安定して動きました。

<details><summary>コード</summary><div>

```ULTRASONIC.ino.c
//#include <M5Atom.h>
#include "Wire.h"

void setup() {
  // put your setup code here, to run once:
    //M5.begin();
    Serial.begin(115200);
    Wire.begin(26,32); //M5Atom
    //Wire.begin(32, 33); // M5StickC
}

float readEUS()
{
    uint32_t data;
    Wire.beginTransmission(0x57);
    Wire.write(0x01);
    Wire.endTransmission();
    delay(120);
    Wire.requestFrom(0x57,3);
    data  = Wire.read();data <<= 16;
    data += Wire.read();data <<= 8;
    data += Wire.read();
    return float(data) / 1000;
}

void loop() {
    float newvalue = 0;
    newvalue = readEUS();
    if(( newvalue < 1500 )&&( newvalue > 20 ))
    {
        Serial.printf("%.2fmm \n",newvalue);
    }
    delay(100);
}
```

</div></details>


###・ToF距離センサー
これも以下の参考のまんまです。M5Atomに変更しています。
【参考】
[M5Stack ToF測距センサユニット(U010)](https://lang-ship.com/blog/work/m5stack-tof-unit-u010/)
コードは以下のものですが、ライブラリ管理から
`<VL53L0X.h> // by Pololu http://librarymanager/All#vl53l0x-arduino `
をインストールします。
※このとき似たような名称のLibraryが他に二個ありますが、それではコンパイル出来ません。

<details><summary>コード</summary><div>

```ToF_VL53LOX.ino.c
#include <Wire.h>
#include <VL53L0X.h> // by Pololu http://librarymanager/All#vl53l0x-arduino https://github.com/pololu/vl53l0x-arduino
VL53L0X sensor;
void setup()
{
  Serial.begin(115200);
  Wire.begin(26,32);  //Atom  (32, 33); // M5StickC
  sensor.setAddress(0x29);
  sensor.setTimeout(500);
  if (!sensor.init()) {
    Serial.println("Failed to detect and initialize sensor!");
    while (1) {}
  }
  sensor.startContinuous();
}
void loop()
{
  Serial.print(sensor.readRangeContinuousMillimeters());
  if (sensor.timeoutOccurred()) {
    Serial.print(" TIMEOUT");
  }
  Serial.println();
}
```

</div></details>


###・PIRセンサー
リンク先の以下のコードの、ライブラリをCore2に変更して動きました。
たぶん、他でも動くと思います。ポイントはINPUTピン。
ちなみに、delay(50)でも動きましたが、不感時間は変わらないのでセンサー内処理かもです。
感知範囲が５０cmくらい、指向性もありです。
【参考】
・[M5-ProductExampleCodes/Unit/PIR/Arduino/pir/pir.ino](https://github.com/m5stack/M5-ProductExampleCodes/blob/master/Unit/PIR/Arduino/pir/pir.ino)

<details><summary>コード</summary><div>

```PIR.ino.c
//#include <M5Stack.h>
#include <M5Core2.h>

void setup() {
  M5.begin();
  //Serial.begin(115200);
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(80, 0); 
  M5.Lcd.println("PIR example");
  Serial.println("PIR example: ");
  M5.Lcd.setCursor(65, 10);
  M5.Lcd.setTextColor(WHITE);
  pinMode(33, INPUT); //36 Core
}

void loop() {
  M5.Lcd.setCursor(0,25); M5.Lcd.print("Status: ");
  M5.Lcd.setCursor(0,45); M5.Lcd.print("Value: ");
  
  M5.Lcd.fillRect(95,25,200,25,BLACK);
  M5.Lcd.fillRect(95,45,200,25,BLACK);
  
  if(digitalRead(33)==1){ //36 core
    M5.Lcd.setCursor(95, 25);M5.Lcd.print("Sensing");
    M5.Lcd.setCursor(95, 45);M5.Lcd.print("1");
    Serial.println("PIR Status: Sensing");
    Serial.println(" value: 1");
  }
  else{
    M5.Lcd.setCursor(95, 25);M5.Lcd.print("Not Sensed");
    M5.Lcd.setCursor(95, 45);M5.Lcd.print("0");
    Serial.println("PIR Status: Not Sensed");
    Serial.println(" value: 0");
  }
  delay(50);  //delay(500);
  M5.update();
}
```

</div></details>

###・IRセンサー
赤外線受発信センサーです。
TVのリコモン（発信）のセンサーですね。
これを使うとロボット間の情報共有（会話）が出来そうです。

M5Stackのセンサーの受発信のサンプルは以下のとおりです。
[M5-ProductExampleCodes/Unit/IR/Arduino/ir_dectect/ir_dectect.ino](https://github.com/m5stack/M5-ProductExampleCodes/blob/master/Unit/IR/Arduino/ir_dectect/ir_dectect.ino)
ただし、このコードを動かすと出力はスマホのカメラで見えますが、受信の信憑性がいまひとつです。

<details><summary>コード</summary><div>

```c
#include <M5Stack.h>
// select the input pin for the potentiometer
int ir_recv_pin = 36;
int ir_send_pin = 26;
int last_recv_value = 0;
int cur_recv_value = 0;

void setup() {
  M5.begin();
  pinMode(ir_recv_pin, INPUT);
  pinMode(ir_send_pin, OUTPUT);
  //send infrared light
  //now, you can see the infrared light through mobile phone camera
  digitalWrite(ir_send_pin, 1);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print("Test for IR receiver: ");
}

void loop() {
  //now, once you press the button on a remote controller to send infrared light
  //the screen will display "detected!"
  cur_recv_value = digitalRead(ir_recv_pin);
  if(last_recv_value != cur_recv_value){
    M5.Lcd.setCursor(0, 25);
    M5.Lcd.fillRect(0, 25, 150, 25, BLACK);
    if(cur_recv_value == 0){//0: detected 1: not detected
      M5.Lcd.print("detected!");
    }
    last_recv_value = cur_recv_value;
  }
  delay(100);
}
```

</div></details>

ということで、TVリモコンをターゲットに以下やってみました。

これも、以下の参考のまんまでした。
【参考】
①[M5Stack Atomで赤外線リモコンを作る（2）受信編](https://iot-gym.com/how-to-receive-ir-signals-by-using-m5atom/)
②[M5Stack Atomで赤外線リモコンを作る（3）送信編](https://iot-gym.com/how-to-send-ir-signals-by-using-m5atom/)

####受信
ライブラリ管理で、IRremoteESP8266ライブラリをインストールする。
スケッチ例のIRremoteESP8266配下にIRrecvDumpV2があるのでこれを使う。
M5Atomで動かすために、以下の変更をする。
これで、TVのリモコンをch押したときの赤外線が読めました。



```c
#include <M5Atom.h>
//#include <Arduino.h>
//#include <assert.h>
...
// ==================== start of TUNEABLE PARAMETERS ====================
// An IR detector/demodulator is connected to GPIO pin 14
// e.g. D5 on a NodeMCU board.
// Note: GPIO 16 won't work on the ESP8266 as it does not have interrupts.
const uint16_t kRecvPin = 32; //14;
...
void setup() {
  M5.begin(true, false, false);　//add
```

<details><summary>コード</summary><div>

```IRrecvDumpV2.ino.c
/*
 * IRremoteESP8266: IRrecvDumpV2 - dump details of IR codes with IRrecv
 * An IR detector/demodulator must be connected to the input kRecvPin.
 *
 * Copyright 2009 Ken Shirriff, http://arcfn.com
 * Copyright 2017-2019 David Conran
 *
 * Example circuit diagram:
 *  https://github.com/crankyoldgit/IRremoteESP8266/wiki#ir-receiving
 *
 * Changes:
 *   Version 1.2 October, 2020
 *     - Enable easy setting of the decoding tolerance value.
 *   Version 1.0 October, 2019
 *     - Internationalisation (i18n) support.
 *     - Stop displaying the legacy raw timing info.
 *   Version 0.5 June, 2019
 *     - Move A/C description to IRac.cpp.
 *   Version 0.4 July, 2018
 *     - Minor improvements and more A/C unit support.
 *   Version 0.3 November, 2017
 *     - Support for A/C decoding for some protocols.
 *   Version 0.2 April, 2017
 *     - Decode from a copy of the data so we can start capturing faster thus<details><summary>コード</summary><div>

 *       reduce the likelihood of miscaptures.
 * Based on Ken Shirriff's IrsendDemo Version 0.1 July, 2009,
 */
#include <M5Atom.h>
//#include <Arduino.h>
//#include <assert.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>

// ==================== start of TUNEABLE PARAMETERS ====================
// An IR detector/demodulator is connected to GPIO pin 14
// e.g. D5 on a NodeMCU board.
// Note: GPIO 16 won't work on the ESP8266 as it does not have interrupts.
const uint16_t kRecvPin = 32; //14;

// The Serial connection baud rate.
// i.e. Status message will be sent to the PC at this baud rate.
// Try to avoid slow speeds like 9600, as you will miss messages and
// cause other problems. 115200 (or faster) is recommended.
// NOTE: Make sure you set your Serial Monitor to the same speed.
const uint32_t kBaudRate = 115200;

// As this program is a special purpose capture/decoder, let us use a larger
// than normal buffer so we can handle Air Conditioner remote codes.
const uint16_t kCaptureBufferSize = 1024;

// kTimeout is the Nr. of milli-Seconds of no-more-data before we consider a
// message ended.
// This parameter is an interesting trade-off. The longer the timeout, the more
// complex a message it can capture. e.g. Some device protocols will send
// multiple message packets in quick succession, like Air Conditioner remotes.
// Air Coniditioner protocols often have a considerable gap (20-40+ms) between
// packets.
// The downside of a large timeout value is a lot of less complex protocols
// send multiple messages when the remote's button is held down. The gap between
// them is often also around 20+ms. This can result in the raw data be 2-3+
// times larger than needed as it has captured 2-3+ messages in a single
// capture. Setting a low timeout value can resolve this.
// So, choosing the best kTimeout value for your use particular case is
// quite nuanced. Good luck and happy hunting.
// NOTE: Don't exceed kMaxTimeoutMs. Typically 130ms.
#if DECODE_AC
// Some A/C units have gaps in their protocols of ~40ms. e.g. Kelvinator
// A value this large may swallow repeats of some protocols
const uint8_t kTimeout = 50;
#else   // DECODE_AC
// Suits most messages, while not swallowing many repeats.
const uint8_t kTimeout = 15;
#endif  // DECODE_AC
// Alternatives:
// const uint8_t kTimeout = 90;
// Suits messages with big gaps like XMP-1 & some aircon units, but can
// accidentally swallow repeated messages in the rawData[] output.
//
// const uint8_t kTimeout = kMaxTimeoutMs;
// This will set it to our currently allowed maximum.
// Values this high are problematic because it is roughly the typical boundary
// where most messages repeat.
// e.g. It will stop decoding a message and start sending it to serial at
//      precisely the time when the next message is likely to be transmitted,
//      and may miss it.

// Set the smallest sized "UNKNOWN" message packets we actually care about.
// This value helps reduce the false-positive detection rate of IR background
// noise as real messages. The chances of background IR noise getting detected
// as a message increases with the length of the kTimeout value. (See above)
// The downside of setting this message too large is you can miss some valid
// short messages for protocols that this library doesn't yet decode.
//
// Set higher if you get lots of random short UNKNOWN messages when nothing
// should be sending a message.
// Set lower if you are sure your setup is working, but it doesn't see messages
// from your device. (e.g. Other IR remotes work.)
// NOTE: Set this value very high to effectively turn off UNKNOWN detection.
const uint16_t kMinUnknownSize = 12;

// How much percentage lee way do we give to incoming signals in order to match
// it?
// e.g. +/- 25% (default) to an expected value of 500 would mean matching a
//      value between 375 & 625 inclusive.
// Note: Default is 25(%). Going to a value >= 50(%) will cause some protocols
//       to no longer match correctly. In normal situations you probably do not
//       need to adjust this value. Typically that's when the library detects
//       your remote's message some of the time, but not all of the time.
const uint8_t kTolerancePercentage = kTolerance;  // kTolerance is normally 25%

// Legacy (No longer supported!)
//
// Change to `true` if you miss/need the old "Raw Timing[]" display.
#define LEGACY_TIMING_INFO false
// ==================== end of TUNEABLE PARAMETERS ====================

// Use turn on the save buffer feature for more complete capture coverage.
IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);
decode_results results;  // Somewhere to store the results

// This section of code runs only once at start-up.
void setup() {
  M5.begin(true, false, false);
#if defined(ESP8266)
  Serial.begin(kBaudRate, SERIAL_8N1, SERIAL_TX_ONLY);
#else  // ESP8266
  Serial.begin(kBaudRate, SERIAL_8N1);
#endif  // ESP8266
  while (!Serial)  // Wait for the serial connection to be establised.
    delay(50);
  // Perform a low level sanity checks that the compiler performs bit field
  // packing as we expect and Endianness is as we expect.
  assert(irutils::lowLevelSanityCheck() == 0);

  Serial.printf("\n" D_STR_IRRECVDUMP_STARTUP "\n", kRecvPin);
#if DECODE_HASH
  // Ignore messages with less than minimum on or off pulses.
  irrecv.setUnknownThreshold(kMinUnknownSize);
#endif  // DECODE_HASH
  irrecv.setTolerance(kTolerancePercentage);  // Override the default tolerance.
  irrecv.enableIRIn();  // Start the receiver
}

// The repeating section of the code
void loop() {
  // Check if the IR code has been received.
  if (irrecv.decode(&results)) {
    // Display a crude timestamp.
    uint32_t now = millis();
    Serial.printf(D_STR_TIMESTAMP " : %06u.%03u\n", now / 1000, now % 1000);
    // Check if we got an IR message that was to big for our capture buffer.
    if (results.overflow)
      Serial.printf(D_WARN_BUFFERFULL "\n", kCaptureBufferSize);
    // Display the library version the message was captured with.
    Serial.println(D_STR_LIBRARY "   : v" _IRREMOTEESP8266_VERSION_ "\n");
    // Display the tolerance percentage if it has been change from the default.
    if (kTolerancePercentage != kTolerance)
      Serial.printf(D_STR_TOLERANCE " : %d%%\n", kTolerancePercentage);
    // Display the basic output of what we found.
    Serial.print(resultToHumanReadableBasic(&results));
    // Display any extra A/C info if we have it.
    String description = IRAcUtils::resultAcToString(&results);
    if (description.length()) Serial.println(D_STR_MESGDESC ": " + description);
    yield();  // Feed the WDT as the text output can take a while to print.
#if LEGACY_TIMING_INFO
    // Output legacy RAW timing info of the result.
    Serial.println(resultToTimingInfo(&results));
    yield();  // Feed the WDT (again)
#endif  // LEGACY_TIMING_INFO
    // Output the results as source code
    Serial.println(resultToSourceCode(&results));
    Serial.println();    // Blank line between entries
    yield();             // Feed the WDT (again)
  }
}
```

</div></details>

####発信
これは、上記の参考②のまんまで動きました。
データは、参考では、以下のコメントアウトしたrawdataを発信していますが、ここでは受信で受信したTVリモコンの１chのデータを発信してみました。
これでやると、TVのチャンネルが１chに変更できました。
ただし、地上波、衛生それぞれの１chに変更されました。
今回は試していませんが、rawdataを使えば生のデータが送れそうです。

<details><summary>コード</summary><div>

```c
#include <M5Atom.h>

#include <IRremoteESP8266.h>
#include <IRsend.h>
const uint16_t kIrLed = 26; //IR hat //12; AtomHontai // M5Atom IR LED GPIO pin
IRsend irsend(kIrLed);  // Set the GPIO to be used to sending the message.
// Example of data captured by IRrecvDumpV2.ino
/*
uint16_t rawData[67] = {9030, 4480,  588, 536,  584, 1628,  612, 536,
                        586, 536,  586, 538,  562, 564,  560, 562,
                        560, 1652,  610, 1652,  564, 560,  584, 540,
                        564, 1672,  584, 1652,  586, 1626,  610, 1628,
                        610, 1632,  606, 538,  562, 1654,  586, 560,
                        562, 1674,  586, 1652,  586, 538,  560, 562,
                        586, 538,  562, 1676,  586, 538,  566, 1672,
                        584, 540,  584, 538,  586, 1628,  610, 1626,
                        612, 1642,  588};  // NEC 419F58A7
uint64_t data = 0x419F58A7;
*/
uint16_t rawData[71] = {9030, 4488,  572, 584,  544, 586,  544, 584,  
                        544, 562,  568, 562,  568, 586,  544, 560,  568,
                        1686,  570, 584,  544, 1688,  570, 1686,  570, 
                        1686,  572, 1684,  572, 560,  568, 1686,  570, 
                        1686,  570, 1686,  572, 584,  544, 562,  568, 584,
                        544, 584,  544, 584,  544, 1684,  572, 562,  566,
                        560,  568, 1686,  570, 1686,  572, 1686,  570, 
                        1686,  570, 1686,  572, 560,  568, 1684,  572,
                        40734,  9028, 2258,  572};  // NEC 17B827D
uint32_t address = 0xDE80;
uint32_t command = 0x41;
uint64_t data = 0x17B827D;

void setup() {
  M5.begin(true, false, false);
  irsend.begin();
}
void loop() {
  M5.update();
  if (M5.Btn.wasReleased()) {
    Serial.println("Button pressed");
    
    //Serial.println("sendRaw");
    //irsend.sendRaw(rawData, 67, 38);  // Send a raw data capture at 38kHz.
    
    Serial.println("sendNEC()");
    irsend.sendNEC(data);
  }
}
```

</div></details>
###・ENV.III温度・湿度・気圧センサー
センサーと言えば定番の温度・湿度・気圧センサーを動かしてみました。
以下の参考のコードで動きました。
測定は、I2Cなので、`Wire.begin(26, 32);`のピン番号だけ気をつけてボードに合わせて変更します。ここではAtomLiteを利用しました。

【参考】
・[UNIT_ENV/examples/UNIT_ENV_III/UNIT_ENV_III.ino](https://github.com/m5stack/UNIT_ENV/blob/master/examples/UNIT_ENV_III/UNIT_ENV_III.ino)

<details><summary>コード</summary><div>

```c
#include "Wire.h"
#include "UNIT_ENV.h"

SHT3X sht30;
QMP6988 qmp6988;

char draw_string[1024];

void setup() {
  Serial.begin(115200);
  Wire.begin(26, 32);  //21, 22);
  qmp6988.init();
}

void loop() {
  if (sht30.get() != 0) {
    return;
  }
  Serial.printf("Temperatura: %2.2f*C  Humedad: %0.2f%%  Pressure: %0.2fPa\r\n", sht30.cTemp, sht30.humidity, qmp6988.calcPressure());
  delay(1000);
}
```

</div></details>

###・THERMAL_MLX9064
面で温度計測が出来るTHERMALセンサーを動かしてみました。
参考のコードがM5Stackでまんま動きました。
以下の参考から、ダウンロードしてinoを開くと問題なければ、すべてのファイルがArduinoIDEに展開されます。
特に、不満ないのでこのまま掲載させていただきます。
ただし、1点`//Serial.begin(115200);`をコメントアウトしています。
また、Core2ではエラーで動きませんでした。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">らびあんさんの真似しようと、購入したけど...<br><br>動いて、先ずはホットしました🎵<a href="https://twitter.com/hashtag/THERMAL_MLX90640?src=hash&amp;ref_src=twsrc%5Etfw">#THERMAL_MLX90640</a><a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://t.co/K5L922DZRf">pic.twitter.com/K5L922DZRf</a></p>&mdash; ウワン (@MuAuan) <a href="https://twitter.com/MuAuan/status/1428986512985640964?ref_src=twsrc%5Etfw">August 21, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
【参考】
[M5Stack/examples/Unit/THERMAL_MLX90640/](https://github.com/m5stack/M5Stack/tree/master/examples/Unit/THERMAL_MLX90640)

<details><summary>コード</summary><div>

```c
/*
    Description: Read the THERMAL Unit (MLX90640 IR array) temperature pixels and display it on the screen.
*/
#include <M5Stack.h>
//#include <M5Core2.h>
#include <Wire.h>

#include "MLX90640_API.h"
#include "MLX90640_I2C_Driver.h"

const byte MLX90640_address = 0x33; //Default 7-bit unshifted address of the MLX90640
#define TA_SHIFT 8 //Default shift for MLX90640 in open air

#define COLS 32
#define ROWS 24
#define COLS_2 (COLS * 2)
#define ROWS_2 (ROWS * 2)

float pixelsArraySize = COLS * ROWS;
float pixels[COLS * ROWS];
float pixels_2[COLS_2 * ROWS_2];
float reversePixels[COLS * ROWS];

byte speed_setting = 2 ; // High is 1 , Low is 2
bool reverseScreen = false;
//bool reverseScreen = true;

#define INTERPOLATED_COLS 32
#define INTERPOLATED_ROWS 32

static float mlx90640To[COLS * ROWS];
paramsMLX90640 mlx90640;
float signedMag12ToFloat(uint16_t val);

//low range of the sensor (this will be blue on the screen)
int MINTEMP = 24; // For color mapping
int min_v = 24; //Value of current min temp
int min_cam_v = -40; // Spec in datasheet


//high range of the sensor (this will be red on the screen)
int MAXTEMP = 35; // For color mapping
int max_v = 35; //Value of current max temp
int max_cam_v = 300; // Spec in datasheet
int resetMaxTemp = 45;

//the colors we will be using

const uint16_t camColors[] = {0x480F,
                              0x400F, 0x400F, 0x400F, 0x4010, 0x3810, 0x3810, 0x3810, 0x3810, 0x3010, 0x3010,
                              0x3010, 0x2810, 0x2810, 0x2810, 0x2810, 0x2010, 0x2010, 0x2010, 0x1810, 0x1810,
                              0x1811, 0x1811, 0x1011, 0x1011, 0x1011, 0x0811, 0x0811, 0x0811, 0x0011, 0x0011,
                              0x0011, 0x0011, 0x0011, 0x0031, 0x0031, 0x0051, 0x0072, 0x0072, 0x0092, 0x00B2,
                              0x00B2, 0x00D2, 0x00F2, 0x00F2, 0x0112, 0x0132, 0x0152, 0x0152, 0x0172, 0x0192,
                              0x0192, 0x01B2, 0x01D2, 0x01F3, 0x01F3, 0x0213, 0x0233, 0x0253, 0x0253, 0x0273,
                              0x0293, 0x02B3, 0x02D3, 0x02D3, 0x02F3, 0x0313, 0x0333, 0x0333, 0x0353, 0x0373,
                              0x0394, 0x03B4, 0x03D4, 0x03D4, 0x03F4, 0x0414, 0x0434, 0x0454, 0x0474, 0x0474,
                              0x0494, 0x04B4, 0x04D4, 0x04F4, 0x0514, 0x0534, 0x0534, 0x0554, 0x0554, 0x0574,
                              0x0574, 0x0573, 0x0573, 0x0573, 0x0572, 0x0572, 0x0572, 0x0571, 0x0591, 0x0591,
                              0x0590, 0x0590, 0x058F, 0x058F, 0x058F, 0x058E, 0x05AE, 0x05AE, 0x05AD, 0x05AD,
                              0x05AD, 0x05AC, 0x05AC, 0x05AB, 0x05CB, 0x05CB, 0x05CA, 0x05CA, 0x05CA, 0x05C9,
                              0x05C9, 0x05C8, 0x05E8, 0x05E8, 0x05E7, 0x05E7, 0x05E6, 0x05E6, 0x05E6, 0x05E5,
                              0x05E5, 0x0604, 0x0604, 0x0604, 0x0603, 0x0603, 0x0602, 0x0602, 0x0601, 0x0621,
                              0x0621, 0x0620, 0x0620, 0x0620, 0x0620, 0x0E20, 0x0E20, 0x0E40, 0x1640, 0x1640,
                              0x1E40, 0x1E40, 0x2640, 0x2640, 0x2E40, 0x2E60, 0x3660, 0x3660, 0x3E60, 0x3E60,
                              0x3E60, 0x4660, 0x4660, 0x4E60, 0x4E80, 0x5680, 0x5680, 0x5E80, 0x5E80, 0x6680,
                              0x6680, 0x6E80, 0x6EA0, 0x76A0, 0x76A0, 0x7EA0, 0x7EA0, 0x86A0, 0x86A0, 0x8EA0,
                              0x8EC0, 0x96C0, 0x96C0, 0x9EC0, 0x9EC0, 0xA6C0, 0xAEC0, 0xAEC0, 0xB6E0, 0xB6E0,
                              0xBEE0, 0xBEE0, 0xC6E0, 0xC6E0, 0xCEE0, 0xCEE0, 0xD6E0, 0xD700, 0xDF00, 0xDEE0,
                              0xDEC0, 0xDEA0, 0xDE80, 0xDE80, 0xE660, 0xE640, 0xE620, 0xE600, 0xE5E0, 0xE5C0,
                              0xE5A0, 0xE580, 0xE560, 0xE540, 0xE520, 0xE500, 0xE4E0, 0xE4C0, 0xE4A0, 0xE480,
                              0xE460, 0xEC40, 0xEC20, 0xEC00, 0xEBE0, 0xEBC0, 0xEBA0, 0xEB80, 0xEB60, 0xEB40,
                              0xEB20, 0xEB00, 0xEAE0, 0xEAC0, 0xEAA0, 0xEA80, 0xEA60, 0xEA40, 0xF220, 0xF200,
                              0xF1E0, 0xF1C0, 0xF1A0, 0xF180, 0xF160, 0xF140, 0xF100, 0xF0E0, 0xF0C0, 0xF0A0,
                              0xF080, 0xF060, 0xF040, 0xF020, 0xF800,
                             };



float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f);
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
float cubicInterpolate(float p[], float x);
float bicubicInterpolate(float p[], float x, float y);
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols, float *dest, uint8_t dest_rows, uint8_t dest_cols);

long loopTime, startTime, endTime, fps;

void setup()
{
  M5.begin();
  M5.Power.begin();
  Wire.begin();
  Wire.setClock(450000); //Increase I2C clock speed to 400kHz
  //Serial.begin(115200);
  M5.Lcd.begin();
  M5.Lcd.setRotation(1);

  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(YELLOW, BLACK);

  while (!Serial); //Wait for user to open terminal
  Serial.println("M5Stack MLX90640 IR Camera");
  M5.Lcd.setTextSize(2);

  //Get device parameters - We only have to do this once
  int status;
  uint16_t eeMLX90640[832];//32 * 24 = 768
  status = MLX90640_DumpEE(MLX90640_address, eeMLX90640);
  if (status != 0)
    Serial.println("Failed to load system parameters");

  status = MLX90640_ExtractParameters(eeMLX90640, &mlx90640);
  if (status != 0)
    Serial.println("Parameter extraction failed");

  int SetRefreshRate;
  //Setting MLX90640 device at slave address 0x33 to work with 16Hz refresh rate:
  // 0x00 – 0.5Hz
  // 0x01 – 1Hz
  // 0x02 – 2Hz
  // 0x03 – 4Hz
  // 0x04 – 8Hz // OK 
  // 0x05 – 16Hz // OK
  // 0x06 – 32Hz // Fail
  // 0x07 – 64Hz
  SetRefreshRate = MLX90640_SetRefreshRate (0x33, 0x05);
  //Once params are extracted, we can release eeMLX90640 array

  //Display bottom side colorList and info
  M5.Lcd.fillScreen(TFT_BLACK);
  int icolor = 0;
  for (int icol = 0; icol <= 248;  icol++)
  {
    //彩色条
    M5.Lcd.drawRect(36, 208, icol, 284 , camColors[icolor]);
    icolor++;
  }
  infodisplay();
}


void loop(){

  loopTime = millis();
  startTime = loopTime;
  ///////////////////////////////
  // Set Min Value - LongPress //
  ///////////////////////////////
  if (M5.BtnA.pressedFor(1000)) {
    if (MINTEMP <= 5 )
    {
      MINTEMP = MAXTEMP - 5;
    }
    else
    {
      MINTEMP = MINTEMP - 5;
    }
    infodisplay();
  }

  ///////////////////////////////
  // Set Min Value - SortPress //
  ///////////////////////////////
  if (M5.BtnA.wasPressed()) {
    if (MINTEMP <= 0)
    {
      MINTEMP = MAXTEMP - 1;
    }
    else
    {
      MINTEMP--;
    }
    infodisplay();
  }

  /////////////////////
  // Reset settings  //
  /////////////////////
  if (M5.BtnB.wasPressed()) {
    MINTEMP = min_v - 1;
    MAXTEMP = max_v + 1;
    infodisplay();
  }

  ////////////////
  // Power Off  //
  ////////////////
  if (M5.BtnB.pressedFor(1000)) {
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(YELLOW, BLACK);
    M5.Lcd.drawCentreString("Power Off...", 160, 80, 4);
    delay(1000);
    M5.powerOFF();
  }

  ///////////////////////////////
  // Set Max Value - LongPress //
  ///////////////////////////////
  if (M5.BtnC.pressedFor(1000)) {
    if (MAXTEMP >= max_cam_v)
    {
      MAXTEMP = MINTEMP + 1;
    }
    else
    {
      MAXTEMP = MAXTEMP + 5;
    }
    infodisplay();
  }

  ///////////////////////////////
  // Set Max Value - SortPress //
  ///////////////////////////////
  if (M5.BtnC.wasPressed()) {
    if (MAXTEMP >= max_cam_v )
    {
      MAXTEMP = MINTEMP + 1;
    }
    else
    {
      MAXTEMP++;
    }
    infodisplay();
  }

  M5.update();

  for (byte x = 0 ; x < speed_setting ; x++) // x < 2 Read both subpages
  {
    uint16_t mlx90640Frame[834];
    int status = MLX90640_GetFrameData(MLX90640_address, mlx90640Frame);
    if (status < 0)
    {
      Serial.print("GetFrame Error: ");
      Serial.println(status);
    }

    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, pixels); //save pixels temp to array (pixels)
	int mode_ = MLX90640_GetCurMode(MLX90640_address);
    //amendment
    MLX90640_BadPixelsCorrection((&mlx90640)->brokenPixels, pixels, mode_, &mlx90640);
    //MLX90640_BadPixelsCorrection((&mlx90640)->outlierPixels, pixels, mode_, &mlx90640);
  }

  //Reverse image (order of Integer array)
  if (reverseScreen == 1)
  {
    for (int x = 0 ; x < pixelsArraySize ; x++)
    {
      if (x % COLS == 0) //32 values wide
      {
        for (int j = 0 + x, k = (COLS-1) + x; j < COLS + x ; j++, k--)
        {
          reversePixels[j] = pixels[k];
  //         Serial.print(x);Serial.print(" = Rev "); Serial.print(j);Serial.print(" ,  Nor ");Serial.println(k);
        }
      }
    }
  }

  float dest_2d[INTERPOLATED_ROWS * INTERPOLATED_COLS];
  int ROWS_i,COLS_j;

  if (reverseScreen == 1)
  {
    // ** reversePixels
    interpolate_image(reversePixels, ROWS, COLS, dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS);
  }
  else
  {

    interpolate_image(pixels, ROWS, COLS, dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS);
    // 32 * 24 = 768
    // 63 * 48 = 3072
    //pixels_2
    for(int y = 0;y < ROWS;y++)
    {
      for(int x = 0;x < COLS;x++)
      {
        // 原始数据
        pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] = pixels[y*COLS+x];
        
        if(x != 31)
          pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+1)] = ( pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] + pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+2)]) / 2;
        else
          pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+1)] = ( pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] );
          
        //Serial.print(pixels_2[(((y * 2) * (COLS*2)) + (x * 2))]);
        //Serial.print(pixels[y*COLS+x]);
        //Serial.print(" ");
      }
      //Serial.println("\r\n");
    }
    /*
   //-------------------------
    // 计算x间隔插入数据
    for(int y = 0;y < ROWS;y++)//24
    {
      for(int x = 0;x < COLS;x++)//32
      {
        if(x != 31)
          pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+1)] = ( pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] + pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+2)]) / 2;
        else
          pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+1)] = ( pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] );
      }
    }
    */
    ///*
    // 计算y间隔插入数据 
    for(int y = 0;y < ROWS;y++)//24
    {
      for(int x = 0;x < COLS_2;x++)//64
      {
        if(y != 23)
          pixels_2[(((y * 2) + 1) * (COLS_2)) + x] = ( pixels_2[(((y * 2) * COLS_2) + x)] + pixels_2[((((y * 2) + 2) * COLS_2) + x)] ) / 2;
        else
          pixels_2[(((y * 2) + 1) * (COLS_2)) + x] = ( pixels_2[(((y * 2) * COLS_2) + x)] + pixels_2[(((y * 2) * COLS_2) + x)] ) / 2;
      }
    }   
    //*/
/*
       //打印数据
    for(int y = 0;y < ROWS_2;y++)
    {
      for(int x = 0;x < COLS_2;x++)
      {
        Serial.print(pixels_2[y * COLS_2 + x]);
        Serial.print(" ");
      }
      Serial.println("\r\n");
    }
    //-------------------------
   // */ 
  }

  uint16_t boxsize = min(M5.Lcd.width() / INTERPOLATED_ROWS, M5.Lcd.height() / INTERPOLATED_COLS);
  uint16_t boxWidth = M5.Lcd.width() / INTERPOLATED_ROWS;
  //uint16_t boxWidth = 192 / INTERPOLATED_ROWS;
  uint16_t boxHeight = (M5.Lcd.height() - 31) / INTERPOLATED_COLS; // 31 for bottom info
  //drawpixels(pixels, 24, INTERPOLATED_COLS, 8, 8, false);
  //drawpixels(pixels_2, 48, 64, 5, 5, false);
  drawpixels(dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS, boxWidth, boxHeight, false);
  max_v = MINTEMP;
  min_v = MAXTEMP;
  int spot_v = pixels[360];
  spot_v = pixels[768/2];
//while(1);
  
   for ( int itemp = 0; itemp < sizeof(pixels) / sizeof(pixels[0]); itemp++ )
  {
    if ( pixels[itemp] > max_v )
    {
      max_v = pixels[itemp];
    }
    if ( pixels[itemp] < min_v )
    {
      min_v = pixels[itemp];
    }
  }


  M5.Lcd.setTextSize(2);
  M5.Lcd.fillRect(164, 220, 75, 18, TFT_BLACK);  // clear max temp text
  M5.Lcd.fillRect(60, 220, 200, 18, TFT_BLACK); // clear spot temp text
    int icolor = 0;
  //for (int icol = 0; icol <= 248;  icol++)
  //{
   // M5.Lcd.drawRect(36, 208, icol, 284 , camColors[icolor]);
   // icolor++;
  //}

  M5.Lcd.setCursor(60, 222);      // update min & max temp
  M5.Lcd.setTextColor(TFT_WHITE);

  if (max_v > max_cam_v | max_v < min_cam_v ) {
    M5.Lcd.setTextColor(TFT_RED);
    M5.Lcd.printf("Error", 1);
  }
  else
  {
    M5.Lcd.print("Min:");
    M5.Lcd.print(min_v, 1);
    M5.Lcd.print("C  ");
    M5.Lcd.print("Max:");
    M5.Lcd.print(max_v, 1);
    M5.Lcd.print("C");
    M5.Lcd.setCursor(180, 94); // update spot temp text
    M5.Lcd.print(spot_v, 1);
    M5.Lcd.printf("C");
    //M5.Lcd.drawCircle(160, 100, 6, TFT_WHITE);     // update center spot icon
    //M5.Lcd.drawLine(160, 90, 160, 110, TFT_WHITE); // vertical line
    //M5.Lcd.drawLine(150, 100, 170, 100, TFT_WHITE); // horizontal line
    M5.Lcd.drawCircle(160, 120, 6, TFT_WHITE);     // update center spot icon
    M5.Lcd.drawLine(160, 110, 160, 130, TFT_WHITE); // vertical line
    M5.Lcd.drawLine(150, 120, 170, 120, TFT_WHITE); // horizontal line
  }
  loopTime = millis();
  endTime = loopTime;
  fps = 1000 / (endTime - startTime);
  //M5.Lcd.fillRect(310, 209, 10, 12, TFT_BLACK); //Clear fps text area
  M5.Lcd.fillRect(300, 209, 20, 12, TFT_BLACK); //Clear fps text area
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(284, 210);
  M5.Lcd.print("fps:" + String( fps ));
  M5.Lcd.setTextSize(1);

}


/***infodisplay()*****/
void infodisplay(void) {
  M5.Lcd.fillRect(0, 198, 320, 4, TFT_WHITE);
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.fillRect(284, 223, 320, 240, TFT_BLACK); //Clear MaxTemp area
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(284, 222); //move to bottom right
  M5.Lcd.print(MAXTEMP , 1);  // update MAXTEMP
  M5.Lcd.print("C");
  M5.Lcd.setCursor(0, 222);  // update MINTEMP text
  M5.Lcd.fillRect(0, 222, 36, 16, TFT_BLACK);
  M5.Lcd.print(MINTEMP , 1);
  M5.Lcd.print("C");</div></details>

  M5.Lcd.setCursor(106, 224);
}

void drawpixels(float *p, uint8_t rows, uint8_t cols, uint8_t boxWidth, uint8_t boxHeight, boolean showVal) {
  int colorTemp;
  for (int y = 0; y < rows; y++) 
  {
    for (int x = 0; x < cols; x++) 
    {
      float val = get_point(p, rows, cols, x, y);
      
      if (val >= MAXTEMP) 
        colorTemp = MAXTEMP;
      else if (val <= MINTEMP) 
        colorTemp = MINTEMP;
      else colorTemp = val;

      uint8_t colorIndex = map(colorTemp, MINTEMP, MAXTEMP, 0, 255);
      colorIndex = constrain(colorIndex, 0, 255);// 0 ~ 255
      //draw the pixels!
      uint16_t color;
      color = val * 2;
      M5.Lcd.fillRect(boxWidth * x, boxHeight * y, boxWidth, boxHeight, camColors[colorIndex]);
    }
  }
}

//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);
}
```
</div></details>

###・Arduino　MKR　１０１０ボード with M5CameraX

<details><summary>コード</summary><div>

```c
/*
  WiFi Web Server LED Blink

 A simple web server that lets you blink an LED via the web.
 This sketch will print the IP address of your WiFi module (once connected)
 to the Serial Monitor. From there, you can open that address in a web browser
 to turn on and off the LED on pin 9.

 If the IP address of your board is yourAddress:
 http://yourAddress/H turns the LED on
 http://yourAddress/L turns it off

 This example is written for a network using WPA encryption. For
 WEP or WPA, change the WiFi.begin() call accordingly.

 Circuit:
 * Board with NINA module (Arduino MKR WiFi 1010, MKR VIDOR 4000 and UNO WiFi Rev.2)
 * LED attached to pin 9

 created 25 Nov 2012
 by Tom Igoe
 */
#include <SPI.h>
#include <WiFiNINA.h>

#include "arduino_secrets.h" 
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID;        // your network SSID (name)
char pass[] = SECRET_PASS;    // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;                 // your network key index number (needed only for WEP)

int status = WL_IDLE_STATUS;
WiFiServer server(80);

void setup() {
  Serial.begin(9600);      // initialize serial communication
  pinMode(9, OUTPUT);      // set the LED pin mode

  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true);
  }

  String fv = WiFi.firmwareVersion();
  if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    Serial.println("Please upgrade the firmware");
  }

  // attempt to connect to WiFi network:
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to Network named: ");
    Serial.println(ssid);                   // print the network name (SSID);

    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);
    // wait 10 seconds for connection:
    delay(10000);
  }
  server.begin();                           // start the web server on port 80
  printWifiStatus();                        // you're connected now, so print out the status
}


void loop() {
  WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("new client");           // print a message out the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        if (c == '\n') {                    // if the byte is a newline character

          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            // the content of the HTTP response follows the header:
            client.print("Click <a href=\"/H\">here</a> turn the LED on pin 9 on<br>");
            client.print("Click <a href=\"/L\">here</a> turn the LED on pin 9 off<br>");

            // The HTTP response ends with another blank line:
            client.println();
            // break out of the while loop:
            break;
          } else {    // if you got a newline, then clear currentLine:
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }

        // Check to see if the client request was "GET /H" or "GET /L":
        if (currentLine.endsWith("GET /H")) {
          digitalWrite(LED_BUILTIN, HIGH);               // GET /H turns the LED on
        }
        if (currentLine.endsWith("GET /L")) {
          digitalWrite(LED_BUILTIN, LOW);                // GET /L turns the LED off
        }
      }
    }
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
  // print where to go in a browser:
  Serial.print("To see this page in action, open a browser to http://");
  Serial.println(ip);
}
```

</div></details>