M5StickC
前回、M5StickCのスケッチ例IMUを見て、M5.IMU.Init、M5.IMU.getGyroData、M5.IMU.getAccelData、M5.IMU.getAhrsData、M5.IMU.getTempDataを学びました。
今回は内蔵ではなく外付けのセンサとしてENV II HATをArduino言語で使う方法を勉強していきましょう。
このENV II HATですが、環境測定用のセンサとして温度、湿度、気圧、磁気を測定することができます。
範囲 | 誤差(磁気は分解能) | |
温度 | -40〜120℃ | ±0.2℃(0〜60℃の範囲) |
湿度 | 10〜90%RH | ±2% |
気圧 | 300〜1100hPa | ±1hPa |
磁気 | ±1300 μT(x、y軸)、±2500 μT(z軸) | 0.3 μT |
ただ残念ながらENV II HAT用のスケッチ例はArduinoのソフトウェア上にはないので、GitHubからダウンロードしましょう。
上記のWebページにアクセスして、「Code」をクリック。
「Download ZIP」をクリック。
すると「M5-ProductExampleCodes-master.zip」というファイルがダウンロードされます。
このファイルをダブルクリックで展開します。
「Hat > ENVII_HAT > Arduino > ENVII_HAT」に進みます。
「ENVII_HAT.ino」がENV II HATのスケッチ例です。
また他のファイルはヘッダーファイルなので、もし他の場所でENV II HATを使用したプログラムを作成する場合は、コピーする必要があるのに注意しましょう。
プログラム全体
まずはプログラム全体を見てみましょう。
ちなみに「ENVII_HAT.ino」以外のファイルは基本的にはいじることはないと思いますので、今回は省略します。
/*
note: need add library Adafruit_BMP280 from library manage
Github: https://github.com/adafruit/Adafruit_BMP280_Library
*/
#include <M5StickC.h>
#include "SHT3X.h"
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#include "bmm150.h"
#include "bmm150_defs.h"
SHT3X sht3x;
BMM150 bmm = BMM150();
bmm150_mag_data value_offset;
Adafruit_BMP280 bme;
float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;
void calibrate(uint32_t timeout)
{
int16_t value_x_min = 0;
int16_t value_x_max = 0;
int16_t value_y_min = 0;
int16_t value_y_max = 0;
int16_t value_z_min = 0;
int16_t value_z_max = 0;
uint32_t timeStart = 0;
bmm.read_mag_data();
value_x_min = bmm.raw_mag_data.raw_datax;
value_x_max = bmm.raw_mag_data.raw_datax;
value_y_min = bmm.raw_mag_data.raw_datay;
value_y_max = bmm.raw_mag_data.raw_datay;
value_z_min = bmm.raw_mag_data.raw_dataz;
value_z_max = bmm.raw_mag_data.raw_dataz;
delay(100);
timeStart = millis();
while((millis() - timeStart) < timeout)
{
bmm.read_mag_data();
/* Update x-Axis max/min value */
if(value_x_min > bmm.raw_mag_data.raw_datax)
{
value_x_min = bmm.raw_mag_data.raw_datax;
// Serial.print("Update value_x_min: ");
// Serial.println(value_x_min);
}
else if(value_x_max < bmm.raw_mag_data.raw_datax)
{
value_x_max = bmm.raw_mag_data.raw_datax;
// Serial.print("update value_x_max: ");
// Serial.println(value_x_max);
}
/* Update y-Axis max/min value */
if(value_y_min > bmm.raw_mag_data.raw_datay)
{
value_y_min = bmm.raw_mag_data.raw_datay;
// Serial.print("Update value_y_min: ");
// Serial.println(value_y_min);
}
else if(value_y_max < bmm.raw_mag_data.raw_datay)
{
value_y_max = bmm.raw_mag_data.raw_datay;
// Serial.print("update value_y_max: ");
// Serial.println(value_y_max);
}
/* Update z-Axis max/min value */
if(value_z_min > bmm.raw_mag_data.raw_dataz)
{
value_z_min = bmm.raw_mag_data.raw_dataz;
// Serial.print("Update value_z_min: ");
// Serial.println(value_z_min);
}
else if(value_z_max < bmm.raw_mag_data.raw_dataz)
{
value_z_max = bmm.raw_mag_data.raw_dataz;
// Serial.print("update value_z_max: ");
// Serial.println(value_z_max);
}
Serial.print(".");
delay(1);
}
value_offset.x = value_x_min + (value_x_max - value_x_min)/2;
value_offset.y = value_y_min + (value_y_max - value_y_min)/2;
value_offset.z = value_z_min + (value_z_max - value_z_min)/2;
}
void setup() {
// put your setup code here, to run once:
M5.begin();
Wire.begin(0,26);
M5.Lcd.setRotation(3);
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0, 2);
M5.Lcd.println("ENV TEST");
pinMode(M5_BUTTON_HOME, INPUT);
if(bmm.initialize() == BMM150_E_ID_NOT_CONFORM) {
Serial.println("Chip ID can not read!");
while(1);
} else {
Serial.println("Initialize done!");
}
if (!bme.begin(0x76)){
Serial.println("Could not find a valid BMP280 sensor, check wiring!");
while (1);
}
calibrate(10);
Serial.print("\n\rCalibrate done..");
}
uint8_t setup_flag = 1;
void loop() {
// put your main code here, to run repeatedly:
if(sht3x.get()==0){
tmp = sht3x.cTemp;
hum = sht3x.humidity;
}
M5.Lcd.setCursor(0, 20, 2);
M5.Lcd.printf("Temp: %2.1f Humi: %2.0f", tmp, hum);
bmm150_mag_data value;
bmm.read_mag_data();
value.x = bmm.raw_mag_data.raw_datax - value_offset.x;
value.y = bmm.raw_mag_data.raw_datay - value_offset.y;
value.z = bmm.raw_mag_data.raw_dataz - value_offset.z;
float xyHeading = atan2(value.x, value.y);
float zxHeading = atan2(value.z, value.x);
float heading = xyHeading;
if(heading < 0)
heading += 2*PI;
if(heading > 2*PI)
heading -= 2*PI;
float headingDegrees = heading * 180/M_PI;
float xyHeadingDegrees = xyHeading * 180 / M_PI;
float zxHeadingDegrees = zxHeading * 180 / M_PI;
Serial.print("Heading: ");
Serial.println(headingDegrees);
Serial.print("xyHeadingDegrees: ");
Serial.println(xyHeadingDegrees);
Serial.print("zxHeadingDegrees: ");
Serial.println(zxHeadingDegrees);
M5.Lcd.setCursor(0, 40, 2);
M5.Lcd.printf("headingDegrees: %2.1f", headingDegrees);
float pressure = bme.readPressure();
M5.Lcd.setCursor(0, 60, 2);
M5.Lcd.printf("pressure: %2.1f", pressure);
delay(100);
if(!setup_flag){
setup_flag = 1;
if(bmm.initialize() == BMM150_E_ID_NOT_CONFORM) {
Serial.println("Chip ID can not read!");
while(1);
} else {
Serial.println("Initialize done!");
}
if (!bme.begin(0x76)){
Serial.println("Could not find a valid BMP280 sensor, check wiring!");
while (1);
}
calibrate(10);
Serial.print("\n\rCalibrate done..");
}
if(digitalRead(M5_BUTTON_HOME) == LOW){
setup_flag = 0;
while(digitalRead(M5_BUTTON_HOME) == LOW);
}
}
これをM5StickCで実行するとこうなります。
それでは順に見ていきましょう。
ヘッダーファイルの読み込みと値の初期化
まずはヘッダーファイルの読み込み部分を見ていきます。
#include <M5StickC.h>
#include "SHT3X.h"
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#include "bmm150.h"
#include "bmm150_defs.h"
ここで気になる表記が出てきます。
いくつかのヘッダーファイルは「< >(山括弧)」で括られていますが、「” “(ダブルクォーテーション)」で括られているものもあります。
ここで「ENVII_HAT.ino」と同じフォルダ内にあるファイルを確認してみましょう。
「Adafruit_Sensor.h」、「bmm150_defs.h」、「bmm150.cpp」、「bmm150.h」、「SHT3X.cpp」、「SHT3X.h」の5つのファイルが同じフォルダにあります。
そして先ほどヘッダーファイルの読み込み部で「” “(ダブルクォーテーション)」で括られているものは、「”SHT3X.h”」、「”Adafruit_Sensor.h”」、「”bmm150.h”」、「”bmm150_defs.h”」です。
つまり同じフォルダ内にあるヘッダーファイルは「” “(ダブルクォーテーション)」で括るわけです。
そして「< >(山括弧)」で括られているものは、「<M5StickC.h>」、「<Wire.h>」、「<Adafruit_BMP280.h>」で、これらは同じフォルダ内ではなくシステム内に存在するヘッダーファイルです。
ということで同じフォルダ内にあるヘッダーファイルは「” “(ダブルクォーテーション)」、システム上に存在するヘッダーファイルは「< >(山括弧)」で括るということらしいです。
そして次の部分は正直よく分かっていませんが、各センサの読み込みをしているのだと思われます。
SHT3X sht3x;
BMM150 bmm = BMM150();
bmm150_mag_data value_offset;
Adafruit_BMP280 bme;
次に温度、湿度、気圧の数値の定義をしています。
float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;
Calibrate関数
ここから各関数を見ていきますが、今までと違って「setup」と「loop」だけではありません。
最初にあるものは「calibrate」という関数です。
void calibrate(uint32_t timeout)
{
int16_t value_x_min = 0;
int16_t value_x_max = 0;
int16_t value_y_min = 0;
int16_t value_y_max = 0;
int16_t value_z_min = 0;
int16_t value_z_max = 0;
uint32_t timeStart = 0;
bmm.read_mag_data();
value_x_min = bmm.raw_mag_data.raw_datax;
value_x_max = bmm.raw_mag_data.raw_datax;
value_y_min = bmm.raw_mag_data.raw_datay;
value_y_max = bmm.raw_mag_data.raw_datay;
value_z_min = bmm.raw_mag_data.raw_dataz;
value_z_max = bmm.raw_mag_data.raw_dataz;
delay(100);
timeStart = millis();
while((millis() - timeStart) < timeout)
{
bmm.read_mag_data();
if(value_x_min > bmm.raw_mag_data.raw_datax)
{
value_x_min = bmm.raw_mag_data.raw_datax;
}
else if(value_x_max < bmm.raw_mag_data.raw_datax)
{
value_x_max = bmm.raw_mag_data.raw_datax;
}
if(value_y_min > bmm.raw_mag_data.raw_datay)
{
value_y_min = bmm.raw_mag_data.raw_datay;
}
else if(value_y_max < bmm.raw_mag_data.raw_datay)
{
value_y_max = bmm.raw_mag_data.raw_datay;
}
if(value_z_min > bmm.raw_mag_data.raw_dataz)
{
value_z_min = bmm.raw_mag_data.raw_dataz;
}
else if(value_z_max < bmm.raw_mag_data.raw_dataz)
{
value_z_max = bmm.raw_mag_data.raw_dataz;
}
Serial.print(".");
delay(1);
}
value_offset.x = value_x_min + (value_x_max - value_x_min)/2;
value_offset.y = value_y_min + (value_y_max - value_y_min)/2;
value_offset.z = value_z_min + (value_z_max - value_z_min)/2;
}
こちらの関数なのですが、多分各センサのキャリブレーションをするための関数だと思われます。
そのため、この部分もいじることはないだろうということで、今回の説明は割愛します。
setup関数
それでは「setup関数」を見ていきましょう。
void setup() {
M5.begin();
Wire.begin(0,26);
M5.Lcd.setRotation(3);
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0, 2);
M5.Lcd.println("ENV TEST");
pinMode(M5_BUTTON_HOME, INPUT);
if(bmm.initialize() == BMM150_E_ID_NOT_CONFORM) {
Serial.println("Chip ID can not read!");
while(1);
} else {
Serial.println("Initialize done!");
}
if (!bme.begin(0x76)){
Serial.println("Could not find a valid BMP280 sensor, check wiring!");
while (1);
}
calibrate(10);
Serial.print("\n\rCalibrate done..");
}
uint8_t setup_flag = 1;
これまでに出てきていないコマンドとして、まずは「Wire.begin(0,26);」があります。
これはM5StickCとセンサユニットをI2C通信するためのコマンドです。
私自身がI2C通信に詳しくないのですが、M5StickCとセンサ間でデータをやり取りするためのものだと認識しておけばいいのかなと思います。
次の「pinMode(M5_BUTTON_HOME, INPUT)」はよくわかりません。
「bmm.initialize() == BMM150_E_ID_NOT_CONFORM」と「!bme.begin(0x76)」が条件分岐で使われていますが、これらは続くプログラムを見てみると、どうやらENV II HATが接続されているかどうかのチェックのようです。
「Serial.print」は出力のようですが、こちらはまた別の機会に勉強していこうと思います。
正直このsetup関数もよく分かっていませんが、起動時にHATの接続チェックやセンサのキャリブレーションということで、そのまま使用すればいいのかなと思います。
loop関数
最後にloop関数です。
void loop() {
if(sht3x.get()==0){
tmp = sht3x.cTemp;
hum = sht3x.humidity;
}
M5.Lcd.setCursor(0, 20, 2);
M5.Lcd.printf("Temp: %2.1f Humi: %2.0f", tmp, hum);
bmm150_mag_data value;
bmm.read_mag_data();
value.x = bmm.raw_mag_data.raw_datax - value_offset.x;
value.y = bmm.raw_mag_data.raw_datay - value_offset.y;
value.z = bmm.raw_mag_data.raw_dataz - value_offset.z;
float xyHeading = atan2(value.x, value.y);
float zxHeading = atan2(value.z, value.x);
float heading = xyHeading;
if(heading < 0)
heading += 2*PI;
if(heading > 2*PI)
heading -= 2*PI;
float headingDegrees = heading * 180/M_PI;
float xyHeadingDegrees = xyHeading * 180 / M_PI;
float zxHeadingDegrees = zxHeading * 180 / M_PI;
Serial.print("Heading: ");
Serial.println(headingDegrees);
Serial.print("xyHeadingDegrees: ");
Serial.println(xyHeadingDegrees);
Serial.print("zxHeadingDegrees: ");
Serial.println(zxHeadingDegrees);
M5.Lcd.setCursor(0, 40, 2);
M5.Lcd.printf("headingDegrees: %2.1f", headingDegrees);
float pressure = bme.readPressure();
M5.Lcd.setCursor(0, 60, 2);
M5.Lcd.printf("pressure: %2.1f", pressure);
delay(100);
if(!setup_flag){
setup_flag = 1;
if(bmm.initialize() == BMM150_E_ID_NOT_CONFORM) {
Serial.println("Chip ID can not read!");
while(1);
} else {
Serial.println("Initialize done!");
}
if (!bme.begin(0x76)){
Serial.println("Could not find a valid BMP280 sensor, check wiring!");
while (1);
}
calibrate(10);
Serial.print("\n\rCalibrate done..");
}
if(digitalRead(M5_BUTTON_HOME) == LOW){
setup_flag = 0;
while(digitalRead(M5_BUTTON_HOME) == LOW);
}
}
こちらも細かく見ていこうとすると途端に理解できなくなるので、塊で大体の使い方を見ていきましょう。
温度、湿度に関してはこのように取得・表示しています。
if(sht3x.get()==0){
tmp = sht3x.cTemp;
hum = sht3x.humidity;
}
M5.Lcd.setCursor(0, 20, 2);
M5.Lcd.printf("Temp: %2.1f Humi: %2.0f", tmp, hum);
磁気センサのデータの取得・表示は少し長くてこの部分。
bmm150_mag_data value;
bmm.read_mag_data();
value.x = bmm.raw_mag_data.raw_datax - value_offset.x;
value.y = bmm.raw_mag_data.raw_datay - value_offset.y;
value.z = bmm.raw_mag_data.raw_dataz - value_offset.z;
float xyHeading = atan2(value.x, value.y);
float zxHeading = atan2(value.z, value.x);
float heading = xyHeading;
if(heading < 0)
heading += 2*PI;
if(heading > 2*PI)
heading -= 2*PI;
float headingDegrees = heading * 180/M_PI;
float xyHeadingDegrees = xyHeading * 180 / M_PI;
float zxHeadingDegrees = zxHeading * 180 / M_PI;
Serial.print("Heading: ");
Serial.println(headingDegrees);
Serial.print("xyHeadingDegrees: ");
Serial.println(xyHeadingDegrees);
Serial.print("zxHeadingDegrees: ");
Serial.println(zxHeadingDegrees);
M5.Lcd.setCursor(0, 40, 2);
M5.Lcd.printf("headingDegrees: %2.1f", headingDegrees);
重要なのはオフセットを引いたX軸、Y軸、Z軸方向のデータですが、それはこちらの部分で取得しているようです。
bmm150_mag_data value;
bmm.read_mag_data();
value.x = bmm.raw_mag_data.raw_datax - value_offset.x;
value.y = bmm.raw_mag_data.raw_datay - value_offset.y;
value.z = bmm.raw_mag_data.raw_dataz - value_offset.z;
これ以降は地磁気がどちらを向いているかの計算と表示なので、ケースバイケースで変更すればいいでしょう。
そして気圧の取得・表示はこちら。
float pressure = bme.readPressure();
M5.Lcd.setCursor(0, 60, 2);
M5.Lcd.printf("pressure: %2.1f", pressure);
これ以降の部分はやはりENV II HATが接続されているかのチェックのプログラムだと思われます。
今回は結構わからない部分もありましたが、わからない部分はそのまま使えば良さそうなので、自分でHATを使うためのプログラムを組むのも何とかできそうです。
次回はM5StickCの内部にデータを保存するためのスケッチ例EEPROMを見ていきましょう。
ではでは今回はこんな感じで。
コメント