Help us understand the problem. What is going on with this article?

M5StickCで時計を作る(テーマはWiFi、deep_sleep、RTC)

手元にM5StickC購入時に付いてきたバンドもあるし、時計を作ってみよう!と思い立ちました。

構想(実現したいこと)

・正確な時間を簡単に初期設定したい。→ WiFiでタイムサーバから時刻を取得する。
・1日はバッテリーを持たせたい。→ 使わない間はスリープさせる。
・外に持ち出して使いたい。→ スリープ復帰時は内部の時計(RTC)から時刻を取得して表示する。

参考にさせていただいた情報

M5StickCのRTCをNTPサーバーからセットする(たなかまさゆきさん @tnkmasayuki
 https://lang-ship.com/blog/work/m5stickc-rtc-ntp/
M5Displayクラスの使い方
 https://lang-ship.com/reference/unofficial/M5StickC/Tips/M5Display/
スリープについて
 https://lang-ship.com/reference/unofficial/M5StickC/System/Sleep/
 https://lang-ship.com/reference/unofficial/M5StickC/System/Sleep/Deep/EXT0/

動作中の画面

至ってシンプルな佇まい。ポイントは液晶っぽい「7セグ風フォント」でしょうか?!
IMG_0760.JPG

スケッチ

※このスケッチはM5StickC用です。(M5StickC Plusでは動作しません。)

Local_Clock4.ino
#include <M5StickC.h>
#include <WiFi.h>
#include <time.h>

// wifiの設定
const char* ssid     = "xxxxxxxxxx"; // WiFiのSSID
const char* password = "xxxxxxxxxx"; // WiFiのパスワード

// 時計の設定
const char* ntpServer = "ntp.jst.mfeed.ad.jp"; // NTPサーバー
const long  gmtOffset_sec = 9 * 3600;          // 時差9時間
const int   daylightOffset_sec = 0;            // サマータイム設定なし

RTC_TimeTypeDef RTC_TimeStruct; // 時刻
RTC_DateTypeDef RTC_DateStruct; // 日付

unsigned long setuptime; // スリープ開始判定用(ミリ秒)
int sMin = 0; // 画面書き換え判定用(分)

void printLocalTime() {
  static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};
  M5.Rtc.GetTime(&RTC_TimeStruct); // 時刻の取り出し
  M5.Rtc.GetData(&RTC_DateStruct); // 日付の取り出し

  if (sMin == RTC_TimeStruct.Minutes) {
    M5.Lcd.fillRect(140,10,20,10,BLACK); // 秒の表示エリアだけ書き換え
  } else {
    M5.Lcd.fillScreen(BLACK); // 「分」が変わったら画面全体を書き換え
  }

  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setCursor(0, 10, 7);  //x,y,font 7:48ピクセル7セグ風フォント

  M5.Lcd.printf("%02d:%02d", RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes); // 時分を表示

  M5.Lcd.setTextFont(1); // 1:Adafruit 8ピクセルASCIIフォント
  M5.Lcd.printf(":%02d\n",RTC_TimeStruct.Seconds); // 秒を表示

  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setCursor(25, 65, 1);  //x,y,font 1:Adafruit 8ピクセルASCIIフォント
  M5.Lcd.printf("Date:%04d.%02d.%02d %s\n", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date, wd[RTC_DateStruct.WeekDay]);

  sMin = RTC_TimeStruct.Minutes; // 「分」を保存
}

void setup() {
  M5.begin();
  M5.Lcd.setRotation(1);
  M5.Axp.ScreenBreath(9);
  M5.Lcd.fillScreen(BLACK);

  // 電源オン時は、WiFiでNTPサーバーから時刻を取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause(); // スリープからの復帰理由を取得

  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      :
    Serial.printf("外部割り込み(RTC_IO)で起動\n"); break; // スリープ復帰時はWiFiで時刻取得しない

    default                         : 
    Serial.printf("スリープ以外からの起動\n"); // 電源オン時は、WiFiでNTPサーバーから時刻取得

    // connect to WiFi
    M5.Lcd.setCursor(0, 0, 1);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(1);
    M5.Lcd.print("WiFi connecting");

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) { // 1秒おきに接続状態を確認
      delay(1000);
      M5.Lcd.print(".");
    }
    M5.Lcd.println("\nconnected");

    // Set ntp time to local
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    // Get local time
    struct tm timeInfo;
    if (getLocalTime(&timeInfo)) {
      M5.Lcd.print("NTP : ");
      M5.Lcd.println(ntpServer);

      // Set RTC time
      RTC_TimeTypeDef TimeStruct;
      TimeStruct.Hours   = timeInfo.tm_hour;
      TimeStruct.Minutes = timeInfo.tm_min;
      TimeStruct.Seconds = timeInfo.tm_sec;
      M5.Rtc.SetTime(&TimeStruct);

      RTC_DateTypeDef DateStruct;
      DateStruct.WeekDay = timeInfo.tm_wday;
      DateStruct.Month = timeInfo.tm_mon + 1;
      DateStruct.Date = timeInfo.tm_mday;
      DateStruct.Year = timeInfo.tm_year + 1900;
      M5.Rtc.SetData(&DateStruct);
    }

    // disconnect WiFi as it's no longer needed
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);

    M5.Lcd.println("WiFi disconnected");
    delay(1000);

    break; // switch 終わり
  }
  setuptime = millis(); // 起動からの経過時間(ミリ秒)を保存
}

void loop() {
  M5.update();
  printLocalTime();
  delay(980); // 0.98秒待ち

  // GPIO37がLOWになったら(M5StickCのA(HOME)ボタンが押されたら)スリープから復帰
  pinMode(GPIO_NUM_37, INPUT_PULLUP);
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW);

  // 表示開始から1分経過後にディープスリープスタート
  if(millis() > setuptime + 1000 * 60){
    M5.Axp.SetSleep(); //Display OFF
    esp_deep_sleep_start();
  }
}

2020.8.30追記 スケッチを一部変更しました。旧スケッチはこのセクションを展開すると参照できます。
 (変更箇所)
 ・画面の書き換え間隔
 ・スリープの判定にmillis()を使用
Local_Clock2.ino
#include <M5StickC.h>
#include <WiFi.h>
#include <time.h>

// wifiの設定
const char* ssid     = "xxxxxxxxxx"; // WiFiのSSID
const char* password = "xxxxxxxxxx"; // WiFiのパスワード

// 時計の設定
const char* ntpServer = "ntp.jst.mfeed.ad.jp"; // NTPサーバー
const long  gmtOffset_sec = 9 * 3600;          // 時差9時間
const int   daylightOffset_sec = 0;            // サマータイム設定なし

RTC_TimeTypeDef RTC_TimeStruct; // 時刻
RTC_DateTypeDef RTC_DateStruct; // 日付

int timeCnt = 0; // スリープ判定用カウンター

void printLocalTime() {
  static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};
  M5.Rtc.GetTime(&RTC_TimeStruct); // 時刻の取り出し
  M5.Rtc.GetData(&RTC_DateStruct); // 日付の取り出し

  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setCursor(0, 10, 7);  //x,y,font 7:48ピクセル7セグ風フォント

  M5.Lcd.printf("%02d:%02d", RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes); // 時分を表示

  M5.Lcd.setTextFont(1); // 1:Adafruit 8ピクセルASCIIフォント
  M5.Lcd.printf(":%02d\n",RTC_TimeStruct.Seconds); // 秒を表示

  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setCursor(25, 65, 1);  //x,y,font 1:Adafruit 8ピクセルASCIIフォント
  M5.Lcd.printf("Date:%04d.%02d.%02d %s\n", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date, wd[RTC_DateStruct.WeekDay]);
}

void setup() {
  M5.begin();
  M5.Lcd.setRotation(1);
  M5.Axp.ScreenBreath(9);
  M5.Lcd.fillScreen(BLACK);

  // 電源オン時は、WiFiでNTPサーバーから時刻を取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause(); // スリープからの復帰理由を取得

  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      :
    Serial.printf("外部割り込み(RTC_IO)で起動\n"); break; // スリープ復帰時はWiFiで時刻取得しない

    default                         : 
    Serial.printf("スリープ以外からの起動\n"); // 電源オン時は、WiFiでNTPサーバーから時刻取得

    // connect to WiFi
    M5.Lcd.setCursor(0, 0, 1);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(1);
    M5.Lcd.print("WiFi connecting");

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) { // 1秒おきに接続状態を確認
      delay(1000);
      M5.Lcd.print(".");
    }
    M5.Lcd.println("\nconnected");

    // Set ntp time to local
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    // Get local time
    struct tm timeInfo;
    if (getLocalTime(&timeInfo)) {
      M5.Lcd.print("NTP : ");
      M5.Lcd.println(ntpServer);

      // Set RTC time
      RTC_TimeTypeDef TimeStruct;
      TimeStruct.Hours   = timeInfo.tm_hour;
      TimeStruct.Minutes = timeInfo.tm_min;
      TimeStruct.Seconds = timeInfo.tm_sec;
      M5.Rtc.SetTime(&TimeStruct);

      RTC_DateTypeDef DateStruct;
      DateStruct.WeekDay = timeInfo.tm_wday;
      DateStruct.Month = timeInfo.tm_mon + 1;
      DateStruct.Date = timeInfo.tm_mday;
      DateStruct.Year = timeInfo.tm_year + 1900;
      M5.Rtc.SetData(&DateStruct);
    }

    // disconnect WiFi as it's no longer needed
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);

    M5.Lcd.println("WiFi disconnected");
    delay(1000);

    break; // switch 終わり
  }
}

void loop() {
  printLocalTime();

  delay(500); // 0.5秒待ち
  timeCnt++;  // スリープ判定用のカウンターに1加算

  // GPIO37がLOWになったら(M5StickCのA(HOME)ボタンが押されたら)スリープから復帰
  pinMode(GPIO_NUM_37, INPUT_PULLUP);
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW);

  // 1分経過後にディープスリープスタート
  if(timeCnt > 120){
    timeCnt = 0;
    M5.Axp.SetSleep(); //Display OFF
    esp_deep_sleep_start();
  }
}

2020.9.6追記 スケッチを一部変更しました。旧スケッチはこのセクションを展開すると参照できます。
 (変更箇所)
 ・画面の書き換え間隔(分が変わったときのみ画面全体を書き換え)
Local_Clock3.ino
#include <M5StickC.h>
#include <WiFi.h>
#include <time.h>

// wifiの設定
const char* ssid     = "xxxxxxxxxx"; // WiFiのSSID
const char* password = "xxxxxxxxxx"; // WiFiのパスワード

// 時計の設定
const char* ntpServer = "ntp.jst.mfeed.ad.jp"; // NTPサーバー
const long  gmtOffset_sec = 9 * 3600;          // 時差9時間
const int   daylightOffset_sec = 0;            // サマータイム設定なし

RTC_TimeTypeDef RTC_TimeStruct; // 時刻
RTC_DateTypeDef RTC_DateStruct; // 日付

unsigned long setuptime; // スリープ開始判定用

void printLocalTime() {
  static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};
  M5.Rtc.GetTime(&RTC_TimeStruct); // 時刻の取り出し
  M5.Rtc.GetData(&RTC_DateStruct); // 日付の取り出し

  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setCursor(0, 10, 7);  //x,y,font 7:48ピクセル7セグ風フォント

  M5.Lcd.printf("%02d:%02d", RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes); // 時分を表示

  M5.Lcd.setTextFont(1); // 1:Adafruit 8ピクセルASCIIフォント
  M5.Lcd.printf(":%02d\n",RTC_TimeStruct.Seconds); // 秒を表示

  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setCursor(25, 65, 1);  //x,y,font 1:Adafruit 8ピクセルASCIIフォント
  M5.Lcd.printf("Date:%04d.%02d.%02d %s\n", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date, wd[RTC_DateStruct.WeekDay]);
}

void setup() {
  M5.begin();
  M5.Lcd.setRotation(1);
  M5.Axp.ScreenBreath(9);
  M5.Lcd.fillScreen(BLACK);

  // 電源オン時は、WiFiでNTPサーバーから時刻を取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause(); // スリープからの復帰理由を取得

  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      :
    Serial.printf("外部割り込み(RTC_IO)で起動\n"); break; // スリープ復帰時はWiFiで時刻取得しない

    default                         : 
    Serial.printf("スリープ以外からの起動\n"); // 電源オン時は、WiFiでNTPサーバーから時刻取得

    // connect to WiFi
    M5.Lcd.setCursor(0, 0, 1);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(1);
    M5.Lcd.print("WiFi connecting");

    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) { // 1秒おきに接続状態を確認
      delay(1000);
      M5.Lcd.print(".");
    }
    M5.Lcd.println("\nconnected");

    // Set ntp time to local
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

    // Get local time
    struct tm timeInfo;
    if (getLocalTime(&timeInfo)) {
      M5.Lcd.print("NTP : ");
      M5.Lcd.println(ntpServer);

      // Set RTC time
      RTC_TimeTypeDef TimeStruct;
      TimeStruct.Hours   = timeInfo.tm_hour;
      TimeStruct.Minutes = timeInfo.tm_min;
      TimeStruct.Seconds = timeInfo.tm_sec;
      M5.Rtc.SetTime(&TimeStruct);

      RTC_DateTypeDef DateStruct;
      DateStruct.WeekDay = timeInfo.tm_wday;
      DateStruct.Month = timeInfo.tm_mon + 1;
      DateStruct.Date = timeInfo.tm_mday;
      DateStruct.Year = timeInfo.tm_year + 1900;
      M5.Rtc.SetData(&DateStruct);
    }

    // disconnect WiFi as it's no longer needed
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);

    M5.Lcd.println("WiFi disconnected");
    delay(1000);

    break; // switch 終わり
  }
  setuptime = millis(); // 起動からの経過時間(ミリ秒)を保存
}

void loop() {
  M5.update();
  printLocalTime();
  delay(980); // 0.98秒待ち

  // GPIO37がLOWになったら(M5StickCのA(HOME)ボタンが押されたら)スリープから復帰
  pinMode(GPIO_NUM_37, INPUT_PULLUP);
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW);

  // 表示開始から1分経過後にディープスリープスタート
  if(millis() > setuptime + 1000 * 60){
    M5.Axp.SetSleep(); //Display OFF
    esp_deep_sleep_start();
  }
}


・2020.11.15追記 スケッチを一部変更しました。(不要な秒表示の書き換え行を削除しました。)

所感

NTPサーバーからの時刻取得は意外とすんなりできたのですが、苦労したのはスリープ復帰時の時刻取得です。スリープ復帰時はsetupから再スタートになるため、WiFi接続しようとします。これでは外出時に困るため、電源オン時はWiFi接続、スリープ復帰時は何もしないという判定を入れました。

この判定はうまく処理されるのですが、今度はスリープ復帰時に日本時間が表示されない(9時間の時差がない時刻が表示される?)という状態になりました。

RTC(Real Time Clock)に時刻を保存する必要があるのでは?どうやったら保存&取得できるのか?色々調べて見つけたのが、たなかまさゆきさんのブログでした。本当に助かりました。

と、いうわけで今回は時計スケッチの作成を通して、WiFi接続、スリープ、RTCについて理解を深めることができました。

tranquility
よろしくお願いします。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away