サイトロゴ

【Arduino】非接触温度センサ(GY-906)をつかってみた

著者画像
Toshihiko Arai

こんなこと、やります。

準備

GY-906ライブラリのインストール

Arduino IDEをお使いの場合は、ライブラリマネージャで「Adafruit MLX90614」検索し 「Adafruit-MLX90614-Library」のバージョン1.1.x をインストールします。最新の2.0.0を使うと、本記事のプログラムでは動かない可能性があります。

https://github.com/adafruit/Adafruit-MLX90614-Library

Arduinoで非接触温度センサGY-906を使う

Arduinoで非接触温度センサGY-906を使ってターゲットの温度と、外気温を取得してみます。

ArduinoとGY-906の配線

ArduinoとGY-906の配線図です。

Arduinoの3.3Vをセンサモジュールへ供給し、SCLとSDAをそれぞれつなぎます。GY-906センサモジュールは、プルアップ抵抗が内蔵されているのでそのまま繋ぐことが可能です。

ソースコード(Arduino版)

こちらがGY-906を使った基本的なプログラムになります。

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MLX90614.h>

Adafruit_MLX90614 mlx = Adafruit_MLX90614();

void setup() {
  Serial.begin(9600);
  mlx.begin();  
}

void loop() {
  Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempC()); 
  Serial.print("*C\tObject = "); Serial.print(mlx.readObjectTempC()); Serial.println("*C");
  // Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempF()); 
  // Serial.print("*F\tObject = "); Serial.print(mlx.readObjectTempF()); Serial.println("*F");

  Serial.println();
  delay(1000);
}

ソースコードの解説

プログラム中のObjectが「対象物の赤外線から得た温度」です。Ambientは環境温度、つまり外気温を意味します。センサ自体が熱せられてしまうと、外気温も上がってしまうので注意しましょう。 プログラムでは摂氏温度を取得しましたが、華氏温度で取得したい場合は、readObjectTempFを使ってください。

体温を測定してみた

実際に指を近づけて体温を測定してみました。こちらの映像のとおり、かなり接近させないと正確な温度測定は難しいです。

M5StickC PLUS改良版

ここからはM5StickC PLUSを使った改良版をご紹介します。GY-906はHATモジュールにしてみました。

M5StickC PLUSとGY-906の配線

M5StickC PLUS GY-906
GND GND
3V3 VIN
G0 SDA
G26 SCL

センサをHAT化したかったので、G0、G26を使ったI2C通信を行います。

GROVEケーブルを使って、G32(SDA)、G33(SCL)に接続しても可能です。

プログラムの仕様

これからつくるプログラムは、次のとおりです。

  • M5StickC PLUSのAボタンを押して、温度測定の開始
  • 測定が完了したらビープ音を鳴らし、測定を停止する
  • 対象物の温度を100回サンプルし、メディアンフィルタで中央値を計算
  • ディスプレイに、対象物の温度と外気温を表示する

ソースコード

こちらが、M5StickC PLUS版に改良されたソースコードとなります。ただし、Filter.hは自作ライブラリですので、後述を参考に実装してください。

#include <M5StickCPlus.h>
#include <Adafruit_MLX90614.h>
#include <Wire.h>
#include "Filter.h"

Adafruit_MLX90614 mlx = Adafruit_MLX90614();
Filter filter = Filter();

#define BUTTON_A 37

bool enableMesure = true;

void measureTemp() {
    Serial.println("Starting measure temp...");
    size_t s_len = 100;
    float s[s_len];

    float ambientTemp = mlx.readAmbientTempC();

    for (size_t i = 0; i < s_len; i++) {
        s[i] = mlx.readObjectTempC();
    }

    float objectTemp = filter.medianFilter(s, s_len);

    displayLCD(ambientTemp, objectTemp);
    playBeep();
}


void setup() {
    M5.begin();

    while(!setCpuFrequencyMhz(10)){ // ←必須(CPU: 240MHz, 160, 80, 40, 20, 10)
        ;
    }
    Wire.begin(0, 26); // ←必須(G0->SDA, G26->SCL)
    mlx.begin(); 

    M5.Axp.ScreenBreath(10);
    M5.Lcd.fillScreen(WHITE);
    M5.Lcd.setTextColor(BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(5, 10);
    M5.Lcd.printf("Press the A button to start and end measurement.");

}

void loop(){
    M5.update();
    if(M5.BtnA.wasPressed()){
        Serial.println("M5.BtnA.wasPressed()");

        if (enableMesure) {
            enableMesure = false;
            measureTemp();
            enableMesure = true;
        }
    }
}



void displayLCD(float amb, float obj) {
    M5.Lcd.fillScreen(WHITE);

    M5.Lcd.setTextSize(1);
    M5.Lcd.setCursor(5, 10);
    M5.Lcd.printf("Ambient temperature", amb);

    M5.Lcd.setTextSize(3);
    M5.Lcd.setCursor(5, 25);
    M5.Lcd.printf("%.1f", amb);

    M5.Lcd.setTextSize(1);
    M5.Lcd.setCursor(5, 70);
    M5.Lcd.printf("Object temperature");

    M5.Lcd.setTextSize(5);
    M5.Lcd.setCursor(5, 85);
    M5.Lcd.printf("%.1f", obj);

}


void playBeep() {
    M5.Beep.tone(3000);
    delay(50);
    M5.Beep.mute();
    delay(50);
    M5.Beep.tone(3000);
    delay(50);
    M5.Beep.mute();
    delay(50);
}

ソースコードの注意点

setCpuFrequencyMhz(10)でCPUのクロック周波数を10MHzに指定しないと動きませんでした。 また、G0、G26をI2Cとして使う場合は、Wire.begin(0, 26, 400000);のように指定します。

メディアンフィルタの実装

先ほどのプログラム中のFilterクラスについて解説します。このクラスは自作ライブラリで、メディアンフィルタの関数が実装されてます。 ### メディアンフィルタとは メディアンフィルタとは、サンプルを昇順または降順にならべて、その中央値を採用するフィルタです。平均値と違い、大幅にズレた値などを無視できるため、画像のノイズ除去などに使われます。

並べ替えて、真ん中の値を採用する

図のように、周りの数字が1、2、3のように低いのに、明らかに不自然な7という大きなセンサノイズが入った場合を考えてみます。

メディアンフィルタでは、サンプル(周囲の値)を小さい順に並べ、真ん中の値を採用します。至ってシンプルです。 平均値の場合はノイズ値も加算されてしまいますが、メディアンフィルタですとセンサノイズをキレイにに除去できます。この利点を活かして、画像のフィルタリングなどでよく使われます。

今回の温度測定でも、手ブレなどによりセンサノイズが混入しますので、メディアンフィルタで少しでも正確な温度を測定できないか試してみました。

ソースコード

次に示すのが、メディアンフィルタを実装しているFilterクラスのソースコードです。 C++のクラスでライブラリ化してみました。Filter.hFilter.cppをそれぞれ作成し、.inoと同じ場所に保存して使ってください。

Filter.h

#ifndef Filter_h
#define Filter_h
#include <Arduino.h>

class Filter {
    public:
        Filter();
        float medianFilter(float s[], size_t len);
};
#endif

Filter.cpp

#include "Filter.h"


Filter::Filter() {
}


float Filter::medianFilter(float s[], size_t len) {
    for (size_t i=0; i<len; ++i) {
        for (size_t j=i+1; j<len; ++j) {
            if (s[i] > s[j]) {
                float tmp =  s[i];
                s[i] = s[j];
                s[j] = tmp;
            }
        }
    }

    int m = len / 2;
    return s[m];
}

C言語の配列の注意点

メディアンフィルタのライブラリをつくっている時に、大変つまづいたことがありますので述べておきます。 それは、C言語で配列を引数に渡す時のことです。

ダメな例

こちらのプログラムでは、sizeof(array)の要素数を正しく取得できません。理由は、関数の引数に渡された配列がポインタ型になるためです。

void hoge(int array[]) {
  for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i) {
     doSomething();
   }
}

sizeof(array)sizeof(int *)と同じことになってしまいます。

正しい例

こちらのように、関数の呼び出し側であらかじめ要素数を計算し、引数へ渡しましょう。

void hoge(int array[], size_t len) {
    for (size_t i = 0; i < len; i++) {
     doSomething();
  }
}

色々なものを測定してみた

HAT化した非接触温度センサとM5StickC PLUSを使って、色々なものを測定して遊んでみました。

フライパンの温度や、はんだごての温度など、直接さわれない高温なモノの温度測定も可能です。正確さはそれほどないですが、興味のある時にサクッと温度測定できるという手軽さはなかなかの魅力ではないでしょうか。

関連記事