リアルタイムクロックRTC8564を使用した時刻表示(インターバル割り込み(IOCA3)、ICSP端子を入力として使用)

icon 項目のみ表示/展開表示の切り替え

概要

I2C接続のリアルタイムクロックRTC8564を使用しLCD液晶に時刻を表示する。
電気二重層キャパシタでバックアップされているので電源を切っても時刻はカウントされる。電気二重層キャパシタは内部インピーダンスが非常に低いため充電時に大電流が流れることになる。充電電流を制限するために330Ωを接続している。
日の修正は月の日数の最大値および閏年の2月の29日に対応している。
PICKit3による書き込み時に障害とならないようにタクトスイッチとの間に抵抗を挿入する。
LCD液晶はSC1602を対象としているが、液晶モジュールの電源の接続をVDD,GNDを入れ替えればSC2004でも対応可能。ただしrtc3c.cを修正しない場合16文字*2行でしか表示されないので注意。LCD表示用ライブラリは制限はない。
約0.2秒の時間はソフトウェアによるディレイ(クロック周波数に依存)で対応。
クロックはUSB-IOで接続済みの12MHzのセラミック振動子を使用しPLLで4倍にしている。書込みはPICKit3を前提とする。
割り込みはINT1端子で受け付ける。RTC8564のINT端子はオープンドレインなのでプルアップ抵抗を接続する。
RB5,RB7を今後RS232C用に使用するのでスイッチ用の入力端子が不足することとなる。
ここでは、ICSP端子と供用でRA0(PGD),RA1(PGC)を入力ポート、IOCA3(VPP)を割り込み端子として使用する。
SW0がRA0,SW1がRA1に接続されている。

割り込み端子

PIC18の割り込み端子はINT系とIOC系がある。
INT系(PIC18F14K50では3本)は入力信号に対して立下り・立ち上がりエッジ(INTEDGn nはビット番号)及び割り込み信号1個ごとに割り込みの許可禁止を設定できるビット(INTnIE nはビット番号)がある。
IOC系(PIC18F14K50では9本)は状態変化割り込みであり、前回読み出した値と変化があった場合、割り込みが発生する。割り込み信号ごとに個別に設定することができない。状態変化割り込みであることを確認(RABIF)してからどの信号が状態変化割り込みをおこしたか確認するためにポートを読み出す必要がある。

入力端子の不足対策(RA0,RA1)

ハードウェア

ICSPのPGD,PGC端子と干渉するため1kΩの抵抗を接続してスイッチ回路を接続する。
../rtc3c/cir_ra01.svg

プログラムの設定

この端子はIOポートして使用する場合は入力専用なのでPORTAの設定は必要でないが、IOCAのIOCA0,IOCA1をセットし状態変化割り込みを許可しなければ入力ポートとして使用できない。IOCA0,IOCA1の設定は割り込みで入力ポートの状態を検出しない場合でも必要である点に注意する必要がある。

入力端子の不足対策(IOCA3)

ハードウェア

ICSPのVPPは書き込み時に9~13Vの電圧がかかる。接続する回路を保護するために6.8kΩを接続する。
cir_ra3.svg

プログラムの設定

この端子はIOポートして使用する場合は入力専用なのでPORTAの設定は必要でないが、IOCAのIOCA3をセット・INTCONのRABIEをセットし状態変化割り込みを許可する。
割り込み処理ルーチンでは他の状態変化割り込みにより割り込みが発生していないことを確認し、割り込みがhiからlowに変化した場合発生したことが確認できればRTCからのインターバル割り込みなので時刻表示等の処理を行う。

液晶表示例

2013/10/11 Fri
10:20:30

時刻修正方法

時刻表示時にSW0を押すと修正モードに入る。
修正する項目の右側に←が表示されるので、SW1で値を合わせる。
秒の修正後SW0を押すと時刻の修正が終了し時計表示を開始する。
回路は次のとおりである。
回路図(PNG) 回路図(PDF) 回路図(DXF)

プログラムの説明

ソースファイル

以下のファイルで構成されている
rtc4c.c ・・・ メインプログラム
lcd.c   ・・・ LCD表示用ライブラリ Version 1.00
lcd.h   ・・・ LCD表示用インクルードファイル Version 1.00
i2c_ex.c   ・・・ I2C 拡張ライブラリー  Version 1.00
i2c_ex.h   ・・・ I2C 拡張ライブラリー  Version 1.00
rtc.c   ・・・ RTC8564 RTCライブラリ Version 1.00
rtc.h   ・・・ RTC8564 RTCライブラリ Version 1.00
sw.c   ・・・ タクトスイッチライブラリ Version 1.00
sw.h   ・・・ タクトスイッチライブラリ Version 1.00

ソースファイルのダウンロード rtc4c.zip

lcd.c

主にSC1602向けのLCD液晶表示用のライブラリである。
液晶モジュールと接続するポートはマクロで変更可能である。
busyフラグはチェックせずソフトウェアディレイで対応しているためクロック周波数が変更された場合はディレイの調整が必要
ユーザー定義キャラクタに対応
詳細は以下のリンクを参照されたい
lcd.c

i2c_ex.c

詳細は以下のリンクを参照されたい
i2c_ex.c

sw.c

詳細は以下のリンクを参照されたい
sw.c

rtc.c

詳細は以下のリンクを参照されたい
rtc.c

rtc4c.c

メインプログラムである。
IOポートの入出力の設定をし、LCDの初期化サブルーチンコール、I2Cモジュールの初期化、割り込みモジュールの初期化、RTCのインターバルタイマーを起動し、スリープする。
割り込みにより1秒置きに時刻を読み出し液晶に表示する。
この際、SW0が押されていた場合、時刻修正モードに入る。


/*
 * RTC8564 LCD 時計 Version 1.10
 * File:        rtc4c.c
 * Target:      PIC18F14K50 48MHz
 * コンパイラ:   Microchip MPLAB XC8 C Compiler V1.20
 * 書き込み:     PICKit 3
 * リリース:     2013/10/08 20:00
 *
 * 時刻修正機能付き時計。曜日表示および閏年や月の日数に合わせて日時修正が可能(月日数 30日,29日等に対応)
 * RTCをバッテリバックアップしている場合は、電源投入時にRTCの初期化はしない。(RTC8564のVLビットを参照)
 * IOSP端子と共用であるIOCA3による状態変化割り込みを1秒間隔で使用
 * IOSP端子と共用であるRA0、RA1をSW0、SW1のスイッチに使用
 * RC1は使用していない(将来的にアナログ入力として使用する予定)
 * 使い方
 *   SW0を押すと時刻修正モードになる
 *   時刻修正モードではSW1を押すと値をインクリメント、SW0を押すと次の項目の修正
 *   修正項目は<-で示される左側の値である。時刻修正モードで秒が表示されているときにSW0を押すと時刻をRTCにセットし時計表示となる。
 *              各端子の接続
 *              RA0 in      :SW0
 *              RA1 in      :SW1
 *              IOCA3 in    :RTCインターバル割り込み(1秒間隔)
 *              RC0 out     :LED0
 *              RC2 out     :LCD RS
 *              RC3 out     :LCD E
 *              RC4-RC7 out :LCD DB4-DB7
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <p18f14k50.h>
#include <plib\delays.h>
#include <plib\i2c.h>
#include "rtc.h"
#include "lcd.h"
#include "sw.h"

#pragma config  FOSC = HS,CPUDIV = NOCLKDIV,PLLEN=ON,WDTEN=OFF,LVP=OFF,MCLRE=OFF

#define LED0 PORTCbits.RC0

char buf[34];
unsigned char RE_PORTA;

unsigned char week(unsigned short y,unsigned char m,unsigned char d);
unsigned char leap_chk(unsigned short year);
void uchar_in(unsigned char* v,unsigned char min,unsigned char max,unsigned char c,unsigned char y);
unsigned char bcd2bin(unsigned char v);
unsigned char bin2bcd(unsigned char v);
void rtc_sput2(unsigned char* buf,struct RTC_DATE* d);
void rtc_set(struct RTC_DATE* rp);
void  interrupt isr(void);

const char* week_s[]={ "Sun","Mon","Tue","Wed","Thu","Fri","Sat",0 };

void main(void) {
    ANSEL=0;
    ANSELH=0;
    SLRCON=0;
    INTCON2=0b10000001; // プルアップなし 状態変化割り込みを高優先度にする
    TRISA=0b00000000;
    TRISB=0b11110000;
    TRISC=0b00000000;
    PORTC=0;
    IOCAbits.IOCA0=1;   //  RA0
    IOCAbits.IOCA1=1;   //  RA1
    IOCAbits.IOCA3=1;   //  RA3
    lcd_init();
    OpenI2C(MASTER,SLEW_ON);    // マスター
    SSPADD=230;
    LED0=0;

    if(rtc_vl_chk()){
        rtc_init();
        rtc_start();
    }
    RE_PORTA=PORTA;
    INTCONbits.RABIF=0;
    INTCONbits.RABIE=1; //  RA3?5,RB4?7の状態変化割り込みを許可
    INTCONbits.GIE=1;

    rtc_timer_on(); //  1秒のインターバルタイマーを起動

    while(1){
    }
}

//  日付より曜日を取得

unsigned char week(unsigned short y,unsigned char m,unsigned char d){
    if(m <= 2){
        --y;
        m += 12;
    }
    unsigned short long w=d;
    w= w + ( ( 8+ 13*m)/5 );
    w= w + (y + (y / 4) - (y/100) + (y/400) );
    return w % 7;
}

// うるう年判定 1が返されるとうるう年

unsigned char leap_chk(unsigned short year){
    unsigned char l;
    signed char r4= ((unsigned short)year % 4);
    signed char r100 = ((unsigned short)year % 100);
    signed char r400 = ((unsigned short)year % 400);
    if(r4==0){
        if(r100==0){
            if(r400==0)
               l=1;
             else
               l=0;
        }else
             l=1;
     }else
          l=0;
    return l;
}

//  タクトスイッチによる1byte入力(下限上限指定・LCD表示付き)
//  引数 c:LCD列位置 y:LCD行位置
//  SW1:値up  SW0:終了

void uchar_in(unsigned char* v,unsigned char min,unsigned char max,unsigned char c,unsigned char y){
    unsigned char swv,swv_trg;
    for(;;){
        lcd_ddramxy( c,y);
        sprintf(buf,"%2.2i",(int)*v);
        lcd_puts(buf);
        for(;;){
            sw_trg_in(&swv,&swv_trg);
            if( (swv & SW1_MASK)==0 ){ // SW0が押されているのでインクリメント
                if(*v>=max)
                    *v=min;
                else
                    (*v)++;
                Delay10KTCYx(200);

                break;
            }
            if( (swv_trg & SW0_MASK) && (swv & SW0_MASK)==0 ){ // SW1が押された瞬間
                do{
                    sw_trg_in(&swv,&swv_trg);
                }while((swv_trg & SW0_MASK) && (swv & SW0_MASK)); // SW1が離された瞬間
                return;
            }
        }
    }
}

//  BCDをバイナリに変換

unsigned char bcd2bin(unsigned char v){
    unsigned char d1=(v<<4);
    unsigned char d2=(v & 0xf);
    if(d2>9){
       ++d1;
       d2 -= 10;
    }
    return d1*10 + d2;
}

//  バイナリをBCDに変換

unsigned char bin2bcd(unsigned char v){
    unsigned d1=(v % 10);
    unsigned d2=(v / 10)<<4;
    return  d2+d1 ;
}

//  日時表示

void rtc_sput2(unsigned char* buf,struct RTC_DATE* d){
    sprintf(buf,"20%2.2x/%2.2x/%2.2x  %s\n%.2x:%.2x:%.2x",d->year,d->month,d->day,week_s[d->week],d->hour,d->min,d->sec);
}

//  日付時刻修正
//  SW1:値up  SW0:次の修正

void rtc_set(struct RTC_DATE* rp){
    unsigned char sel=0;   // 修正位置
    unsigned char v;
    unsigned char leap;   //  うるう年のとき1がセットされる
    unsigned char dmax;  //  日の最大値
    unsigned char w;    //  曜日
    unsigned char e=0;  //  終了

    for(;;){
        rtc_sput2(buf,rp);
        lcd_disp(buf);
        switch(sel){
        case 0: //  年
            lcd_ddramxy( 4,0);
            lcd_putc(0x7f); //  <-

            v=bcd2bin(rp->year);
            uchar_in(&v,0,99,2,0);
            rp->year=bin2bcd(v);
            leap=leap_chk(v+2000);

            lcd_ddramxy( 4,0);
            lcd_putc('/');

            w=week( bcd2bin(rp->year)+2000 , bcd2bin(rp->month) , bcd2bin(rp->day));
            rp->week=w;

            ++sel;
            break;
        case 1: //  月
            lcd_ddramxy( 7,0);
            lcd_putc(0x7f); //  <-

            v=bcd2bin(rp->month);
            uchar_in(&v,1,12,5,0);
            rp->month=bin2bcd(v);
            switch(v){
                case 1:
                case 3:
                case 5:
                case 7:
                case 8:
                case 9:
                case 10:
                case 12:
                    dmax=31; break;
                case  2:
                    dmax=28+leap; break;
                default:
                     dmax=30; break;
            }
            lcd_ddramxy( 7,0);
            lcd_putc('/');
            w=week( bcd2bin(rp->year)+2000 , bcd2bin(rp->month) , bcd2bin(rp->day));
            rp->week=w;

            ++sel;
            break;
        case 2: //  日
            lcd_ddramxy(10,0);
            lcd_putc(0x7f); //  <-

            v=bcd2bin(rp->day);
            uchar_in(&v,1,dmax,8,0);
            rp->day=bin2bcd(v);
            w=week( bcd2bin(rp->year)+2000 , bcd2bin(rp->month) , bcd2bin(rp->day));
            rp->week=w;
            lcd_ddramxy( 10,0);
            lcd_putc('/');
            ++sel;
            break;
        case 3: //  時
            lcd_ddramxy(2,1);
            lcd_putc(0x7f); //  <-

            v=bcd2bin(rp->hour);
            uchar_in(&v,0,23,0,1);
            rp->hour=bin2bcd(v);
            lcd_ddramxy(2,1);
            lcd_putc(':');
            ++sel;
            break;
        case 4: //  分
            lcd_ddramxy(5,1);
            lcd_putc(0x7f); //  <-

            v=bcd2bin(rp->min);
            uchar_in(&v,0,59,3,1);
            rp->min=bin2bcd(v);
            lcd_ddramxy(5,1);
            lcd_putc('/');
            ++sel;
            break;
        case 5: //  秒
            lcd_ddramxy(8,1);
            lcd_putc(0x7f); //  <-

            v=bcd2bin(rp->sec);
            uchar_in(&v,0,59,6,1);
            rp->sec=bin2bcd(v);
            lcd_ddramxy(8,1);
            lcd_putc(' ');
            ++sel;
            break;
        case 6: //  終了
            return;

        }
    }
}


//  1秒置きの割り込み処理ルーチン

void  interrupt isr(void){
    char buf[34];
    struct RTC_DATE rtc;
    unsigned char swv,swv_trg;

    unsigned char now=PORTA;
    unsigned char re=RE_PORTA;
    RE_PORTA=now;
    INTCONbits.RABIF=0;
    if( ((re ^ now) & 0b00001000) ) //  IOCA3が変化(IOCA0とIOCA1は無視)
        if(now & 0b00001000)    //  立ち上がりエッジなので戻る
            return;
    rtc_read(&rtc);
    rtc_sput2(buf,&rtc);
    lcd_disp(buf);

    sw_trg_in(&swv,&swv_trg);
    if( (swv_trg & SW0_MASK) && (swv & SW0_MASK)==0 ){ // SW0が押された瞬間
        INTCONbits.RABIE=0;
        rtc_timer_off();
        do{
            sw_trg_in(&swv,&swv_trg);
        }while((swv_trg & SW0_MASK) && (swv & SW0_MASK)); // SW0が離された瞬間
        rtc_set(&rtc);
        rtc_write(&rtc);
        rtc_timer_on();
        RE_PORTA=PORTA;
        INTCONbits.RABIE=1;
        return;
    }else
        LED0 = ~LED0;
}