PIC

PIC16f690を使った周波数測定器と発振回路

2009年9月19日

色々と考えるところ有って、PIC16f690を用いて、1 Hzから50 MHzまで測定できる簡易周波数測定器と、その動作検証のための発振回路を作成した。

周波数測定器/PIC16f690

まず、発振回路のプログラムから。RA3とRA5の2つの入力を調整することで、1 Hzから2 MHzまでの周波数で方形波を発生することができる。どの周波数が発生できるかは、プログラム中のコメントを参照。

#include <pic.h>

/*
// Oscillator 
#define EXTCLK        0x3FFF    // External RC Clockout
#define EXTIO        0x3FFE    // External RC No Clock
#define INTCLK        0x3FFD    // Internal RC Clockout
#define INTIO        0x3FFC    // Internal RC No Clock
#define EC        0x3FFB    // EC
#define HS        0x3FFA    // HS
#define XT        0x3FF9    // XT
#define LP        0x3FF8    // LP
// Watchdog Timer 
#define WDTEN        0x3FFF    // On
#define WDTDIS        0x3FF7    // Off
// Power Up Timer 
#define PWRTDIS        0x3FFF    // Off
#define PWRTEN        0x3FEF    // On
// Master Clear Enable 
#define MCLREN        0x3FFF    // MCLR function is enabled
#define MCLRDIS        0x3FDF    // MCLR functions as IO
// Code Protect 
#define UNPROTECT    0x3FFF    // Code is not protected
#define CP        0x3FBF    // Code is protected
#define PROTECT        CP    //alternate
// Data EE Read Protect 
#define UNPROTECT    0x3FFF    // Do not read protect EEPROM data
#define CPD        0x3F7F    // Read protect EEPROM data
// Brown Out Detect 
#define BORDIS        0x3CFF    // BOD and SBOREN disabled
#define SWBOREN        0x3DFF    // SBOREN controls BOR function (Software control)
#define BORXSLP        0x3EFF    // BOD enabled in run, disabled in sleep, SBOREN disabled
#define BOREN        0x3FFF    // BOD Enabled, SBOREN Disabled
// Internal External Switch Over Mode 
#define IESOEN        0x3FFF    // Enabled
#define IESODIS        0x3BFF    // Disabled
// Monitor Clock Fail-safe 
#define FCMEN        0x3FFF    // Enabled
#define FCMDIS        0x37FF    // Disabled
*/
__CONFIG(INTCLK & WDTDIS & PWRTDIS & MCLRDIS & UNPROTECT & BORDIS & IESODIS & FCMDIS);

unsigned char g_tmr0=0;
void interrupt int_function (void) {
    if (!T0IF) return;
    TMR0=36;
    T0IF=0;
    g_tmr0=1;
}

void main(){
START:
    // Disable all interrupt events
    INTCON=0;
    // all output except for RA3 and RA5
    TRISA=0x28;
    TRISB=0;
    TRISC=0;
    // Non-analog mode
    ANSEL=0x00;
    ANSELH=0x00;

    /*
        Fastest mode (RA3=1, RA5=0)
    
        RA4: 2 MHz
        RC0: 250 kHz
        RC1: 125 kHz
        RC2: 62500 Hz
        RC3: 31250 Hz
        RC4: 15625 Hz
        RC5: 7813 Hz    
        RC6: 3906 Hz
        RC7: 1953 Hz
    */
    OSCCON=0x70;// CLK=8 MHz
    PORTC=0x00;
#asm
LOOP1:
    INCF 0x7,F
    BTFSC 0x5,3
    GOTO LOOP1  // loop if RA3=1
#endasm

    /*
        Fast mode (RA3=0, RA5=1)
    
        RA4: 500 kHz
        RC0: 62500 Hz
        RC1: 31250 Hz
        RC2: 15625 Hz
        RC3: 7813 Hz    
        RC4: 3906 Hz
        RC5: 1953 Hz
        RC6: 977 Hz
        RC7: 488 Hz
    */
    OSCCON=0x50;// CLK=2 MHz
    PORTC=0x00;
#asm
LOOP2:
    INCF 0x7,F
    BTFSC 0x5,5
    GOTO LOOP2  // loop if RA5=1
#endasm

    /*
        Slow mode (RA3=0, RA5=0)
    
        RA4: 1 MHz
        RC0: 2048 Hz    
        RC1: 1024 Hz
        RC2: 512 Hz
        RC3: 256 Hz
        RC4: 128 Hz
        RC5: 64 Hz
        RC6: 32 Hz
        RC7: 16 Hz
        RB4: 8 Hz
        RB5: 4 Hz
        RB6: 2 Hz
        RB7: 1 Hz
    */
    OSCCON=0x60;// CLK=4 MHz
    GIE=1;
    T0CS=0; // Use internal clock
    T0IF=0; // Clear flag
    T0IE=1; // Interrupt by timer
    PSA=1; // Don't use prescaler for TMR0
    
    unsigned char rb=0,rc=0;
    while((PORTA&0x28)==0) {
        for(g_tmr0=0;!g_tmr0;/* wait until intteruption */);
        PORTC=++rc;
        if (rc==0) {
            rb+=0x10;
            PORTB=rb;
        }
    }
    goto START;
}

この装置については多分回路図を描くまでも無い。RA3とRA5にHかLの入力を与え、RC0-7とRB4-7に出力されるという簡単なもの。ちなみに最初のほう、長々とコメントがあるのはPIC16f690のCONFIGビットの設定の仕方について。

次は、周波数測定器のプログラム。内部クロックを用いている。このクロックがどれくらい正確なのかによるが、誤差は多くても数パーセント。自作コンピュータのチェックなどには十分な性能のはずである。

#include <pic.h>
__CONFIG(INTIO & WDTDIS & PWRTDIS & MCLRDIS & UNPROTECT & BORDIS & IESODIS & FCMDIS);

unsigned char led7(unsigned char num){
    unsigned char ret=0x80;
    if (num & 0x10) ret=0x00;
    switch (num & 0x0F) {
        case 0: return ret | 0x08;
        case 1: return ret | 0x3B;
        case 2: return ret | 0x41;
        case 3: return ret | 0x21;
        case 4: return ret | 0x32;
        case 5: return ret | 0x24;
        case 6: return ret | 0x04;
        case 7: return ret | 0x38;
        case 8: return ret | 0x00;
        case 9: return ret | 0x20;
        default: return 0xff;
    }
}

// 10000 cycles @ 8 MHz / 4 == 5 ms
#define WAIT_CYCLE (10000-23)
#define TMR1H_INIT ((unsigned char)(((unsigned short)(65536-WAIT_CYCLE))>>8))
#define TMR1L_INIT ((unsigned char)(((unsigned short)(65536-WAIT_CYCLE))&0x00ff))

unsigned char g_ledp=0;
unsigned short g_num=0;
unsigned char g_count=0;

void interrupt int_function (void) {
    if (!TMR1IF) return;
    TMR1H=TMR1H_INIT;
    TMR1L=TMR1L_INIT;
    TMR1IF=0;

    g_count++;

    static unsigned char ledp=0x10;
    if (!(ledp=ledp<<1)) ledp=0x10;

    unsigned short i=g_num;
    unsigned char j;
    for (j=0x80;j!=ledp;j=j>>1) i=i/10;
    i=i % 10 + (ledp==g_ledp?0x10:0x00);

    PORTB=0;
    PORTC=led7(i);
    PORTB=ledp;
}

void main(){
    // CLK=8 MHz
    OSCCON=0x70;
    // all output
    TRISA=0;
    TRISB=0;
    TRISC=0;
    // Non-analog mode
    ANSEL=0x00;
    ANSELH=0x00;

    // Timer0 is used as a counter.
    TRISA=TRISA | 0x04; // RA2 is for input.
    T0CS=1; // counter mode
    T0SE=0; // increment when L -> H
    PSA=1; // Prescaler not used for TMR0, first
    TMR0=0;

    // Scaler 1:1
    T1CON=0x00;
    // Reset timer
    TMR1H=0x00;
    TMR1L=0x00;
    // Clear flag
    TMR1IF=0;
    // Enable interrupt
    TMR1IE=1;
    PEIE=1;
    GIE=1;
    // Start timer
    TMR1ON=1;
    
    unsigned long i;
    unsigned char mhz=0;
    while(1){
        if (i<10000) {
            mhz=0;
            PSA=1; // Prescaler not used for TMR0
            i=0;
            TMR0=g_count=0;
            T0IF=0;
            TMR1H=TMR1H_INIT;
            TMR1L=TMR1L_INIT;
            while(g_count<200){
                if (T0IF){
                    T0IF=0;
                    i++;
                }
            }
            i=(i<<8)+(unsigned long)TMR0;
        } else if (i<100000) {
            mhz=0;
            PSA=0; // Prescaler used for TMR0
            PS2=PS0=0; // Prescaler; 1:8
            PS1=1;     // Prescaler; 1:8
            i=0;
            TMR0=g_count=0;
            T0IF=0;
            TMR1H=TMR1H_INIT;
            TMR1L=TMR1L_INIT;
            while(g_count<200){
                if (T0IF){
                    T0IF=0;
                    i++;
                }
            }
            i=(i<<8)+(unsigned long)TMR0;
            i=i<<3;
        } else if (i<1000000) {
            mhz=0;
            PSA=0; // Prescaler used for TMR0
            PS2=PS0=1; // Prescaler; 1:64
            PS1=0;     // Prescaler; 1:64
            i=0;
            TMR0=g_count=0;
            T0IF=0;
            TMR1H=TMR1H_INIT;
            TMR1L=TMR1L_INIT;
            while(g_count<200){
                if (T0IF){
                    T0IF=0;
                    i++;
                }
            }
            i=(i<<8)+(unsigned long)TMR0;
            i=i<<6;
        } else if (i<9500000 || (i<10000000 && mhz==0)) {
            mhz=0;
            PSA=0; // Prescaler used for TMR0
            PS2=PS1=PS0=1; // Prescaler; 1:256
            i=0;
            TMR0=g_count=0;
            T0IF=0;
            TMR1H=TMR1H_INIT;
            TMR1L=TMR1L_INIT;
            while(g_count<200){
                if (T0IF){
                    T0IF=0;
                    i++;
                }
            }
            i=(i<<8)+(unsigned long)TMR0;
            i=i<<8;
        } else {
            mhz=1;
            PSA=0; // Prescaler used for TMR0
            PS2=PS1=PS0=1; // Prescaler 1:256
            i=0;
            TMR0=g_count=0;
            T0IF=0;
            GIE=0; // Disable interrupt
            PORTB=0;
            TMR1H=0xB;
            TMR1IF=0;
            TMR1L=0xDC; //0xBDC = 65536-62500
            while(!TMR1IF){
                if (T0IF){
                    T0IF=0;
                    i++;
                }
            }
            i=(i<<8)+(unsigned long)TMR0;
            i=i<<8;
            i=i<<5; // 2000000/62500 = 32
            TMR1IF=0;
            GIE=1; // Enable interrupt
            while(g_count<200);
        }
        if (mhz) i=i/1000;
        if (i<10000) {
            g_num=i;
            g_ledp=0x10;
        } else if (i<100000) { 
            g_num=i/10;
            g_ledp=0x20;
        } else if (i<1000000) { 
            g_num=i/100;
            g_ledp=0x40;
        } else if (i<10000000) { 
            g_num=i/1000;
            g_ledp=0x80;
        } else {
            g_ledp=0x00;
            g_num=8888;
        }
        if (mhz) i=i*1000;
    }
}

表示は基本的に、KHz単位。10 MHzを超える場合は、MHz単位で点滅して表示される。プログラムでは、10 kHz以下、10-100 kHz、100 kHz - 1 MHz、1 - 10 Mhz、10 Mhz以上の5つの場合に分けて、TMR0のプリスケーラの使用方法や測定の仕方などを変化させるようにした。色々と実験を行って、PIC16f690の理論的に可能な速度である50 MHzでも問題なく測定できるプログラムにしてある。

信号は、RA2から入力する。回路は4つの7セグメントを用いたもので、プロトタイプになりそう。

090921追記:
先日の記事において、回路図の一部に誤りがあったのを修正。

<%media(20090922-hz_0022.zip|プログラムと回路図のソースコードはここ。)%>

コメント

コメントはありません

コメント送信