●Arduinoでリアルタイムクロックを使う

DS3231というRTC(リアルタイムクロック)モジュールを使って時計らしきものを作成します。
EasyWordMall DC 3.3-5.5V DS3231 AT24C32 高精度リアルタイムクロックモジュール [並行輸入品]
DS3231を買っても、DS3231Mが来る場合もあるし、M以外が来る場合があります。
このMが付いている物と、それ以外の文字の物は似て異なるチップで、DS3231Mは「microelectromechanical systems (MEMS)」発振器で、
それ以外は2ppmの「crystal oscillator (TCXO) 」となり精度が違うみたいです。
精度が低いと感じたなら確認してみてはいかがでしょうか?
私も精度が低かったので確認したらMでした。

DS1307であっても同じ構成と同じプログラムで動作します。
Rasbee DS1307 RTC クロックモジュール
私の場合はこちらの温度補正の無いRTCの方がDS3231Mよりも精度が高かったです(しかも低価格)


液晶の表示部分は、
Arduinoで1602 I2C液晶モジュールを使う
で作成した表示部分を流用します。


▼ハードウエアの接続

RTCのVccはArduinoの5Vに、GNDはGNDに接続します。
RTCのSDAはArduinoのA4
RTCのSCLはArduinoのA5に接続しました。

VccとGNDとA4、A5ピンは液晶と共有になります。
I2CリアルタイムクロックとI2C液晶モジュールはI2Cというシリアル通信で同じ線を使って通信するためにこのようになります。


▼ソフトウエアの作成

ライブラリの準備を行います。
RTClibという名前のライブラリですが、小文字のlibであることに注意です。
大文字の物は別のライブラリになります。
Arduinoの開発環境でツール→ライブラリの管理でライブラリマネージャを開き、検索をフィルタ…の所にRTClibと入力して探します。



もしくは、


インストールすることにより、RTClib.hが使えるようになります。


▼RTCの時間の設定

RTC.adjust(DateTime(年,月,日,時間,分,秒))

この文で時間を設定できます。
このプログラムを最初に走らせてRTCに時間を設定しました。

#include <RTClib.h>

void setup () {
    RTC_DS1307 RTC;
    RTC.begin();
    RTC.adjust(DateTime(2019, 11, 9, 18, 30, 0));
}
void loop () {
}

Arduinoに書き込んだ後に、目的の時間の2秒前にリセットボタンを押すと設定がうまく行きます。


▼RTCの時間の表示

プログラム中ではDS1307となっていますが、DS3231でも動作します。
このページの先頭の画像が動作状態です。

#include <RTClib.h>
#include <LiquidCrystal_I2C.h>

//アドレス0x27 16文字2行の液晶
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS1307 RTC;

void setup () {
    RTC.begin();
    lcd.init();
    lcd.backlight();
}

void loop () {
    lcd.setCursor(0, 0);

    if (RTC.isrunning()) {
        DateTime now = RTC.now();
        lcd.print(now.year(), DEC);
        lcd.print('/');
        lcd.print(now.month(), DEC);
        lcd.print('/');
        lcd.print(now.day(), DEC);
        lcd.print("                ");
        
        lcd.setCursor(0, 1);
    
        lcd.print(now.hour(), DEC);
        lcd.print(':');
        lcd.print(now.minute(), DEC);
        lcd.print(':');
        lcd.print(now.second(), DEC);
        lcd.print("                ");
    }else{
        lcd.print("RTC not find    ");
    }
    delay(100);
}


■時刻設定のできる時計を作成する

時間の表示が出来ても設定が出来ないと悲しいので時間が設定できるデジタル時計風にしてみました。
時間を設定するスイッチは、デジタルピン2番とGND、3番とGNDに取り付けて2個のスイッチで時間設定するように作成しました。。

#include <stdio.h>
#include <RTClib.h>
#include <LiquidCrystal_I2C.h>

//アドレス0x27 16文字2行の液晶
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS1307 RTC;

void setup () {
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);

    RTC.begin();
    lcd.init();
    lcd.backlight();
}

//時刻設定の設定箇所の変数
int mode;

int year;
int month; 
int day;
int hour;
int minute;
int second;

void loop () {
    static int old_second;
    if (RTC.isrunning()) {
        //時刻を取得
        getNow();
        
        if(old_second!=second){
          //LCDに時間を表示
          lcdshow();
          old_second=second;
        }

        //デジタルピン2番のスイッチが押された場合には時刻設定に入る
        if(chatteringCut_pullupPin(2)){
            mode++;
            if(6<mode) mode=0;
        }

        //デジタルピン3番のスイッチが押された場合には時間をカウントアップして設定する
        if(mode && chatteringCut_pullupPin(3)){
            getNow();
            switch (mode){
                case 1:
                  year++;
                  if(year>2050) year=2000;
                  break;
                case 2:
                  month++;
                  if(month>12) month=1;
                  break;
                case 3:
                  day++;
                  if(day>31) day=1;
                  break;
                case 4:
                  hour++;
                  if(hour>23) hour=0;
                  break;
                case 5:
                  minute++;
                  if(minute>59) minute=0;
                  break;
                case 6:
                  if(second>30 && minute<59) minute++;
                  second=0;
                  break;
                default:
                  break;
            }
            RTC.adjust(DateTime(year,month,day,hour,minute,second));
            lcdshow();
        }
    }else{
        lcd.setCursor(0, 0);
        lcd.print("RTC RESET    ");
        lcd.setCursor(0, 1);
        lcd.print("ERROR           ");
        //エラーの場合には適当な日付けをセットする’
        delay(1000);
        RTC.adjust(DateTime(2022, 2, 22, 22, 22, 22));
    }
}

//現在の時刻を取得する
void getNow(){
    DateTime now = RTC.now();
    year = now.year();
    month = now.month(); 
    day = now.day();
    hour = now.hour();
    minute = now.minute();
    second = now.second();
}

//時刻をLCDに表示する
void lcdshow(){
    //時刻設定用の300msカーソル点滅を作成
    static bool brink;
    static unsigned long old_millis;

    if((millis()-old_millis)>300){
        brink=!brink;
        old_millis=millis();
    }

    //画面に時刻を表示
    char str[17];
    
    lcd.setCursor(0, 0);

    sprintf(str,"%4d/",year);
    sprintf(str+5,"%2d/",month);
    sprintf(str+8,"%2d      ",day);
    
    //時刻設定のカーソル点滅
    if(1==mode && brink) sprintf(str,"    ");
    if(2==mode && brink) sprintf(str+5,"  ");
    if(3==mode && brink) sprintf(str+8,"  ");
    lcd.print(str);
    
    lcd.setCursor(0, 1);

    sprintf(str,"%002d:",hour);
    sprintf(str+3,"%002d:",minute);
    sprintf(str+6,"%002d        ",second);

    //時刻設定のカーソル点滅
    if(4==mode && brink) sprintf(str,"  ");
    if(5==mode && brink) sprintf(str+3,"  ");
    if(6==mode && brink) sprintf(str+6,"  ");
    lcd.print(str);

}

//ボタンを押した時のチャタリング対策
unsigned int chatteringCut_pullupPin(int pinNo){
    unsigned int i=0;
    while(!digitalRead(pinNo)){
        i++;
        //if(i>200) break;
        delay(1);
    }
    //1111111111000000 でアンドを取り下位ビットをマスクする
    //つまりループが63を超えるまで0しか返さない
    return i&(~(unsigned int)0x3F);
}


この150行近くあるプログラムを実行すると安いデジタル時計風に時間設定ができるようになります。
ただ、割り込みを使ってないのでボタン操作がもっさりしています。
ちなみに写真のDS3231は電池の代わりにスーパーキャパシタを取り付けてあります。


■ ライブラリを使用せずに時刻を読みだす


DS1307のデータシートによると、内部のレジスタはこのようになっているそうです。
以下のコードではArduino標準の Wire.h によりI2C通信をして時刻を読み出します。
先頭アドレスから読みだして表示するため、 秒 分 時 曜日 日 月 年 と順番が逆に表示されます。
#include <Wire.h>
#define ADDRESS 0x68
char *week[] = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"};

void setup() {
  Serial.begin(9800);
  Wire.begin();
  Wire.endTransmission();
}

void loop() {
  Wire.beginTransmission(ADDRESS);
  Wire.write(0x00);//アドレスを指定
  Wire.endTransmission();

  Wire.requestFrom(ADDRESS, 7);
  Serial.print(0b01111111 & Wire.read(), HEX); Serial.print(" ");
  Serial.print(Wire.read(), HEX); Serial.print(" ");
  Serial.print(Wire.read(), HEX); Serial.print(" ");
  Serial.print(week[Wire.read()]); Serial.print(" ");
  Serial.print(Wire.read(), HEX); Serial.print(" ");
  Serial.print(Wire.read(), HEX); Serial.print(" ");
  Serial.print(Wire.read(), HEX); Serial.println(" ");
  delay(1000);
}


▼ 時刻を設定する

以下のコードでは、13年 12月 11日 月曜 10時 09分 08秒 をレジスタに書き込んで設定します。
#include <Wire.h>
#define ADDRESS 0x68

void setup() {
  Wire.begin();
  Wire.endTransmission();
}

void loop() {
  Wire.beginTransmission(ADDRESS);
  Wire.write(0x00);//アドレスを指定

  Wire.write(0x08);
  Wire.write(0x09);
  Wire.write(0x10);
  Wire.write(0x01);
  Wire.write(0x11);
  Wire.write(0x12);
  Wire.write(0x13);
  
  Wire.endTransmission();
  while (1);
}


▼ RTCをメモリーとして使用する(DS1307のみ)

DS1307のデータシートのレジスタを見ると、下のマスにRAMと書かれていて56byteのメモリとして使用できるようになっています。
ココに適当な値を書き込んでみます。
DS3231ではアラームとして割り当てられているためRAMとして使えません、書き込む事は出来ますが値が化けます。
以下のコードでは、a b c d e f g の値をメモリーに書き込みます。
先頭アドレスを0x08で指定することにより目的のレジスタに書き込みます。
#include <Wire.h>
#define ADDRESS 0x68

void setup() {
  Wire.begin();
  Wire.endTransmission();
}

void loop() {
  Wire.beginTransmission(ADDRESS);
  Wire.write(0x08);//アドレスを指定

  Wire.write('a');
  Wire.write('b');
  Wire.write('c');
  Wire.write('d');
  Wire.write('e');
  Wire.write('f');
  Wire.write('g');
  
  Wire.endTransmission();
  while (1);
}


▼ メモリーからの読み出し

以下のコードでは、書き込んだ値を読み出します。
読み出す値の数を Wire.requestFrom(ADDRESS, 7); で7個と指定していますが値の数により変更する必要があります。
#include <Wire.h>
#define ADDRESS 0x68

void setup() {
  Serial.begin(9800);
  Wire.begin();
  Wire.endTransmission();
}

void loop() {
  Wire.beginTransmission(ADDRESS);
  Wire.write(0x08);//アドレスを指定
  Wire.endTransmission();

  Wire.requestFrom(ADDRESS, 7);
  Serial.print((char)Wire.read()); Serial.print(" ");
  Serial.print((char)Wire.read()); Serial.print(" ");
  Serial.print((char)Wire.read()); Serial.print(" ");
  Serial.print((char)Wire.read()); Serial.print(" ");
  Serial.print((char)Wire.read()); Serial.print(" ");
  Serial.print((char)Wire.read()); Serial.print(" ");
  Serial.print((char)Wire.read()); Serial.print(" ");

  while (1);
}


▲トップページ > マイコンなど