実用的な高度計を作成する ~ M5StickCで腕時計型高度計

2022年1月23日

以前にM5StickCBME280を使用した高度計を作成しましたが、バッテリー切れが早い欠点がありました。

M5StickCは、ディスプレイが付いてバッテリーも内蔵するため、実用性の高い電子工作が可能です。これがRaspberry Piであれば、表示機やバッテリー、ボタンなどをどうしようか、いろいろ迷ってしまいます。また、ポータブルでコンパクトでなければなりません。対してM5StickCなら多くが内蔵しているので、理想に近いマイコンです。

しかしバッテリーが付いているとは言え、何も考えないでプログラムを作成すると、1時間位でバッテリーが切れてしまいます。これでは、とても実用に耐えません。表示を暗くしたり、CPUの動作を抑える方法もありますが、やはり2~4時間程度が限度かなと思います。

そこで、バッテリーが1日は持つ高度計の作成を行ってみました。

1.DeepSleepを使う

バッテリーを節約するために、高度や時間を見たい時だけM5StickCを動作させます。それ以外は休止状態にします。そこで、DeepSleepという機能を使います。これは、以前の高度計(BME280を使わない)でもやりました。

    // ディープスリープスタート
    M5.Axp.SetSleep();
    esp_deep_sleep_start();

今回も、DeepSleepを利用します。

2.時刻の取得

M5StickCにはRTCによる時計機能がありますが、バッテリー切れを頻繁に起こすので、電源オン時に時刻を取得して、RTCに設定する必要があります。しかし、あくまでも電源オンの時だけ時間合わせをすればいいので、DeepSleepからの復帰では時刻合わせを行いません。

  esp_sleep_wakeup_cause_t wakeup ;
  wakeup = esp_sleep_get_wakeup_cause();

  switch(wakeup){
    case ESP_SLEEP_WAKEUP_EXT0 :
        // DeepSleepから復帰した場合

また時刻合わせは、ネットから取得します。そのため、ネット環境を必要とします。

      struct tm timeInfo;
      getLocalTime(&timeInfo) ;

3.高度補正機能を付ける

実は、一番の問題点が高度補正機能です。高度は気圧計(BME280)から取得するので、気圧によって高度が上下してしまいます。そのため、高度補正が必要です。

これをどうするか悩みました。M5StickCには、電源ボタン以外に2つボタン(ボタンA、ボタンB)があるだけです。その2つのボタンで高度を+-するのですが、DeepSleepの復帰時にもボタンを押す必要があるので、その切り分けがうまくいきませんでした。長押しと組み合わせればいいかなと思ったのですが、うまくいかなかったのです。

そこで、電源ON時に限り、2~30秒の間だけ高度補正ができるようにしました。その間は高度表示が白で表示され、 ボタンAとボタンBで高度を+-できます。時間が過ぎると高度表示は緑になり、高度5秒、時計5秒を表示したのち、DeepSleepします。

    pinMode(GPIO_NUM_37, INPUT_PULLUP);
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW);

DeepSleepからの復帰は、ボタンAを押します。押した後は同じく、高度5秒、時計5秒を表示したのち、DeepSleepします。

これで、バッテリーの消費を最小化しました。

4.コード

日付の処理が少し入っていますが、今回は日付の表示を省いています。ご了承ください。

#include <M5StickC.h>
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BME280.h>
#include <math.h>

Adafruit_BME280 bme;

#include <WiFi.h>
#include <time.h>

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

const char* g_ntpServer = "ntp.jst.mfeed.ad.jp" ;
const long  g_gmtOffset = 9 * 3600 ;

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 × 回数)

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 DispLCD(float wAltitude) {

  M5.Lcd.fillScreen(BLACK);

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

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

  if (g_isPowerON == false) {
    M5.Lcd.fillScreen(BLACK);

    M5.Lcd.setTextColor(GREEN);
    M5.Lcd.setCursor(0, 10, 7);

    M5.Rtc.GetTime(&g_RTC_Time);

    M5.Lcd.printf("%02d:%02d", g_RTC_Time.Hours, g_RTC_Time.Minutes);
    delay(5000);
  }
}

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

  esp_sleep_wakeup_cause_t wakeup ;
  wakeup = esp_sleep_get_wakeup_cause();

  switch(wakeup){

    case ESP_SLEEP_WAKEUP_EXT0 :
      Serial.printf("deepsleepから起動\n");

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

    default : 
      Serial.printf("電源ONから起動\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");

      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

  if (!bme.begin(0x76)){  
      Serial.println("BME280を検出できません");
      while (1);
  }

  pinMode(GPIO_NUM_10, OUTPUT);
  digitalWrite(GPIO_NUM_10, HIGH);

}
  
void loop() {
  float temp, altitude, pressure, humid;

  // 一応、BME280では以下の取得ができます
  temp = (float)bme.readTemperature();
  pressure = (float)bme.readPressure() / 100;
  altitude = getAltitude(temp, pressure) + g_hosei ; 
  humid = (float)bme.readHumidity() ;

  DispLCD(altitude);

  if (g_isPowerON) {
    // 高度の補正が可能
    if (g_editTimeCount++ > 50) {
       // 修正モード終了
       g_isPowerON = false ;
       Serial.println("End of Edit Mode");
    } else {
   
      Serial.println("Edit Mode");
      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();
    
  }

}

5.実用性はあるけれど筐体を何とかしたい

使ってみると、なかなかいい感じです。高度補正ですが、delay関数を使っているので、押しても反応しない場合があります。これは、少し長押し気味に押して反応させると良いでしょう。もっといい方法にした方が良いでしょうが、これでも十分操作できます。

また、高度修正が可能な時にマイナスの高度が表示される時は3桁の表示になってしまいますが、今回のコードでは対処していません。ご了承ください。

今回やってみて一番の問題は、M5StickCとセンサー部を合わせると、横に長いことです。これで腕に着けて歩くのは、少し邪魔です。自作のセンサーではなくてEnv Hatを購入すればマシになりますが、それでも邪魔になりそうです。(今回は右手に装着することを前提にしています。左手の場合は180度回転した表示にしてください)

これはあくまでもプロトタイプで、3Dプリンタで筐体を作り、ESP32、バッテリー、センサー、ボタンを配したパッケージを作る必要があるでしょう。でも、さすがにそこまでして作成するつもりはありません。

なかなか日常で使い続ける工作はできません。これからも考えて行こうと思います。