「えだまめ」しているラズパイ

30年ぶりに半田ごて握ってラズパイ勉強中。

WROOM02でRTC8025を使えるようになる(1) まずはRTC8564で時計作り

◯やりたいこと

値段が爆上がり(秋月:800円)してしまったRTC8564、数をそろえるには少々お高くなってしまったので少し安いRX8025(同:450円)を使いこなせるようになりたいという要求が。そこでRTC8564とSSD1306を利用した時計(BME280:温湿度・気圧計付き)を作り必要な部分のみを変更する事によってRX8025へすんなり移行することを目指します。


◯やったこと

・製作

まずはWROOM02でRTC8564とBME280を使いOLED(SSD1306)にデータを表示する時計を作ります。RTC8564バージョンは過去に温湿度計ロガーやモーションセンサーで必要な関数が出来上がっていますので比較的簡単に作ることができます。

回路的にはWROOM02のi2cラインにSSD1306とBME280をぶら下げるだけですのでそれほど難しい回路ではありません。ブレッドボードにササっと組み上げていきます。

配線をし

WROOM02,SSD1306,BME280,RTC8564をセットしたら

もう完成です。ブレッドボードは手間がかからないのでホント助かります。

・スケッチ

スケッチは以下の通りとなります。

/*
*    WR02_RTC_OLED v0.0.4
*/

// BSP(Board Support Package)
//   ESP8266 Boards v3.1.2
//     FlushSize ESP01:1MB Wroom-02D:2MB -02:4MB

// Built-in Library (Arduino IDE v1.8.19)
#include            <Arduino.h>
#include            <time.h>
#include            <Wire.h>

// BSP Built-in Library
#include            <ESP8266WiFi.h>       // v1.0

// Installed Library
#include            <Adafruit_GFX.h>      // v1.11.3
#include            <Adafruit_SSD1306.h>  // v2.5.7
#include            <Adafruit_BME280.h>   // v2.2.2

// Global Variable
/* WiFi */
#define WIFI_SSID   "YOUR_SSID"
#define WIFI_PASS   "YOUR_PASS"

/* TCP/IP */
IPAddress ip        (YOUR_IP);            // ex. (192,168,0,2)
IPAddress gw        (YOUR_GW);
IPAddress dns       (YOUR_DNS);           // Google:(8,8,8,8)
IPAddress mask      (YOUR_MASK);          // ex. (255,255,255,0)

/* I2C */
#define I2C_SDA     2
#define I2C_SCK     0
#define I2C_CLK     100000                // fSCL=100KHz

/* NTP DATA */
#define NTP_SRV1    "ntp.nict.jp"
#define NTP_SRV2    "ntp.jst.mfeed.ad.jp"

/* RTC8564 */
#define RTC_ADR     0x51                  // I2Cアドレス
#define RTC_CTR1    0x00                  // RTC CTRL1レジスタ
#define RTC_CTR2    0x01
#define RTC_SEC     0x02
#define RTC_TMRC    0x0E
#define RTC_TMR     0x0F

struct rtcTime {
  uint8_t sec;                            // 0-59
  uint8_t min;                            // 0-59
  uint8_t hour;                           // 0-23
  uint8_t day;                            // 1-31
  uint8_t mon;                            // 1-12
  uint8_t year;                           // 00-199
  uint8_t wday;                           // 0(Sun)-6(Sat)
};
rtcTime RT =        {0,0,0,1,1,0,0};

/* OLED */
#define OLED_ADR    0x3C                  // I2Cアドレス
#define OLED_RESET  -1
#define OLED_WIDTH  128                   // OLED width(pixels)
#define OLED_HEIGHT 64                    // OLED height

Adafruit_SSD1306    dsp(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
static const char   *WDay[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};

/* BME280 */
#define BME_ADR     0x76
Adafruit_BME280     bme;
float               tmp, hum, prs;

/* Time */
time_t   UXTM =     0;                    // UnixTime
uint32_t STTM =     0;                    // スタート時間 milli()

///// ハードウエア 初期設定
void setup() {
  // Start time
  STTM = millis();

  // WiFi
  //WiFi.setOutputPower(10); // [dBm] max 20.5dBm

  // I2C
  Wire.begin(I2C_SDA, I2C_SCK);
  Wire.setClock(I2C_CLK);
  delayy(10);

  // Pin Mode
  digitalWrite(UART_TX, HIGH);
  pinMode     (UART_TX, OUTPUT);

  // BME280 
  bme.begin(BME_ADR, &Wire);
 
  // スタート表示
  dsp_start();

  // RTC VLビットチェック
  /* VL on */
  if (rtc_chkvl()) {
    // RTC8564 安定化待ち,初期化
    while ((millis()-STTM)<1000) delayy(1);
    rtc_init();

    // NTP時刻をRTCにセット WiFi->NTP->UXTM->RT->RTC
    rtc_setNTP();
  }

  /* VL off */
  else {
    /* RTC時刻->dt, RTCタイマーセット */
    rtc_init();
    rtc_getRT();

    /* 定時時刻合わせチェック */
    if(RT.day==1 && RT.hour==1 && (RT.min/10)==3) {
      // NTP時刻をRTCにセット WiFi->NTP->UXTM->RT->RTC
      rtc_setNTP();
    }
  }
}

///// メインルーチン
void loop() {
  // BME280 温湿度・気圧測定 temperature,humidity,press
  tmp = bme.readTemperature();
  hum = bme.readHumidity();
  prs = bme.readPressure()/100;

  // 時刻・温湿度気圧表示
  dsp_datetime();
  dsp_data();

  // LED点滅
  byte led = digitalRead(UART_TX);
  led = (led==LOW) ? HIGH : LOW;
  digitalWrite(UART_TX, led);
  delay(1000);
}

///// I2C Control /////
// i2cのデータが確定するまで待つ
inline void i2c_wait() { while(Wire.available()<1); }

///// Display Control /////
/* OLED 起動処理 */
void dsp_start() {
  // 初期化
  dsp.begin(SSD1306_SWITCHCAPVCC, OLED_ADR);

  // 初期表示
  dsp.clearDisplay();
  dsp.setTextColor(WHITE);
  dsp.setTextSize(1);
  dsp.setCursor( 0, 20);
  dsp.print("Syncing NTP time");
  dsp.display();
  dsp.clearDisplay();
}

/* OLED 時刻表示 */
void dsp_datetime() {
  // RTC 現在時刻の取得
  rtc_getRT();

  // DATE 文字列作成
  char fmtDate[11];
  sprintf( fmtDate, "%02d/%02d(%s)",
           RT.mon,
           RT.day,
           WDay[RT.wday] );
  // TIME 文字列作成
  char fmtTime[6];
  sprintf( fmtTime, "%02d:%02d",
           RT.hour,
           RT.min );
  // 表示
  //dsp.clearDisplay();
  dsp.fillRect(  0,  0,128, 16,BLACK);
  dsp.setTextSize(1);
  dsp.setCursor(  4, 4);
  dsp.println(fmtDate);
  dsp.setTextSize(2);
  dsp.setCursor( 68, 0);
  dsp.println(fmtTime);
  dsp.display();
}

/* OLED 測定結果表示 */
void dsp_data() {
   // 初期化
  char buf1[9], buf2[21];

  // 測定結果表示
  dsp.fillRect( 30, 19,128, 53,BLACK);
  dsp.setTextColor(WHITE);
  dsp.setTextSize(1);
  /* Temp 表示 */
  dsp.setCursor(  0, 19);
  dtostrf      (tmp,4,1,buf1);
  sprintf      (buf2, "Tmp: %sC", buf1);
  dsp.print    (buf2);
  /* Humi 表示 */
  dsp.setCursor(  0, 28);
  dtostrf      (hum,4,1,buf1);
  sprintf      (buf2, "Hum: %s", buf1);
  dsp.print    (buf2);
  dsp.print    ('%');
  /* Press 表示 */
  dsp.setCursor(  0, 37);
  dtostrf      (prs,4,0,buf1);
  sprintf      (buf2, "Prs: %sHp", buf1);
  dsp.print    (buf2);

  dsp.display();
}

///// RTC8564 Control /////
// RTC 初期化
/* 時刻再設定なし,タイマー初期化 */
void rtc_init() {
  rtc_setTMR(1);
}

/* 指定時刻でRTC設定,タイマー初期化 */
void rtc_init(time_t uxtm) {
  /* UnixTimeを RTC時刻(RT構造)にセット */
  uxt2rt(uxtm);

  /* RTCへ指定時刻を設定,タイマー初期化 */
  rtc_setRT();
  rtc_setTMR(1);    // [sec] タイマーセット
}

// RTCのレジスタへRT構造から時刻をセット
void rtc_setRT() {
  // stop ビットセット
  rtc_regWR(RTC_CTR1, 0x20);

  // RT構造上の時刻データをBCDに変換してセット
  uint8_t data[7];
  data[0] = dec2bcd(RT.sec);
  data[1] = dec2bcd(RT.min);
  data[2] = dec2bcd(RT.hour);
  data[3] = dec2bcd(RT.day);
  data[4] = dec2bcd(RT.wday);
  data[5] = dec2bcd(RT.mon);

  // yearが100年を超えた場合CENTURYビットを立てる
  if (RT.year > 100) {
    data[6] = dec2bcd(RT.year - 100);
    data[5] |= 0x80;  // RTCS8564_CAL_CENTURY
  } else {
    data[6] = dec2bcd(RT.year);
  }

  // 7レジスタ 一気に書込む
  rtc_regSET(RTC_SEC, 7, data);

  // stop ビットクリア
  rtc_regWR(RTC_CTR1, 0x00);
}

// RTCのレジスタから時刻を読出しRT構造にセット
int rtc_getRT() {
  uint8_t data[7];

  rtc_regGET(RTC_SEC, 7, data);
  RT.sec     = bcd2dec(data[0] & 0x7f);
  RT.min     = bcd2dec(data[1] & 0x7f);
  RT.hour    = bcd2dec(data[2] & 0x3f);
  RT.day     = bcd2dec(data[3] & 0x3f);
  RT.wday    = bcd2dec(data[4] & 0x07);
  RT.mon     = bcd2dec(data[5] & 0x1f);
  RT.year    = bcd2dec(data[6]);
   
  if (data[5] & 0x80) {  // RTCS8564_CAL_CENTURY
    RT.year += 100;
  }
   
  return 0;
}

// 指定時間でタイマー初期化
void rtc_setTMR(int tmr) {
  rtc_regWR(RTC_CTR2, 0x11);  // bit4 1:割込繰返し bit0 1:INT発生
  rtc_regWR(RTC_TMRC, 0x82);  // bit7 1:タイマー有効 bit1,0 3:min 2:sec
  rtc_regWR(RTC_TMR,   tmr);  // カウント値
}

// VLビット読込み
bool rtc_chkvl() { return (rtc_regRD(RTC_SEC) & 0x80); }

// RTCのタイマー値を読込む
int rtc_gettmr() { return rtc_regRD(RTC_TMR); }

// RTCのストップビットクリア
void rtc_stpclr() { rtc_regWR(RTC_CTR1, 0x00); }

// RTCの指定レジスタからデータを読込む
uint8_t rtc_regRD(uint8_t reg) {
  Wire.beginTransmission(RTC_ADR);
  Wire.write(reg);
  Wire.endTransmission();
  Wire.requestFrom(RTC_ADR, 1);
  return Wire.read();
}

// RTCのアクセスするレジスタを書込む
void rtc_regWR(uint8_t reg, uint8_t value) {
  Wire.beginTransmission(RTC_ADR);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
}

void rtc_regSET(uint8_t reg, int num, uint8_t *data) {
  Wire.beginTransmission(RTC_ADR);
  Wire.write(reg);
  Wire.write(data, num);
  Wire.endTransmission();
}
 
void rtc_regGET(uint8_t reg, int num, uint8_t *data) {
  Wire.beginTransmission(RTC_ADR);
  Wire.write(reg);
  Wire.endTransmission();
  Wire.requestFrom(RTC_ADR, num);
   
  for (int i = 0; i < num; i++) {
    i2c_wait();
    data[i] = Wire.read();
  }
}
 
// RTCから時刻(RT構造)を読込みUnixTimeを得る
time_t rtc_getUXTM() {
  rtc_getRT();
  return rt2uxt();
}

// NTPから時刻を取得しRTCにセット
boolean rtc_setNTP() {
  /* WiFi接続:未接続はRTC時刻を更新しない */
  if (wifi_connect(60000)) {
    if (ntp_connect(10000)) {
      /* NTP時刻 -> UXTM,RTC */
      UXTM = time(NULL);
      rtc_init(UXTM);
      return true;
    }
  }
  //wifi_stop();  // stopしない方が稼働時間短縮
  return false;
}

// UnixTimeからRTC時刻(RT構造)を得る (UnixTime->tm構造->RT構造)
void uxt2rt(time_t uxtm) {
  struct tm *t = localtime(&uxtm);
  RT.year = (t->tm_year + 1900) % 100; // tm_year: 1900年から
  RT.mon  = t->tm_mon + 1;             // tm_mon : 0-11
  RT.wday = t->tm_wday;
  RT.day  = t->tm_mday;
  RT.hour = t->tm_hour;
  RT.min  = t->tm_min;
  RT.sec  = t->tm_sec;
}

// RTC時刻(RT構造)からUnixTimeを得る (RT構造->tm構造->UnixTime)
time_t rt2uxt() {
  struct tm t;
  t.tm_year = RT.year + 100 ;  // tm_year: 1900年から
  t.tm_mon  = RT.mon- 1;       // tm_mon : 0-11
  t.tm_wday = RT.wday;
  t.tm_mday = RT.day;
  t.tm_hour = RT.hour;
  t.tm_min  = RT.min;
  t.tm_sec  = RT.sec;
  t.tm_isdst = -1;             // 夏時間設定を無効化

  return mktime(&t);
}

///// WiFi Control /////
/* WiFi接続開始処理(ノンブロッキング風)
 *   <引数> Timeout: 接続制限時間(0 or 省略は接続するまで無限ループ)
 *   <戻値> 接続:true 未接続:False
 */
boolean wifi_connect() { return wifi_connect(0); }
boolean wifi_connect(int timeout) {
  // 初期化
  unsigned long start = 0;
  unsigned long last  = 0;   // ノンブロッキング用時間変数
  unsigned long now   = 0;
  unsigned long itvl  = 500; // クライアント再接続時のインターバル時間

  // WiFi 初期化
  wifi_init();

  // WiFiが未接続の場合のみbeginする
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(WIFI_SSID, WIFI_PASS);
    delayy(100);
  }

  // WiFi接続(指定時間内) 接続:true 接続失敗:false
  start = millis();
  /* タイムアウトチェック */
  while (timeout==0 || millis() - start < timeout) {
    // WiFi接続は非同期(ノンブロッキング)風で
    now = millis();
    if (now-last >= itvl) {
      last = now;
      // 接続チェック
      if (WiFi.status() == WL_CONNECTED) {
        return true;
      }
      // 未接続の場合,0.3-0.5sec おきに再接続
      itvl = random(300, 500);
    }
    yield();
  }
  // 接続不可 エラーリターン
  return false;
}

/* WiFi 初期化処理 */
boolean wifi_init() {
  // WiFi初期化 (順番大事)
  //   disconnect(true)されて再接続の場合 mode,configの再設定が必須
  WiFi.mode(WIFI_STA);                      // 1.WiFi モード設定
  WiFi.persistent(false);                   // 2.WiFi設定情報 保存しない
  if (!WiFi.config(ip, gw, mask, dns)) {    // 3.固定ip 設定
    /* DHCPフォールバック(DHCP切替え処理) */
    if (!WiFi.config(0U, 0U, 0U)) {
      return false;
    }
  }
  delayy(100);
  return true;  
}

/* WiFi stop処理 */
void wifi_stop() {
  // WiFiが接続の場合のみdisconnectする
  if (WiFi.status() == WL_CONNECTED) {
    WiFi.disconnect(true);
  }
}

///// NTP Control /////
/* NTP 同期処理(ノンブロッキング風)
 *   <引数> Timeout[ms]:同期制限時間(0 or 省略は同期するまで無限ループ)
 *   <戻値> Unixtime or 0(エラー)
 */
time_t ntp_connect() { return ntp_connect(0); }
time_t ntp_connect(int timeout) {
  // WiFi接続している時のみ時間取得する
  if (WiFi.status() != WL_CONNECTED) {
    return 0;
  }

  // 初期化
  unsigned long start;
  unsigned long itvl = 250; // NTP再接続時のインターバル時間
  unsigned long last = 0;   // ノンブロッキング用時間変数

  // タイムゾーン,NTPサーバー設定
  configTzTime("JST-9", NTP_SRV1, NTP_SRV2, NTP_SRV3);
  /* 最初に一定時間待機すると同期時にループが省かれる(短時間化) */
  delayy(50);

  // NTPサーバー接続 (timeout秒間 接続確認)
  start = millis();
  while (timeout==0 || millis()-start < timeout) {
    // NTP接続は非同期(ノンブロッキング)風で
    if (millis()-last >= itvl) {
      last = millis();
      // 現在時刻の取得
      time_t uxtm  = time(NULL);        // unix時間
 
      // NPT同期チェック (RTC初期値=1,000,000,000)
      if(uxtm>1000000000) {
        return uxtm;
      }
      // 未接続の場合,0.2-0.3sec おきに再接続
      itvl = random(200, 300);
    }
    yield();
  }

  // 同期不可 エラーリターン
  return 0;
}

///// Other Control /////
/* BCD Control */
inline int dec2bcd(int dec) {
  return (((dec / 10) << 4) | (dec % 10));
}
 
inline int bcd2dec(int bcd) {
  return ((bcd >> 4) * 10 + (bcd & 0x0f));
}

/* yield delay(ノンブロッキング ディレイ) */
inline void delayy(unsigned long tm) {
  unsigned long now = millis();
  while (millis()-now < tm) { yield();}
}

(2025.10.25 v0.0.4 曜日表示のバグ修正)

<初期設定>
インクルードファイルや変数の初期化を行なっています。インストールすべきライブラリはInstalled Libraryにまとめてあります。時刻を操作するrtcTime構造はグローバルな変数RTとして定義し各関数でポイント渡しをしなくて済むようにしています。

<setup>
各デバイスの初期化終了後タイトルを表示、RTC8564のVLビットをチェックして初回起動ならばWiFiに接続しNTPから時刻を取得してRTC8564にセットしています。

<loop>
温湿度、気圧を測定しRTC8564から読み込んだ時刻と測定データをOLEDに表示、LEDを反転したら1秒待機、を繰り返します。

<関数>
・i2c関連
・OLED関連
・RTC8564関連
WiFi関連
・NTP関連
・その他
でそれぞれまとめてあります。

・動作確認

ハード、ソフトともに出来上がった時計へ自家製電源で電源を供給してみると...

やり〜。一発動作です♪
センサーを指で温めると温湿度が変わりますのでうまい具合に動いてくれているようです。

順調、順調。
電流値は平時標準的な20mA〜80mAでの推移でした。


◯やってみて

まずは第一ステップ完了です。
RTC8564とRX8025はどちらもエプソン製のせいかピン配列が同じなので(って秋月電子さんの温かい配慮のおかげですね)そのまま差し替えがききます。現状のハードウェアのままRX8025だけを差し替えてRX8025専用スケッチの製作に入っていきたいと思います。