2020年5月17日日曜日

ラジオの時報で合わせる正確な時計を作る2マイコン編

前回は、ラジオの音声から時報を検出する回路を製作しましたが、
ラジオの時報で合わせる正確な時計を作る1

なんと前回の記事からほぼ1年!
記事は久々に書きますが、空き時間に少しずつ進めていました。
今回は時報検出回路の信号を受けて、時計をマイコンで動かす回路を試作してみようと思います。
新型コロナウィルスの影響で家にいる時間も多いので、結構完成に近づいてきました。

マイコンは作例も多いPIC16F88を使用します。

時刻をデジタル時計で表示するなどしません。
時計はアナログが一番!
写真はSEIKOのバス時計しかもトランジスタのモデルですが、
調子が悪かったので、クォーツに変えてあります。この時計を動かしました。

マイコンでアナログ時計を動かす方法はこちらの記事を参考にしました。

Arduinoで時計にいたずら
https://n.mtng.org/ele/arduino/clock.html
アナログ時計の動かし方はシンプルで、
時計のムーブメントを分解して、コイルに流す電流の+と-を入れ替える度に1秒進みます。インバーターICを追加することで、1pinで駆動できるようにしました。こうすることでPICを壊す心配も減ります。
試作回路はこちら、

32.628kHzの水晶振動子を74HC4060で分周して2Hzを作り、さらに4ビットバイナリカウンタICのTC4520で分周して1Hzを作り、これを1秒の基準とします。
1秒の作り方はこちらのサイトが参考になりました。

74HC4060と水晶発振子を用いた1Hzの生成
http://tyk-systems.com/LEDflash/LEDflash.html

最初から水晶振動子の出力をPICに入れて直接カウントさせることもできますが、ロジックICで遊びたかったんです。TC4520にはカウンタが2個入っているので、空いている方で、時刻の表示もさせてみました。(LEDが点灯して2進数で時刻がわかります)
ブレッドボード上に水晶の発振回路を組むとなかなか動かなくて、カットアンドトライで抵抗やコンデンサの値を変えているので、水晶の周りは回路図の値ではないです。(笑)

今回。試作した回路の写真

使い方は簡単、
1. DIPスイッチで最初の時報を受信する時刻を設定する。
2. 電源を入れると、秒針が進むので秒針が59秒の位置になったら、プッシュスイッチ(SW1)を押す。ラジオの電源が入り、時報受信待機状態になり、2進数で時刻が表示される。
3. 秒針を59秒の位置で合わせたら、時計の裏のツマミを回して時報を受信する時刻に時針と分針を合わせる。(普通にアナログ時計を合わせる感じ)
4. 時報を受信すると時計が動き出す。
あとは午前5時と午後5時の10秒前に時報受信の待機状態になり勝手に時報に合わせてくれます。
午前と午後は区別しない作りになっています。

プログラムはこちら、
interrupt isrで1Hzが割り込んできて、秒針を動かすようになっています。
/* ラジオの時報を受信する時計
 * File:   main.c
 * Author: LEFT-AMD
 *
 * Created on 2020/01/27, 22:09
 */
// PIC16F88 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB3      // CCP1 Pin Selection bit (CCP1 function on RB3)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

// CONFIG2
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal External Switchover bit (Internal External Switchover mode disabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>

#define _XTAL_FREQ 8000000  // 8MHz
void secmove(void)
{
    //秒針を一秒動かす関数
    RB2 = ~RB2;
}
void zyushin(void)
{
    //時報を受信する関数
    int n=0;
          RB4 = 0;    //リレーON
         while(n < 2){     //440Hzを2回以上検知するとループを抜ける
           if(RB6 == 1){
           n++;
          __delay_ms(980);   //Delay1秒
      }
        }
         n = 0;
    while(RB5 == 0){     //880Hz検知するとループを抜ける
    } 
}

void printhour(unsigned char hr)
{
 //時刻表示する
    int j=0;
    RB7 = 1;    //時刻表示リセット
    RB7 = 0;
        while(j<hr*2){
            RB3 = ~RB3;
            __delay_ms(50);
            j++;
       }
       j=0;
       hr = 0;
}
int sec=0;
void main(void)
{
    OSCCON = 0b01110000;    // 内蔵クロックの周波数を8MHzに設定
    ANSEL = 0b00000000;     // A/D変換を無効化
    ADCON0 = 0x00;
    ADCON1 = 0x00;
    PORTA = 0x00;           // PORTAを初期化
    PORTB = 0x00;           // PORTBを初期化
    TRISA = 0b00011111;     // PORTAの入出力設定
    TRISB = 0b01100001;     // PORTBの入出力設定
    
    INTCON = 0b00000000;        //GIEとINTEを0にする。割り込み無効化
    __delay_ms(3000);      //水晶振動子が安定するまで待つ3秒
    int i=0;
    unsigned char hour=0;
    unsigned char zyuf=0;   //時報受信フラグ、0受信しない、1受信する
    unsigned char zyuh=5;   //時報を受信する時刻
    RB3 = 0;    //LED
    RB2 = 0;    //秒針駆動端子の初期値
    RB4 = 1;    //リレーOFF
    RB1 = 1;    //秒カウンタOFF
    while(RA4 == 1){     //SW1ボタンを押すまで針が早く進む正時指定
        secmove();
         __delay_ms(500);        // 500ミリ秒の待ち時間
    }
    hour = (unsigned char)(~PORTA & 0x0F);    //Aポートの状態取得 DIPスイッチがリアルコードのため反転している。論理積で上位4ビットはマスクしている。
        printhour(hour);    //時刻の表示
        zyushin();      //時報の受信
    while(1){   //無限ループ
    RB1=0;  //秒カウンタON   
    OPTION_REG = 0b01000000;    //INTEDG 割込みエッジ選択ビット1=INTピンの立ち上がりエッジによる割込み
    INTCON = 0b10010000;        //GIEとINTEを1にする。
    RB4 = 1;    //時報受信リレーOFF

    printhour(hour);    //時刻の表示        
    if(zyuh-1 == hour)  //受信設定時刻の1時間前に受信フラグを立てる
        zyuf=1;   
    
    if(zyuf==0){    //受信フラグの判別
        while(sec <= 3599){  //3599秒になったとき
        }
        sec = 0; //秒はリセットする

    }else{
        while(sec <= 3590){ //時報を受信する条件 3590
        }
        sec = 0;          //秒リセット
        INTCON = 0b00000000;        //GIEとINTEを0にする。無効化
        RB1 = 1;    //秒カウンタOFF
        while(i<9){               // 正時10秒前に秒針を正時の位置に待機させる
            RB3 = ~RB3;
            secmove();
            __delay_ms(200);        // 200ミリ秒の待ち時間
            i++;       
            }
        i=0;
        zyushin();      //時報の受信
        zyuf=0;
        }
       
     if(hour >= 12){  //12時のときは
            hour = 0; //hourをリセットする。
            }
        hour++;  //1時間増やす
    }
}

void interrupt isr(void)
{
    //割込み処理プログラム
    secmove();
    sec++;
    __delay_ms(20);
    INTF = 0; //フラグのクリア
}

C言語の教科書引っ張り出して、作ってみました。
セミコロンの入れ忘れがあったり、文法を結構忘れていて思い出すのに時間がかかりました。