M5 Stack Core2における時計合わせ(NTP)

,

マイコンの時計を合わせるには、NTP(Network Time Protocol)かGPSによるものが一般的かと思います。

またM5 Stack Core2は、RTC(Real Time Clock)を内部に持っています。

以前、NTPによる時計合わせを行う製品を出荷しましたが、どうも条件によって時計が最大20分もずれることがあり、現場でトラブルが発生してしまいました。

結果から伝えると、次の対応が必要でした。

① 起動時にRTCから時計情報を得て内部時計を設定する。

② WiFi接続時、NTPで時計合わせを行い、時計合わせが行えたというコールバック関数により確認を行う。

③ コールバック関数(NTPによる時計合わせ成功)で、内部時計情報をRTCに反映させる。

④ 内部で使用する時計は、RTCではなく内部時計を用いる。

下のサンプルではM5 Stack core2を使用しています(ボードマネージャー esp32 by Espresslf 2.0.17 / Arduino IDE 2.3.2)。

 

<_header.h>

C++
#ifndef HEADER_H
#define HEADER_H

#include <M5Core2.h>

// 1時間数値...
#define JST 3600
#define TIME_ZONE 9

//! タスクハンドル(マルチスレッド)
TaskHandle_t thp[1];

//! WiFiアクセスポイント設定
String ssid = "YOUR SSID";
String pass = "YOUR PASSWORD";

//! 時計関連
bool isSetNTP = false;           // NTPを設定したかどうか
struct tm timeinfo;

#endif

 

<main.ino>

C++
#include "_header.h"

void setup() {
  M5.begin();
  writePref(ssid, pass);
  //! スレッドを実行...
  xTaskCreatePinnedToCore(connectWiFi, "connectWiFi", 4096, NULL, 5, &thp[0], 0);  //! 起動時に内部RTCから時計情報を得る...
  setRTC2TIME();
}

void loop() {
  char tmp[32];
  unsigned long tim = millis();
  //! 時計情報を得る...
  while (!getLocalTime(&timeinfo)) delay(10);
  //! 日にち...
  sprintf(tmp, "%02d-%02d-%02d", timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday);
  Serial.print(tmp);
  Serial.print("  ");
  //! 時間...
  sprintf(tmp, "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
  Serial.print(tmp);
  //! 三項演算子での記載...  
  isSetNTP ? Serial.println(" [NTP is done]") : Serial.println(" [NTP is not yet]");
  //! 1秒待つ...
  while (millis() <= tim + 1000) delay(1);
}

 

<T_WiFi.ino>

C++
#include <Preferences.h>  // 不揮発性メモリへの記録(Wi-Fi情報)
#include <WiFiMulti.h>    // マルチWiFi
#include <sntp.h>         // for sntp_sync_status

WiFiMulti wifiMulti;

Preferences preferences;      // declare class object
const char* prefKey = "mms";  //! 不揮発性メモリのキー値

/* =================================================================================
   機能:preferencesにWIFI情報を書き込む...
   引数:第一引数 SSID
      第二引数 パスワード
   戻値:なし
   ================================================================================= */
void writePref(String PrefSSID, String PrefPass) {
  // Serial.println("Write Pref " + PrefSSID + "/" + PrefPass);
  preferences.begin("multi_wifi", false);
  //! 何個、情報が書き込まれているか...
  int cnt = preferences.getInt("cnt", 0);
  //! 1個以上の情報があり、すでに書き込まれていたらすでに書き込まれているか確認...
  bool duplication = false;
  if (0 < cnt) {
    for (int i = 0; i < cnt; i++) {
      String tempA = preferences.getString(("ssid_" + String(i + 1)).c_str(), "");
      String tempB = preferences.getString(("psk_" + String(i + 1)).c_str(), "");
      if ((tempA == PrefSSID) and (tempB == PrefPass)) {
        duplication = true;
      }
    }
  }
  //! 重複せずSSIDが空でなかったら書き込み...
  if (!duplication && PrefSSID != "") {
    preferences.putString(("ssid_" + String(cnt + 1)).c_str(), PrefSSID);
    preferences.putString(("psk_" + String(cnt + 1)).c_str(), PrefPass);
    preferences.putInt("cnt", cnt + 1);
    Serial.println("ADD SSID/PSK " + PrefSSID + "/" + PrefPass);
  }
  preferences.end();
}

/* =================================================================================
   機能:WiFiに接続する...
   引数:汎用ポインタ
   戻値:なし
   ================================================================================= */
void connectWiFi(void* args) {
  while (1) {
    if (!get_wifi_status()) {
      WiFi.disconnect();
      preferences.begin("multi_wifi", true);
      //! 何個、情報が書き込まれているか...
      int cnt = preferences.getInt("cnt", 0);
      Serial.println("WiFi COUNT is " + String(cnt));
      delay(1000);
      //! 1個以上の情報があり、すでに書き込まれていたらすでに書き込まれているか確認...
      Serial.println("========= WiFi List =========");
      if (0 < cnt) {
        for (int i = cnt; 0 < i; i--) {
          String tempA = preferences.getString(("ssid_" + String(i)).c_str(), "");
          String tempB = preferences.getString(("psk_" + String(i)).c_str(), "");
          if (tempA != "" && tempB != "") {
            Serial.println(tempA + "/" + tempB);
            wifiMulti.addAP(tempA.c_str(), tempB.c_str());
          } else {
            Serial.println("--- Empty account, ignore. ---");
          }
        }
      }
      preferences.end();
      //! wifiMulti.run()のTimeOutは指定なければ5秒...
      if (wifiMulti.run(10 * 1000) == WL_CONNECTED) {
        Serial.println("[WiFi] WiFi connected [" + WiFi.SSID() + "]");
      } else {
        Serial.println("[WiFi] Could not connect to wifi");
      }
    } else {
      // RTC設定
      if (isSetNTP == false) {
        unsigned long tim = millis();
        Serial.println("[NTP] (before) " + get_log_dt());
        //! NTPのコールバック関数を設定...
        sntp_set_time_sync_notification_cb(timeavailable);
        configTime(JST * TIME_ZONE, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
        Serial.println("[NTP] configtime has been executed.");
        //! 10秒またはNTPが実施されるまで待つ...
        while (millis() < tim + 10 * 1000 && !isSetNTP) delay(10);
      }
    }
    delay(3000);
  }
}

/* =================================================================================
   機能:WiFiのステータスを得る
   引数:<なし>
   戻値:接続していたらtrue そうでないならfalse
   ================================================================================= */
boolean get_wifi_status() {
  return WiFi.status() == WL_CONNECTED;
}

/* =================================================================================
   機能:WiFiの電波強度を得る(一般的には-75dBmより大きい数字が理想・切断時は0)
   引数:<なし>
   戻値:電波強度(dBm)
   ================================================================================= */
long getRSSI() {
  if (WiFi.status() != WL_CONNECTED) {
    return 0;
  } else {
    return WiFi.RSSI();
  }
}

/* =================================================================================
   機能:NTPにおけるコールバック関数
   引数:時刻情報
   戻値:<なし>
   ================================================================================= */
void timeavailable(struct timeval* t) {
  setNTP2RTC();
  isSetNTP = true;
  Serial.println("[NTP] (after)  " + get_log_dt());
}

/* =================================================================================
   機能:M5 Core2 リアルタイムクロックに設定する(NTP->RTC)
   引数:<なし>
   戻値:<なし>       
   ================================================================================= */
void setNTP2RTC() {
  RTC_DateTypeDef RTC_DateStruct;  // Data(Year, Date, Month)
  RTC_TimeTypeDef RTC_TimeStruct;  // Time(Hours, Minutes, Seconds)
                                   // timeSet
  while (!getLocalTime(&timeinfo)) delay(10);
  // read RTC
  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetDate(&RTC_DateStruct);
  // --- to over write date&time
  RTC_DateStruct.Year = timeinfo.tm_year + 1900;
  RTC_DateStruct.Month = timeinfo.tm_mon + 1;
  RTC_DateStruct.Date = timeinfo.tm_mday;
  RTC_DateStruct.WeekDay = timeinfo.tm_wday;
  M5.Rtc.SetDate(&RTC_DateStruct);
  RTC_TimeStruct.Hours = timeinfo.tm_hour;
  RTC_TimeStruct.Minutes = timeinfo.tm_min;
  RTC_TimeStruct.Seconds = timeinfo.tm_sec;
  M5.Rtc.SetTime(&RTC_TimeStruct);
}

/* =================================================================================
   機能:M5 Core2 リアルタイムクロックから時計情報を得る(RTC->N5)
   引数:<なし>
   戻値:<なし>       
   ================================================================================= */
void setRTC2TIME() {
  RTC_DateTypeDef RTC_DateStruct;  // Data(Year, Date, Month)
  RTC_TimeTypeDef RTC_TimeStruct;  // Time(Hours, Minutes, Seconds)
  // read RTC
  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetDate(&RTC_DateStruct);

  struct tm tm;
  tm.tm_year = RTC_DateStruct.Year - 1900;
  tm.tm_mon = RTC_DateStruct.Month - 1;
  tm.tm_mday = RTC_DateStruct.Date;
  tm.tm_hour = RTC_TimeStruct.Hours;
  tm.tm_min = RTC_TimeStruct.Minutes;
  tm.tm_sec = RTC_TimeStruct.Seconds;
  time_t t = mktime(&tm);
  Serial.printf("Setting time: %s", asctime(&tm));
  struct timeval now = { .tv_sec = t };
  settimeofday(&now, NULL);
}

/*=================================================================================
   機能:今時点の時間をYYYY/MM/DD HH:NN:SS形式で得る(log用)
   引数:なし
   戻値:日時情報
  =================================================================================*/
String get_log_dt() {
  char tmp[24];
  while (!getLocalTime(&timeinfo)) delay(10);
  sprintf(tmp, "[%04d/%02d/%02d %02d:%02d:%02d] ", timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
  return tmp;
}

実行結果

WiFi COUNT is 2
Setting time: Wed Jun 12 22:40:31 2024
2024-06-12  22:40:31 [NTP is not yet]
========= WiFi List =========
********/***********
********/***********
2024-06-12  22:40:32 [NTP is not yet]
2024-06-12  22:40:33 [NTP is not yet]
2024-06-12  22:40:34 [NTP is not yet]
[WiFi] WiFi connected [********]
2024-06-12  22:40:35 [NTP is not yet]
2024-06-12  22:40:36 [NTP is not yet]
2024-06-12  22:40:37 [NTP is not yet]
[NTP] (before) [2024/06/12 22:40:37] 
[NTP] configtime has been executed.
2024-06-13  07:40:38 [NTP is done]
[NTP] (after)  [2024/06/12 22:40:39] 
2024-06-12  22:40:40 [NTP is done]
2024-06-12  22:40:41 [NTP is done]
2024-06-12  22:40:42 [NTP is done]

PAGE TOP