RTCとsleep

  1. RTCとSleep

    1. RTCとは

       RTCはRealTimeClock で、ディジタル時計にも利用されているタイマー/カレンダー用ICです。ここではRTCの1種のRTC-8564 (以下RTCと略記します)を紹介します。RTCは水晶発振子を内臓し、カレンダー機能、24時間の時計機能の他に、指定した時間間隔あるいは指定した時刻で 割り込み信号を生成することができます。計時誤差は月1分以内で、通常の(高精度でない)ディジタル時計の精度に相当します。

    2. RTCの特徴

       RTCは時計機能に特化しているため、低消費電力で VDD=3.0 V で、275nA (nAは10-9A)で動作します。これは、ほぼ1マイクロWに相当し、電池の自然放電に近い値です。PIC本体でタイマー/カレンダー機能をプログラムすることは可能ですが、プログラムが大きくなる、他の割り込み処理があると時刻の更新に失敗する、こともあります。また、PICには高精度の水晶発振が必要になります。
       また、特定の時間間隔で長時間に渡りバッテリー動作をさせたい場合、PICはSleep状態に移行して消費電力を抑え、タイマーからの割り込み信号で定時処理を行い、処理を終了したらすぐ Sleep 状態に戻すと、バッテリーの節約が可能です。

    3. PICのSleep機能

       PIC は Sleep 命令 を実行すると、Sleep 状態に移行し最低限の消費電力による待機モードに入ります。このSleep状態は外部からの割り込みにより終了し、元の状態に戻ります。Sleepからの脱出は、タイマーなどの内部割込みでも可能ですがSleep状態に入ると、外部の発振回路なども停止するため、注意が必要です。
       PICの消費電力は、クロックや動作電圧で変化しますが、数mWから数十mW程度です。これは1本LEDの消費電力とほぼ同じですが、ボタン電池を利用する場合少ない消費電力ではありません。しかし、Sleep状態に入ると、数十nW 程度の消費電力になります。
      CCSCでは、sleep命令は 関数 Sleep() で実行できます。Sleepを脱出すると、次の命令から実行を再開します。

  2. RTC-8564

    1. RTC-8564

      RTC-8564は I2C で接続可能になっています。したがって、PICとは2本の信号で接続可能です。RTCは内部に15のレジスタがあり、このレジスタを読み書きすることで、時刻情報を取得したり、割り込み信号を発生させることができます。



      32.768kHzの発信器を内臓しています。消費電流は270nA、1.8V〜5.5Vで動作します。時間精度は温度に依存します。25度付近が誤差が少なく、標準的なクオーツ時計の精度(月差10秒)程度かと思います。


    2. 端子

       RTCの端子構造をしてU示します。このICは表面実装タイプで、ピン間は0.12mmです。ここでは、秋月電子が供給する2.54mmのDIP型に変換した基板を利用します。

       4ピンがグランド、8ピンがVCCです。3ピンがタイマーの割り込み信号、6ピンがSCL、5ピン;,SDAが I2C の接続信号です。割り込み信号は /INT でこれは負論理のオープンドレイン型です。負論理は、常時Hレベルで割り込みが起こるとLレベル(0V)になることを意味します。オープンドレインは、複数のデバイスが割り込み信号を共有できるようにするための方式で、RTCなどの外付けデバイスは割り込み信号をLに落とすのみです。割り込みがない場合Hレベルになるよう、PIC側で割り込み信号をHレベルにプルアップします。これは、I2Cの信号線と同じです。
       1,2ピンはクロックを出力する端子です。1ピンCLKEをHにすると、2ピンからクロックを取り出すことができます。クロックは、1,32,1024,32.768kのどれかで、制御レジスタで設定します。

    3. チップアドレス

       RTC-8564 のI2Cアドレスは 0xA2 で固定です。メモリI2C素子を接続する場合、衝突しないよう注意が必要です。

    4. RTCのプログラム

      1. コントロール

         0,1番地に制御用のレジスタがあります。起動時には0を書き込み初期化します。先頭のdelayは、RTCの内部の発振起動時間で、これを実行しないとイベント関連の初期化に失敗します。チップアドレスは1ですから 書き込みの先頭のコマンドは書き込みの場合 0xA2 となります。二つ目の書き込みデータがチップ内部のアドレスで、コントロールレジスタへの書き込みの場合 0 になります。

        void rtc_control_init()
        {     
            delay_ms(700);
            i2c_start();
            i2c_write(0xa2);  // 書き込みモード
            i2c_write(0x00);  // control0のアドレス
            i2c_write(0x0); // test=0
            i2c_write(0x0); //AIE=TIE=0
            i2c_stop(); 
        }
      2. 日付、時刻の設定

         sec、min、hour、に秒、分、時、をBCD形式で設定しておきます。例えば、35分に設定する場合、 hour = 0x35 とします。この形式は簡単に文字コードに変換できます。また、day,week,month,yearmに日付、曜日を設定します。月の最上位ビットを1にすると、2000年以後の年になります。曜日は0..6 の数字で設定します。

        void rtc_date_set()
        {
            i2c_start();
            i2c_write(0xa2); // 書き込みモード
            i2c_write(0x02); // 秒のアドレス
            i2c_write(sec);  // 秒の値 0-59
            i2c_write(min);  // 分の値 0-59
            i2c_write(hour); // 時の値 0-23
            i2c_write(day);  // 日の値 1-31
            i2c_write(week); // 曜の値 日月火水木金土 0123456
            i2c_write(month);// 月の値 (C:MSB)1-12   Cは1のとき21世紀
            i2c_write(year); // 年の値 00-99
            i2c_stop();
        }
      3. 日付、時刻の設定

         日付時刻を読み出し、変数に記録します。BCD形式で読み取りができますから、0x30を加えることで文字コードに変換できます。ただし、読み取り値の不要ビットの値は未確定になりますから、切り捨てないと、変な値になります。たとえば、月の値は、month & 0x1F で設定されないビットは切り捨てます。

        void rtc_date_read()
        {
            i2c_start();
            i2c_write(0xa2); // 書き込みモード
            i2c_write(0x02); // 秒のアドレス
            i2c_start();
            i2c_write(0xa3); // 読み込みモード
            sec=  i2c_read(1); // 秒の値
            min=  i2c_read(1); // 分の値
            hour= i2c_read(1); // 時の値
            day=  i2c_read(1); // 日の値
            week= i2c_read(1); // 曜の値
            month=i2c_read(1); // 月の値
            year= i2c_read(0); // 年の値
            i2c_stop();
            
        }
      4. タイマー、アラームの設定

        アラームは指定した時刻でイベントを発生します。曜日、日付、時刻、別に設定できます。各項目の最上位ビットを1にすると、他の項目とは無関係に割り込みを発生できます。タイマーやアラームを設定すると、指定した時間間隔で割り込み信号を出します。この信号はプルアップが必要です。

        // アラームの設定
        //BCDでアラームをセット、最上位bitを1にすると
        //他の値は無関係に control:AF をセットする
        void rtc_alarm_set(int aweek,int aday,int ahour,int amin)
        {
            i2c_start();
            i2c_write(0xa2);  // 書き込みモード
            i2c_write(0x09);  // アラーム分のアドレス
            i2c_write(amin);  // アラーム分の値 0-59 
            i2c_write(ahour); // アラーム時の値 0-23
            i2c_write(aday);  // アラーム日の値 1-31
            i2c_write(aweek); // アラーム曜の値 日月火水木金土 0123456 
            i2c_stop();       //                上位5bitは Don't Care
        }
        
        タイマーは指定した時間間隔で割り込みを発生します。
        
        // タイマの設定
        //timer:タイマーの値 unsigned char
        void rtc_timer_set(int timer)
        {
            i2c_start();
            i2c_write(0xa2);  // 書き込みモード
            i2c_write(0x0E);  // タイマ制御値のアドレス
            i2c_write(0x02);  //TE=0
            i2c_write(timer); // タイマ値
            i2c_stop();
        }
        
        時間間隔を設定後、タイマーを開始します。tcの最上位ビットを1にし、
        下位2ビットは以下のようにカウントダウンする単位時間を指定します。
        4096Hz:0x81,64Hz:0x81,1sec:0x82,1min:0x83
        //タイマースタート
        void rtc_timer_start(int tc)
        {
            i2c_start();
            i2c_write(0xa2);  // 書き込みモード
            i2c_write(0x0E);  // タイマ制御値のアドレス
            i2c_write(tc); // タイマ値
            i2c_stop();
        }
      5. イベント関連

         制御レジスタに割り込みの設定を行います。1,0ビットに1を書き込むとアラームとタイマーの割り込みが可になります。3,2ビットは割り込み条件が満たされると1になります。この値は一定時間を経過すると自動的に0になります。

        //0bit:Timer Enable 1bit:Alarm Enable
        //2bit:TimerFlag    3bit:Alarm Flag
        //4bit:if set to 1, reload timer value after timer event  
        void rtc_control_set(int mode)
        {
            i2c_start();
            i2c_write(0xa2);  // 書き込みモード
            i2c_write(0x01);  // control2のアドレス
            i2c_write(mode); // モード設定
            i2c_stop(); 
        }

        割り込み原因を調べるには、次の関数を利用します。戻り値の3,2ビットで割り込み原因を特定できます。

        //2bit:TimerFlag 3bit:Alarm Flag
        int rtc_control_get()
        {
            int mode;
            i2c_start();
            i2c_write(0xa2); // 書き込みモード
            i2c_write(0x01); // control2のアドレス
            i2c_start();
            i2c_write(0xa3); // 読み込みモード
            mode =  i2c_read(0); // 
            i2c_stop();
            return mode;
        }
  3. 実験回路

    1. 回路

      RTCとPICをI2Cで接続し、表示と入力はRS232Cを利用してハイパーターミナルで行います。
       RTCとPICはI2Cで接続します。PIC側の端子はB1,B2です。この端子はPIC16F648ではRs232Cと共通ですからシリアル接続はハードウエアでなく、ソフトウエアで行うこととし、B4,B3にシリアル端子を接続します。RTCのINT信号をPICのRB0(外部割込み端子)に接続します。
       また、プログラムでは動作確認のためPICのA4端子にLEDを接続し sleep から脱出するごとに点灯しています(回路図には入っていません)。RTC8564の端子番号はIC直接でなく、RTCを通常のピン間隔基盤に搭載したモジュール(秋月電子販売)のピン番号です。

    2. プログラム

       まず、日付と時刻をハイパーターミナルから読み込みます。scanf() 関数はサポートされていませんから、2文字を読み取り、16進数に変換します。次に、RTCの初期設定を行い、日付を設定します。
       続けて、クロック出力を秒単位とし、アラームを毎時25分、タイマーを10秒単位に設定し、タイマーとアラームの割り込みを可にします。
      最後に、PIC側の割り込みを設定します。外部割込みを H_TO_L (HighからLowの立下りで割り込む)に設定し、割り込み許可を設定します。これが完了したら sleep() を出して割り込みを待ちます。
       void ext_isr() で始まる割り込み処理では、まず、rtc_control_get(); で割り込み原因を調べます。割り込み原因によって、"timerEvent" または"AlarmEvent"を表示し、現在の日付と時刻を表示します。

      #include <16f648a.h>
      
      //#fuses INTRC_IO,NOWDT,NOLVP,NOMCLR //内部クロック、WDT,LVPなし
      #fuses HS,NOWDT,NOLVP,NOMCLR //内部クロック、WDT,LVPなし
      #use delay(CLOCK=20000000)
      #use RS232(BAUD=9600,xmit=PIN_B4,rcv=PIN_B3)//use delayの後に配置する
      
      #use i2c(MASTER, SDA=PIN_B1, SCL=PIN_B2, FORCE_HW) // I2C使用宣言
      
      #byte port_a=5 //Aポート番地
      #byte port_b=6 //Bポート番地
      
      char year,month,week,day;
      char hour,min,sec;
      
      char h_year,h_month,h_week,h_day,h_hour,h_min,h_sec; // 現時刻文字2桁め
      char l_year,l_month,l_week,l_day,l_hour,l_min,l_sec; // 現時刻文字1桁め
      
      void rtc_date_set();
      void rtc_date_read();
      void rtc_alarm_set(int aweek,int aday,int ahour,int amin);
      
      void rtc_timer_set(int timer);
      void rtc_timer_start(int tc);
      
      int rtc_control_get();
      void rtc_control_set(int mode);
      void rtc_clockout_set(int tfreq);
      void rtc_control_init();
      
      //外部割込み処理
      #int_ext
      void ext_isr(){
       //timer event
       int mode;
       mode=rtc_control_get();
       if(mode & 0x4) printf("Timer Event:%x\n\r",mode);
       if(mode & 0x8) printf("Alarm Event:%x\n\r",mode);
       
       rtc_date_read();
       printf(" \n\r Date   %c%c:%c%c:%c%c\n\r",
            h_year,l_year,h_month,l_month,h_day,l_day);
       printf(" Time   %c%c:%c%c:%c%c\n\r",
            h_hour,l_hour,h_min,l_min,h_sec,l_sec);
       //rtc_control_set(mode & 0xFB);
       //rtc_timer_set(0x82,5);
       //output_high(PIN_B0);
      }
      
      //main処理
      main(){
        char cmnd;
        int len;
        char ch0,ch1;
        
        set_tris_b(0x0b);
        set_tris_a(0xEF);
        port_a=0x00;
        
        output_float(PIN_B2);         //SCLピン定義
        output_float(PIN_B1);         //SDAピン定義
        //output_high(PIN_B0);
        
      //日付読み取り
        printf("\r\n set:year:");
        ch0=getch();ch1=getch();
        year = (ch0-0x30)*16+(ch1-0x30);
        printf("\r\n set:mon:");
        ch0=getch();ch1=getch();
        month = (ch0-0x30)*16+(ch1-0x30);
        printf("\r\n set:day:");
        ch0=getch();ch1=getch();
        day = (ch0-0x30)*16+(ch1-0x30);
        printf("\r\n set:week:0..7:");
        ch0=getch();
        week = (ch0-0x30);
      
       //時刻読み取り 
        printf("\r\n set:hour:");
        ch0=getch();ch1=getch();
        hour = (ch0-0x30)*16+(ch1-0x30);
        printf("\r\n set:min:");
        ch0=getch();ch1=getch();
        min = (ch0-0x30)*16+(ch1-0x30);
        
      //初期化と日付設定
        rtc_control_init();
        rtc_date_set();
       
      //アラームとタイマー設定
        rtc_clockout_set(0x83);//set Clockout 1sec
        rtc_alarm_set(0x0,0x0,0x0,0x825);
        rtc_timer_set(10);//Interval sec
        
        rtc_control_set(0x13);//set Timer & Alarm Enable
        rtc_timer_start(0x82);//TimerStart
       
      //PIC側割り込み設定 
        ext_int_edge(H_TO_L);
        enable_interrupts(INT_EXT);
        enable_interrupts(GLOBAL);
       
      //sleep処理 
        while(1){          
          output_low(PIN_A4);
          delay_ms(1000);
          output_high(PIN_A4);
          delay_ms(1000);
      
          sleep();   
        }
        
        return 0;
      }

      以下はRTC制御を行う関数です。

      //原版  By H.machida@MNCT-S 2003/10/9
      // イベント処理関数追加
      //1pin:^int 2pin:Gnd,5:sda,
      //6:scl,7:clkout,8:vdd,9:clkoe,
      //^int is set if alerm or timer condition
      //clkout is enabled by clkoe
      
      //int year,month,week,day,hour,min,sec;                // 現時刻
      //char h_year,h_month,h_week,h_day,h_hour,h_min,h_sec; // 現時刻文字2桁め
      //char l_year,l_month,l_week,l_day,l_hour,l_min,l_sec; // 現時刻文字1桁め
      
      // 【日付時刻設定】
      //値は BCD で設定
      //2000年以後は月のMSBを1にする
      void rtc_date_set()
      {
          i2c_start();
          i2c_write(0xa2); // 書き込みモード
          i2c_write(0x02); // 秒のアドレス
          i2c_write(sec);  // 秒の値 0-59
          i2c_write(min);  // 分の値 0-59
          i2c_write(hour); // 時の値 0-23
          i2c_write(day);  // 日の値 1-31
          i2c_write(week); // 曜の値 日月火水木金土 0123456
          i2c_write(month);// 月の値 (C:MSB)1-12   Cは1のとき21世紀
          i2c_write(year); // 年の値 00-99
          i2c_stop();
      }
      
      // 日付時刻読み出し
      void rtc_date_read()
      {
          i2c_start();
          i2c_write(0xa2); // 書き込みモード
          i2c_write(0x02); // 秒のアドレス
          i2c_start();
          i2c_write(0xa3); // 読み込みモード
          sec=  i2c_read(1); // 秒の値
          min=  i2c_read(1); // 分の値
          hour= i2c_read(1); // 時の値
          day=  i2c_read(1); // 日の値
          week= i2c_read(1); // 曜の値
          month=i2c_read(1); // 月の値
          year= i2c_read(0); // 年の値
          i2c_stop();
          
          h_sec=  ((  sec>>4)&0x07)|0x30; 
          l_sec=  (  sec&0x0f)|0x30; 
          
          h_min=  ((  min>>4)&0x07)|0x30;
          l_min=  (  min&0x0f)|0x30; 
           
          h_hour= (( hour>>4)&0x03)|0x30;
          l_hour= ( hour&0x0f)|0x30; 
           
          h_day=  ((  day>>4)&0x03)|0x30;
          l_day=  (  day&0x0f)|0x30; 
           
          h_week=   0;
          l_week= ( week&0x0f)|0x30;
            
          h_month=((month>>4)&0x01)|0x30;
          l_month=(month&0x0f)|0x30; 
           
          h_year= (( year>>4)&0x0f)|0x30;
          l_year= ( year&0x0f)|0x30; 
      }
      
      
      // アラームの設定
      //BCDでアラームをセット、最上位bitを1にすると
      //他の値は無関係に control:AF をセットする
      void rtc_alarm_set(int aweek,int aday,int ahour,int amin)
      {
          i2c_start();
          i2c_write(0xa2);  // 書き込みモード
          i2c_write(0x09);  // アラーム分のアドレス
          i2c_write(amin);  // アラーム分の値 0-59 
          i2c_write(ahour); // アラーム時の値 0-23
          i2c_write(aday);  // アラーム日の値 1-31
          i2c_write(aweek); // アラーム曜の値 日月火水木金土 0123456 
          i2c_stop();       //                上位5bitは Don't Care
      }
      
      // タイマーの設定
      //timer:タイマーの値 unsigned char
      void rtc_timer_set(int timer)
      {
          i2c_start();
          i2c_write(0xa2);  // 書き込みモード
          i2c_write(0x0E);  // タイマ制御値のアドレス
          i2c_write(0x02);  //TE=0
          i2c_write(timer); // タイマ値
          i2c_stop();
      }
      
      
      //tc: 4096Hz:0x81,64Hz:0x81,1sec:0x82,1min:0x83
      //タイマーが0になるとcontrol:TFが1になる
      void rtc_timer_start(int tc)
      {
          i2c_start();
          i2c_write(0xa2);  // 書き込みモード
          i2c_write(0x0E);  // タイマ制御値のアドレス
          i2c_write(tc); // タイマ値
          i2c_stop();
      }
      
      // 出力周波数の設定
      //tfreq: 32768Hz:0x80,1024Hz:0x81,32Hz:0x82,1Hz:0x83 
      void rtc_clockout_set(int tfreq)
      {
          i2c_start();
          i2c_write(0xa2);  // 書き込みモード
          i2c_write(0x0D);  // クロック出力周波数のアドレス
          i2c_write(tfreq); // クロック出力周波数の値
          i2c_stop();         
      }
              
      
      //0bit:Timer Enable 1bit:Alarm Enable
      //2bit:TimerFlag    3bit:Alarm Flag
      //4bit:if set to 1, reload timer value after timer event  
      void rtc_control_set(int mode)
      {
          i2c_start();
          i2c_write(0xa2);  // 書き込みモード
          i2c_write(0x01);  // control2のアドレス
          i2c_write(mode); // モード設定
          i2c_stop(); 
      }
      
      set control reg0
      void rtc_control_init()
      {     
          delay_ms(700);
          i2c_start();
          i2c_write(0xa2);  // 書き込みモード
          i2c_write(0x00);  // control0のアドレス
          i2c_write(0x0); // test=0
          i2c_write(0x0); //AIE=TIE=0
          i2c_stop(); 
      }
      
      //get control reg1
      int rtc_control_get()
      {
          int mode;
          i2c_start();
          i2c_write(0xa2); // 書き込みモード
          i2c_write(0x01); // control2のアドレス
          i2c_start();
          i2c_write(0xa3); // 読み込みモード
          mode =  i2c_read(0); // 
          i2c_stop();
          return mode;
      }
    3. 実行

      set:year:05
      set:mon:02
      set:day:25
      set:week:0..7:0
      set:hour:22
      set:min:15

      Timer Event:17
      Date 05:02:25
      Time 22:16:09

      Timer Event:17
      Date 05:02:25
      Time 22:16:19

      Timer Event:17
      Date 05:02:25
      Time 22:16:29
      Timer Event:17