ArduinoにRTCつけているけど時刻がすこしずつずれてくるので

rtc.adjust(DateTime(2021,3,1,11,00,00))なんて書いてコンパイルして書き込んで使ってたけど

まあ大体でいいけどと思いながらも、当然ながら10~20秒程度はどうしてもずれるし、だんだんRTCの時刻はずれてくる。となるとたびたび時刻合わせないといけなくなる。

 

と言うことでESP32開発ボードではNTPでの標準時刻取得できるので、せっかくならその時刻をRTCに書き込もうとプログラム追記。

 

最初は今までのスケッチに加える形で追加して書いてたら、いじってるうちに急にコンパイルエラーでどこまで戻してもうまくいかないようなと言うか、妙なコンパイルエラーが出たり、どこでバグってるかわからないトホホ状態になってしまいました。

 

そんな時は、最初からってことで最初から、WifiとRTCだけにして。

やっとうまくいきました。

 

setupでwifiにつないで、NTP時刻取得出来たら、その時刻をRTCに書込み、loopで1秒おきに表示ってことで作りました。

これで書き込んだRTCを取り外してArduino側にくっつければArduino側の時刻もあってるってことで。

 

RTCはamazonで買った3足千円のDS3231のやつです。image

 

 

ちなみに最初にバグった原因はたぶんここです

  char rsDTime[20]

yyyy/mm/dd hh:mi:ss 

ここのサイズギリギリで19byteにしてました。1byte足りなかったようで、20byteにしないとコンパイルはできても暴走でリブートの繰り返しになってしまいます。C言語初心者のよくハマるところのようで。

 

スケッチはこんな感じです

 

/*
 * NTP_TimeSet2RTC
   WiFi接続テスト最初から再確認
    20210320 URK
*/

//SSID3つどれかつながりますように
#define WID1 "ssid1"
#define WID2 "ssid2"
#define WID3 "携帯テザリングssid3"
#define WPass "password"

#include <Wire.h>  //I2C接続用
#include <RTClib.h>
RTC_DS3231 rtc;

// WiFi Setting
#include <WiFi.h>
#include <time.h>
#define JST     3600*9

/***********************
   初期設定
 ***********************/
void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("-------------");

  //RTC
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  // Wifi接続トライ
  int WiFiCon = WifiConnect(WID1, WPass);
  Serial.println("WiFiCon:" + String(WiFiCon) + " WiFi.status:" + String(WiFi.status()) + " WiFi.IP:" + ipToString(WiFi.localIP()));
  if (WiFiCon < 1) {
    WiFiCon = WifiConnect(WID2, WPass);
    Serial.println("WiFiCon:" + String(WiFiCon) + " WiFi.status:" + String(WiFi.status()) + " WiFi.IP:" + ipToString(WiFi.localIP()));
  }
  if (WiFiCon < 1) {
    WiFiCon = WifiConnect(WID3, WPass);
    Serial.println("WiFiCon:" + String(WiFiCon) + " WiFi.status:" + String(WiFi.status()) + " WiFi.IP:" + ipToString(WiFi.localIP()));
  }

  if (WiFiCon < 1) {  //WifiだめだったらRTCから時刻取得
    Serial.println("Wifiつながらないので RTCから時刻読込");
  } else {             //OKだったら NTCから時刻取得して RTCに時刻設定
    Serial.println("Wifi NTPから時刻読込");
    configTzTime("JST-9", "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");  // 2.7.0以降, esp32コンパチ
    delay(500);
    struct tm *tm;
    time_t t;
    t = time(NULL);
    tm = localtime(&t);
    Serial.println(tm);
    Serial.println("RTCへ時刻設定");
    rtc.adjust(DateTime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec)); //日付と時刻の書き込み
  }
}

/************************************
* Main  毎秒時刻表示
 *************************************/
void loop() {
  char rsDTime[20];  //yyyy/mm/dd_hh:mm:ss で19byte+1必要
  DateTime RTCnow = rtc.now();
  sprintf(rsDTime, "%02d/%02d/%02d %02d:%02d:%02d", RTCnow.year(), RTCnow.month(), RTCnow.day(), RTCnow.hour(), RTCnow.minute(), RTCnow.second());
  Serial.println(rsDTime);
  delay(1000);
}

/*********************************************
   Wifi 10回トライ ダメだったら0を返す
*********************************************/
int WifiConnect(char *WID, char *Wpass) {
  int WIFIid = 0;
  WiFi.begin(WID, Wpass);
  for (int i = 0; i <= 10; i++) {
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("Wifi Connect:" + String(WID));
      return 1;
    }
    Serial.print(".");
    delay(500);
  }
  Serial.println("Wifi Can’t:" + String(WID));
  return 0;
}

String ipToString(uint32_t ip) {
  String result = "";
  result += String((ip & 0xFF), 10);
  result += ".";
  result += String((ip & 0xFF00) >> 8, 10);
  result += ".";
  result += String((ip & 0xFF0000) >> 16, 10);
  result += ".";
  result += String((ip & 0xFF000000) >> 24, 10);
  return result;
}

 

 

 

気圧もデータログに入れたいので秋月でBME280モジュールキット購入

 
I2C接続で使うのでJ3ハンダ付け、J1,J2もハンダで繋いでプルアップ、後は配線するだけで使えるのは便利。

接続は下記です
SCK SCL     GPIO22
SDO GND (0x76)
SDI SDA      GPIO21
CSB 未使用
GND GND
VDD 3.3V
J3 ハンダで接続  I2Cで利用
J1,J2 ハンダで接続 プルアップ利用(4.7kΩ)
 
クロック(白)とデータ(青)の線はSSD1306のところから延長でI2Cはこれで済むのでいい。

スケッチではBME280のライブラリー読み込んだらそのまま簡単に値読めるんですね。ビットでとりだしてシフトして数字作る必要もなく
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
Adafruit_BME280 bme;
 
void setup() {
 Serial.begin(115200);
  //BME280接続
  bool status;
  status = bme.begin(0x76);
  while (!status) {
    Serial.println("BME280 sensorが使えません");
    delay(1000);
  }
}
 
void loop() {
  float BME_temp = bme.readTemperature();
  float BME_press = bme.readPressure() / 100.0f;
  float BME_hum = bme.readHumidity();
 
  Serial.print(" BME temp=" + String(BME_temp, 1) + " Press=" + String(BME_press, 0) + " Hum=" + String(BME_hum, 1));
}
これだけで取得できるので、初心者の私には有難いです。
 
サンプルスケッチはWak-techさんのサイトからそのままコピペでまずテスト
 
後はBME280はどの程度の精度なのか。いつものWeatherReporterで確認
気圧:ほぼ同じでOK。
 
温度:1〜3℃ほど高い??? サミスタの先端をBME280の下に入れて測ったら、ほぼ一致。サミスターの値は補正してほぼ確認取れてるので。
ブレッドボードの表面とその少し上、ほんの少しの場所の違いで温度が2,3℃は違うようです。
まして部屋の中だと足元に置くか、棚の上に置くかで2~5℃程度はすぐ違ってくるので、まあそんな物なんでしょう。

 
湿度:ううんこればかりは何が正しいのか、BME280で43%  WeatherRepoterで52~53%
ちなみにArduino側に付けているDHT11だと44%。こればかりは何をもって正しいとするのかがわからないので。「ハー」っと息をかけると一気に上がるので、それなりに測定されていると思うので、きっとチップのところの湿度はこれで正しいのでしょう。
 
実際に使う場合はBME280の設置位置や空気の流れによるところは大きいので、温室などの測定だと何を測っているのかってことにならないようにしないとね。
ただサミスターよりは電圧や温度での補正などしなくていい(できない)ので、そこは便利かも。
 

Arduinoと違いESP32開発ボードでのプログラムの書きこみはコンパイルが終わり「Connecting...___...」の表示の時に[Boot]ボタンを押さなきゃ書込みできないものだと思ってたらどうもそうでもないらしい。下記の@h_nariさんのサイト読んだら

 

 

「問題は USB経由だとUARTの制御線(RTSやDTR)をあまり高速に変化させることができないということです。制御線を変化させるには、USBにパケットを送出する必要があるので、高速にはできないのです。」

「対策 EN-GND間に0.1uFのコンデンサを追加しました」

と書いてありました。書込みモードへの切り替えの信号波形が微妙に(0.5ms程度)タイミングがずれてるのでモードの切り替えがうまくいかずないので、波形のタイミングと合わせるるためにコンデンサを入れると良いようです。

ということで、EN-GNDの間に0.1μFのコンデンサ入れたら確かにBOOTボタン押さなくても安定して自動的に書込みできるようになりました。0.5ms程度の微妙なタイミングのずれなんですね。

これで指先に丸い跡がつかなくなりました。

 

ボードの下の配線で入れました


 

 

 

ESP32でOLEDディスプレイ(SSD1306)つけてプログラムの検証用に1つ。これで別のライブラリーでフォントサイズや表示方向のテストなど簡単なアプリのテストがちょいと思いついたときにできそう。 バッグの中に入れておいて移動中にも遊べるかな。
image
ピンの配置幅ギリギリなので、
配線は一部ボードの下でこんな感じになってます

10kのプルアップ抵抗は無くても動作したので、配線見直してシンプルに

まずはスケッチも最低限でテスト用のベース出来上がり

まだディスプレイの下が空いてるのでマイクロSDカードでも付けられそう。

ESP32は本体にWiFi,Blutoothも入ってるからこのままでもけっこう遊べそう

*
   ESP32 Display SSD1306のテスト
*/
#include <Wire.h>
#include "SSD1306.h"      //ディスプレイ用ライブラリを読み込み

SSD1306  display(0x3c, 21, 22);   //SSD1306インスタンスの作成(I2Cアドレス,SDA,SCL)

void setup() {
  display.init();    //ディスプレイを初期化
  display.setFont(ArialMT_Plain_10);    //フォントを設定
  display.drawString(0, 0, "123456789A123456789B123");
  display.setFont(ArialMT_Plain_16);    //フォントを設定
  display.drawString(0, 10, "123456789A123456789B123");
  display.setFont(ArialMT_Plain_24);    //フォントを設定
  display.drawString(0, 26, "123456789A123456789B123");
  display.display();   //指定された情報を描画
}

void loop() {
}

 ーーーーーーーーーーーーーーーーーーーーー

こちらはNTP時計表示バージョン。GPIO5にはLED付けてちょっとしたテストチカチカも出来る様にしてみました。

 


 

 

さて次はNTPで時刻合わせ。

ArduinoではRTC購入して配線して現在時刻をプログラムで書き込んでとやったけど

ESP32は標準でWiFi機能持ってるとはいえどんなものかなと。

あれまあほんとに1行だ

  configTzTime("JST-9", "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); 

Wifiのアクセスポイントに接続して上の1行で時刻取得、あとは取得した時刻を表示形式を見やすくするだけ。

こりゃあいい。配線も何もいらない。コンパイルして書き込むだけ。

アクセスポイントつなげない場所だとRTCいるけど。

image

今日のスケッチ

/*
 * ESP32 JST から時間取得
 * 
 * ref: https://qiita.com/h_nari/items/d0374d1e1e36b9d988c0
 */

#include <WiFi.h>
#include <time.h>

#include <SPI.h>
#include <Wire.h>
#include "SSD1306.h" 
SSD1306  display(0x3c, 21, 22); //SSD1306インスタンスの作成(I2Cアドレス,SDA,SCL)

// WiFi Setting
#define WIFI_SSID       "AP名前"
#define WIFI_PASSWORD   "APパスワード"
#define JST     3600*9

void setup() {
  Serial.begin(115200);
  display.init();    //ディスプレイを初期化

  Serial.print("\n\nReset:\n");

  // WiFi starting
//  drawLog("WiFi connecting...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while(WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(500);
  }
  Serial.println();
  Serial.printf("Connected, IP address: ");
  Serial.println(WiFi.localIP());
//  drawLog("WiFi connected!");

  // NTP start
  //configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");  // こっちでも動いた
  configTzTime("JST-9", "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); 

                              // 2.7.0以降, esp32コンパチ
  delay(1000);
}

void loop() {
  time_t t;
  struct tm *tm;
  static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};
  char rdate[30], rtime[30];


  t = time(NULL);
  tm = localtime(&t);

  Serial.printf(" %04d/%02d/%02d(%s) %02d:%02d:%02d\n",
        tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
        wd[tm->tm_wday],
        tm->tm_hour, tm->tm_min, tm->tm_sec);

  sprintf(rdate, " %04d/%02d/%02d(%s)",
        tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, wd[tm->tm_wday]);
  
  sprintf(rtime, " %02d:%02d:%02d", 
        tm->tm_hour, tm->tm_min, tm->tm_sec);

  display.clear();
  display.setFont(ArialMT_Plain_16);    //フォントを設定
  display.drawString(0, 0,rdate);
  display.drawString(0, 16,rtime);
  display.display();   //指定された情報を描画

//  drawClock(rdate, rtime);      
  delay(1000);
}