PIC AVR 工作室別館 arduinoの館->TopPage->接続くん->リアルタイムクロック(RTC)との接続

リアルタイムクロック(RTC)との接続

セイコーRTC-8564NBについて

RTC-8564NBというのは、セイコー製のリアルタイムクロック(RTC)機能のICで、時計やアラームなどの機能を内蔵しており、 各種マイコンとはI2Cというインターフェースで簡単に接続が出来ます。(arduinoの場合はI2CではなくTWIと呼びます)

通常マイコンで時計を作ろうと思ったら、正確な割り込み処理や時刻のカウントアップ機能、うるう年計算などのカレンダー 機能といった処理を自分で書かないといけないのですが、リアルタイムクロックのICを使えばその身代わりになって 頑張ってくれます。

具体的には、最初に一度RTCに日時を教えておけば、あとはRTC内部で自動的に時間の計算を行ってくれて、 知りたい時に「今何時?」って聞けば教えてくれるってぇ寸法です。時間やカレンダーの計算はRTCに任せておいて、 メインのマイコン側ではアプリケーション処理に徹することが出来てラクチンというわけですね。アラームをセットしておけば 時刻が到来した時に教えてくれたりもします。

今回はこれをarduinoと繋いでみようというお話です。というよりは、以前実験してみて上手く行かなかったのが、 最近tokoyaさんのブログの2008年11月25日の日記で 「うまくうごいたよ」という報告を拝見してから、自分のスケッチの間違えをようやく発見し、動かすことに成功したので 早速纏めることにしました。(といった経緯です…その間違えた原因は注意事項って形であとで纏めます)

セイコーのRTC「RTC-8564NB」は、秋月で8ピンDIPタイプの変換基板にハンダ付けされた扱いやすい形で販売していますので、 それを使うのがよろしいかと思います。

なお、今回使うTWIインターフェースに関する話は別ページ の方に纏めます。

実行風景

まずは実行風景を。arduinoの実機としては例によってreduino-nanoを使ってます。タクトスイッチ等がボード上に付いてて 便利なので。写真の中ほどが秋月製のRTC-8564NBボードです。8ピンDIP形状になってます。

LEDが2個ありますが、1個は秒針コチコチ。もう1個はアラーム出力です。秒針コチコチの方はDIPパッケージの 2番ピンから出力されてて、1秒毎に点滅するように設定してあります。アラームの方は3番ピンで、指定日時になると出力されます (アラームは負論理出力なので注意!)。

なおTWIバス上のプルアップ抵抗はボードに登載した2.2kΩ抵抗を適用しました。(JP1、JP2をハンダでショートすると プルアップ抵抗が有効になります)

で、肝心の日付や時刻ですが、UARTでPC宛に表示することにします。↓こんな感じ。

初期化処理で日時とアラームを設定しておき、その後1/10秒毎に読み出した内容をPCに送ります。

だいぶ手抜きのスケッチなので見た目がイマイチ(イマニ、イマサン…イマヒャク)ですが…

まぁarduinoだし、実用性より簡単なスケッチで「日時設定」「日時読み出し」「アラーム出力」「秒針点滅」 といった一通りの機能を盛り込むことに徹しました。

というわけで、こういう内容のスケッチを組みます。

スケッチと回路

スケッチ

スケッチを記します。スケッチ中の不等号は全角文字に置き換えてあります。あと、ダブルコーテーションなども コピペすると置き換わってしまうと思うので、適宜直してからご利用ください。

#include <Wire.h>


int RTCDATA[16];  //read buf. from RTC unit

#define RTCaddress 0xa2 >> 1
                 //TWI address of RTC-unit

#define inPin 8
                 //set digital8 as input


void setup() {

  pinMode(inPin, INPUT);
  digitalWrite(inPin, HIGH);   //pull up

  
  Serial.begin(9600);

  Serial.println("initializing RTC unit");
                 //init message

  delay(1000);

  Wire.begin();  // join the TWI as bus-master
  
  Wire.beginTransmission(RTCaddress);
               //send to RTC address (write mode)
  Wire.send(0x00);
  //set internal register address
  // of RTC as 0 (auto increment)

  //set initial time & date & year
  // as 2008.12.25  12:20:15
  Wire.send(0x00);
      //control1 = 0x00
  Wire.send(0x03);
      //control2 = 0x03
  Wire.send(0x15);
      //seconds = 0x15
  Wire.send(0x20);
      //minutes = 0x20
  Wire.send(0x12);
      //hours = 0x12
  Wire.send(0x25);
      //days = 0x25
  Wire.send(0x04);
      //weekdays = 0x04 (Thursday)
  Wire.send(0x92);
      //month/century = 0x92 (0x80 | 0x12)
  Wire.send(0x08);
      //years = 0x08
  
  Wire.send(0x21);
      //minutes alarm = 0x21
  Wire.send(0x12);
      //hours alarm = 0x12
  Wire.send(0x25 | 0x80);
      //days alarm = 0x25
  Wire.send(0x04 | 0x80);
      //weekdays alarm = 0x04 (Thursday)

  Wire.send(0b10000011);
      //clk out = 0x10000011 (1hz)
  
  Wire.endTransmission();
  delay(10);

  Serial.println("finished initializing RTC unit");
                //init message

}


void loop(void){

  int i;

  Wire.beginTransmission(RTCaddress);
    //send to RTC address with write mode
  Wire.send(0x00);
    //set internal register address of RTC as 0
  Wire.endTransmission();
  //
  Wire.requestFrom(RTCaddress,16);
              //request 16byte data from RTC
  //reading loop
  for (i=0; i<16; i++)
  {
    while (Wire.available() == 0 ){
    }
    RTCDATA[i] = Wire.receive();
  }

    //read data from RTC unit
  Serial.print(RTCDATA[8],HEX);
  Serial.print(" ");
  Serial.print(RTCDATA[7]& 0x1f,HEX);
  Serial.print(" ");
  Serial.print(RTCDATA[5] & 0x3f,HEX);
  Serial.print(" ");
  Serial.print(RTCDATA[6] & 0x07,HEX);
  Serial.print("     ");

  Serial.print(RTCDATA[4] & 0x3f,HEX);
  Serial.print(" ");
  Serial.print(RTCDATA[3] & 0x7f,HEX);
  Serial.print(" ");
  Serial.print(RTCDATA[2] & 0x7f,HEX);
  Serial.print("     ");

  Serial.print(RTCDATA[11] & 0x3f,HEX);
  Serial.print(" ");
  Serial.print(RTCDATA[12] & 0x07,HEX);
  Serial.print(" ");
  Serial.print(RTCDATA[10] & 0x3f,HEX);
  Serial.print(" ");
  Serial.println(RTCDATA[9] & 0x7f,HEX);
  
  
  if (digitalRead(inPin)  == LOW){
    Wire.beginTransmission(RTCaddress);
         //send to RTC address (write mode)
    Wire.send(0x01);
         //set internal register address of RTC as 1
    Wire.send(0x03);
         //clear control2 as 0x03
    Wire.endTransmission();    
  }

  delay(100);
}

内容を簡単に補足します。

実行すると、RTCの日時を2008.12.25(木)12:20:15に強制的に設定し、アラームを25日木曜日の12:21に、clock out端子への 出力を1Hzに設定します。秋月のRTCはclkoe端子が常時プルアップ設定なので、clkout端子からの出力信号は常に生きた状態になっています。

ただし、アラームの日付(25日)と曜日(木曜日)は0x80と論理和を取ることで無効化しています。(要は毎日アラームです)

実行すると、約45秒後にアラームがオンになるような設定です。

時刻や日付はuartでPC宛に送っているのですが、最低限の編集処理しかやってません。せめて前ゼロを付けた方が 見やすいのですが、面倒なので止めました。

今回の回路

RTCの電源端子とarduinoの電源端子は共にVcc、gndを共用しています。RTCの動作電圧は1.8~5.5Vと幅広いので、 5V版でも3.3V版でもどちらのarduinoとも接続可能です。RTCとarduinoの間はTWI端子で接続しています。 arduinoがTWIバスマスタ、RTCがスレーブです。

RTCのピン6とピン5はボード上の2.2kΩプルアップを使っているので、外付けのプルアップ抵抗は使ってません。 内蔵プルアップを使用していない場合はsda、scl両線のプルアップを別途行ってください。

1秒毎にピン2からオン/オフ信号を出力し、左側のLEDを点滅させます。秒針のイメージです。LEDは抵抗を通じてGNDに接続しています。

アラーム日時に至るとピン3からアラーム信号が出力されます。アラーム出力は負論理なので、右のLEDは抵抗を通じてVccに繋いで有ります (LEDの向きにも注意!)。アラームLEDを消すためには、RTCのコントロールレジスタ2のAFビットをクリアする必要があるのですが、 これはarduinoのdigital8ピン押下によってTWI経由でレジスタクリアの命令をRTCに送ることで実現します。

今回、アラーム出力はLED表示にしてみたのですが、通常はマイコンのピンチェンジ割り込みなどを使って 指定日時の到来を検知→何らかの処理をするというのが一般的でしょう。

arduinoのwire.hライブラリとRTC

以前もarduinoとRTCを接続してみたことがあったんですが、そのときは上手く動きませんでした。 今回tokoyaさんのブログで私のスケッチの間違えに気付いたので、ようやく動くに至りました。tokoyaさん、ありがとうございました。

元々arduinoにはwire.hというTWI用のライブラリがあって、簡単にI2Cデバイスに接続できるんですが、 RTCは上手く動かず仕舞いだったので、私の中ではなんというか鬼門になってました。

で、その間違えって言うのは、RTCデバイスと接続するためのスレーブアドレスの指定方法でした。

I2Cのバスマスタは、接続相手のデバイスをアドレス番号で指定するのですが、このI2Cのアドレスの指定の方法はちょっと クセがあり、気をつけないと間違えてしまいます(言い訳とも言う)。

具体的には、アドレスの最下位ビットのことです。最下位ビットは、I2Cスレーブデバイスに書き込むのか、I2Cスレーブデバイス から読み出すのか、この読み書きどちらなのかをアドレスと一緒に指定するためのビットに割り当たっています。 そして残りの上位7ビットが実際に割り当てられたスレーブデバイスのアドレスです(7ビットモードの場合)。

今回のRTCの場合、このスレーブアドレスは2進数で1010001(以下の図の水色のところ)のように固定のアドレスが割り振られています。 最下位ビットはこのようにR/W指定となっています。

で、arduinoのwire.hライブラリの場合、最下位ビットのところを右に詰めて7ビットの部分だけで指定するという仕様になっているのですが、 間違えて8ビットで指定(最下位ビットには0を埋めて)していたため上手くいかなかったわけです。 アセンブラなどでは、8ビット指定(最下位=0)としておいて、読み書きに合わせて最下位ビットをオン/オフする 方が一般的かと思うのですが、PIC用のプログラムをarduinoに移行するときにそこをあまり考えずに組んじゃって、 「以前動いた実績があるから…」と疑うことをしなかったことが敗因でしたな…。

というわけで、arduinoに限りませんが、I2Cで接続する場合はスレーブアドレスを右詰で7ビット指定するのか、 それとも最下位ビットに0を埋めて8ビット指定するのかを意識しましょう。

スケッチの冒頭で

#define RTCaddress 0xa2 >> 1

と指定しているのは、wire.hライブラリの仕様に合わせて0xA2を右に1ビットシフトすることで0x51に変換しているわけです。

まとめ

AVRやPICといったマイコンのプログラム内で独自に時計機能やカレンダー機能を実現しようとすると、 当然ながらメモリを消費したり、タイマー機能を利用(場合によっては占有)したり、処理能力を若干圧迫することになります。 するとマイコンのリソースを多かれ少なかれ消費したり、ソフトウェアを独自に組み込まなければならなかったり、さらには その独自に組み込んだソフトの品質を確保する手間が必要だったり…と色々手間がかかります。

RTCモジュールを使用することで、少なくとも時計やカレンダーについては機能が確保されることになります (まぁ、RTCとのI/F機能についてはコーディングしたりテストしたりする必要がありますが)。 そういう手間を省けるのがこの外部RTCのICというわけですね。

このセイコーのRTCは一般的に広く使われている通信規格I2C(atmelではTWI)のバス上に接続することができますが、 このバスはSPIと違って2本の通信線(sda、scl)だけで接続することが可能で、しかもバス上に複数のデバイスを接続しても マイコン側のピンはいつも2本しか消費しません。(接続するデバイスがいくら増えてもマイコンのピンを消費しません)

I2Cに接続できるICといえばEEPROMメモリや高精度ADコンバーターなどといったデバイスもあるので、 ADコンバーターでデータを取り込んで、マイコンで数値処理をして、EEPROMに溜め込んでおくデータロガーみたいな使い方は もってこいかもしれません。

最近の規模がある程度大きなマイコンにはRTCを登載しているものもあるんですが、arduinoのメインCPU、MEGA168には 登載されてませんし、接続も簡単なので、ちょっと繋いで遊ぶには面白いデバイスかという気がします。