32.768KHzの水晶振動子を使ってRTCを行ってみます

〔PICの動かせ方入門に戻る〕



マイコンの内部タイマーで時刻カウントを行う事が出来るのですが精度が良くなくずれずれです、
なのでマイコン回路では外部に時刻カウント専用ICのRTCを取り付けて利用したりします。

PICでのRTC(RTC-8564NB)を利用した記事はこちらを参照下さい。
ArduinoでのRTC(RTC-8564NB)を利用した記事はこちらを参照下さい。

PICによってはメインの外付け又は内部のシステムクロック(プライマリクロック)以外に、32.768KHzの
水晶振動子を外付けしてセカンダリクロックとして使用出来るようになっているPICが有ります。

PICに32.768KHzの水晶振動子を取り付ると、Timer1のオシレータ入力とするのか、システムクロックとして入力するのかを 選択出来る様になります。
ここでの記事はTimer1のオシレータ入力としてリアルタイムクロックを実現させて見ます。

尚、水晶振動子は秋月電子のこちらから購入しています、また、水晶発振子とも呼ばれています。
この水晶自体は許容偏差:±20ppmなので1分/月の誤差性能です。

PICの水晶発振回路

PICの水晶発振回路 水晶発振器(例えばこれ)が有りますが、
これは発振回路を内蔵しているので電源を
接続するだけでこれ単体で周波数を発生
させられますが、
水晶振動子(XTAL)は水晶発振回路が
無いと周波数を発生出来ません、
PICには発振回路が内蔵されています。

左図の様に水晶とコンデンサを端子に
接続するだけでOKです。


水晶振動子の負荷容量

今回利用する水晶の負荷容量は12.5pFとなっています、この負荷容量に合わせる事でちゃんと
32.768KHzを発生させますよと言う事です、なので負荷容量が変わると32.768KHzがズレます。
負荷容量の計算方法は、 負荷容量(12.5pF) = C1 * C2 / (C1+C2) + 寄生容量
寄生容量とは基板の浮遊容量やPIC内部の容量の事で大体3〜10pFらしいです。

PICのデータシート(18F14K50の107頁にて)では上図の様にC1=C2=27pFを推奨していますがぁ、
なら 27*27/(27+27)+3=16.5pF でちょっとぉ負荷容量がオーバーですねぇ。
12.5pFにするには、20*20/(20+20)+3=13pF あるいは 15*15/(15+15)+5=12.5pF あたりですかね。
何れにせよ寄生容量が不明なのでカットアンドトライで決めるしかなさそうです。
今回は手持ちから22pFx2個のコンデンサを使いましたが、もう少し容量小さめが良い様な気がします、
コンデンサの片方をトリマーコンデンサ(手持ちなし未実験)にすれば調整が出来て良さげかもね。

また、データシートにコンデンサを大きくするとオシレータの安定性は増すものの、起動に要する時間が
長くなったり、水晶が発振せずクロックを出力しなくなる恐れがあります。と書いて有るような。

《18F14K50》

[実験1]

この頁では、PIC18F14K50を使用して実験を行いますが、他のPICでもT1CONレジスタの使い方が
少し異なるだけで同じ様に利用出来ると思います。

この頁での記事では18F14K50のDIPタイプで記述しますが、 私が購入したのは秋月電子のUSB対応超小型マイコンボード(18F14K50のSOPタイプ)ですので実験はこれで行っています。

ピン構成図
上図は18F14K50のピン構成図ですが18F13K50も同じ使い方です。
今回使用するピン番号は1番(VDD)と20番(VSS)と8番(T1OSCI)と9番(T1OSCO)です。
又、16番(RC0)ピンにLEDを接続しています。

配線図  LEDは水晶が発振しているかの確認用です。
 点灯と消灯を1秒毎に繰り返します。

 本当は水晶やコンデンサなどは基板に半田付けした方が
 良いと思います。
 まぁ、実験なのでこれでも動作はしますがぁ....
 ただぁ、水晶の足が細いのでブレッドボードの穴には
 少しゆるゆるなのが気にかかりますがぁ....

 コンデンサは15pF〜22pFまで位やトリマーコンデンサ等を
 揃えてから行う方が良いと思ふ、私も手に入れたら再度実験
 をして見ようかなぁと思ふ。


下記がプログラムソースです、
MPLAB(R) XC8 C Compiler Version 1.38コンパイラを使用しています。
MPLAB Xにてプロジェクトを作成して新規ファイルにコピーペーストして貼り付けて下さい。
次にコンパイルPIC書き込みを実行して下さい。 *2)
---------------------------------------------------------------------
#include <xc.h>


#define T1COUT 49152    // タイマー用カウントアップ開始の初期値

int LEDflg ;            // LEDのON/OFF状態フラグ

// コンフィギュレーションの設定
// 外部・内部クロックの切替えでの起動はなし(IESO_OFF)
// 動作クロックを4倍では動作させない(PLLEN_OFF):内部クロックを使用する(IRC)
#pragma config IESO=OFF,PLLEN=OFF,FOSC=IRC         // CONFIG1H
// 電源電圧降下常時監視機能ON(BOREN_ON):監視電圧は(3.0V)に設定
// 電源ONから後65.6msにプログラムを開始する(PWRTEN_ON)
#pragma config BOREN=NOSLP,BORV=30,PWRTEN=ON       // CONFIG2L
// ウオッチドッグタイマー無し(WDTE_OFF)
#pragma config WDTEN=OFF                           // CONFIG2H
// 外部リセット信号は使用せずにデジタル入力(RA3)ピンとする(MCLRE_OFF)
// オシレータが安定するのを待ってシステムクロックを供給する(HFOFST_OFF)
#pragma config MCLRE=OFF,HFOFST=OFF                // CONFIG3H
// 低電圧プログラミング機能使用しない(LVP_OFF)
#pragma config LVP=OFF                             // CONFIG4L


// タイマー1割込みの処理
void interrupt InterTimer( void )
{
     if (TMR1IF == 1) {             // タイマー1の割込み発生か?
          TMR1H = (T1COUT >> 8) ;   // タイマー1の再初期値設定
          TMR1L = (T1COUT & 0x00ff) ;
          TMR1IF = 0 ;              // タイマー1割込フラグをリセット
          // 1秒毎にLEDのフラグをON/OFFさせる処理
          if (LEDflg == 0) LEDflg = 1 ;
          else             LEDflg = 0 ;
     }
}
// メインの処理
void main()
{
     OSCCON = 0b01110010 ;     // 内部クロックとする(16MHz)
     ANSEL  = 0b00000000 ;     // ANS3-7 アナログは使用しない、デジタルI/Oに割当
     ANSELH = 0b00000000 ;     // ANS8-11アナログは使用しない、デジタルI/Oに割当
     TRISA  = 0b00000000 ;     // 1で入力 0で出力 RA4-RA5全て出力に設定(RA3は入力専用)
     TRISB  = 0b00000000 ;     // RB4-RB7全て出力に設定 
     TRISC  = 0b00000000 ;     // RC0-RC7全て出力に設定 
     UCONbits.USBEN = 0 ;      // USBは使用しない
     IOCAbits.IOCA0 = 1 ;      // USBピン(D+)をRA0のデジタル入力として使用する
     IOCAbits.IOCA1 = 1 ;      // USBピン(D-)をRA1のデジタル入力として使用する
     PORTA  = 0b00000000 ;     // 出力ピンの初期化(全てLOWにする)
     PORTB  = 0b00000000 ;     // 出力ピンの初期化(全てLOWにする)
     PORTC  = 0b00000000 ;     // 出力ピンの初期化(全てLOWにする)

     T1CON  = 0b10011110 ;     // 16ビット、外部クロック(32.768KHz)使用、プリスケーラカウント値 1:2、同期しない
     TMR1H  = (T1COUT >> 8) ;  // タイマー1の初期値設定
     TMR1L  = (T1COUT & 0x00ff) ;
     TMR1IF = 0 ;              // タイマー1割込フラグを0にする
     TMR1IE = 1 ;              // タイマー1割り込みを許可する
     PEIE   = 1 ;              // 周辺装置割り込みを許可する
     GIE    = 1 ;              // 全割り込み処理を許可する 
     TMR1ON = 1 ;              // タイマー1の開始

     LEDflg = 0 ;              // LEDのフラグ状態をOFFとする

     while(1) {
          // LEDのフラグ状態ON/OFFによりLEDをON/OFFする処理
          if (LEDflg == 0) LATCbits.LATC0 = 0 ;
          else             LATCbits.LATC0 = 1 ;
     }
}
---------------------------------------------------------------------
プログラムについて

LEDが1秒間点灯し1秒間消灯を繰り返すと思います。
1Hzは1秒で1サイクルですね、"1秒間点灯し1秒間消灯"なので半分の0.5Hzだから、
テスターで周波数出力を測って見ました、んん...0.499Hzと表示されています。

T1CONのレジスタ

ビッ ト
機能 RD16 T1RUN T1CKPS T1OSCEN T1SYNC TMR1CS TMR1ON

Bit 7 :RD16       16 ビット読み書きモード イネーブル ビット
              =16ビット動作によるTimer1 のレジスタ読み書きを有効に設定する
              0=8 ビット動作によるTimer1 のレジスタ読み書きを有効に設定する

Bit 6 :T1RUN     メイン システムクロックにTimer1オシレータを利用するのかを決めるビット
              1=メイン システムクロックをTimer1 オシレータから供給する
              =メイン システムクロックを他のソースから供給する

Bit 4-5:T1CKPS   プリスケール値選択ビット
              00=1:1  01=1:2  10=1:4  11=1:8

Bit 3 :T1OSCEN  Timer1 オシレータ回路のイネーブルビット
              =有効にする
              0=無効にする
              使用しない場合は0にするとオシレータのインバータとフィードバック抵抗を
              遮断して消費電力を低減する

Bit 2 :T1SYNC    Timer1 外部クロック入力同期選択ビット(TMR1CS = 1 の場合のみ有効)
              =外部クロック入力を同期させない
              0=外部クロック入力を同期させる

Bit 1 :TMR1CS    Timer1のクロック源を選択するビット
              =T13CKI ピンに入力される外部クロック( 立ち上がりエッジ)
              0=内部クロック(FOSC/4)

Bit 0 :TMR1ON    Timer1 ON/OFF 制御ビット
              =Timer1をON にする
              0=Timer1をOFF にする

TMR1H/TMR1Lのレジスタ

Timer1はレジスターをカウントアップして行きオーバーフローになった時点で割込みを発生させます。
8ビットモードならTMR1Lのレジスタで256になった時点(オーバーフロー)で割込みを発生
16ビットモードならTMR1H/TMR1Lのレジスタで65536になった時点(オーバーフロー)で割込みを発生

今回のワンカウントの時間を計算
Timer1のクロック源は水晶32.768KHzで、プリスケーラ値は2なので、
 (1/クロック周波数) x プリスケーラ設定値
 ( 1 / 0.032768MHz ) * 2 = 61.03515625us がワンカウントの時間です。

16ビット分カウントアップさせると 61.03515625x65535=3999938.960us(約4s)が1回の最大割込み時間
だからぁ1秒分カウントアップさせるには  61.03515625x16384=1000000us(1s)で16384回カウントアップさせます。
16384回カウントアップさせるには65536-16384 = 49152からカウントアップ開始させればOKなのでぇ
     TMR1H  = (49152 >> 8) ;   // タイマー1の初期化
     TMR1L  = (49152 & 0x00ff) ;
とレジスタに設定します。
オーバーフローになればレジスタは0なので、上の様に割込みの中で再設定を行う必要が有りますよ。

上記でも書いた様に、テスターでLEDの周波数出力を測って見たら0.499Hzだったので、
C1/C2コンデンサを調整したかったのですがぁ......
他にコンデンサ無いしぃ、トリマーコンデンサも無いしぃ、でぇ、
カウンター値の49152 を49155にしてみたら0.5Hzになったのですがぁ......
調整は実際に時計表示をさせ、しばらく動作させた後に時刻が進むならコンデンサ容量を増やし、
遅れるなら容量を減らす又はカウンター値をいじって見るなどを行った方が良いかもね。
まぁ、自分で色々実験をしてみそ!

T3CONのレジスタ(タイマー3のレジスタ)

ここでの実験はTimer1を使用していますが18F14K50にはTimer3も有るので利用する事が出来ます。
     T1OSCEN = 1 ;                 // Timer1のオシレータを有効にする(このPICではこの行が必要)
     T3CON  = 0b10010111 ;         // 16ビット、外部クロック(32.768KHz)使用、プリスケーラカウント値 1:2、同期しない
     TMR3H  = (49152 >> 8) ;       // タイマー3の初期値設定
     TMR3L  = (49152 & 0x00ff) ;
     TMR3IF = 0 ;                  // タイマー3割込フラグを0にする
     TMR3IE = 1 ;                  // タイマー3割り込みを許可する
     PEIE   = 1 ;                  // 周辺装置割り込みを許可する
     GIE    = 1 ;                  // 全割り込み処理を許可する 
     TMR3ON = 1 ;                  // タイマー3の開始

[実験2]

実験風景
 左写真は実験2の風景です。

 実験1と同じ回路ですが、RB4の端子からLCDモニタ
 に接続し時刻を表示させています。

 LCDモニタの1行目は"Sun Jun 9 2013"と表示され
 ています、ぼちぼちLCD交換したいような気がぁ...

[ダウンロードプログラムについて]

サンプルプログラムは"2013/06/09 16:40:00"から時間設定を行っています、
まずはこの設定で実験を行い動作を確認した後に色々設定を変更して見て下さい。


↓ここからサンプルプログラムソースファイルをダウンロードして下さい。
t1osc.lzh(2014/02/02) *1)

プログラムソースをダウンロードしたら、MPLABにてプロジェクトを作成します。
以下のファイルをプロジェクトディレクトリにコピーしてプロジェクトに取込んで下さい。
次にコンパイルPIC書き込みを実行して下さい。 *2)
MPLAB(R) XC8 C Compiler Version 1.38コンパイラを使用しています。

ダウンロードしたら解凍して下さい、以下のファイル構成です。
 t1osc.c ・・・・・・本体のサンプルソースプログラム
 skTime.c   ・・・・Timer1オシレータの時間管理関数を記述したライブラリソースプログラム
 skTime.h   ・・・・ヘッダファイル

このプログラムにはデバッグモニタープログラム「skMonitorLCD.c」「skMonitorLCD.h」が必要です。
デバッグLCDモニターについてはこちらを参照して下さい。
「skMonitorLCD.c」は送信データをTimer2のタイミングで送る様に変更しました、
その為にt1osc.cプログラムを変更しています。
 *1)

また、LCDモニターの出力はRB4から行っているので「skMonitorLCD.h」を下記の様に変更します。
#define _XTAL_FREQ  16000000  // 使用するPIC等により動作周波数値を設定する
#define BAUDRATE   103     // 9600bps(8MHz=51)(4MHz=25)(16MHz=103)
#define MONITOR_PIN LATB4     // モニタ出力するピンの番号を設定する

t1osc.c

このプログラムは、LCDモニターに時刻を表示させています、
また、起動後5分(16:45)するとLEDが点灯し1分後(16:46)に消灯します。


skTime.c

このライブラリは、32.768KHzの水晶振動子を使って時間を管理する為の関数集です。
基本的な時間管理は XC8 C Compiler 標準のライブラリ(time.h)を利用しています。

時間管理の標準ライブラリ(time.h)は、time( )とstime( )関数を実装していません、これは現在の時刻を
供給するシステムがハードウエアに依存している為です、なのでここではtime( )の代わりにTime( )を、
stime( )の代わりにsTime( )を実装しています。

関数ライブラリの使い方を説明します。

ans = Time( )
 1970/1/1 00:00:00から現在時刻までの通算秒を得る処理です。
 得る通算秒はグリニッジ標準時間です。
 ですので、 sTime( )で現在時刻を設定しないと 1970/1/1 00:00:00 から時刻を刻みます。
 例)
     time_t t ;

     t = time() ;
     printf("%s",ctime(&t)) ;

ans = sTime(const time_t * tp)
 現在の時間と日付を設定する処理です。
 グリニッジ標準時間で設定します。
 ans は0を返すだけです。

InitTime( )
 タイマー1の設定と初期化を行う処理です。
 タイマー1の設定は、16ビット読み書きモード、Timer1のクロックは外部から供給(32.768KHz)
 プリスケール値は1:2で、同期はしないとし、1秒間に1回の割り込みを発生させています。

ans = MakeTime(Year,Mon,mDay,wDay,Hour,Min,Sec)
 指定した時間を1970/1/1 00:00:00からの経過秒数に変換する処理です。
 返す経過秒数はグリニッジ標準時間に変換(タイムゾーン値で変換)された値です。
  Year : 日付の年(70-99/00-38)を指定する(西暦の下2ケタ 1970年-2038年)
  Mon  : 日付の月(1-12)を指定する
  mDay : 日付の日(1-31)を指定する
  Hour : 時刻の時(0-23)を指定する
  Min  : 時刻の分(0-59)を指定する
  Sec  : 時刻の秒(0-61)を指定する(閏秒を考慮)
  ans  : 成功時は指定した時間までの経過秒数を返す、−1なら指定した時間がおかしいよ
 例)
     time_t t ;

     t = MakeTime(13,6,9,16,40,0) ; // 2013/6/9 16:40:00 の経過秒数を得る
     sTime(&t) ;                    // 得た経過秒数を現在の時刻として設定する
 ※ グリニッジ標準時間に変換したくない場合は、mktime( )標準関数を使って下さい。

ans = ScheduleTimer(onHour,onMin,offHour,offMin)
 指定した時間でON/OFFのスケジュール起動を行う処理です。
 この関数は常に実行させて置く必要が有ります。
  onHour : ONをさせる時刻の時(0-23)を指定する
  onMin  : ONをさせる時刻の分(0-59)を指定する
  offHour : OFFをさせる時刻の時(0-23)を指定する
  offMin : OFFをさせる時刻の分(0-59)を指定する
  ans   : onHour/onMinの時間が来たら1で、offHour/offMinの時間が来たら0を返す
 例)
     int ans ;

     while(1) {
          ans =  ScheduleTimer(16,45,16,46) ;
          LATCbits.LATC0 = ans ;  // 16時45分〜16時46分の間だけLEDをONさせる
     }
 また注意として、
          ans =  ScheduleTimer(16,45,16,46) ;
          ans =  ScheduleTimer(19,00,5,00) ;
 という感じに2個の同時スケジュール起動は出来ません。

InterTMR1( )
 タイマー1関連の割り込み処理です。
 今回は1秒間に1回の割込み発生します、なので秒数をカウントしているだけです。
 メインプログラム(例"t1osc.c")の割込み処理で必ず呼びます。
 例)
     void interrupt InterFunction( void )
     {
          // タイマー1関連の割り込み処理
          InterTMR1() ;
     }
skTime.h

32.768KHzの水晶振動子を使って時間を管理する関数ライブラリ用インクルードファイルです。
"skTime.c"を利用する場合に
#include "skTime.h" をプログラムの先頭で記述しないとだめです。
#define TZ            -9           // 日本のタイムゾーン(GMT-9)
と記述されています、これはグリニッジ標準時間からの日本での時間差で9時間進んでいます。
もし他の国の時間差で行う場合はここを変更して下さい。
#define T1COUT        49152        // タイマー用カウントアップ開始の初期値
と記述されているのは、タイマー1の1秒間に1回割り込みを行う為の設定値です、
時間が遅れたり、進んだりした場合に微調整が出来ると思いますがぁ.....

時間管理の標準ライブラリ(time.h)

XC8 C Compiler 標準ライブラリにはlocaltime( )/gmtime( )/ctime( )/mktime( )が準備されています。
なお、このライブラリを利用する場合は、time.h をインクルードする必要が有ります。

この関数についての説明は、ともじさんの「初心者のためのポイント学習C言語」のこちらを参照下さい。

以下の記事は未実験です

《18F25K22》

このPICにはTimer1/Timer3/Timer5が有るのでこれらで外部水晶振動子32.768KHzを利用する事が
出来ます。
接続端子はSOSCO(RC0:11番端子)とSOSCI(RC1:12番端子)に繋ぎます。
また、18F23K22/18F24K22/18F26K22も同じです。
     T1CON  = 0b10011110 ;         // 16ビット、外部クロック(32.768KHz)使用、プリスケーラカウント値 1:2、同期しない
     TMR1H  = (49152 >> 8) ;       // タイマー1の初期値設定
     TMR1L  = (49152 & 0x00ff) ;
     TMR1IF = 0 ;                  // タイマー1割込フラグを0にする
     TMR1IE = 1 ;                  // タイマー1割り込みを許可する
     PEIE   = 1 ;                  // 周辺装置割り込みを許可する
     GIE    = 1 ;                  // 全割り込み処理を許可する 
     TMR1ON = 1 ;                  // タイマー1の開始
Timer3/Timer5はレジスタ名称を1から3/5に変えます。

《12F1822/16F1823/16F1826/16F1827/16F1938》

接続端子はT1OSO/T1OSIに繋ぎますが、端子番号は異なりますよ。
     T1CON  = 0b10011100 ;         // 外部クロック(32.768KHz)使用する、プリスケーラカウント値 1:2、同期しない
     TMR1H  = (49152 >> 8) ;       // タイマー1の初期値設定
     TMR1L  = (49152 & 0x00ff) ;
     TMR1IF = 0 ;                  // タイマー1割込フラグを0にする
     TMR1IE = 1 ;                  // タイマー1割り込みを許可する
     PEIE   = 1 ;                  // 周辺装置割り込みを許可する
     GIE    = 1 ;                  // 全割り込み処理を許可する 
     TMR1ON = 1 ;                  // タイマー1の開始



リンクの見直し(*2) 2017/01/13
"skMonitorLCD.c"変更により"t1osc.c"のプログラム変更(*1) 2014/02/02


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2017 Shigehiro Kimura All Rights Reserved.