ATtiny2313を使って、周波数カウンタのプログラムを実験をしました。Timer0で正確な1秒を作り、その間の入力パルスの数をTimer1のカウンタ機能を使ってカウントします。実験にあたっては、JA9TTT⁄1氏のホームページ「Radio Experimenter's site」(現在閉鎖中)内、「Simple Digital Readout for Your Radio」のページを参考にさせていただきました。
[ ご注意 ]
JA9TTT⁄1氏のサイト「Radio Experimenter's site」にはプログラムソースの引用に関して「専ら個人的な趣味への利用に限ります.他所への掲載を含め、無断で販売や頒布を目的に利用しないで下さい.部分的であっても利用したい場合は問い合わせること.」との記載があります。当ページ「BASCOM-AVR〜周波数カウンタ(その1)」へのプログラムの掲載にあたっては、同氏よりメールにてご承諾をいただいています。当ページに掲載されたプログラムの他への再引用に関しても、同様にJA9TTT⁄1氏の許可が必要ですのでご注意下さい。
まずはオーディオ用の周波数カウンタに挑戦してみました。回路図を下に掲げます。「タイマー機能(その3)」に書いた周波数カウンタとほぼ同じです。マイコンICのクロックは、分周回路付き水晶発振器EXO-3で16.384MHzを16分周した1.024MHzを用います。1MHzでもできないことはないと思いますが、今回はTimer0割り込みで時間を測るので、クロックを2の累乗の周波数にした方が端数が出なくてプログラムが簡単になります。
テスト用の入力信号は、マイコンのクロックと同じ1.024MHzをBCDカウンタIC・74HC4518で分周したものを使用しました。10分周の102.4kHzと100分周の10.24kHzをジャンパー線で適宜切り換えて、マイコンICのT1端子に入力します。前にも書きましたが、こうすることで、プログラムに間違いがなければ端数のない周波数が表示されるはずです。
ブレッドボード上の配線図と試作写真を下に示します。小さいボードには載り切らないので大きいボードを用いました。入力周波数の切替えはジャンパー線の挿し換えによって行ないます。
最初に実験したのは、Timer1の1周分、65535Hzまで計測できる周波数カウンタです。「タイマー機能(その3)」でやったのと同じですが、ここではTimer0で時間をカウントして1秒を作っています。
プログラムファイル fcount1a.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1024000 | クロック周波数を1.024MHzに設定。 | |
3 | |||
4 | Config Lcdpin=Pin, Db7=Portb.0, Db6=Portb.1 | LCDの接続設定。DB7=PB0, DB6=PB1。 | |
5 | Config Lcdpin=Pin, Db5=Portb.2, Db4=Portb.3 | LCDの接続設定。DB5=PB2, DB4=PB3。 | |
6 | Config Lcdpin=Pin, E=Portb.4, Rs=Portb.5 | LCDの接続設定。E=PB4, RST=PB5。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Config Timer0 = Timer , Prescale = 1 | Timer0はタイマー。分周比1。 | |
9 | Config Timer1 = Counter , Edge = Rising | Timer1はカウンタ。立上りエッジ。 | |
10 | |||
11 | Dim Flag As Bit | ビット型変数「Flag」を使用する。 | |
12 | Dim Tint As Word | ワード型変数「Tint」を使用する。 | |
13 | |||
14 | Stop Timer0 | Timer0をストップ。 | |
15 | Stop Timer1 | Timer1をストップ。 | |
16 | On Timer0 Timer0_int | Timer0割込みが発生したら | |
割込みプログラム「Timer0_int」へ。 | |||
17 | Enable Interrupts | 割込み全般を許可する。 | |
18 | Enable Timer0 | Timer0割込みを許可する。 | |
19 | |||
20 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
21 | Start Timer0 | Timer0をスタート。 | |
22 | Start Timer1 | Timer1をスタート。 | |
23 | |||
24 | Do | Doループの範囲ここから。 | |
25 | If Flag = 1 Then | 条件分岐。「Flag=1」ならば、 | |
26 | Cls | LCDの表示を消去する。 | |
27 | Lcd "FREQ " ; Timer1 ; "Hz" | LCDに周波数を表示する。 | |
28 | Flag = 0 | 変数「Flag」を0にする。 | |
29 | Tint = 0 | 変数「Tint」を0にする。 | |
30 | Timer1 = 0 | Timer1のカウントを0にする。 | |
31 | Start Timer0 | Timer0をスタート。 | |
32 | Start Timer1 | Timer1をスタート。 | |
33 | End If | 条件分岐プログラムここまで。 | |
34 | Loop | Doループの範囲ここまで。 | |
35 | |||
36 | End | メインプログラム終わり。 | |
37 | |||
38 | Timer0_int: | 割込みプログラム「Timer0_int」ここから。 | |
39 | Incr Tint | 変数「Tint」に1を加える。 | |
40 | If Tint = 4000 Then | 条件分岐。「Tint=4000」になったら、 | |
41 | Stop Timer0 | Timer0をストップ。 | |
42 | Stop Timer1 | Timer1をストップ。 | |
43 | Flag = 1 | 変数「Flag」を1にする。 | |
44 | End If | 条件分岐プログラムここまで。 | |
45 | Return | 割込みプログラム「Timer0_int」ここまで。 |
このプログラムでは、Timer0は時間をカウント、Timer1は入力パルスの数をカウントします。まず、Timer0割り込みで正確な1秒を作ります。クロック周波数は1.024MHz、Timer0の分周比は「1」なので、1カウントの周期は 1÷1.024MHz=0.9765625μSになります。これを256倍すると250μSですから、Timer0割り込みは250μSごとに起こります。割り込みが起きるたび、割込みプログラム「Timer0_int」で変数「Tint」の値を1ずつ加算します。割り込みが4000回起きるとちょうど1秒になります。
変数「Tint」の値が「4000」になったら、40行目の条件分岐プログラム「If Tint = 4000 Then」でタイマーを2つともストップさせ、変数「Flag」の値を「1」に変えます。これによりメインプログラム内25行目「If Flag = 1 Then」からのプログラムが実行されます。ここではTimer1のカウント数すなわち周波数を表示したのち、2つの変数とTimer1のカウントを0に戻し、タイマーを再スタートさせます。
上記のプログラムでは65536Hz以上の周波数を入力すると、65536を差し引いた数が表示されます。これを正しく表示するには、Timer1がオーバーフローした回数をカウントしておいて、オーバーフロー回数に65536を掛けたものにその時点のカウント数を加えて周波数として表示します。これにより、測定範囲の上限はクロック周波数の半分弱、今回の場合は500kHzあたりまで伸びます。
プログラムファイル fcount1b.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1024000 | クロック周波数を1.024MHzに設定。 | |
3 | |||
4 | Config Lcdpin=Pin, Db7=Portb.0, Db6=Portb.1 | LCDの接続設定。DB7=PB0, DB6=PB1。 | |
5 | Config Lcdpin=Pin, Db5=Portb.2, Db4=Portb.3 | LCDの接続設定。DB5=PB2, DB4=PB3。 | |
6 | Config Lcdpin=Pin, E=Portb.4, Rs=Portb.5 | LCDの接続設定。E=PB4, RST=PB5。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Config Timer0 = Timer , Prescale = 1 | Timer0はタイマー。分周比1。 | |
9 | Config Timer1 = Counter , Edge = Rising | Timer1はカウンタ。立上りエッジ。 | |
10 | |||
11 | Dim Flag As Bit | ビット型変数「Flag」を使用する。 | |
12 | Dim Over As Byte | バイト型変数「Over」を使用する。 | |
13 | Dim Tint As Word | ワード型変数「Tint」を使用する。 | |
14 | Dim Freq As Long | ロング型変数「Freq」を使用する。 | |
15 | |||
16 | Stop Timer0 | Timer0をストップ。 | |
17 | Stop Timer1 | Timer1をストップ。 | |
18 | On Timer0 Timer0_int | Timer0割込みが発生したら | |
割込みプログラム「Timer0_int」へ。 | |||
19 | On Timer1 Timer1_int | Timer1割込みが発生したら | |
割込みプログラム「Timer1_int」へ。 | |||
20 | Enable Interrupts | 割込み全般を許可する。 | |
21 | Enable Timer0 | Timer0割込みを許可する。 | |
22 | Enable Timer1 | Timer1割込みを許可する。 | |
23 | |||
24 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
25 | Start Timer0 | Timer0をスタート。 | |
26 | Start Timer1 | Timer1をスタート。 | |
27 | |||
28 | Do | Doループの範囲ここから。 | |
29 | If Flag = 1 Then | 条件分岐。「Flag=1」ならば、 | |
30 | Freq = Over * 65536 | 「Over×65536」を「Freq」とする。 | |
31 | Freq = Freq + Timer1 | 「Freq+Timer1」を「Freq」とする。 | |
32 | Cls | LCDの表示を消去する。 | |
33 | Lcd "FREQ " ; Freq ; "Hz" | LCDに周波数を表示する。 | |
34 | Flag = 0 | 変数「Flag」を0にする。 | |
35 | Over = 0 | 変数「Over」を0にする。 | |
36 | Tint = 0 | 変数「Tint」を0にする。 | |
37 | Timer1 = 0 | Timer1のカウントを0にする。 | |
38 | Start Timer0 | Timer0をスタート。 | |
39 | Start Timer1 | Timer1をスタート。 | |
40 | End If | 条件分岐プログラムここまで。 | |
41 | Loop | Doループの範囲ここまで。 | |
42 | |||
43 | End | メインプログラム終わり。 | |
44 | |||
45 | Timer0_int: | 割込みプログラム「Timer0_int」ここから。 | |
46 | Incr Tint | 変数「Tint」に1を加える。 | |
47 | If Tint = 4000 Then | 条件分岐。「Tint=4000」になったら、 | |
48 | Stop Timer0 | Timer0をストップ。 | |
49 | Stop Timer1 | Timer1をストップ。 | |
50 | Flag = 1 | 変数「Flag」を1にする。 | |
51 | End If | 条件分岐プログラムここまで。 | |
52 | Return | 割込みプログラム「Timer0_int」ここまで。 | |
53 | |||
54 | Timer1_int: | 割込みプログラム「Timer1_int」ここから。 | |
55 | Incr Over | 変数「Over」に1を加える。 | |
56 | Return | 割込みプログラム「Timer1_int」ここまで。 |
Timer1のカウント数が65535を超えるとTimer1割り込みが起こります。ここで割込みプログラム「Timer1_int」が起動し、変数「Over」の値が「0」から「1」に変わります。Timer1が1周するたび「Over」は1ずつ増えます。メインプログラムでは、まず「Over」に65536を掛けてこれを変数「Freq」とし、続いてその時点でのTimer1のカウント数に「Freq」の値を加えたものを新たな「Freq」としてLCD画面に表示します。入力周波数が102400Hz(102.4kHz)の場合は、Timer1は1周+36864カウント進みます。したがって表示は1×65536+36864=102400となります。
測定周波数によって周波数の単位を変えて表示するプログラムです。999Hzのときは「999Hz」、1000Hzのときは「1.000kHz」と表示します。「LCDの表示(その5)」でやった「Str」と「Format」を使いました。
プログラムファイル fcount1c.bas
(前略) | |||
15 | Dim Freq2 As String * 7 | 7文字の変数「Freq2」を使用する。 | |
(中略) | |||
31 | Freq = Over * 65536 | ||
32 | Freq = Freq + Timer1 | ||
33 | Cls : Lcd "FREQ " | LCDの表示を消去して「FREQ」と表示する。 | |
34 | If Freq < 1000 Then | 条件分岐。「Freq<1000」ならば、 | |
35 | Lcd Freq ; "Hz" | LCDに「Freq」の値と「Hz」を表示する。 | |
36 | Else | そうでなければ(「Freq≥1000」ならば)、 | |
37 | Freq2 = Str(freq) | 「Freq」の値を文字変数「Freq2」に変換。 | |
38 | Freq2 = Format(freq2 , "0.000") | 「Freq2」を「0.000」の形にする。 | |
39 | Lcd Freq2 ; "kHz" | LCDに「Freq2」の値と「kHz」を表示する。 | |
40 | End If | 条件分岐プログラムここまで。 | |
41 | Flag = 0 | ||
(後略) |
上記のプログラムではいずれもTimer0の分周比が「1」です(Prescale=1)。これを「1」以外にすることもできます。例えば分周比「8」にすると、Timer0の1カウントあたりの時間が8倍になりますので、割込み回数を8分の1、つまり500回にすれば1秒の時間を作ることができます。プログラムを下記のように修正します。
分周比が「1024」の場合はTimer0の1カウントの周期が1mSになります。このままだと256mSごとに割り込みが起こって半端な数字になるので、Timer0の初期値を「6」とし、6から256まで250カウント(250mS)ごとに割り込みが起きるようにします。割込み回数4回で1秒になります。(プログラムファイルfcount1d.bas)
分周比が「256」のときも同じ考え方でいけます。Timer0の1カウントの周期は0.25mS、200カウントで50mSになりますから、「Timer0=56」にすれば割り込み回数20回で1秒になります。
上記3例はいずれも正しい周波数を表示できました。よくわからないのは分周比「64」のときです。Timer0の1カウントの周期は0.0625mS、160カウントで10mSなので、Timer0の初期値を「96」にして割り込み100回目で1秒になると思ったんですが、実際にやってみると、10240Hzを入力したとき「10304Hz」、102400Hzを入力したとき「1030400Hz」と表示されました。少し余計にカウントしています。測定時間が1秒より長くなっているということですね。試しに、「Timer0=97」と書いたところ、測定値はそれぞれ「10240Hz」「1023999Hz」になりました。
なぜこうなるのかきちんと調べていませんが、64分周だとTimer0のカウントがけっこう速いので「Timer0=96」の実行にかかる時間(100回分)が無視できなくなるのではないか、と考えています。このあたりが解明できれば、1MHなど切りのいい周波数のクロックを用いた周波数カウンタも作れると思うのですが。