こんにちは。ビーグルソフトの真鍋です。
前回はM5StackでDNS設定を行うということで、M5StackでWi-Fi接続を行うときにDNSだけを設定する方法をまとめました。
今日はもう少しおもろいテーマで、M5StickC PlusにAMG8833を取り付けてセンサーを表示する内容になります。具体的にはこのような内容のセンサー表示となります。

この記事で想定する読者
この内容はプログラムを書いたことがある人、IoTに関心のある人を対象としています。プログラムを書いたことがない人でも概要は把握できるように構成していますが、M5SticC Plusとセンサーを接続して活用する事を説明しています。
デバイスとセンサー
デバイスはM5StickC Plusを利用しました。SWITCH SCIENCEで購入することができます。
センサーもスイッチサイエンスさんのセンサーをGroveコネクタに接続可能なモジュールを利用しました。
それぞれこのようなデバイスになります。



M5StickC Plus(以下M5)とAMG8833(以下センサー)はGroveコネクタで接続することが可能となっています。

最初の実装(サンプルコードによるシリアルコンソールへの出力)
開発にはPlatformIOを利用しています。platformio.iniは以下のように設定しました。
[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
monitor_speed = 115200
monitor_filters = esp32_exception_decoder, time, log2file
lib_deps =
m5stack/M5StickCPlus@^0.1.1
sparkfun/SparkFun GridEYE AMG88 Library @ ^1.0.2
次に、具体的な実装ですが、SparkFun_GridEYE_Arduino_Libraryを参考に実装を行いました。この実装では、サーモカメラが取得した8×8の64セルを"○"、"0"、"O"の3種類で表示する実装となります。
この実装では、シリアルポートに出力されます。
❯ pio device monitor
--- Terminal on /dev/cu.usbserial-6D5AED0C46 | 115200 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, esp32_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
14:21:24.646 > o o o o o o o o
14:21:24.646 > o o o o o o o o
14:21:24.646 > o o o o o o o o
14:21:24.650 > o o o o o o o o
14:21:24.650 > o o o o o o o o
14:21:24.654 > o o o o o o o o
14:21:24.654 > o o o o o o o o
14:21:24.654 > o o o o o o o o
14:21:24.658 >
14:21:24.658 >
14:21:24.781 > o o o o o o o o
14:21:24.786 > o o o o o o o o
14:21:24.786 > o o o o o o o o
14:21:24.790 > o o o o o o o o
14:21:24.790 > o o o o o o o o
14:21:24.790 > o o o o o o o o
14:21:24.794 > o o o o o o o o
14:21:24.794 > o o o o o o o o
Code language: JavaScript (javascript)
ここまでで何となくどのようなデータ構造で、どのような処理が行えるかがわかりました。
LCD画面に温度を表示する
シリアルポートにデータを出力できるようになりました。しかし、M5は画面がついています。少し小さいですが、画面がついているのでこちらに温度センサの内容を表示したいものです。そこで、参考になりそうなサイトを確認しながら実装を行いました。
#include <M5StickCPlus.h>
#include <SparkFun_GridEYE_Arduino_Library.h>
#include <Wire.h>
#define WIDTH (240 / 8)
#define HEIGHT (135 / 8)
float pixelTable[64];
GridEYE grideye;
void setup() {
M5.begin();
M5.Lcd.setRotation(1);
Wire.begin();
grideye.begin();
Serial.begin(115200);
M5.Lcd.fillScreen(BLACK);
}
float gain = 10.0;
float offset_x = 0.2;
float offset_green = 0.6;
float sigmoid(float x, float g, float o) {
return (tanh((x + o) * g / 2) + 1) / 2;
}
uint16_t heat(float x) {
x = x * 2 - 1;
float r = sigmoid(x, gain, -1 * offset_x);
float b = 1.0 - sigmoid(x, gain, offset_x);
float g = sigmoid(x, gain, offset_green) + (1.0 - sigmoid(x, gain, -1 * offset_green)) - 1;
return (((int)(r * 255)>>3)<<11) | (((int)(g * 255)>>2)<<5) | ((int)(b * 255)>>3);
}
void loop() {
M5.update();
for(unsigned char i = 0; i < 64; i++){
pixelTable[i] = grideye.getPixelTemperature(i);
}
unsigned char x, y;
for (y = 0; y < 8; y++) {
for (x = 0; x < 8; x++) {
int t = pixelTable[(8 - y - 1) * 8 + 8 - x - 1];
uint16_t color = heat(map(constrain(t, 0, 40), 0, 40, 0, 100) / 100.0);
M5.Lcd.fillRect(x * WIDTH, y * HEIGHT, WIDTH, HEIGHT, color);
M5.Lcd.setCursor(x * WIDTH + WIDTH / 2, y * HEIGHT + HEIGHT / 2);
M5.Lcd.setTextColor(BLACK, color);
M5.Lcd.printf("%d", (int)t);
}
}
delay(500);
}
Code language: C++ (cpp)
この実装は先ほどのサンプルコードに加えて下記の記事を参考にしました。(サンプルにしたサイトはM5Stackを利用していたため、画面の違い等については修正しました。)
実際に動作させるとこのような感じになります。なるほど、色と数字で温度がわかる感じですね。
ところで、センサーは本体に取り付けるというよりぶら下がっているだけなのでマスキングテープで背面に貼り付けています。実際にどこかで利用するときにはケースを工夫する必要がありそうです。多分3Dプリンタとか使えるといい感じにできるのでしょうね。

LCD表示をよく見るセンサーっぽくしたい
そもそも取得できるデータは8x8の64個について温度を取得することす。このデータをサーバーに登録することが目的です。しかし、見た目をもう少し何とかできないかなと思いました。せっかくM5にはLCDがついていて、いろいろと表示を制限できるのですから、見た目も少しはそれっぽくしたいじゃないですか。
ということで、いくつか調べた上でChatGPTに相談したところ、バイリニア補間とOpenCVでよく利用する色調に変更する提案を受けました。実装自体はたいした内容ではなく、良いのではということでこちらを採用しました。
#include <M5StickCPlus.h>
#include <SparkFun_GridEYE_Arduino_Library.h>
#include <Wire.h>
float pixelTable[64];
GridEYE grideye;
void setup() {
M5.begin();
M5.Lcd.setRotation(1);
Wire.begin();
grideye.begin();
Serial.begin(115200);
M5.Lcd.fillScreen(BLACK);
}
// 色調を変更
uint16_t colormap_jet(float x) {
x = constrain(x, 0, 1);
float r = constrain(1.5f - fabs(4 * x - 3), 0, 1);
float g = constrain(1.5f - fabs(4 * x - 2), 0, 1);
float b = constrain(1.5f - fabs(4 * x - 1), 0, 1);
return (((int) (r * 255) >> 3) << 11) | (((int) (g * 255) >> 2) << 5) | ((int) (b * 255) >> 3);
}
// 温度を補正
float interpTemp(float gx, float gy) {
int x0 = floor(gx), x1 = min(x0 + 1, 7);
int y0 = floor(gy), y1 = min(y0 + 1, 7);
float dx = gx - x0, dy = gy - y0;
float t00 = pixelTable[y0 * 8 + x0];
float t10 = pixelTable[y0 * 8 + x1];
float t01 = pixelTable[y1 * 8 + x0];
float t11 = pixelTable[y1 * 8 + x1];
return t00 * (1 - dx) * (1 - dy) + t10 * dx * (1 - dy) + t01 * (1 - dx) * dy + t11 * dx * dy;
}
#define DISP_W 240
#define DISP_H 135
#define GRID_X 30
#define GRID_Y 17
#define TILE_W (DISP_W / GRID_X) // = 8
#define TILE_H (DISP_H / GRID_Y) // = 8
#define MIN_TEMP 20
#define MAX_TEMP 40
void loop() {
M5.update();
for (unsigned char i = 0; i < 64; i++) {
pixelTable[i] = grideye.getPixelTemperature(i);
}
for (int y = 0; y < GRID_Y; y++) {
for (int x = 0; x < GRID_X; x++) {
float gx = x * 7.0 / (GRID_X - 1);
float gy = y * 7.0 / (GRID_Y - 1);
float temp = interpTemp(gx, gy);
float norm = map(constrain(temp, MIN_TEMP, MAX_TEMP), MIN_TEMP, MAX_TEMP, 0, 100) / 100.0;
uint16_t color = colormap_jet(norm);
M5.Lcd.fillRect(x * TILE_W, y * TILE_H, TILE_W, TILE_H, color);
}
}
delay(1);
}
Code language: C++ (cpp)
この実装で動作させるとこのような感じになります。
どうでしょうか。温度の範囲を20度〜40度に制限していることで少しそれっぽくなったのではないでしょうか。もう少し赤みが強く出ると輪郭がくっきりするのでしょうが、いったんはそれらしく見えました。
まとめ
今回はM5とセンサーを活用してデータが取得できること、取得したデータを可視化することを行いました。両方あわせて1万円くらいでしょうか。iPhone等に接続できる製品を購入すれば、最初からきれいなセンサーデータを表示できますがデータを自分で収集することに制限があったりします。M5を活用するとこの点がクリアになり、自分でどんどん作ることができます。
何ができるかは実際試してみないとわかりません。今回も、表示に関しては対象毎にキャリブレーションが必要だということがわかりました。もっとも、生データを収集する場合にはセンサーの閾値のみが重要になるのでまた少し違ってきますね。
