勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

回路不要のかんたんなRTCライター:Arduinoで

f:id:a-tomi:20200816121455j:plain

RTC(リアルタイムクロック)は今やとても正確で、1年間の最大の狂いが秒単位。上のArduinoにつないだRTCは、なかでも正確なDS3231というICのブレークアウトモジュールです。

f:id:a-tomi:20200816121832j:plain

このモジュールは、比較的容量の大きいCR2032電池を使い、大体5年ほどはそのまま動きます。小さな電池を入れる型であればもちろんもっと短いですが。

f:id:a-tomi:20200816122012j:plain

この精密なクロックが海外ネットでは1ドル未満で買えるので驚きです。今はコロナの影響でなんでも少し高いですが、今ちらりとAliexpressをみると、やはりそんな廉価です。中には、ばか高い同じものもあるので、気を付けないといけませんが。

 

f:id:a-tomi:20200816122243j:plain

何かの自作にRTCを使うためには、普通はカレンダーや時刻の設定機能を作りこみます。そのため少なくとも3つ(Mode、 Time、Set)のボタンかタクトスイッチが要ります。

しかし、RTCがこれだけ正確だと、自作機器では読み込むだけにしたい感じもします。必要に応じて外して更新したり、あるいはRTC更新の際は外部からの入力に差し替えるなどすればよいわけなので。(ブレークアウトモジュールではなく、DS323xのICを直接組みこむ場合はもちろんそうはいきませんね。ただし千個とか1万個ではなく趣味用の少量購入ならブレークアウトモジュールが圧倒的に廉価。)

読み込むだけにする場合は、別途RTCへの書き込み装置を必要とするわけですが、それをArduinoで回路なしに楽に行う勝手な方法をご紹介したいと思います。

電池を入れて最初の写真のように接続し、まずArduinoIDEで次のプログラムを書き込みます。

/*************************************************************
    Simple RTC writer with Arduino-IDE Serial-Monitor only
    How to use:
    	1) Connect RTC to I2C pins (ex.UNO; SCL=A5, SDA=A4).
    	2) Use Serial-Monitor for dialogues.
    V.00 Aug.15,2020 (c) Akira Tominaga, All rights reserved.
**************************************************************/

#include "Wire.h"             // use I2C library
#define RTC_addr 0x68
byte  vR[8];                  // values in RTC format
uint8_t vI[8];                // integer values
#define mds 1                 // mode=ss
#define mdm 2                 // mode=mm
#define mdh 3                 // mode=hh
#define mdW 4                 // mode=W : Day of Week (Sunday=1)
#define mdD 5                 // mode=DD
#define mdM 6                 // mode=MM
#define mdY 7                 // mode=YY
char YMDwHMS[20];             // editing area to show time
String strTyped;              // characters typed-in
uint8_t lTyped;               // length of typed chars + enter
byte Data[2];                 // 2 bytes work area

void setup() { // ***** Arduino setup *****
  byte rYN;     // work area for response value
  Serial.begin(9600);
  Wire.begin();
  Serial.println("** RTC editor **");
  getRTC();
  Serial.println("YY/MM/DD-W-hh:mm:ss =") ; Serial.println(YMDwHMS);
Inq1st: Serial.print("Is it OK? (Y or N)");
  rKBin();
  rYN = Data[0] & (0xFF - 0x20); // get upper-case letter
  switch (rYN) {
    case 'N': Serial.println("=N"); goto UpdInput;  // No, goto input proc
    case 'Y': Serial.println("=Y"); goto doNothing; // Yes, do nothing
    default: goto Inq1st;     // else, re-inquiry
  }
UpdInput: updateVal();
Inq2nd: Serial.print("OK? to update RTC (Y or N)");
  rKBin();
  rYN = Data[0] & (0xFF - 0x20); // get upper-case letter
  switch (rYN) {
    case 'Y': Serial.println("=Y"); goto UpdRTC;    // Yes, write to RTC
    case 'N': Serial.println("=N"); goto UpdInput;  // No, set values again
    default: goto Inq2nd;     // else, re-inquiry
  }
UpdRTC: wRTC();
  Serial.println("** RTC updated **"); Serial.println("");
doNothing: delay(100);
}

void loop() { // ****** Arduino Loop *****
  getRTC();                 // get and edit RTC contents
  Serial.println(YMDwHMS);  // show Real time clock
  delay(1000);
}

/**************************************
      User defined functions
* *************************************/
// *** read serial Keyboard input
void rKBin(void) {
  while (Serial.available() < 1) {}
  strTyped = Serial.readStringUntil(0x10); // read KB until DLE
  Data[0] = strTyped.charAt(0);
  Data[1] = strTyped.charAt(1);
}

// *** get RTC contents into chars YMDwHMS
void getRTC(void) {
  rRTC();      // read RTC device and get integers
  edTMchr();   // edit Time to characters
}

//*** edit Time to characters YMDwHMS
void edTMchr(void) {
  sprintf(YMDwHMS, "%02d/%02d/%02d-%01d-%02d:%02d:%02d", vI[mdY], vI[mdM], vI[mdD], vI[mdW], vI[mdh], vI[mdm], vI[mds]);
}

// *** prepare values to update RTC
void updateVal(void) {
  char mC[] = {0xFF, 's', 'm', 'h', 'W', 'D', 'M', 'Y' };  // mode chars
  uint8_t maxvI[8] = { 0, 59, 59, 23, 7, 31, 12, 99}; // max values
  uint8_t minvI[8] = {0, 0, 0, 0, 1, 1, 1, 0};        // min values
  uint8_t mdN = mdY;        // index to RTC field sequence
byMode:
  Serial.print("Input ");
  Serial.print(mC[mdN]);
  if (mdN != mdW) {   // excluding day-of-week,
    Serial.print(mC[mdN]);  // duplicate characters
  }
  Serial.print("=");
  rKBin();
  uint8_t inVal = (Data[0] & 0x0F) * 10 + (Data[1] & 0x0F);

  if (mdN == mdW) inVal = (Data[0] & 0x0F);
  if (Data[0] == 0x0A) {  // if just enter only
    inVal = vI[mdN];      // use as-is value
    goto byMskip;   // and skip validation
  }
  if ((inVal > maxvI[mdN]) | (inVal < minvI[mdN])) {
    Serial.println("Err "); goto byMode;
  }
byMskip:
  if ((inVal < 10) & (mdN != mdW)) { // if value<10 and not mdW
    Serial.print("0");        // insert left zero
  }
  Serial.println(inVal);
  vI[mdN] = inVal;
  mdN = mdN - 1;          // next mode
  if (mdN == 0) {
    goto byMend;
  } else {
    goto byMode;
  }
byMend:
  edTMchr();             // edit Time to characters YmDwHMS
  Serial.println("YY/MM/DD-W-hh:mm:ss =") ; Serial.println(YMDwHMS);
}

// *** read RTC and convert values to integers ***
void rRTC(void) {
  Wire.beginTransmission(RTC_addr);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(RTC_addr, 7);
  while (Wire.available() < 7) {} // wait for data ready
  for (int i = 1; i < 8; i++) {
    vR[i] = Wire.read();
  }
  Wire.endTransmission();
  // convert RTC values to integers
  vI[mds] = ((vR[mds] & 0x70) / 16) * 10 + (vR[mds] & 0x0F);
  vI[mdm] = ((vR[mdm] & 0x70) / 16) * 10 + (vR[mdm] & 0x0F);
  vI[mdh] = ((vR[mdh] & 0x20) / 32) * 20 + ((vR[mdh] & 0x10) / 16) * 10 + (vR[mdh] & 0x0F);
  vI[mdW] = vR[mdW];
  vI[mdD] = ((vR[mdD] & 0x70) / 16) * 10 + (vR[mdD] & 0x0F);
  vI[mdM] = ((vR[mdM] & 0x10) / 16) * 10 + (vR[mdM] & 0x0F);
  vI[mdY] = ((vR[mdY] & 0xF0) / 16) * 10 + (vR[mdY] & 0x0F);
}

// *** write to RTC - converting Integers to RTC values ***
void wRTC(void) {
  vR[mds] = (vI[mds] / 10) * 16 + vI[mds] % 10;
  vR[mdm] = (vI[mdm] / 10) * 16 + vI[mdm] % 10;
  vR[mdh] = (vI[mdh] / 20) * 32 + ((vI[mdh] % 20) / 10) * 16 + vI[mdh] % 10;
  vR[mdW] = vI[mdW];
  vR[mdD] = (vI[mdD] / 10) * 16 + vI[mdD] % 10;
  vR[mdM] = (vI[mdM] / 10) * 16 + vI[mdM] % 10;
  vR[mdY] = (vI[mdY] / 10) * 16 + vI[mdY] % 10;
  Wire.beginTransmission(RTC_addr);
  Wire.write(0x00);
  for (int i = 1; i < 9; i++) {
    Wire.write(vR[i]);
  }
  Wire.endTransmission();
}
/* End of program */

 シリアルモニターを起動すると操作ができます。

シリアルモニターを起動するには、IDEのメニューで「ツール」→「シリアルモニター」とクリックするか、または、IDEの右上あたりにある虫眼鏡のマークをクリックします。

次にシリアルモニターで、操作を次の様に行います。

f:id:a-tomi:20200816133338j:plain

詳細は次のとおりです。

①現在の数値を更新しないでよいかを聞いてきます。更新する場合はNかnを入力してEnterキーをおします。その場合入力は一番上②の送信欄です。また入力の最後には必ずEnterキーを入れてください。答がYかyだと更新せずに終えて、そのまま⑥の時計表示へ行ってしまいます。

④で、設定したいYY、MM、DD、W(曜日で1が日曜で月曜は2、・・・土曜が7)、hh、mm、ssとして年、月、日、曜、時、分、秒を順番に聞いてきます。W以外は2桁の数値を入力します。この時に、何も入れずにEnterキーを押せば、①に出た値を使います。

最後にss(秒)をいれたら、⑤でそれでよいかを聞いてきます。Yまたはyと答えればそのタイミングでRTCが更新されます。もしNまたはnと答えれば、再び④を繰り返します。

間違えて更新を終えた時には、ArduinoのUSB接続を外して再度つけなおしてやりなおせばよいだけです。

 

なお、上記のスケッチはよいコーディングの構造とは言えません。このままで動きますが、必要ならお好きなようにお直しください。またDS3231以外のRTCでも、デバイスがもつレジスターの内容はよく似ています。データシートをご参照のうえ比較してもし必要な個所があれば直してお使いください。

 

短いですが、この記事は以上です。お役に立てば幸いです。

 

©2020 Akira Tominaga, All rights reserved.