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

まず、発振回路のプログラムから。RA3とRA5の2つの入力を調整することで、1 Hzから2 MHzまでの周波数で方形波を発生することができる。どの周波数が発生できるかは、プログラム中のコメントを参照。
この装置については多分回路図を描くまでも無い。RA3とRA5にHかLの入力を与え、RC0-7とRB4-7に出力されるという簡単なもの。ちなみに最初のほう、長々とコメントがあるのはPIC16f690のCONFIGビットの設定の仕方について。
次は、周波数測定器のプログラム。内部クロックを用いている。このクロックがどれくらい正確なのかによるが、誤差は多くても数パーセント。自作コンピュータのチェックなどには十分な性能のはずである。
表示は基本的に、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|プログラムと回路図のソースコードはここ。)%>

まず、発振回路のプログラムから。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|プログラムと回路図のソースコードはここ。)%>