マイコンの時計を合わせるには、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]