M5StickC Plusで時報付きの時計を作る

##はじめに
子供の頃、家に振り子がついた壁掛け時計がありました。毎正時になると時刻の数だけチャイムがなるのです。（例えば３時になるとボーン、ボーン、ボーンと3回鳴る。）今はあまり見かけなくなりましたね。M5StickC Plusは本体のみでビープを鳴らせるので、時刻の数だけピロ、ピロっと音が鳴る時計を作りました。

##構想(実現したいこと)
・M5StickC Plusの内蔵ブザーを使って毎正時に時刻の数だけ音を鳴らす。
・LEDも時刻の数だけ点滅させる。
・画面のデザインは、以前、作成した[時計のスケッチ（７セグ液晶風）](https://qiita.com/tranquility/items/5d0b1a259a0570be35ec "時計のスケッチ（７セグ液晶風）")を流用する。
・常時電源接続、またはモバイルバッテリーで運用する。（M5StickC Plusはスリープできないため）

##動作中の画面
![IMG_4060 2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677344/31cc64d9-fd7e-3090-c173-4bb58540ac84.jpeg)
（期せずしてクリスマスっぽい色合いの写真に…笑）
##スケッチ

```c:Local_Clock7_Plus.ino
#include <M5StickCPlus.h>
#include <WiFi.h>
#include <time.h>

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

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

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

int sMin = 0; // 画面書き換え判定用（分）
int countHour = 0; // 時報のカウンター

void setup() {
  M5.begin();
  M5.Lcd.setRotation(1);
  M5.Axp.ScreenBreath(9);
  M5.Lcd.fillScreen(BLACK);
  
  pinMode(10, OUTPUT);    // LEDを使用する
  digitalWrite(10, HIGH); // LED消灯
}

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

  if (M5.BtnA.wasPressed()) { // Aボタン１回押し
    wifiConnect(); // WiFi接続してNTPサーバーから時刻取得
  }
}

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(185,35,40,20,BLACK); // 秒の表示エリアだけ書き換え（Plusの解像度：135x240）
  } else {
    M5.Lcd.fillScreen(BLACK); // 「分」が変わったら画面全体を書き換え
  }
  
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setCursor(45,35,7);  //x,y,font 7:48ピクセル7セグ風フォント

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

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

  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setCursor(50, 90, 2);  //x,y,font 2:16ピクセル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; // 「分」を保存
  
  if (RTC_TimeStruct.Minutes == 0){ // 毎時0分のときだけ判定する
    if (countHour == 0){ // 時報をまだ鳴らしていないとき
      
      int jihouHour = 0;
      
      // 音を鳴らす回数を設定
      if (RTC_TimeStruct.Hours == 0){ // 夜中の0時
        jihouHour = 12;
      } else if (RTC_TimeStruct.Hours > 12) { // 13時から23時
        jihouHour = RTC_TimeStruct.Hours - 12;
      } else {
        jihouHour = RTC_TimeStruct.Hours;
      }

      // 時刻の数だけ音を出す
      while (jihouHour > countHour) {
        
        digitalWrite(10, LOW); // LED点灯
        M5.Beep.tone(3300); // 329.628
        delay(40);
        M5.Beep.mute();
    
        M5.Beep.tone(2600); // 261.626
        delay(60);
        M5.Beep.mute();
        digitalWrite(10, HIGH); // LED消灯

        delay(800);
        countHour = countHour + 1;
      }
      
    } else {
      // 時報を一度鳴らしたら、0分の間は鳴らさない
    }

  } else { // 毎時0分以外のとき
    countHour = 0; // 時報カウンターをリセット
  }
  
}

void wifiConnect() {
  
  // connect to WiFi
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0, 1);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(1);
  M5.Lcd.print("WiFi connecting");
  WiFi.begin(ssid, password);

  int lCount = 0; // WiFi試行判定回数カウンター
  
  while (lCount < 10) { // 10回接続状態を判定する
    if (WiFi.status() == WL_CONNECTED) { // 接続できたとき
        
      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);
      M5.Lcd.fillScreen(BLACK);

      break; // while を抜ける
    }
    
    // WiFi未接続のとき
    delay(1000);
    M5.Lcd.print(".");
    lCount = lCount + 1;
  }
}

```

##所感など
電源オン直後はRTCから取り出した時刻を表示するようにしました。その上で、Aボタンを押せば、タイムサーバーから時刻を取得してRTCに保存できるようにしました。RTCの時刻は電源を切っても消えないので、日常的にはこれで十分と思います。

M5StickC Plusは、現時点で（2020年11月現在）スリープができません。これを逆手に取って、常時電源接続、またはモバイルバッテリーを使用する前提で、時刻を音で知らせてくれる、ちょっと懐かしい感じの（布団の中でまどろみながら時刻が分かる）時計ができました。