ESP8266でNTPサーバーから時刻を取得してRTCに書き込む

前回はESP8266でRTC-8564NBを動かすことをやりました。
alasixosaka.hatenablog.com

ただ、これでは時刻が適当に設定されるだけなので、ESP8266の真骨頂であるWiFi機能を使ってNTPサーバーから時刻を取得し、正確な時刻をRTCに書き込んでみます。ESP8266やESP32を使ってNTPサーバーから時刻を取得するプログラムはいろいろと公開されています。WiFiにさえ繋いでしまえば実はNTPサーバーから時刻を取得するのは非常に簡単です。
今回はこちらのサイトを参考にしました。
garretlab.web.fc2.com
ソースコードが自分的に読みやすかったからです。でも、実はこれはESP32用のプログラムでそれが原因でまたはまってしまいました。どうなったかは後で書きます。
まず、NTPサーバーから時刻を取得する部分ですが、たったの1行で、

configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");

で済みます。
そいつを

struct tm timeInfo;

で定義したtimeInfoを使って

getLocalTime(&timeInfo);

としてやればtimeInfoに年、月、日、曜日、時、分、秒が格納されます。
年なら、timeInfo.tm_year、月なら、timeInfo.tm_monthといった具合です。
ところが、このgetLocalTime()という関数がESP32の関数になっていて、ESP8266用のプログラムでは動きません。
検索すると、Arduinoのフォーラムに解決策が書いてありました。
arduino.stackexchange.com
ここに書いてあるサブルーチンをそのまま使ってやるとエラーが解消されてちゃんと動きました。
これで、現在時刻が取得できたので、それをBCDコードに直して、RTCに書き込んでやればよいということになります。
それだけではおもしろくないので、ディープスリープを使って、必要ない時はESP8266をスリープさせるようにしました。本当はRTCで目覚めるようにしたいのですが、前回書いたようにそのままつないでも起きてくれないので、今回はタイマーを使って1時間ごとに目覚めるようにしました。ディープスリープの関数は下記のようになります。

ESP.deepSleep(3600 * 1000 * 1000, WAKE_RF_DEFAULT);

また、電池駆動にしてAmbientというネットサービスを使ってVCCの電圧をウェイクアップするたびに送信するようにしてみました。
Ambientを使えば送信したデータをグラフ化してくれます。
ambidata.io

配線はこんな感じです。図では、LiPoを電池に使っていますが、実際は、NiMHのエネループを3本直列に繋いでいます。

電圧の取得は、

int v=ESP.getVcc();

で取得できます。
それを、ループ関数内で

ambient.set(1, v);
delay(100);
ambient.send();
delay(1000);

としてAmbientに送っています。delayはデータが確実に送られるように適当に入れています。
Ambientを使うには、HPで登録をして、チャンネルを作成し、チャンネルのNo と ライトキーをあらかじめ取得しておく必要があります。詳細な手順はAmbentのHPに書いてあるのでその通りにやれば問題なくできると思います。
また、AmbientをArduinoで使うにはライブラリをインクルードする必要があります。ライブラリは、Arduino IDEのライブラリを追加から、Ambientを検索することで見つけることができます。見つかったライブラリをクリックしてインストールしておきます。
プログラムの全文です。

#include <ESP8266WiFi.h>
#include <time.h>
#include <Wire.h>
#include <Ambient.h>

#define ssid "XXXXXXXX"      //your SSID
#define password "YYYYYYYY"    //your Password
#define channelId ZZZZZZ  // 生成したAmbientチャネルID
#define writeKey "WWWWWWWW"  // Ambient のライトキーをセット

ADC_MODE(ADC_VCC)
Ambient ambient;
WiFiClient client;
char s[32];
int year;
int month;
int day;
int hour;
int minute;
int sec;
int wday;
int RegTbl[16]; 
byte DEVICE_ADDRESS= 0x51; 

byte yearBCD;
byte monthBCD;
byte dayBCD;
byte hourBCD;
byte minuteBCD;
byte secBCD;

// 2進化10進数(BCD)を10進数に変換
byte BCDtoDec(byte value){
  return ((value >> 4) * 10) + (value & 0x0F) ;
}
//整数変数をBCDに変換する関数
byte DectoBCD(int value){
  int dec = (value / 10)*16;
  return ((value%10)+dec);
}

void init_RTC_8564(void){
  Serial.println("write start");
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write(0x00);  //Cotrol Reg1
  Wire.write(0x20);  //Stop RTC
  Wire.write(0x01);  //Control Reg2
  Wire.write(secBCD);  //0 second
  Wire.write(minuteBCD);  //0 min
  Wire.write(hourBCD);  //0 hour
  Wire.write(dayBCD);  //1st
  Wire.write(wday);  //Tuesday
  Wire.write(monthBCD);  //November
  Wire.write(yearBCD);  //2022
  Wire.write(minuteBCD+1);  //Alarm min
  Wire.write(0x80);  //Alarm hour not 
  Wire.write(0x80);  //Alarm day not
  Wire.write(0x80);  //Alarm weekday not
  Wire.write(0x00);  //Stop interval timer
  Wire.write(0x00);  //interval timer counter
  Wire.write(0x00);  //not clockout
  Wire.endTransmission();
  delay(50);
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write(0x00);  //control reg1
  Wire.write(0x00);  //start RTC
  Wire.endTransmission();
  //delay(50);  
}

bool getLocalTime(struct tm * info, uint32_t ms = 5000)
{
    uint32_t start = millis();
    time_t now;
    while((millis()-start) <= ms) {
        time(&now);
        localtime_r(&now, info);
        if(info->tm_year > (2016 - 1900)){
            return true;
        }
        delay(10);
    }
    return false;
}

void setup(){
  Serial.begin(115200);
  delay(1000);
  Serial.print("\n\Start:\n");
  // マスタとしてI2Cバスに接続する
  Wire.begin(4,14); 
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
  }
  Serial.println();
  Serial.printf("Connected, IP address: ");
  Serial.println(WiFi.localIP());
  configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");

  struct tm timeInfo;
   Serial.println("get time");
   getLocalTime(&timeInfo);
   
   year = timeInfo.tm_year + 1900;
   month = timeInfo.tm_mon + 1;
   day = timeInfo.tm_mday;
   hour = timeInfo.tm_hour;
   minute = timeInfo.tm_min;
   sec = timeInfo.tm_sec;
   wday = timeInfo.tm_wday;
   Serial.println("local time get");
   
   yearBCD = DectoBCD(year-2000);
   monthBCD = DectoBCD(month);
   dayBCD = DectoBCD(day);
   hourBCD = DectoBCD(hour);
   minuteBCD = DectoBCD(minute);
   secBCD = DectoBCD(sec);
   init_RTC_8564();
  Serial.println("start RTC");
  ambient.begin(channelId, writeKey, &client);  //  チャネルIDとライトキーを指定してAmbientの初期化
  delay(100);
}

void loop(){
   
  int v=ESP.getVcc();
  Serial.println("VCC="+String(v/1000.0)+"V");
  ambient.set(1, v);
  delay(100);
  ambient.send();
  delay(1000);
  Serial.println("Go to Sleep");
  ESP.deepSleep(3600 * 1000 * 1000, WAKE_RF_DEFAULT);
  delay(1000);
}

今回は、RTCのデータを読み取ってシリアル出力する部分は省略しています。
AmbientでVCCの電圧を見ると4Vを越えています。昔、Ni-MHの電池は1.2Vと習ったので3本で3.6VでESP8266などの3.3V系の動作にはちょうど良いと思ったのですが、ちょっと電圧が高すぎます。まあ、壊れずに動いているので一応セーフなのかもしれませんが、ちょっと怖い。
それと、状況をモニタするために、要所要所にシリアル出力が入っていますが、何時間もモニタするのも面倒だと思い、USBケーブルを外してPCをシャットダウンしたらプログラムが止まったのか、Ambientにデータが更新されなくなりました。

USBケーブルを外すとAmbientのデータ更新が止まった。

図のグラフは、外してしばらくほっておいて、データが更新されていないので、もう一度USBケーブルを繋いで、リセットしたデータで、本来1時間おきにデータがあるはずなのに、ずっとデータが抜け落ちている。ただほっておいて、Ambientにだけデータを送るということであればプログラム中のシリアル出力文を全部コメントアウトする必要がありそう。