M5StickC ENV Hat III で気圧を測る ~ 高度計を作る

2022年7月20日

今までM5StickC高度計を作成しましたが、センサーBME280を使い、Proto Hatに組み込んで作ったものでした。

これで機能的には問題ないのですが、Hat部分が大きくて実際に使うと邪魔な感じが拭えませんでした。そこで、正規のHatであるENV Hat IIIを購入しました。

このように、自作Hatの半分の大きさです。ENV Hat IIIM5StickCに取り付けると

M5StickC ENV Hat III (SHT30/QMP6988)
スイッチサイエンス

こんな感じでスッキリします。これなら、行動中でも邪魔になることは少なそうです。

さて、ENV Hat IIIですが、ENV Hat IIで使用されていたBMP280QMP6988に変更されています。また、今まで温度と湿度はBME280から取得していましたが、これもSHT30から取得の変更になります。

これらの変更のため、ライブラリをダウンロードしました。githubにサンプルが用意されています。

srcフォルダにあるソースを、プロジェクトのあるフォルダにコピーしてください。

以下、ENV Hat IIIに対応したソースです。

#include <M5StickC.h>
#include <Wire.h>
#include <math.h>
#include <time.h>
#include <WiFi.h>

#include "Adafruit_Sensor.h"
#include "QMP6988.h"
#include "SHT3X.h"

SHT3X sht30;
QMP6988 qmp6988;

const char* g_ssid     = "XXXXXXXXXXXXXX";
const char* g_password = "XXXXXXXXXXXXXX";

const char* g_ntpServer = "ntp.jst.mfeed.ad.jp" ;
const long  g_gmtOffset = 9 * 3600 ;
const int   g_daylightOffset_sec = 0; // サマータイム設定なし

RTC_TimeTypeDef g_RTC_Time;
RTC_DateTypeDef g_RTC_Date;

bool g_isPowerON = false ;

RTC_DATA_ATTR int g_hosei = 0 ;

int g_editTimeCount = 0 ;  // 修正できる時間 (500ms × 20回)

bool g_isDateDisp = true ;

float getAltitude(float wkTemp, float wkPress) {
    float wkAltitude = 0;
    float seaAltitude = (float)1013.25 ;
    float PressJyou = (float)1 / (float)5.257 ;

    float wkPressHi = seaAltitude / wkPress ;

    float wkPress2 =  powf(wkPressHi, PressJyou) ;
    wkPress2 = wkPress2 - (float)1 ;

    float wkTemp2 = wkTemp + 273.15 ;

    wkAltitude = wkPress2 * wkTemp2 / (float)0.0065 ;

    return wkAltitude ;
}

void FooterLCD() {
  static const char *week[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};

  M5.Rtc.GetTime(&g_RTC_Time);

  M5.Rtc.GetData(&g_RTC_Date);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setCursor(2, 65, 1);
  M5.Lcd.printf("%04d.%02d.%02d %s %02d:%02d", g_RTC_Date.Year, g_RTC_Date.Month, g_RTC_Date.Date, week[g_RTC_Date.WeekDay], g_RTC_Time.Hours, g_RTC_Time.Minutes);  
  
}

void DispLCD(float wAltitude) {

  M5.Lcd.fillScreen(BLACK);

  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setCursor(135, 35, 4);
  M5.Lcd.printf("m") ;

  M5.Lcd.setCursor(0, 10, 7);
 
  if (g_isPowerON == false) {

    M5.Lcd.setTextColor(GREEN);
    M5.Lcd.printf("%04.0f", wAltitude) ;
  
    if (g_isDateDisp) {
      FooterLCD();
    }

    delay(10000) ;

  } else { 
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.printf("%04.0f", wAltitude) ;
    delay(500);
  }  

}

void setup() {
  // put your setup code here, to run once:
  M5.begin();
  Wire.begin(0,26);
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);

  qmp6988.init();

  esp_sleep_wakeup_cause_t wakeup ;
  wakeup = esp_sleep_get_wakeup_cause();

  switch(wakeup){

    case ESP_SLEEP_WAKEUP_EXT0 :
      Serial.printf("Boot from deep sleep\n");

      g_isPowerON = false ; 
      
      break; // スリープ復帰時はWiFiで時刻取得しない

    default : 
      Serial.printf("Initial startup\n");

      // Wi-Fiから時刻を取得
      M5.Lcd.setCursor(0, 0, 1);
      M5.Lcd.setTextColor(WHITE);
      M5.Lcd.setTextSize(1);
      M5.Lcd.print("Wi-Fi Connecting");
  
      WiFi.begin(g_ssid, g_password);

      while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        M5.Lcd.print(".");
      }
      M5.Lcd.println("Wi-Fi Connected");

      // Set ntp time to local
      configTime(g_gmtOffset, g_daylightOffset_sec, g_ntpServer);

      struct tm timeInfo;
      if (getLocalTime(&timeInfo)) {

        RTC_TimeTypeDef wTime;
        wTime.Hours   = timeInfo.tm_hour;
        wTime.Minutes = timeInfo.tm_min;
        wTime.Seconds = timeInfo.tm_sec;
        M5.Rtc.SetTime(&wTime);
  
        RTC_DateTypeDef wDate;
        wDate.WeekDay = timeInfo.tm_wday;
        wDate.Month = timeInfo.tm_mon + 1;
        wDate.Date = timeInfo.tm_mday;
        wDate.Year = timeInfo.tm_year + 1900;
        M5.Rtc.SetData(&wDate);
      }

      g_isPowerON = true ; 

      break ;
    
  } // end switch
  
}
  
void loop() {
  float temp, altitude, pressure, humid;

  if (sht30.get() == 0) { 
    humid = sht30.humidity ;  
    temp = sht30.cTemp;  
  } else {
    humid = 0.0 ;  
    temp = 0.0 ;  
  }
  
  pressure = qmp6988.calcPressure() / 100 ;

  Serial.printf("pressure=%f\n", pressure);  
  
  altitude = getAltitude(temp, pressure) + g_hosei ; 

  DispLCD(altitude);

  if (g_isPowerON) {
    // 高度の補正が可能
    if (g_editTimeCount++ > 50) {
       // 修正モード終了
       g_isPowerON = false ;
       Serial.println("Exit Edit Mode\n");
    } else {
   
      Serial.println("Edit Mode\n");

      if ( digitalRead(M5_BUTTON_HOME) == LOW ) {
        // +補正
        g_hosei += 10 ;
        Serial.print("+");
      } else if (digitalRead(M5_BUTTON_RST) == LOW ) {
        // -補正
        g_hosei -= 10 ;
        Serial.print("-");
      }
    }     
  } else {
  
    // ボタンAでスリープ復帰
    pinMode(GPIO_NUM_37, INPUT_PULLUP);
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW);
  
    // ディープスリープスタート
    M5.Axp.SetSleep();
    esp_deep_sleep_start();
    
  }

}

ボタンA(Home)で10m高度を上げる補正をし、ボタンB(Reset)で10m高度を下げる補正をします。また、isModeを変更することで温度湿度の表示もできますが、プログラムの修正が必要です。

ひとつ問題点があります。高度を計算する時に温度が必要ですが、M5StickCに隣接するセンサーは実際よりも高い温度となってしまいます。また、腕の体温の影響もあります。そのため、高度の計算に狂いが出てきます。しかし、気圧の変化の方が高度に大きく影響するので、補正をすることで十分とも言えそうです。

今回のプログラムは、5秒ごとに気圧を計測しているので、バッテリーが1時間ほどしか持たないでしょう。もし、実用的なプログラムにするのであれば、以下の記事で紹介したソースをENV Hat IIIに対応させてください。バッテリーを1日持たせるために、deep sleepに対応しました。ボタンAを押すと復帰して10秒間高度を表示します。日付時刻も表示できるようにしました。

実際に試してみたいのですが、登山を辞めたのでできません。近くのハイキングか、車での峠越えでテストしてみたいと思います。