ESP32 でNTPサーバーから現在時刻を取得する方法を試してみました。

NTP(Network Time Protocol)

 インターネットにつながっているPC やスマートフォンの時刻は昔の時計みたいにいちいち合わせなくても正しい時刻を表示してくれます。

 それはインターネット上に標準時刻を取得することができるサーバーがあって、そこに問い合わせれば今、何年何月何日の何時何分何秒かを正確に知ることができるからです(とは言え通信の遅延があり、補正されていますが受け取った時にはコンマ数秒程度ズレることがあります)。

 Windows PCならば初期設定はアメリカマイクロソフト社にある ”time.windows.com” サーバー、Mac ならApple アジアの ”time.asia.appe.com” に定期的にアクセスして時刻を合わせています 。 Androd スマートフォンは ”pool.ntp.org” から時刻を入手していて変更できない仕様のようです。

 日本では国立研究開発法人情報通信研究機構(NICT) が ”ntp.nict.jp” で原子時計で得た正確な日本標準時を配信しています。

 ここに直接アクセスして時刻を入手することができますが、日本中のすべての情報機器が同じサーバーにアクセスするとパンクしてしまうので(1秒間に100万リクエストくらいまではいけるそうです)、NICT のコンピューター(stratum 1)に同期して配信するサーバー(stratum 2)、そのサーバーに同期して配信するサーバー(stratum 3)等が公開されており使うことができます。

ESP32での時刻取得

 ESP32(ESP32-DevKitC)で WiFiを通じてインターネットにアクセスしてNTP サーバーから時刻を入手します。

サンプルスケッチ

 ESP32の開発環境が整ったArduino IDEを使います。開発環境の構築はこちらで書きました。

 『ファイル』 ⇒ 『スケッチ例』 ⇒ (”ESP32 Dev Module 用のスケッチ例”の下)『ESP32』 ⇒ 『Time』⇒ 『SimpleTime』を開きます。

 4行目 ”YOUR_SSID” にアクセスポイントのSSIDを、5行目 ”YOUR_PASS” を実際のパスワードに書き換えます。

 8行目 const long gmtOffset_sec = 3600; の ”3600” を”9 * 3600” に書き換えます(世界協定時からの時差、単位は秒)。9行目 const int daylightOffset_sec = 3600; の ”3600” を”0” に書き換えます(夏時間の調整時差、単位は秒)。

 ESP32(ESP32-DevKitC)をUSB でPC とつなぎ、シリアルモニタを起動して、書き込みします。

 正常に書き込みが出来てインターネットから時刻を取得できれば、シリアルモニタに1秒毎に現在時刻が表示されます。

 サンプルスケッチでは ”pool.ntp.org” (7行目)に接続して時刻を取得しています。 ここを自分の環境から接続可能なサーバーに書き換えることもできます。

 インターネット時刻の取得は35行目

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

 で行っています。 書式は

configTime(”世界協定時からの時差、単位は秒”, 
           ”夏時間の調整時差、単位は秒”, 
           ”NTP サーバー(3つまで指定可能)”);

 となります。

 取得した時刻を基準にESP32 の内部クロックでカウントし、time.h ライブラリ(2行目)で取り出すことができます(11行目~ ”printLocaTime()” 関数)。

広告

温湿度ロガーに記録時刻を付与する

 前回作った温湿度ロガーのタイムスタンプはプログラム開始後の経過時間しか記録することができませんでした。
 時刻の入手方法が分かったので、記録時刻を付与するようにスケッチを改変しました。

配線

 配線はそのまま使いました。

スケッチ

 温湿度ロガーで作ったスケッチにサンプルスケッチを参考にNTP サーバーから時刻を入手して出力するコードを加えました。

 スケッチが長くなったのでタブ(ファイル)を分けて作成しました。

メインタブ(フィアル)
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#include <DHT.h>
#include <DHT_U.h>
#include <Ticker.h>
#include <WiFi.h>
#include "time.h"

#define DHTPIN 16 // 温湿度センサーのピン番号
#define DHTTYPE DHT11 // 温湿度センサーの型番

volatile bool  meas = 0; // 測定が有効か否か
DHT dht(DHTPIN, DHTTYPE); // 温湿度センサの設定
Ticker ticker; // 割り込み名

const char* ssid     = "yourSSID"; //WiFiのSSID
const char* password = "yourpassword"; //WiFiのパスワード

const char* ntpServer = "pool.ntp.org"; //NTPサーバー
const long  gmtOffset_sec =  9 * 3600; //時差(秒)
const int   daylightOffset_sec = 0; //夏時間の修正時差(秒)

void setup() {
  Serial.begin(115200);
  while (!Serial) {
  }
  // SD カード初期化
  Serial.println("Initializing SD card...");
  if(!SD.begin()){
      Serial.println("Card Mount Failed");
      return;
  }
  Serial.println("card initialized.");

  pinMode(4, OUTPUT); // LED出力
  pinMode(2, INPUT_PULLUP); // プッシュボタン入力

  dht.begin(); // 温湿度センサー開始
  setNTP(); // 現在時刻取得
  ticker.attach_ms(60 * 1000, measure); // 60秒毎に”measure”関数を割り込みで呼び出す
}

unsigned long period = 1 * 3600 * 1000; // 時刻校正の周期(ミリ秒)
unsigned long rst = 0;

void loop() {
  // プッシュボタンが押されたとき、”meas”が"0"なら"1"にしてLED点灯"1"なら"0"にしてLED消灯 
  if (digitalRead(2) == LOW) {
    if (meas == 0) {
      meas = 1;
      digitalWrite(4 ,HIGH);
    }
    else  {
      meas = 0;
      digitalWrite(4 ,LOW);
    }
    delay(1000);
  }
  // "period"で設定した時間ごとにNTPサーバーに接続して時刻を補正する。
  if (millis() > period + rst) {
    setNTP(); // 現在時刻取得
    rst += period;
  }
}
サブタブ(ファイル)
// SDカードに追記
void appendFile(fs::FS &fs, const char * path, String message){
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}

// NTP時刻を取得
void setNTP(){
  Serial.println("Swt Up ntp time");
  Serial.println("WiFi Connection");
  //WiFiに接続
  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }
  Serial.println(" CONNECTED");
  
  //時刻を取得
  Serial.println("Set time");
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

  //WiFi切断
  Serial.println("Wifi disconnect");
  WiFi.disconnect(false);
  WiFi.mode(WIFI_OFF);
}

// 温湿度測定
void measure() {
  // "meas"が1なら温湿度を測定してSDカードに保存
  if (meas == 1) {
    String dataString;
    struct tm timeinfo;
    if(getLocalTime(&timeinfo)){ // 現在時刻
      char cTime[256] = {'\0'};
      strftime(cTime, sizeof(cTime), "%Y/%m/%d %H:%M:%S", &timeinfo);
      dataString += String(cTime);
      dataString += ",";
    }else{
      Serial.println("Failed to obtain time");
      dataString += ",";
    }
    float t = dht.readTemperature(); // 温度測定(℃)
    if(!isnan(t)){
      dataString += String(t);
    }else{
      dataString += " ";
    }
    dataString += ",";
    float h = dht.readHumidity(); // 湿度測定(%))
    if(!isnan(h)){
      dataString += String(h);
    }else{
      dataString += " ";
    }
    dataString += "\n";

    // ファイルにデータを追記する
    appendFile(SD, "/LOGGING.CSV", dataString);
    Serial.println(dataString);
    }
}

動作確認

 配線を行ったESP32-DevKitC にスケッチを書きこんで、温湿度測定を行いました。 測定日時が付与されたデータが得られました。

広告