周波数カウンタ
デジタルICカウンタの74HC4040は、70MHzまで対応できます。
マイコンの内蔵カウンタと合わせて周波数カウンタを作成しました。
74HC4040を外部カウンタで利用し、8ビットの値を保持させます。
8ビットがオーバーフローしたとき、マイコンの内蔵カウンタを
インクリメントすると、マイコンの動作周波数は2MHz程度でよく
なります。
ブロック図で機能を切り分けると、以下。
全体で32ビットのカウンタを構成。74HC4040を外部カウンタで
利用し、内部に24ビットのカウンタを用意。
74HC4040の8ビットのオーバーフローで、16ビットカウンタを
インクリメント。さらに16ビットがオーバーフローしたならば
8ビットのカウンタをインクリメントします。
周波数は、1秒間の信号の変化回数と解釈します。
1秒間のマイコン内蔵24ビットカウンタの値と外部の74HC4040の
8ビット値を合体し、32ビットにします。
ピン数が少ないマイコンでも対応できるように、74HC4040の8ビット
を、パラレルシリアル変換ICの74HC164で転送。
マイコンを含めた回路図は、以下。
マイコン内蔵のタイマーで1秒間を生成し、その間に74HC4040が
周波数をカウントします。1秒経過したならば、計算して結果を
7セグメントLEDに転送します。
マイコンのファームウエアは、以下。
/* redefine data type */
typedef unsigned char UBYTE ;
typedef unsigned int UWORD ;
typedef unsigned long ULONG ;
#define OFF 0
#define ON OFF+1
/* global variables */
volatile UBYTE flag ;
volatile UBYTE state ;
volatile UBYTE lcnt ;
volatile UBYTE timv ;
volatile UBYTE timx ;
volatile UBYTE msg[10] ;
volatile UBYTE lsx[8] ;
volatile ULONG fcnt ;
volatile ULONG fcntx ;
volatile UBYTE te ;
volatile UBYTE th ;
volatile UBYTE tm ;
volatile UBYTE tl ;
volatile UBYTE xtmp ;
volatile UBYTE xcnt[5] ;
/* function prototype */
void init_usr(void);
void loadecounter(void);
void separatedigit(void);
void generatedigit(void);
void perform_led_control(void);
/* interrupt handler */
void interrupt(void)
{
/* timer1 overflow interrupt */
if ( PIR1.TMR1IF ) {
/* clear flag */
PIR1.TMR1IF = OFF ;
/* increment */
te = te + 1 ;
}
/* timer0 overflow interrupt */
if ( INTCON.T0IF ) {
/* clear flag */
INTCON.T0IF = OFF ;
/* initialize */
TMR0 = 6 ;
/* set flag (1250Hz/4 = 312.5Hz) */
if ( (timx & 3) == 0 ) { flag.F0 = 1 ; }
/* increment */
timx = timx + 1 ;
timv = timv - 1 ;
/* judge 10Hz (100ms) F counter */
if ( timv == 0 ) {
/* clear */
timv = 124 ;
/* set flag */
flag.F1 = 1 ;
}
}
}
void main(void)
{
/* user initialize */
init_usr();
/* endless loop */
while ( ON ) {
/* frequency counter handling */
if ( flag.F1 == ON ) {
/* clear flag */
flag.F1 = OFF ;
/* start count */
if ( state == 0 ) {
/* clear counter */
te = 0 ;
TMR1H = 0 ;
TMR1L = 0 ;
/* enable timer1 */
T1CON.TMR1ON = ON ;
/* enable peripheral interrupt */
INTCON.PEIE = ON ;
/* enable 74HC4040 */
PORTB.F0 = ON ;
/* check */
flag.F2 = OFF ;
if ( PORTA.F0 == OFF ) { flag.F2 = ON ; }
}
/* stop count */
if ( state == 10 ) {
/* disable timer1 */
T1CON.TMR1ON = OFF ;
/* disable peripheral interrupt */
INTCON.PEIE = OFF ;
/* disable 74HC4040 */
PORTB.F0 = OFF ;
}
/* load counter */
if ( state == 11 ) {
th = TMR1H ;
tm = TMR1L ;
loadecounter();
}
/* get counter */
if ( state == 12 ) {
/* top byte */
fcnt = te ; fcnt <<= 8 ;
/* next byte */
fcnt |= th ; fcnt <<= 8 ;
/* semi final byte */
fcnt |= tm ; fcnt <<= 8 ;
/* final byte */
fcnt |= tl ;
/* adjust */
fcnt <<= 1 ;
/* copy */
fcntx = fcnt ;
}
/* ? -455kHz */
if ( state == 13 ) {
/* check */
if ( flag.F2 == ON ) {
if ( fcntx >= 455000 ) {
fcntx = fcnt - 455000 ;
}
}
/* separate digits */
separatedigit();
}
/* generate digits */
if ( state == 14 ) { generatedigit(); }
/* zero surpress */
if ( state == 15 ) {
if ( *(msg+0) == 0 ) {
*(msg+0) = 15 ;
if ( *(msg+1) == 0 ) {
*(msg+1) = 15 ;
if ( *(msg+2) == 0 ) {
*(msg+2) = 15 ;
if ( *(msg+3) == 0 ) {
*(msg+3) = 15 ;
if ( *(msg+4) == 0 ) {
*(msg+4) = 15 ;
if ( *(msg+5) == 0 ) { *(msg+5) = 15 ; }
}
}
}
}
}
/* add Decimal Point */
msg[3].F7 = 1 ;
msg[6].F7 = 1 ;
}
/* transfer */
if ( state == 16 ) {
*(lsx+0) = *(msg+2) ; *(lsx+1) = *(msg+3) ;
*(lsx+2) = *(msg+4) ; *(lsx+3) = *(msg+5) ;
*(lsx+4) = *(msg+6) ; *(lsx+5) = *(msg+7) ;
*(lsx+6) = *(msg+8) ; *(lsx+7) = *(msg+9) ;
}
/* increment */
state = state + 1 ;
/* update */
if ( state == 17 ) { state = 0 ; }
}
/* LED handling */
if ( flag.F0 == ON ) {
/* clear flag */
flag.F0 = OFF ;
/* add digit location */
xtmp = swap(lcnt) ;
/* get digit from Array */
xtmp |= lsx[lcnt] ;
/* disable 74HC138 */
PORTA.F2 = ON ;
/* show */
perform_led_control();
/* enable 74HC138 */
PORTA.F2 = OFF ;
/* increment */
lcnt = lcnt + 1 ;
/* update */
lcnt &= 7 ;
}
}
}
/* define function body */
void init_usr(void)
{
/* disable compare module */
CMCON = 0x07 ;
/* I/O state */
PORTA = 0x04 ;
PORTB = 0x00 ;
/* I/O directions */
TRISA = 0x03 ; /* RA0,RA1 inputs , others outputs */
TRISB = 0x50 ; /* RB6,RB4 inputs , others outputs */
/* initialize Timer 0 */
{
/*
20MHz/4 = 5MHz -> 5MHz/16 = 312.5kHz prescaler = 1:16
*/
OPTION_REG = 0x03 ;
/* 256 - 250 = 6 , 312.5kHz/250 = 1250Hz */
TMR0 = 6 ;
/* enable timer 0 overflow interrupt */
INTCON.T0IE = ON ;
}
/* initialize Timer 1 */
{
/* select external clock input , synchronous and stop */
T1CON = 0x02 ;
/* enable timer 1 overflow interrupt */
PIE1.TMR1IE = ON ;
}
/* enable general interrupt */
INTCON.GIE = ON ;
/* clear flags */
flag = OFF ;
/* initialize variables */
state = 0 ;
lcnt = 0 ;
timx = 0 ;
timv = 124 ;
}
void loadecounter(void)
{
UBYTE i ;
/* Latch external counter 74HC4040 -> 74HC165 */
PORTB.F7 = ON ; i = 0 ; PORTB.F7 = OFF ;
/* clear */
tl = 0 ;
/* loop */
for ( i = 0 ; i < 8 ; i = i + 1 ) {
/* shift */
tl <<= 1 ;
/* get LSB */
if ( PORTB.F4 == ON ) { tl = tl + 1 ; }
/* CLOCK : H */
PORTB.F5 = ON ;
/* CLOCK : L */
PORTB.F5 = OFF ;
}
/* clear external counter */
PORTB.F1 = ON ; i = 0 ; PORTB.F1 = OFF ;
}
void separatedigit(void)
{
UBYTE i;
for ( i = 0 ; i < 5 ; i = i + 1 ) {
*(xcnt+4-i) = fcntx % 100 ;
if ( i < 4 ) { fcntx /= 100 ; }
}
}
void generatedigit(void)
{
UBYTE i;
UBYTE j;
UBYTE k;
UBYTE l;
for ( i = 0 ; i < 5 ; i = i + 1 ) {
j = i + i ;
k = *(xcnt+i) ;
l = k % 10 ;
k = k / 10 ;
*(msg+j) = k ;
*(msg+j+1) = l ;
}
}
void perform_led_control(void)
{
UBYTE i ;
UBYTE tmp ;
/* copy */
tmp = xtmp ;
/* send */
for ( i = 0 ; i < 8 ; i = i + 1 ) {
/* impress */
PORTB.F2 = tmp.F7 ;
/* 74HC164_CLK : H */
PORTB.F3 = ON ;
/* shift */
tmp <<= 1 ;
/* 74HC164_CLK : L */
PORTB.F3 = OFF ;
}
}
455kHzの減を入れられるようにしました。
アマチュア無線のトランシーバーで中間周波数に変換したい
場合の操作が単純になるようにと入れてます。
周波数カウント、シーケンサを利用。
シーケンス動作は、以下。
- ステート0 タイマーカウント開始
- ステート1 タイマーカウント停止
- ステート2 計算変換
- ステート3 0ステートに戻す
上のシーケンサは、10Hz=100msごとに発生する
タイマー割込みをトリガーにしました。
周波数の表示は、約2msの周期で発生するタイマー割込みを
利用して、1桁ごとに数字を送り出します。
マイコン基板は、1枚にまとめてます。
7セグメントLEDは、別基板に。
GCBASICでも動かせるかを、次のコードでテスト。
#chip 16F628A,20
#config MCLRE = ON
#define CNT_ENA PORTB.0
#define CNT_CLR PORTB.1
#define SDIN PORTB.2
#define SCLK PORTB.3
#define SDOUT PORTB.4
#define SSCK PORTB.5
#define SFTLOAD PORTB.7
#define YMON PORTA.4
#define XMON PORTA.3
#define XENA PORTA.2
#define XMODE PORTA.0
Dim state As Byte
Dim lcnt As Byte
Dim te As Byte
Dim th As Byte
Dim tm As Byte
Dim tl As Byte
Dim timv As Byte
Dim timu As Byte
Dim tflag As Byte
Dim lflag As Byte
Dim msg(10) As Byte
Dim lsx(8) As Byte
Dim i As Byte
Dim j As Byte
Dim k As Byte
Dim l As Byte
Dim xtmp As Byte
Dim fcntx As Long
Dim fcnt As Long
Dim xcnt(5) As Byte
' timer2 compare match
On Interrupt Timer2Match Call Xtim
' timer1 overflow
On Interrupt Timer1Overflow Call Ytim
' timer0 overflow
On Interrupt Timer0Overflow Call Ztim
' initialize
InitUsr
' endless loop
Main:
' 7 segment LED handling (about 1ms)
If lflag.0 = 1 Then
' clear flag
lflag.0 = 0
' set digit location
xtmp = swap4(lcnt)
' get digit from Array
xtmp = xtmp + lsx(lcnt)
' LED handling
PLC
' increment
lcnt = lcnt + 1
' update (0-7)
If lcnt.3 = 1 Then
lcnt = 0
End If
End If
' frequency counter handling (1000ms) and convert
If tflag.0 = 1 Then
' clear flag
tflag.0 = 0
' start count (0ms)
If state = 0 Then
Btim
End If
' stop count (1000ms)
If state = 1 Then
Ftim
End If
' calculate (2000ms)
If state = 2 Then
ExecuteCalc
End If
' increment
state = state + 1
' update
If state = 3 Then
state = 0
End If
End If
GOTO Main
Sub InitUsr
' I/O directions
TRISA = 0x03
' RA0,RA1 inputs , others outputs
TRISB = 0x50
' start Timer0
TMR0 = 6
OPTION_REG = 0x03
' select external clock input and asynchronous
T1CON = 0x06
' start Timer2
PR2 = 249
T2CON = 0x56
' clear flags
tflag = 0
lflag = 0
' initialize variables
state = 0
lcnt = 0
timv = 0
timu = 0
For i = 0 TO 9
msg(i) = 0
Next
End Sub
' generate 1Hz
Sub Xtim
' monitor
XMON = timv.0
' increment
timv = timv + 1
' judge 1Hz (1000ms) F counter
If timv = 125 Then
' clear
timv = 0
' set flag
tflag.0 = 1
End If
End Sub
' overflow counter
Sub Ytim
' increment
te = te + 1
End Sub
' generate 1250Hz
Sub Ztim
' initialize
TMR0 = 6
' increment
timu = timu + 1
' set flag
lflag.0 = 1
' monitor
YMON = timu.0
End Sub
Sub Btim
' clear overflow counter
te = 0
' enable timer1
T1CON.TMR1ON = 1
' enable 74HC4040
CNT_ENA = 1
End Sub
Sub Ftim
' disable timer1
T1CON.TMR1ON = 0
' disable 74HC4040
CNT_ENA = 0
End Sub
Sub LoadECounter
' Latch external counter 74HC4040 -> 74HC165
SFTLOAD = 1
SFTLOAD = 0
' clear
tl = 0
' loop
For i = 0 To 7
' shift
tl = tl + tl
' get LSB
If SDOUT = 1 Then
tl.0 = 1
End If
' CLOCK : H
SSCK = 1
' CLOCK : L
SSCK = 0
Next
' clear external counter
CNT_CLR = 1
CNT_CLR = 0
End Sub
Sub LoadTimer1
' get timer1 counter
th = TMR1H
tm = TMR1L
' clear timer1
TMR1H = 0
TMR1L = 0
End Sub
Sub GetCounter
' upper 16bits
fcnt = te
fcnt = FnLSL(fcnt,8)
fcnt = fcnt or th
fcnt = FnLSL(fcnt,8)
' lower 16bits
fcnt = fcnt or tm
fcnt = FnLSL(fcnt,8)
fcnt = fcnt or tl
' adjust
fcnt = FnLSL(fcnt,1)
' copy
fcntx = fcnt
End Sub
Sub AjustFreq
' adjust
If XMODE = 0 Then
If fcntx >= 455000 Then
fcnt = fcnt - 455000
End If
End If
End Sub
Sub SeparateDigit
' make digit
For i = 4 To 0
xcnt(i) = fcnt % 100
fcnt = fcnt / 100
Next
End Sub
Sub GenerateDigit
For i = 0 To 4
l = xcnt(i)
j = 2 * i
k = j + 1
msg(j) = l / 10
msg(k) = l % 10
Next
End Sub
Sub ZeroSurpress
'
If msg(0) = 0 Then
msg(0) = 15
If msg(1) = 0 Then
msg(1) = 15
If msg(2) = 0 Then
msg(2) = 15
If msg(3) = 0 Then
msg(3) = 15
If msg(4) = 0 Then
msg(4) = 15
If msg(5) = 0 Then
msg(5) = 0
End if
End if
End If
End If
End If
End If
' add Decimal Point
msg(3).7 = 1
msg(6).7 = 1
' copy
For i = 0 To 7
j = i + 2
lsx(i) = msg(j)
Next
End Sub
Sub ExecuteCalc
' get external counter
LoadECounter
' get timer 1
LoadTimer1
' get 2 counter
GetCounter
' ajust
AjustFreq
'
SeparateDigit
' convert
GenerateDigit
' zero surpress
ZeroSurpress
End Sub
' LED handling
Sub PLC
' disable 74HC138
XENA = 1
' send
For j = 0 To 7
' impress
SDIN = xtmp.7
' 74HC164_CLK : H
SCLK = 1
' shift
xtmp = xtmp + xtmp
' 74HC164_CLK : L
SCLK = 0
Next
' enable 74HC138
XENA = 0
End Sub
MikroCではプログラム容量の46%を使ったのに
GCBASICでは33%程度でした。
アセンブリ言語のコードで比較すると、MikroCでは
R0からR15の16バイトの16レジスタを使い、スタック
を消費しない仕様でした。このレジスタへの入出力
で、余計なコードが含まれてしまうので、コード量
が増えているようです。
CとBASICのコンパイラともに、次の記述をした方が
アセンブリ言語レベルで、レジスタ間転送をしない
で短いコードになっています。
tl++ -> tl = tl + 1
PICではWレジスタと、レジスタファイル間でのデータ転送
命令をもっていると加減算と論理演算では、Wレジスタと
レジスタファイルを独立して扱えます。
Cで「tl++」とすると、Wレジスタ経由で扱おうとするのに
対して「tl = tl + 1」では、レジスタファイルだけで事が
おさまるように解釈されていました。
BASICでは、「tl = tl + 1」という記述しか許されてない
ので、レジスタファイルだけでの処理になるようなコード
を生成できています。
7セグメントLEDの基板と本体は、20ピンコネクタで接続。
信号とピン番号の組み合わせは、以下。
Vcc 1 2 GND
3 4
nY7 5 6 nY6
nY5 7 8 nY4
nY3 9 10 nY2
nY1 11 12 nY0
Dp 13 14 g
f 15 16 e
d 17 18 c
b 19 20 a
写真では、右上がピン2と対応します。
目次
前
次