ESP32でニキシー管時計を作る part 3：NTPとRTCで時間管理編

# はじめに

ニキシー管時計づくりにチャレンジしてみました。

[前回](https://qiita.com/Gen-Gen-0504/items/8547e912f89edc2cce9d)、4 つのニキシー管で数字の表示を行いました。
あとはその数字を現在時刻にできれば、時計システムの完成です。

>他の記事はこちらから
>- [part 0：事前学習＆準備編](https://qiita.com/Gen-Gen-0504/items/2e57ea467f9f76910bff)
>- [part 1：昇圧チョッパ編](https://qiita.com/Gen-Gen-0504/items/9f885674f90cadf54797)
>- [part 2：ニキシー管点灯編](https://qiita.com/Gen-Gen-0504/items/8547e912f89edc2cce9d)
>- part 3：NTPとRTCで時間管理編
>- [part 4：プログラム完成編](https://qiita.com/Gen-Gen-0504/items/ccbd5b7e18c817d844bd)
>- part 5：comming soon...(？)

<img width="500" alt="part002-ダイナミック点灯実験.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/0df10e76-48cf-c878-ca2f-738bacdcea47.png">



そこで今回は、NTP による時間の取得と、RTC への書き込み＆読み出しをテストしてみたいと思います。


# NTP による時刻の取得

#### NTP とは

Network Time Protocol という名前がそのままです。ネットワークを通じ、現在時刻を取得します。


#### 時刻の取得

ESP32 で時間を取得してみます。次のようなプログラムを書き込みました。ファイル数は 2 つです
（ [GitHub のリンク](https://github.com/Gen-Gen-0504/nixie-clock/tree/master/test%20programs/ntp_test)）

```cpp:NTP.h
#pragma once
#include <Arduino.h>
#include <WiFi.h>
#include <time.h>

class NTP {
  private:
    const char*    SSID;
    const char*    PASS;
    const char*    NTP_SERVER_1    = "ntp.nict.jp";
    const char*    NTP_SERVER_2    = "time.google.com";
    const char*    NTP_SERVER_3    = "ntp.jst.mfeed.ad.jp";
    const uint32_t GMT_OFFSET      = 3600*9;
    const uint8_t  DATLGIHT_OFFSET = 0;
    const uint8_t  CONN_TRY_TIMES  = 10;
    const uint8_t  CONN_TRY_INTVL  = 500;
    bool           is_configured   = false;

  public:
    NTP(const char* _SSID, const char* _PASS) : SSID(_SSID), PASS(_PASS) {};

    void setup() {
      WiFi.mode(WIFI_STA);
      WiFi.begin(SSID, PASS);
      /* 接続試行 */
      for(uint8_t i = 0; i < CONN_TRY_TIMES; ++i) {
        if(WiFi.status() == WL_CONNECTED) {
          /* 時刻を設定 */
          configTime(GMT_OFFSET, DATLGIHT_OFFSET, NTP_SERVER_1, NTP_SERVER_2, NTP_SERVER_3);
          is_configured = true;
          return;
        }
        delay(CONN_TRY_INTVL);
      }
    }

    void getTime(struct tm *tm) {
      /* 秒数が更新された瞬間の時刻を格納 */
      getLocalTime(tm);
      uint8_t previous_sec = tm->tm_sec;
      do {
        getLocalTime(tm);
      }while(tm->tm_sec == previous_sec); // 秒が変わるまで待つ
    }

    void disconnect() const {
      WiFi.disconnect();
    }

    bool getIsConfigured() const {return is_configured;}
};
```

```cpp:ntp_test
#include "NTP.h"

const char* SSID = "YOUR_SSID";
const char* PASS = "YOUR_PASSWORD";
NTP ntp(SSID, PASS); // NTPクラスのインスタンスを生成
struct tm tm;        // 時刻保存用の構造体


void setup() {
  Serial.begin(115200);
  ntp.setup();
  if(ntp.getIsConfigured()) { // Wi-Fi接続成功時
    ntp.getTime(&tm);
    Serial.println(&tm, "%A, %B %d %Y %H:%M:%S");
    ntp.disconnect();
  }
  else { // 失敗時
    Serial.println("Could not connect to network.");
  }
}

void loop() {
  /* none */
}
```

基本となるソースコードは、IDE のサンプルコード「SimpleTime」（「ファイル」→「スケッチ例」→「ESP32」→「Time」→「SimpleTime」）です。関数や定数の詳しい説明は[こちらの記事](https://soar130650.net/2021/02/17/esp32-ntp/)が大変わかりやすいです。

このプログラムを基にクラス化した NTP クラスが、NTP.h に定義されています。

プログラムの流れは、まず ntp.setup() によって Wi-Fi への接続に 10 回チャレンジします。
成功すれば NTP サーバと通信して ESP32 の内部時刻を設定し、現在時刻を ntp.getTime() によって取り出し表示する、という感じです。接続に失敗した場合はエラーメッセージを出力します。

すこし説明したいのは、時刻の取り出しを行う getTime() です。

```cpp
void getTime(struct tm *tm) {
  /* 秒数が更新された瞬間の時刻を格納 */
  getLocalTime(tm);
  uint8_t previous_sec = tm->tm_sec;
  do {
    getLocalTime(tm);
  }while(tm->tm_sec == previous_sec); // 秒が変わるまで待つ
}
```

時刻の格納には構造体 tm を使います。しかし tm に格納できるのは秒までなので、getLocalTime() によって NTP と同期した ESP32 の内部時間を取得しても、タイミングによっては最大 0.999... 秒の誤差が生じます。

configTime() による時刻の設定はそこそこ精度が高い（少なくとも秒単位ではズレない）らしいので、可能な限り秒の値が切り替わった瞬間を tm に保存したいです。

そこで、一度 getLocalTime() で取得した時刻の秒数を保存しておき、その秒数と異なる値（つまり 1 増えた値）が手に入るまで、do-while 文による時刻の取得を繰り返します。これによって、可能な限り 1 秒進んだ直後に時間を表示しています。
次章で扱う RTC に時間を設定する際、なるべく正確な時間を使いたいと考えこのようなプログラムにしました。

***

実際に動かしてみます。
ネットワークに接続できた場合は次のように、現在時刻がシリアルモニタに表示されます。
（ [NICT の日本標準時](https://www.nict.go.jp/JST/JST5.html)と並べています）

<img width="650" alt="part003-NTP接続テスト結果.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/05dc85e2-f69f-f70d-7251-3e4c8930b4b2.png">

また失敗した場合はエラーメッセージが表示されます。

<img width="300" alt="part003-NTP接続エラー結果.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/f1d6de76-b724-c35f-4479-2097d3b94586.png">

時刻の取得は思ったより簡単でした！


# RTC による時刻の管理

#### RTC とは

Real Time Clock と呼ばれる時計のようなものです。内部に発振回路を持ち、正確に時刻を進めます。
ESP32 も内部に RTC を持ちますが、精度があまり良くないようなので外部に取り付けます。

><img width="250" alt="part000-RX8900.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/7a292016-f0c4-48b1-79be-62ebd10e4d88.jpeg">
>
>リンク（秋月）： [RX8900](https://akizukidenshi.com/catalog/g/gK-13009/)

RX8900 は資料が充実しています。[DIP 化モジュールの秋月のページ](https://akizukidenshi.com/catalog/g/gK-13009/)から簡単な取扱説明書が、[小型パッケージ版の秋月のページ](https://akizukidenshi.com/catalog/g/gI-13320)から~~アプリケーションマニュアルや~~サンプルスケッチがダウンロードできます。
加えて、[こちらのページ](https://smtengkapi.com/engineer-esp32-wroom-32-rtc)のプログラムを参考にさせていただきました。

>2025/06/02 追記
アプリケーションマニュアルは[エプソンの公式ページ](https://www.epsondevice.com/crystal/ja/products/rtc/rx8900sa.html)からダウンロードしてください。度々修正されています。


#### 時刻の保存と取り出し

さて、時刻の保存と取り出しをやってみます。

まずは回路図を考えます。アプリケーションマニュアルのピンアサインを参考に、次のような回路を組みました。

>2025/06/02 追記
VDDとVBATに0.1μFのバイパスコンデンサを追加。
VBATに電流制限抵抗を追加。
SCL , SDA , /INT にプルアップ抵抗を追加。
値は全てアプリケーションマニュアルを参考にしました。

<img width="800" alt="part003-RTCテスト回路図_修正版_20250602.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/fc3803a9-f02f-4201-9b70-6fc2f76ca651.png">


データの書き込み、読み出しには SDA ピンと SCL ピンを使います（ I2C 通信）。内部で時間が進んだときは /INT ピンが変化するようです（割り込み用）。あとは FOUT から基準クロック信号を出力させる場合 FOE を HIGH にするようですが、使わないので LOW にしています。

また、RX8900 はボタン電池などを接続する用のピン VBAT が儲けられています。多くの RTC は電源の切り替え回路を外に取り付けると思いますが、RX8900 は自動で切り替えてくれるようです。ただし電流の逆流を防ぐためにダイオードを取り付けています。

使用するボタン電池は CR2032 です。ケースと一緒に秋月で購入しました。

>[リチウム電池 CR2032 ゴールデンパワー製](https://akizukidenshi.com/catalog/g/gB-05694/)
>
>[ボタン電池基板取付用ホルダー CR2032用（小型タイプ）](https://akizukidenshi.com/catalog/g/gP-00706/)

簡単なのでサクッと回路を作ります。

<img width="400" alt="part003-RTCテスト回路.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/4b1c2aa1-413d-8e03-4a15-afc497f56412.jpeg">

次のプログラムを書き込みます（ 3 つのファイルに分かれています）。
（ [GitHub のリンク](https://github.com/Gen-Gen-0504/nixie-clock/tree/master/test%20programs/rtc_test)）

```cpp:RTC.h
#pragma once
#include <Arduino.h>
#include <Wire.h>
#include <time.h>

/* ピン情報をまとめる構造体 */
struct RTCPinInfo {
  uint8_t scl;
  uint8_t sda;
  uint8_t n_int;
};

class RTC {
  private:
    const RTCPinInfo PIN;
    constexpr static uint8_t RTC_ADRS      = 0x32; // RX8900のアドレス
    constexpr static uint8_t DATE_TIME_REG = 0x00; // 時間が格納されている領域の先頭アドレス
    constexpr static uint8_t EXTENSION_REG = 0x0D; //-------------------------------// 
    constexpr static uint8_t FLAG_REG      = 0x0E; // 各種設定用の3つのレジスタのアドレス //
    constexpr static uint8_t CONTROL_REG   = 0x0F; //-------------------------------//

  public:
    RTC(RTCPinInfo rtcPinInfo);
    void setup();
    void setISR(void (*isr)()) const;      // 時刻更新割り込み時に実行する関数の設定
    void setDateTime(struct tm *tm);       // 時間の保存
    void getDateTime(struct tm *tm) const; // 時間の取得

  private:
    void    setRegs(uint8_t addr, uint8_t sz, uint8_t *data) const; // レジスタに値を設定
    void    getRegs(uint8_t addr, uint8_t sz, uint8_t *data) const; // レジスタの値を取得
    uint8_t dec2bcd(uint8_t dec) const; // 10進数→BCD
    uint8_t bcd2dec(uint8_t bcd) const; // BCD→10進数
};
```

```cpp:RTC.cpp
#include "RTC.h"

RTC::RTC(RTCPinInfo _PIN) : PIN(_PIN) {}

void RTC::setup() {
  uint8_t extreg = 0x20; // 分が更新されるごとに割り込み
  uint8_t flgreg = 0x00; // フラグのリセット
  uint8_t conreg = 0x20; // 時間更新割り込み開始
  uint8_t init_flgreg;   // フラグレジスタの初期状態
  struct  tm init_time = {0, 0, 0, 1, 0, 100, 6, 0, 0}; // 初期時刻 2000/01/01/00:00:00(SUT)
  
  delay(100);
  pinMode(PIN.n_int, INPUT_PULLUP);
  Wire.begin((int8_t)PIN.sda, (int8_t)PIN.scl);

  /* バッテリーの状態確認 */
  getRegs(FLAG_REG, 1, &init_flgreg); // フラグレジスタの値を取得
  if (init_flgreg & 0x02) {           // VLF(Voltage Low Flag)が1なら
    setDateTime(&init_time);          // 初期時刻を保存
  }
  else {/* 何もしない */}

  /* 各レジスタに値を設定 */
  setRegs(EXTENSION_REG, 1, &extreg);
  setRegs(FLAG_REG,      1, &flgreg);
  setRegs(CONTROL_REG,   1, &conreg);
}

void RTC::setISR(void (*isr)()) const {
  attachInterrupt(digitalPinToInterrupt(PIN.n_int), isr, FALLING);
}

void RTC::setDateTime(struct tm *tm) {
  uint8_t data[7];
  data[0] = dec2bcd(tm->tm_sec % 60);
  data[1] = dec2bcd(tm->tm_min);
  data[2] = dec2bcd(tm->tm_hour);
  data[3] = 1 << (tm->tm_wday);
  data[4] = dec2bcd(tm->tm_mday);
  data[5] = dec2bcd(tm->tm_mon + 1);
  data[6] = dec2bcd(tm->tm_year - 100);
  setRegs(DATE_TIME_REG, 7, data);
}

void RTC::getDateTime(struct tm *tm) const {
  uint8_t data[7];
  getRegs(DATE_TIME_REG, 7, data);
  tm->tm_sec  = bcd2dec(data[0] & 0x7f);
  tm->tm_min  = bcd2dec(data[1] & 0x7f);
  tm->tm_hour = bcd2dec(data[2] & 0x3f);
  tm->tm_wday = [&]{uint8_t wday = 0; while(data[3]>>=1){++wday;} return wday;}();
  tm->tm_mday = bcd2dec(data[4] & 0x3f);
  tm->tm_mon  = bcd2dec(data[5] & 0x1f) - 1;
  tm->tm_year = bcd2dec(data[6]) + 100;
}

void RTC::setRegs(uint8_t addr, uint8_t sz, uint8_t *data) const {
  Wire.beginTransmission(RTC_ADRS);
  Wire.write(addr);
  Wire.write(data, sz);
  Wire.endTransmission();
}

void RTC::getRegs(uint8_t addr, uint8_t sz, uint8_t *data) const {
  Wire.beginTransmission(RTC_ADRS);
  Wire.write(addr);
  Wire.endTransmission();
  Wire.requestFrom(RTC_ADRS, sz);
  for (uint8_t i = 0; i < sz; i++) {
    data[i] = Wire.read();
  }
}

uint8_t RTC::dec2bcd(uint8_t dec) const {
  return (((dec / 10) << 4) | (dec % 10));
}

uint8_t RTC::bcd2dec(uint8_t bcd) const {
  return ((bcd >> 4) * 10 + (bcd & 0x0f));
}
```

```cpp:rtc_test
#include "RTC.h"

const uint8_t PIN_SCL   = 32;
const uint8_t PIN_SDA   = 33;
const uint8_t PIN_N_INT = 27;

const RTCPinInfo rtcPinInfo {
  PIN_SCL,
  PIN_SDA,
  PIN_N_INT
};

RTC rtc(rtcPinInfo);                     // RTCクラスのインスタンスを生成
bool tick_flag = false;                  // 時間更新割り込み発生フラグ
void setupTickFlag() {tick_flag = true;} // 時間更新割り込み時に実行する関数
struct tm tm;                            // 時刻保存用の構造体


void setup() {
  rtc.setup();
  rtc.setISR(setupTickFlag);
  Serial.begin(115200);
}

void loop() {
  if(tick_flag) {
    rtc.getDateTime(&tm);
    Serial.println(&tm, "%A, %B %d %Y %H:%M:%S");
    tick_flag = false;
  }
}
```


すこし長く見えますが、ほとんどが I2C 通信に関するものです。

順に解説していきます。


#### RTC.h と RTC.cpp

まずは、RTC.h と RTC.cpp を見ていきます。
このヘッダファイルと cpp ファイルは、RTC と I2C 通信を行い、時刻の読み書きを行います。



RTC.h の最初は必要なライブラリのインクルードと構造体の宣言です。
Wire.h は I2C 通信に必要です。また、time.h には構造体 tm が定義されており、時刻を格納するために使います。

```cpp:RTC.hの一部
#include <Arduino.h>
#include <Wire.h>
#include <time.h>

/* ピン情報をまとめる構造体 */
struct RTCPinInfo {
  uint8_t scl;
  uint8_t sda;
  uint8_t n_int;
};
```

次に定義されているのが、RTC.h および RTC.cpp のメインとなる RTC クラスです。


```cpp:RTC.hの一部
class RTC {
  private:
    const RTCPinInfo PIN;
    constexpr static uint8_t RTC_ADRS      = 0x32; // RX8900のアドレス
    constexpr static uint8_t DATE_TIME_REG = 0x00; // 時間が格納されている領域の先頭アドレス
    constexpr static uint8_t EXTENSION_REG = 0x0D; //-------------------------------// 
    constexpr static uint8_t FLAG_REG      = 0x0E; // 各種設定用の3つのレジスタのアドレス //
    constexpr static uint8_t CONTROL_REG   = 0x0F; //-------------------------------//

  public:
    RTC(RTCPinInfo rtcPinInfo);
    void setup();
    void setISR(void (*isr)()) const;      // 時刻更新割り込み時に実行する関数の設定
    void setDateTime(struct tm *tm);       // 時間の保存
    void getDateTime(struct tm *tm) const; // 時間の取得

  private:
    void    setRegs(uint8_t addr, uint8_t sz, uint8_t *data) const; // レジスタに値を設定
    void    getRegs(uint8_t addr, uint8_t sz, uint8_t *data) const; // レジスタの値を取得
    uint8_t dec2bcd(uint8_t dec) const; // 10進数→BCD
    uint8_t bcd2dec(uint8_t bcd) const; // BCD→10進数
};
```

RTC クラスは、次のようなメンバ変数（定数）を持ちます。

- PIN
    通信に必要な 3 つのピンをまとめた構造体 RTCPinInfo のオブジェクト

- RTC_ADRS
    I2C 通信時に必要となる RX8900 のアドレス。サンプルスケッチを参照

- DATE_TIME_REG
    時刻が格納されている領域の先頭アドレス（「秒」のアドレス）

- EXTENSION_REG 、FLAG_REG 、CONTROL_REG
    アラームや割り込み設定などといった RTC の設定を行うためのレジスタのアドレス

***

次に関数を説明します。
最初に定義しているのはコンストラクタです。内容は簡潔で、RTCPinInfo 構造体を引数にとり、その値でメンバ変数 PIN を初期化しています。

```cpp:RTC.cppの一部
RTC::RTC(RTCPinInfo _PIN) : PIN(_PIN) {}
```

次に初期設定を行うメンバ関数 setup() です。

```cpp:RTC.cppの一部
void RTC::setup() {
  uint8_t extreg = 0x20; // 分が更新されるごとに割り込み
  uint8_t flgreg = 0x00; // フラグのリセット
  uint8_t conreg = 0x21; // 時間更新割り込み開始
  uint8_t init_flgreg;   // フラグレジスタの初期状態
  struct  tm init_time = {0, 0, 0, 1, 0, 100, 6, 0, 0}; // 初期時刻 2000/01/01/00:00:00(SUT)
  
  delay(100);
  pinMode(PIN.n_int, INPUT_PULLUP);
  Wire.begin((int8_t)PIN.sda, (int8_t)PIN.scl);

  /* バッテリーの状態確認 */
  getRegs(FLAG_REG, 1, &init_flgreg); // フラグレジスタの値を取得
  if (init_flgreg & 0x02) {           // VLF(Voltage Low Flag)が1なら
    setDateTime(&init_time);          // 初期時刻を保存
  }
  else {/* 何もしない */}

  /* 各レジスタに値を設定 */
  setRegs(EXTENSION_REG, 1, &extreg);
  setRegs(FLAG_REG,      1, &flgreg);
  setRegs(CONTROL_REG,   1, &conreg);
}
```

最初に定義している 3 つのレジスタにそれぞれ書き込む値（ extreg 、flgreg 、conreg ）は、アプリケーションマニュアルとにらめっこしながら決めました。

このプログラムで重要な設定は、
- 分が更新されるたびに割り込みを発生させる（ USEL = 1 ）
- 割り込みのみ作動させ、アラーム、タイマーは作動させない（ UIE = 1 、AIE = TIE = 0 ）

ぐらいです。(USELなどの名称はアプリケーションマニュアルで説明されています)

変数の定義の後、RTCPinInfo 構造体の PIN の値を使って設定を行っています。
PIN.n_int は RTC の /INT ピンに繋がっていて、割り込み発生時に値が変わります。プルアップ回路の代わりに内部プルアップを使い設定します。

また I2C 通信を始めるため、Wire.begin() 関数を呼び出します。I2C 通信に必要な 2 つのピン情報を、int8_t にキャストして与えます。これで I2C 通信が可能になります。
（キャストしているのは、begin() の引数が int 型だからです。このキャストをしないとまともに通信ができません。見事にハマって原因特定のために丸 1 日要しました.....）

その後、フラグレジスタ内の VLF という値を確認しています。
VLF（ Voltage Low Flag ）が 1 だった場合、ボタン電池による電源供給ができていなかったことになります。
その場合は初期時刻（ 2000/01/01/00:00:00(SUT) ）を書き込むことにしています。

最後に、定義した各レジスタへの値を I2C 通信によって書き込み、終了です。

他に特筆すべきメンバ関数に、setISR() がありますが

```cpp:RTC.cppの一部
void RTC::setISR(void (*isr)()) const {
  attachInterrupt(digitalPinToInterrupt(PIN.n_int), isr, FALLING);
}
```

これは特定のピンの状態変化によって呼び出す関数を設定する、attachInterrupt() をラップしているだけです。
分が更新されたとき、RTC の /INT ピンが立ち下がります。その時に呼びたい関数のポインタを引数にとり、PIN.n_int と立ち下がりを表す FALLING を指定して attachInterrupt() で設定します。
なお ISR とは、割り込みルーチン（ Interrupt Service Routine ）の略です。割り込みの際呼び出される処理をこう呼ぶそうです。

残りのメンバ関数は、[参考サイト](https://smtengkapi.com/engineer-esp32-wroom-32-rtc)やサンプルスケッチなどから I2C 通信を行う部分を抜き取り、部分的に手を加えたものなので、詳しい説明は省きます。

setDateTime() や getDateTime() は、構造体 tm のオブジェクトを引数にとり、その内容を RTC に書き込んだり、逆に RTC の内容を tm に書き込みます。

#### rtc_test について

定義した RTC クラスを使って 1 分ごとに時刻を表示します。

まずはピン番号の定義と構造体を作成し、その構造体を用いて RTC クラスのインスタンスを生成します

```cpp:rtc_testの一部
const uint8_t PIN_SCL   = 32;
const uint8_t PIN_SDA   = 33;
const uint8_t PIN_N_INT = 27;

const RTCPinInfo rtcPinInfo {
  PIN_SCL,
  PIN_SDA,
  PIN_N_INT
};

RTC rtc(rtcPinInfo);  // RTCクラスのインスタンスを生成
```

また、割り込み検知用のフラグ tick_flag と、割り込み発生時に呼ばれ、tick_flag を立てる関数 setupTickFlag() を定義します。

```cpp:rtc_testの一部
bool tick_flag = false;                  // 時間更新割り込み発生フラグ
void setupTickFlag() {tick_flag = true;} // 時間更新割り込み時に実行する関数
```

setup() では、RTC クラスの setup() を呼び出し、setupTickFlag() を setISR() で割り込みがあった際に呼び出されるよう設定します。

```cpp:rtc_testの一部
void setup() {
  rtc.setup();
  rtc.setISR(setupTickFlag);
  Serial.begin(115200);
}
```

loop() では、tick_flag を監視し、フラグが立った場合は RTC から時刻を取得してシリアルモニタに表示します。

```cpp:rtc_testの一部
void loop() {
  if(tick_flag) {
    rtc.getDateTime(&tm);
    Serial.println(&tm, "%A, %B %d %Y %H:%M:%S");
    tick_flag = false;
  }
}
```

***

#### 保存された時刻データが無い場合でテスト

まず、ボタン電池を接続していない、つまり RTC 内に正常なデータが残っていない状態でテストします。
VLF が 1 なので、初期時刻が書き込まれるはずです。


<img width="500" alt="part003-保存された時刻データが無い場合でテスト結果.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/9020ef56-14b7-9a97-e160-15e81c2b2545.png">

初期時刻が RTC 書き込まれ、1 分ごとに時刻が表示されます。正常に割り込みが起こっていることも確認できました。

次のテストのため、実行中にボタン電池を接続します。これで、RTC 内のデータが保存されるようになるはずです。
その後、ESP32 への電源供給を断ち、5 分間放置しました。


#### 保存された時刻データが有る場合でテスト

再度 ESP32 を起動させ、RTC から時刻を読み込めるかを確認します。

<img width="500" alt="part003-保存された時刻データが有る場合でテスト結果.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/b3706574-3aba-c5a6-ef3b-b054aab80873.png">

ボタン電池によって内部でカウントが進められていることと、保存されたデータが読み出せることが確認できました！




# NTP と RTC で時刻を管理する

NTP による時刻の取得と、RTC への保存を組み合わせてみます。
回路は RTC の回路と同じです。

以下のプログラムを書き込みます。NTP.h 、RTC.h と RTC.cpp は引き続き使います。
（ [GitHub のリンク](https://github.com/Gen-Gen-0504/nixie-clock/tree/master/test%20programs/ntp_and_rtc_test)）



```cpp:ntp_and_rtc_test
#include "NTP.h"
#include "RTC.h"
#include <WiFi.h>

const char* SSID = "YOUR_SSID";
const char* PASS = "YOUR_PASSWORD";
NTP ntp(SSID, PASS); // NTPクラスのインスタンスを生成

const uint8_t  PIN_SCL   = 32;
const uint8_t  PIN_SDA   = 33;
const uint8_t  PIN_N_INT = 27;
const RTCPinInfo rtcPinInfo {PIN_SCL, PIN_SDA, PIN_N_INT};
RTC rtc(rtcPinInfo); // RTCクラスのインスタンスを生成

bool tick_flag = false;                  // 時間更新割り込み発生フラグ
void setupTickFlag() {tick_flag = true;} // 時間更新割り込み時に実行する関数
struct tm tm;                            // 時刻保存用の構造体


void setup() {
  Serial.begin(115200);
  ntp.setup();
  rtc.setup();
  rtc.setISR(setupTickFlag);
  if(ntp.getIsConfigured()) { // Wi-Fi接続成功時
    ntp.getTime(&tm);
    rtc.setDateTime(&tm); // RTCに時刻を保存
    Serial.println(&tm, "%A, %B %d %Y %H:%M:%S");
    ntp.disconnect();
  }
  else { // Wi-Fi接続失敗時
    Serial.println("Could not connect to network.");
  }
}

void loop() {
  if(tick_flag) {
    rtc.getDateTime(&tm);
    Serial.println(&tm, "%A, %B %d %Y %H:%M:%S");
    tick_flag = false;
  }
}
```

ほとんどそのまま NTP のプログラムと RTC のプログラムを合体させています。
NTP で取得した時刻を RTC に保存しています。

***

テストしてみます。

まずは Wi-Fi に接続できる状況で動作させてみます。

<img width="700" alt="part003-NTPとRTC Wi-Fi接続有テスト結果.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/a5d073c9-8d43-ddaa-11e2-cf96ccf36cd6.png">

NTP によって取得された時刻の表示と、時刻を保存した RTC からの割り込みによる表示を確認できました。

次に、接続している Wi-Fi をオフにして実行してみます。

<img width="700" alt="part003-NTPとRTC Wi-Fi接続無データ有テスト結果.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/cae4f54a-7221-6a34-7fe3-a6cee52a8565.png">

ネットワーク接続エラーの表示がされていますが、RTC にはボタン電池によって時刻データが保持されていたため正常に動作しています。

最後に、RTC への電源供給を断ち、再度実行してみます。

<img width="700" alt="part003-NTPとRTC Wi-Fi接続無データ無テスト結果.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821099/8960dafd-4d7b-603c-a565-0a9a7ea91e3c.png">

この場合は初期時刻が書き込まれています。

NTP と RTC を組み合わせて、時計システムに必要な時刻の取得と表示ができました！！！


# おわりに

情報に恵まれていたおかげで、NTP と RTC はどっちも想像以上に簡単でした。ありがたいですね。

次回は、今回の時刻の取得とニキシー管点灯のプログラムと合体させ、とうとうニキシー管に時刻を表示します！

次回：[ESP32でニキシー管時計を作る part 4：プログラム完成編](https://qiita.com/Gen-Gen-0504/items/ccbd5b7e18c817d844bd)