DS3231 RTC (for Arduino)

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

AMAZOMで見つけたのですが”、5個で993円(送料込)。1個約200円、Banggoodも顔負けの価格です。

オーダ後、品物は郵便で2日後に届きました。品物を開けてびっくり5個が板チョコ状に一列につながっていました。

資料集めと動作確認回路

Webで探して、製品の回路図と”SPEC” を見つけました。

上部の回路図は製品のHPに、下はSPECに有った参考回路です。HP製品はダイオードの先のBATとICのBATは接続されていたので赤線で繋いでみました。この回路では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で宣言されている関数をまとめたものです。

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”の様です。

スケッチの作成

スケッチを作成していると、どうも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” と表示します。

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