手元に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セグ風フォント」でしょうか?!
#スケッチ
※このスケッチはM5StickC用です。(M5StickC Plusでは動作しません。)
#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()を使用
#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追記 スケッチを一部変更しました。旧スケッチはこのセクションを展開すると参照できます。 (変更箇所) ・画面の書き換え間隔(分が変わったときのみ画面全体を書き換え)
#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();
}
}
#所感
NTPサーバーからの時刻取得は意外とすんなりできたのですが、苦労したのはスリープ復帰時の時刻取得です。スリープ復帰時はsetupから再スタートになるため、WiFi接続しようとします。これでは外出時に困るため、電源オン時はWiFi接続、スリープ復帰時は何もしないという判定を入れました。
この判定はうまく処理されるのですが、今度はスリープ復帰時に日本時間が表示されない(9時間の時差がない時刻が表示される?)という状態になりました。
RTC(Real Time Clock)に時刻を保存する必要があるのでは?どうやったら保存&取得できるのか?色々調べて見つけたのが、たなかまさゆきさんのブログでした。本当に助かりました。
と、いうわけで今回は時計スケッチの作成を通して、WiFi接続、スリープ、RTCについて理解を深めることができました。