RTC DS3231

リアルタイムクロックは、”RTC-8564NB(リアルタイムクロック)”で1つ紹介しています。今回は、DS3231を使ったリアルタイムクロックです。

AMAZOMで見つけたのですが”、5個で993円(送料込)。しかもオーダーの翌日(実際には翌々日でしたが)には届く様です。1個約200円です。Banggoodも顔負けの価格です。さっそくオーダーしてみました。

品物は郵便で来たのですが開けてびっくり、5個が板チョコ状に一列につながっていました。チョコレートの様に切れ目から折ると、1個づつきれいに取れました。

資料集めと動作確認回路

Webで探すと製品の回路図、DS3231の、”SPEC” が見つかりました。

背景が黄色の回路図は製品のHPに有ったもの(赤線部追加)、下の回路図はSPECに有った参考回路です。HPに有った回路からDS3231の電源は、VBAT端子と思っていました。しかしSPECに有った回路から本体電源はVcc端子、VBAR端子はバックアップ電源で本体駆動には使用されない様です。

この構造ではVccの電圧がボタン電池の電圧より高いと、Vccによってボタン電池が充電されます。この事からHPに充電式のボタン電池(LIR2032)を使用するよう指示が有りました。

普通のボタン電池(充電式では無いもの)を使いたいなら、VccとVBATの間にあるダイオードか抵抗を外すせば良いと思います。

この製品のインターフェイスは、”I2C”です。またヘッダーピンには以下の信号が出ています。

  • GND  グランド
  • Vcc  HPには、3.3−5.5V。 DS3231のSPECには、2.3−5.5V
  • SDA  I2Cのデータライン。
  • SCL  I2Cのクロックライン。
  • SQW  アラーム信号または矩形波を出力。
  • 32K  32kHzを出力。

これらの資料を元に、動作確認用にESP32を使った回路を書いて見ました。

SDA,SCL,SQW,32K端子は基板上抵抗でプルアップされているので、ESP32の端子とヘッダーピンをただつないだ非常に簡単な回路です。ヘッダーピンとESP32は以下の様につないでいます。

  • GND  ー> GND(17)
  • Vcc   ー> 3V3(19)
  • SDA  ー> GPIO21(32)
  • SCL  ー> GPIO22(35)
  • SQW  ー> GPIO23(36) 

I2C Addressが分からない

どの資料を見てもI2C Addressが載っていません。基板に有るジャンパーA0,A1,A2は回路からAT24C32(ROM)のアドレス指定用です。今回はこちらは使いません。Webで、”ds3231 i2c アドレス”で検索をかけると幾つかのサイトでI2C Addressは、”0x68”と見かけました。実際にRaseberry PIに接続してアドレスを確認した人も、”0x68”と書いているのでこれで問題無いのでしょう。

ArduinoにはLibraryが有る

ArduinoにはDS3231用のLibraryが有ります。それをインクルードすればいくつかのサンプルスケッチも同時にインストールされます。

Arduino IDEで、スケッチー>ライブラリをインクルード>ライブラリーを管理 を選んで下さい。ライブラリーマネージャの画面が表示されます。上部の検索欄に、”DS3231″と入力すると、関係ライブラリーが表示されます。

ここで、DS3231をインストールして下さい。インストールが終われば、閉じるをクリック。IDEのホーム画面に戻って、ファイルー>スケッチの例ー>DS3231と選べば、いくつかのサンプルスケッチが見れます。

DS3231.hの確認

インクルードされたライブラリは、通常スケッチが保管されているホルダー内の、”libraries” ホルダーの中に有ります。今回は、DS3231をインクルードしたので、”DS3231″というホルダで保存されています。中身を確認すると、

以下は、DS3231.hで宣言されている関数をまとめたものです。

byte getSecond(); 
byte getMinute(); 
byte getHour(bool& h12, bool& PM); 
	// In addition to returning the hour register, this function
	// returns the values of the 12/24-hour flag and the AM/PM flag.
byte getDoW(); 
byte getDate(); 
byte getMonth(bool& Century); 
	// Also sets the flag indicating century roll-over.
byte getYear(); 
	// Last 2 digits only

void setSecond(byte Second); 
	// In addition to setting the seconds, this clears the 
	// "Oscillator Stop Flag".
void setMinute(byte Minute); 
	// Sets the minute
void setHour(byte Hour); 
	// Sets the hour
void setDoW(byte DoW); 
	// Sets the Day of the Week (1-7);
void setDate(byte Date); 
	// Sets the Date of the Month
void setMonth(byte Month); 
	// Sets the Month of the year
void setYear(byte Year); 
	// Last two digits of the year
void setClockMode(bool h12); 
	// Set 12/24h mode. True is 12-h, false is 24-hour.

float getTemperature(); 

1行から10行までが時間の読み込み用関数。13行から28行までが時間セット用関数。31行が温度測定用関数です。

時間読み込み用関数でちょっと説明

  • byte getHour(bool& h12, bool& PM) 時間を返す
    • bool& h12  
      • 読み込まれた時間が24時間制か12時間制(AM/PM)かを判断。
      • True:12時間制。False:24時間制。
    • bool& PM
      • 12時間制の場合、読み込まれた時間のAM/PMを判断。
      • True:PM。False:AM。
  • byte getMonth(bool& Century) 月を返す
    • bool& Century 99年の次は00年になるようですがこの場合に、True。それ以外はFalse.

上記の引数は参照ですので、値がセットされて帰って来ます。

時間セット用関数でちょっと説明

  • void setClockMode(bool h12); 時間のモードを設定
    • 時間の設定を行う前にこの関数でモードを指定する。
    • この設定を行わなければ、24時間制になる。
    • bool h12  True:12-h,  False:24-hour.
  • void setDoW(byte DoW); 曜日の設定
    • byte DoW 曜日を指定。 1:日曜  7:土曜
  • void setHour(byte Hour); 時間を設定する関数
    • byte Hour 引数は、12時間制を設定しても24時間形式。
    • 午後8時は、20。 午前8時は8を指定。12時間制になっていれば関数内で午前午後を判断する。

各々関数は、同じホルダーの中に有るDS3231.cppの中に有ります。詳細はこのファイルを参照することになります。

DS3231.cppファイルの中でI2C通信を行っている箇所にI2C Addressと思われる変数、”CLOCK_ADDRESS”が有りました。この変数はスケッチの最初の方で、” define CLOCK_ADDRESS 0x68”と定義されていました。やっぱりアドレスは”0x68”の様です。アドレスが固定されているので、1つのシステムにDS3231を複数使用する時は何かの手段を考える必要が有ります。

スケッチの作成

スケッチを作成している、どうも12/24の切り替えが上手く行かない事が分かりました。void setClockMode(bool h12)で12時間モードに設定し、その後時間の設定void setHour(byte Hour);を行うと、引数が12より大きいときは12時間モードが保たれるのですが、12より小さい時には、24時間モードになってしまう事が分かりました。そこで、DS3231.cpp内のvoid setHour(byte Hour)のコードを見てみました。

void DS3231::setHour(byte Hour) {
	// Sets the hour, without changing 12/24h mode.
	// The hour must be in 24h format.

	bool h12;

	// Start by figuring out what the 12/24 mode is
	Wire.beginTransmission(CLOCK_ADDRESS);
	Wire.write(0x02);
	Wire.endTransmission();
	Wire.requestFrom(CLOCK_ADDRESS, 1);
	h12 = (Wire.read() & 0b01000000);
	// if h12 is true, it's 12h mode; false is 24h.

	if (h12) {
		// 12 hour
		if (Hour > 12) {
			Hour = decToBcd(Hour-12) | 0b01100000;
		} else {
//			Hour = decToBcd(Hour) & 0b11011111;
			Hour = decToBcd(Hour) | 0b01000000;
		}
	} else {
		// 24 hour
		Hour = decToBcd(Hour) & 0b10111111;
	}

	Wire.beginTransmission(CLOCK_ADDRESS);
	Wire.write(0x02);
	Wire.write(Hour);
	Wire.endTransmission();
}

オリジナルのコードは、20行で、Hour = decToBcd(Hour) & 0b11011111;としています。ここは入力された24時間制の時間(Hour)を12時間制に変換している箇所で、17,18行で入力された時間が12より大きい場合の処理を行い、この箇所で12より小さい場合の処理を行っています。

decToBcd(Hour)はHourをBCDに変換するのですが、Hourが12より小さいので、結果は下位5ビットを使用し上位3ビットは0になります。つまりHourは、2進数表記で”000xxxxx”の形になります。ここで、Hour = decToBcd(Hour) & 0b11011111;を行うと上位の3ビットは必ず、”0”になります。

一方で、時間を12時間制とするには、第6ビットを、”1”とする必要が有ります。(ちなみに第5ビットはAM/PM用 AM:0 PM:1)本来のコードではこのビットが0になり24時間制になってしまいます。

そこで、この条件を満たすに21行の様に、Hour = decToBcd(Hour) | 0b01000000;としました。20行をコメントアウトして21行に変更したら問題無く動く様になりました。

ライブラリのソースを開いて、上記の作業をして、同じ名前で保存しただけですが、ソースを変更したのは初めてです。

下記は時間を1秒毎に表示するスケッチです。

#include "Arduino.h"
#include <Wire.h>

#include <DS3231.h>

DS3231 Clock;

bool Century,h12,PM;

byte tm_data[10];           //Year,Month,Date,DoW,Hour,Minute,Secon

void setup() 
{
    Serial.begin(115200);
    Wire.begin();
    delay(500);
    
    h12 = false;                  // 24h case
//    h12 = true;                   // 12h case
    Clock.setClockMode(h12);

    tm_data[0] = 20;
    tm_data[1] = 9;
    tm_data[2] = 18;
    tm_data[3] = 7;
    tm_data[4] = 20;
    tm_data[5] = 25;
    tm_data[6] = 0;

    set_time(tm_data);   
}

void loop() 
{
    int a;
    String str,str1;

    get_time(tm_data);
    str = "";
    str = "20" + String(tm_data[0]) + "年";
    str += String(tm_data[1]) + "月";
    str += String(tm_data[2]) + "日";
    switch(tm_data[3])
    {
        case 1: str1 = "日" ; break;
        case 2: str1 = "月" ; break;
        case 3: str1 = "火" ; break;
        case 4: str1 = "水" ; break;
        case 5: str1 = "木" ; break;
        case 6: str1 = "金" ; break;
        case 7: str1 = "土" ; break;
    }
    str += (str1 + "曜");
    if(h12)
    {
        if(PM) str += "午後" ;
        else str += "午前" ;
    }
    str += String(tm_data[4]) + "時";
    str += String(tm_data[5]) + "分";
    str += String(tm_data[6]) + "秒";
    Serial.println(str);

    a = Clock.getSecond();
    while( a == Clock.getSecond()) delay(200);     
}

void set_time(byte* tm_data)
{
    Clock.setYear(tm_data[0]);          //Set the year (Last two digits of the year)
    Clock.setMonth(tm_data[1]);                
    Clock.setDate(tm_data[2]);         
    Clock.setDoW(tm_data[3]);           //Set the day of the week  SUN=1 / SAT=7
    Clock.setHour(tm_data[4]);          
    Clock.setMinute(tm_data[5]);        
    Clock.setSecond(tm_data[6]);       
}

void get_time(byte* tm_data)
{
    tm_data[0] = Clock.getYear();
    tm_data[1] = Clock.getMonth(Century);
    tm_data[2] = Clock.getDate();
    tm_data[3] = Clock.getDoW();
    tm_data[4] = Clock.getHour(h12,PM);
    tm_data[5] = Clock.getMinute();
    tm_data[6] = Clock.getSecond();
}
  • 18,20行 24時間制に設定。
  • 22から30行 時間を2020年9月18日土曜日20時25分00秒に設定。
  • Loop()で1秒毎に時間を表示。

実行するとシリアルモニタに以下の様に表示されます。

18行をコメントアウトし、19行のコメントを外すと12時間制表示になります。20時25分…が午後8時25分…に変わっています。

アラーム関数

アラーム関係の関数は、

void getA1Time(byte& A1Day, byte& A1Hour, byte& A1Minute, byte& A1Second, byte& AlarmBits, bool& A1Dy, bool& A1h12, bool& A1PM); 
void getA2Time(byte& A2Day, byte& A2Hour, byte& A2Minute, byte& AlarmBits, bool& A2Dy, bool& A2h12, bool& A2PM); 
void setA1Time(byte A1Day, byte A1Hour, byte A1Minute, byte A1Second, byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM); 
void setA2Time(byte A2Day, byte A2Hour, byte A2Minute, byte AlarmBits, bool A2Dy, bool A2h12, bool A2PM); 

void turnOnAlarm(byte Alarm); 
void turnOffAlarm(byte Alarm); 
bool checkAlarmEnabled(byte Alarm); 
bool checkIfAlarm(byte Alarm); 
  • 2つのアラーム(アラーム1とアラーム2)が有る。
  • getAxTime()  はアラームの状態を読み込む。
  • setAxTime()  はアラームの値をセットする。
  • turnOnAlarm(byte Alarm); 引数で指定したアラームをオンする。
    1. アラーム1ー>1 アラーム2ー>2
  • turnOffAlarm(byte Alarm); アラームをオフする。
  • checkAlarmEnabled(byte Alarm); アラームが有効がチェックする。
  • bool checkIfAlarm(byte Alarm); 
    • アラームのフラグをチェックとフラグのクリア

例えば、void setA1Time(byte A1Day, byte A1Hour, byte A1Minute, byte A1Second, byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM);を使ってアラームを指定する場合、各引数は

  • byte A1Day:   日にちを指定
  • byte A1Hour:   時間(24時間制)を指定
  • byte A1Minute:  分を指定
  • byte A1Second:  秒を指定
  • byte AlarmBits: どの条件でアラームを発生されるか。
    • 下位4ビットを以下の下記の表の様に設定してモードを選ぶ。
    • 例えば、指定した秒でアラームを発生させたければー>0x0Eを指定
  • bool A1Dy:  byte A1Dayの扱いを指定
    • False: 日にち / True: 曜日
  • bool A1h12:  使用する時間制の設定。
    • False: 24時間制 / True: 12時間制
  • bool A1PM: 12時間制の場合、指定した時間のAM/PM指定
    • False: AM制 / True: PM 

としてします。次は、bool checkIfAlarm(byte Alarm);でフラグをクリアーし、turnOnAlarm(byte Alarm);を実行するば、指定した時間にアラーム信号が発生します。(SQWがLOWになる)。

下記は3秒に1回アラームを発生させるスケッチです。

#include "Arduino.h"
#include <Wire.h>

#include <DS3231.h>

#define every_s         0x0f    //Alarm once per second
#define match_s         0x0e    //Alarm when seconds match
#define match_ms        0x0c    //Alarm when min, sec match
#define match_hms       0x08    //Alarm when hour, min, sec match
#define match_dhms      0x00    //Alarm when date, h, m, s match
#define match_whms      0x00    //Alarm when DoW, h, m, s match

#define alarm1          1
#define alarm2          2

DS3231 Clock;

byte tm_data[10];           //Year,Month,Date,DoW,Hour,Minute,Secon
byte interval;
byte flg;
bool Century,h12,PM;

void Prt_msg()
{
    flg=0;
    Serial.println("Interrupt");
}

void setup() 
{
    Serial.begin(115200);
    Wire.begin();
    delay(500);
    
    Clock.turnOffAlarm(alarm1);               // Disables alarm 1 or 2 (default is 2 if Alarm != 1);
    Clock.turnOffAlarm(alarm2);               // Disables alarm 1 or 2 (default is 2 if Alarm != 1);

    
    h12 = false;                    // 24h case
//    h12 = true;                      // 12h case

    Clock.setClockMode(h12);

    tm_data[0] = 20;
    tm_data[1] = 9;
    tm_data[2] = 18;
    tm_data[3] = 7;
    tm_data[4] = 20;
    tm_data[5] = 25;
    tm_data[6] = 0;

    set_time(tm_data);   

/* ---------------------------------------------------------------
    //Alarm once per second
    Clock.setA1Time(0, 0, 0, 0, every_s, 0, 0, 0);            
-----------------------------------------------------------------*/  

    //Alarm when seconds match  
    interval = 3;
    Clock.setA1Time(0, 0, 0, interval, match_s, 0, 0, 0);

/* ---------------------------------------------------------------
    //Alarm when min, sec match
    interval = 25;
    Clock.setA1Time(0, 0, interval, 3, match_ms, 0, 0, 0);
-----------------------------------------------------------------*/  

/* ---------------------------------------------------------------
    //Alarm when hour, min, sec match (24h)
    interval = 20;
    Clock.setA1Time(0, interval, 25, 3, match_hms, 0, h12, 0);
-----------------------------------------------------------------*/  

/* ---------------------------------------------------------------
    //Alarm when hour, min, sec match (12h)
    interval = 20;
    PM = false;
    if(interval > 12) PM = true;
    Clock.setA1Time(0, interval, 25, 3, match_hms, 0, h12, PM);
-----------------------------------------------------------------*/  
           
/* ---------------------------------------------------------------
    //Alarm when date, h, m, s match (24h)
    interval = 18;
    Clock.setA1Time(interval,20, 25, 3, match_dhms, 0, 0, 0);
-----------------------------------------------------------------*/  

/* ---------------------------------------------------------------
    //Alarm when DoW, h, m, s match (24h)
    interval = 7;
    Clock.setA1Time(interval,20, 25, 3, match_whms, 1, 0, 0);
-----------------------------------------------------------------*/  
    
    Clock.checkIfAlarm(alarm1);
    Clock.turnOnAlarm(alarm1);               

    pinMode(23, INPUT_PULLUP);
    attachInterrupt(23, Prt_msg, FALLING);
    
    flg=1;
}

void loop() 
{
    int a;
    String str,str1;

    get_time(tm_data);
    str = "";
    str = "20" + String(tm_data[0]) + "年";
    str += String(tm_data[1]) + "月";
    str += String(tm_data[2]) + "日";
    switch(tm_data[3])
    {
        case 1: str1 = "日" ; break;
        case 2: str1 = "月" ; break;
        case 3: str1 = "火" ; break;
        case 4: str1 = "水" ; break;
        case 5: str1 = "木" ; break;
        case 6: str1 = "金" ; break;
        case 7: str1 = "土" ; break;
    }
    str += (str1 + "曜");
    if(h12)
    {
        if(PM) str += "午後" ;
        else str += "午前" ;
    }
    str += String(tm_data[4]) + "時";
    str += String(tm_data[5]) + "分";
    str += String(tm_data[6]) + "秒";
    Serial.println(str);
    
    if(flg == 0)
    {
    
        Clock.checkIfAlarm(alarm1);                                  // Read Alarm flg & clear
        
/* ---------------------------------------------------------------
        //Alarm once per second
        Clock.setA1Time(0, 0, 0, 0, every_s, 0, 0, 0);        
-----------------------------------------------------------------*/  
    
        //Alarm when seconds match  
        interval += 3; 
        if(interval == 60) interval = 0;
        Clock.setA1Time(0, 0, 0, interval, match_s, 0, 0, 0);

/* ---------------------------------------------------------------
        //Alarm when min, sec match 
        interval ++; 
        if(interval == 60) interval = 0;
        Clock.setA1Time(0, 0, interval, 3, match_ms, 0, 0, 0);
        Clock.setSecond(59);
-----------------------------------------------------------------*/ 
        
/* ---------------------------------------------------------------
        //Alarm when hour, min, sec match (24h)
        interval ++; 
        if(interval == 24) interval = 0;
        Clock.setA1Time(0, interval, 25, 3, match_hms, 0, h12, PM);
        Clock.setHour(interval);
        Clock.setMinute(25);
        Clock.setSecond(0);
-----------------------------------------------------------------*/ 
        
/* ---------------------------------------------------------------
        //Alarm when hour, min, sec match (12h)
        interval ++; 
        if(interval == 24) interval = 0;
        PM = false;
        if(interval > 12) PM = true;
        Clock.setA1Time(0, interval, 25, 3, match_hms, 0, h12, PM);
        Clock.setHour(interval);
        Clock.setMinute(25);
        Clock.setSecond(0);
-----------------------------------------------------------------*/ 

/* ---------------------------------------------------------------
        //Alarm when date, h, m, s match (24h)
        interval ++; 
        Clock.setA1Time(interval, 20, 25, 3, match_dhms, 0, 0, 0);
        Clock.setDate(interval);
        Clock.setHour(20);
        Clock.setMinute(25);
        Clock.setSecond(0);
-----------------------------------------------------------------*/ 
        
/* ---------------------------------------------------------------
        //Alarm when DoW, h, m, s match (24h)
        interval ++;
        if(interval == 8) interval = 1; 
        Clock.setA1Time(interval, 20, 25, 3, match_whms, 1, 0, 0);
        Clock.setDoW(interval);
        Clock.setDate(18);
        Clock.setHour(20);
        Clock.setMinute(25);
        Clock.setSecond(0);
-----------------------------------------------------------------*/ 
 
        flg = 1;
    }
    
    a = Clock.getSecond();
    while( a == Clock.getSecond()) delay(200);     
}

void set_time(byte* tm_data)
{
    Clock.setYear(tm_data[0]);          //Set the year (Last two digits of the year)
    Clock.setMonth(tm_data[1]);                
    Clock.setDate(tm_data[2]);         
    Clock.setDoW(tm_data[3]);           //Set the day of the week  SUN=1 / SAT=7
    Clock.setHour(tm_data[4]);          
    Clock.setMinute(tm_data[5]);        
    Clock.setSecond(tm_data[6]);       
}

void get_time(byte* tm_data)
{
    
    tm_data[0] = Clock.getYear();
    tm_data[1] = Clock.getMonth(Century);
    tm_data[2] = Clock.getDate();
    tm_data[3] = Clock.getDoW();
    tm_data[4] = Clock.getHour(h12,PM);
    tm_data[5] = Clock.getMinute();
    tm_data[6] = Clock.getSecond();
}

実行するとこの様に3秒に1回、”Interrupt” と表示します。

スケッチにはいくつかのアラーム発生パターンをコメント表示で含んでいます。現在の箇所をコメントし、実行したいパターンのコメントを外してスケッチを実行してみて下さい。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする