RTC(リアルタイムクロック)は今やとても正確で、1年間の最大の狂いが秒単位のものがあります。上のArduinoにつないだRTCは、なかでも正確なDS3231というICのブレークアウトモジュールです。ファームウェアで短い間隔で温度補正をする工夫がなされたコストパフォーマンスの優れたRTCです。
このモジュールは、比較的容量の大きいCR2032電池を使い、大体5年ほどはそのまま動きます。小さな電池を入れる型であればもちろんもっと短いですが。
この精密なクロックが海外ネットでは1ドル未満で買えるので驚きです。今はコロナの影響でなんでも少し高いですが、今ちらりとAliexpressをみると、やはりそんな廉価です。中には、ばか高い同じものもあるので、気を付けないといけませんが。
(2022.11.22追記:ここ数か月で急に高くなっていますが、米中貿易摩擦の関係でなければいいのですが・・。)
何かの自作にRTCを使うためには、普通はカレンダーや時刻の設定機能を作りこみます。そのため少なくとも3つ(Mode、 Time、Set)のボタンかタクトスイッチが要ります。
しかし、RTCがこれだけ正確だと、自作機器では読み込むだけにしたい感じもします。必要に応じて外して更新したり、あるいはRTC更新の際は外部からの入力に差し替えるなどすればよいわけなので。(ブレークアウトモジュールではなく、DS323xのICを直接組みこむ場合はもちろんそうはいきませんね。ただし大量ではなく趣味用の少量購入ならブレークアウトモジュールが圧倒的に廉価。)
読み込むだけにする場合は、別途RTCへの書き込み装置を必要とするわけですが、それをArduinoで回路なしに楽に行う勝手な方法をご紹介したいと思います。
これだけのことなら最近の標準ライブラリーを使っても良いかもしれませんが、一定間隔の割り込みなどを使いたいときは、次のようなI2Cへの直接コーディングなら自由度が高くて楽です。
電池を入れて最初の写真のように接続し、まずArduinoにIDEで次のプログラムを書き込みます。(更新:2022.11.22 あまりにも書きなぐりのひどいコーディングでしたので、構造化だけしておきます。機能は全て前と同じです。)
/*************************************************************
Simple RTC writer with Arduino-IDE Serial-Monitor only
Initial Version V.00 8/15,2020 (c) Akira Tominaga
V.01: Just re-coded to be structured. 11/22, 2022
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 // DS3231 I2C addr
byte vR[8]; // values in RTC registers
uint8_t vI[8]; // integer values for RTC data
char YMDwHMS[20]; // editing area to show time
uint8_t kVal; // user keyed-in number value
byte Data[2]; // 2 bytes work area
byte rYN; // response keyed-in
void setup() { // ***** Arduino setup *****
Serial.begin(9600);
Wire.begin();
Serial.println("** RTC editor **");
getRTC();
Serial.println("YY/MM/DD-W-hh:mm:ss =") ; Serial.println(YMDwHMS);
while (true) {
Serial.print("Updating time? (Y or N)");
rKBin(); rYN = Data[0] & (0xFF - 0x20); // get upper-case
if (rYN == 'Y') {
Serial.println("=Y"); updateVal(); break;
}
if (rYN == 'N') {
Serial.println("=N"); break;
}
}
}
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 *** rKBin() *****
void rKBin(void) {
String strTyped; // characters typed-in
while (Serial.available() < 1) {}
strTyped = Serial.readStringUntil(0x10); // read KB until DLE
Data[0] = strTyped.charAt(0);
Data[1] = strTyped.charAt(1);
}
// *** Get RTC values in vI[] & YMDwHMS *** getRTC() *****
void getRTC(void) {
rRTC(); // read RTC into integers
edTMchr(); // edit time to characters
}
// macros used in the following subroutines
#define mds 1 // ss
#define mdm 2 // mm
#define mdh 3 // hh
#define mdW 4 // W = Day of Week (Sunday=1)
#define mdD 5 // DD
#define mdM 6 // MM
#define mdY 7 // YY
//*** edit Time to characters YMDwHMS *** edTMchr() *****
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]);
}
// *** update RTC *** updateVal() *****
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, 0, 1, 1, 0}; // min.values
// W(day of the week) can be either 0-6 or 1-7
getRTC();
while (true) { // big loop1
uint8_t mdN = mdY; // RTC register position index
while (mdN > 0) { // for all the modes
while (true) { // smaller loop2
Serial.print("Input "); Serial.print(mC[mdN]);
if (mdN != mdW) { // excluding day-of-week,
Serial.print(mC[mdN]); // set double characters
}
Serial.print("="); rKBin();
if (Data[0] == 0x0D) { // if just enter only
kVal = vI[mdN]; // use as-is value
}else{
kVal = (Data[0] & 0x0F) * 10 + (Data[1] & 0x0F);
if (mdN == mdW) kVal = (Data[0] & 0x0F);
}
if ((kVal < 10) & (mdN != mdW)) { // if value<10 and not mdW
Serial.print("0"); // insert left zero
}
Serial.println(kVal);
if ((kVal > maxvI[mdN]) | (kVal < minvI[mdN])) {
Serial.println("Err ");
} else {
break;
}
}// exit from while(true) loop 2
vI[mdN] = kVal;
mdN = mdN - 1; // next mode
} // exit from while(mdN > 0)
edTMchr(); // edit Time to characters YmDwHMS
Serial.println("YY/MM/DD-W-hh:mm:ss =") ; Serial.println(YMDwHMS);
Serial.print("OK? to update RTC (Y or N)");
rKBin(); rYN = Data[0] & (0xFF - 0x20); // get upper-case
if (rYN == 'Y') {
Serial.println("=Y"); wRTC(); // Yes, write to RTC
Serial.println("** RTC updated **"); Serial.println("");
break;
}
if (rYN == 'N') {
Serial.println("=N");
}
} // while true loop1 exit (else, re-inquiry )
}
// *** read RTC and convert values to integers *** rRTC() *****
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 Int. to RTC values *** wRTC() *****
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); // RTC register addr 0x00
for (int i = 1; i < 9; i++) {
Wire.write(vR[i]);
}
Wire.endTransmission();
}
// End of program //
シリアルモニターを起動すると操作ができます。
シリアルモニターを起動するには、IDEのメニューで「ツール」→「シリアルモニター」とクリックするか、または、IDEの右上あたりにある虫眼鏡のマークをクリックします。
次にシリアルモニターで、操作を次の様に行います。
詳細は次のとおりです。
①現在の数値を更新しないでよいかを聞いてきます。更新する場合は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-2022 Akira Tominaga, All rights reserved.