周波数カウンタ(簡易版)
RTC(maxim DS3234S)使用版 tiny2313 完成版 11.09.22
RTC(maxim DS3234S)使用版 tiny2313によるブレッドボード実験 11.09.11
RTC(maxim DS3231S)使用版 tiny2313による実験 11.09.04
RTC(maxim DS3231S)使用版 mega328 11.09.01
超簡易周波数カウンタ Tiny2313 (リンク)
mega48版(アンプとプリスケーラ付き) 06.03.05
90S2313版(最初に作ったもの) 06.01.15改訂 WinAVR20050214使用
90S2313版(最初に作ったもの)06.01.15改訂 WinAVR20050214使用
tiny26Lやmega8はRC発振子を内蔵しています。これらはヒューズビットを書き換えることで、クロックを変えることができま
す。また、このクロックは温度や電源電圧によって変動することも記されています。クロックの周波数を知るために周波数カウンタが
作られないものだろうかと思いWebページを検索しますと、90S1200や4414を使ったものがありました。上記の理由から、本格的なも
のを必要としているわけではありません。数MHz〜数百HzのTTLレベルの信号が3〜4桁程度読みとれればよいとします。Web
ページのものを改造しようと思いましたが、アセンブリ言語を読みとる力はありません。そこで、勉強を始めたC言語、winAVRに挑戦することに決めました。
−−−というわけで、2004年8月になんとか理解できるC言語の数少ない語彙を使って原型を作りました。ところが、2005年12月にBBSの質問を
考えている内にバグがあることがわかりました。ときどき256カウント少なく測定されるのです。akibowさんから、タイマ1の割込中にカウンタ0の割込が発生しても
実行されないのが原因であり、フラグから別途処理する方法を教えていただきました。同時に多くの改良点を教えていただきましたが、私自身が理解しやすいところだけ
とりあえず変更して、プログラムを新しく作りました。
大まかな考えは、つぎのとおりです。
1 基準時間は京セラの12.8MHzクリスタルモジュールを使う。これで基準時間には安心できる。(他に基準となるものがない)
2 デバイスは90S2313-10PCとする。オーバークロックはokの報告がある。
3 表示はポートの数から液晶表示とする。6ポートで表示できる。
4 2313にはタイマー/クロックが8ビットと16ビットの2つがある。16ビットタイマで1秒を作り、8ビットカウンタパルスを数える。
まずは下準備から。液晶表示の方法です。
液晶はデジットで購入の「BASICで液晶表示(16桁×2行)」のものを使います。C言語での表示については、楠 昌浩さんが
電子工作のメモ帳「AVR 勉強2 液晶パネル」 に詳しく書かれています
(感謝)。 原文はAT90S4433ですが、これをAT90S2313に置き換えて書いてみました。液晶モジュール用コードのlcd.cを同時に
コンパイルする方法が分からず困りました。試行錯誤の上、makefileの「SRC = $(TARGET).c」のところに半角の空白を置いて続けて
lcd.cと書くとよいことがわかりました。なお、fc.c lcd.c lcd.h は同じディレクトリに置いています。
次は1秒ゲートの作り方と使い方です。
クロックが12.8MHzですから、それをプリスケーラで1024分周し、16進カウンタで12500カウントすると1Hzが得られます。
12.8MHz÷1024÷12500=1Hz
16進カウンタは0xFFFFから0x0000に戻るときに割込を発生しますから、カウンタに65536-12500=53036を設定すればよいことになります。
20050214版から2バイト値を直接取り扱えるようになりましたから、TCNT1=53036 と書くことができます。
続いて、スイッチを使い1秒ごとに「周波数測定カウンタのリッセット」と「カウンタの読みとり」を行えば2秒ごとに
カウンタ表示が更新されることになります。後に修正して、表示のために1秒を待つことなく、すぐに(0.08秒で)計測に入るように変えま
した。これでほぼ1秒ごとに更新されます。
最後に測定入力のカウントです。
カウント/タイマ0を使います。これは8ビットですから、255までカウントできます。初期の目的から3バイト(24ビット、約
16メガ)必要ですから、16ビットの整数を足します。これで16777215=16MHzまで可能になります。2313の測定上限は
クロック12.8MHzから5MHz程度ですからこれで十分です。表示の仕方はCがよく分からないので、桁分割して1桁ずつ表示して
います。当初はすべて32ビット計算をしていましたが、負担が大きすぎるとのことですから、akibowさんの上下を分けて16ビットで計算する方法に変更しました。
関係するレジスタは、TTCR1A, TTCR1B, TIMSK, OCR1AH, OCR1AL, TCNT1H, TCNT1L, TCNT0, ........と多くあります。
データシートをよく読み(日本語訳はありがたいのですが、私にとって意味が分からないことが多く..)各レジスタの
働きを調べ、どう設定したらよいのか判断しながら書き出します。メモがなかったらまとまりません。
ブレッドボードでテストランした上で、万能基板に組みました。左にスペースがありますが、後日アンプやプリスケーラを
追加できるようにと考えたものです。ケースに収めるとよいのですが、今は1枚の板の上に基板と液晶を貼り付けています。
写真は12.8MHzの京セラの発振器(別の個体)を4分周して測定したものです。概ね満足な結果です。別の16MHzの水晶で4MHzは測定できることを確認しました。
MCUデバイスの発信周波数は直接測れないので、短いループでポートのon、offを繰り返し、その周波数を測ってみました。
tiny26LのRC発振を8MHzに変更して、ステップ5のループの結果を測りましたが補正なしでは8.3MHzほどになっていることが
わかり、補正をすると7.98MHz(29℃)になりました。指で暖めるとどんどん下がります。
セラミックレゾネータが不要なのはありがたいのですが、時間を基準として使うには厳しい条件だと思いました。
入力アンプもバッファもない測定部分だけですが、AVRを使うときの論理レベルの測定には実用性があります。
これでPWMの周期も測定できます。
回路図
プログラム fc.c(OverFlow動作) fcntr.c(CTC動作) lcd.c lcd.txt
.hファイルはサーバが許可しないので.txtファイルにしています。使うときは"lcd.h"に必ず変更してください。
プログラムの変更に伴って、本文も追加訂正しましたがそのために読みにくくなった部分もあるかと思いますがご容赦下さい。なお、添付ファイル(圧縮ファイル)には
2種類あって、
fc.lzh ははじめの考えどおりオーバーフロー割込のもので、バグ修正、2バイト代入、メインルーチンで表示、7桁表示用計算法を変更したものです。
このプログラムの実行ファイルはTiny2313でもそのまま使えることが分かっています。
fcntr.lzh は、CTC動作(タイマカウンタが定数と一致するとリセットと割込が発生する)を使っています(akibowさんに教えていただいたものです)。
毎回のカウンタ設定が
不要でプログラムが簡単になり、かつ、測定と読み取りが同時にできるので正確に1秒毎に測定できます。このhexファイルはTiny2313では使えません。256カウント大きく
測定されるとともに、255カウント揺れが出ます。原因はつかんでいません。
カウンタのバグは、タイマ1(TC1)の割込動作中はカウンタ0(TC0)のオーバーフロー割込が禁止されることで、例えば、TC0が255でTC1の割込が生じたとき、TC0を
読み取るときには0になっていることが考えられます。しかしTC0の繰り上がりは禁止のままですから256カウント少なくなります。オーバーフローが起これば、TIFRレジスタ
のTOV0ビットが立ちますからそれを判断してカウンタ変数をインクリメントする事で修正できます。TC0=255を読んでからこのフラグが立つとこのフラグは意味がありませんから
TCNT0<128 で条件分けをしています。
if (bit_is_set(TIFR,TOV0) && TCNT0<128 ){ /* 繰り上がりが割込で止められていたら */ f1++; /* フラグを見て加算、カウントが大きければ f2に読んだ直後に */ } /* 繰り上げ(TCNT=255の様な)だから加算してはいけない */ TIFR=_BV(TOV0);
mega48版(アンプとプリスケーラ付き)
最初に作った周波数カウンタは、クロック12.8MHzから測定上限が6MHzですが分解能がなくてもそれ以上の周波数を測定したいと考え、8分周のプリスケーラをつけて
みました。分解能は8Hzとなりますが、手持ちの発振ICの24MHzは測定できました。また、入力が5Vなくても使えるようにしようとアンプを付けました。ただ、このアンプは
周波数特性が良くなくて、12MHzまでは1V入力で働くのですが、それ以上の周波数ではゲインがありませんので、5V入力で辛抱することになります。
前回は90S2313を使いましたが、今回は手持ちの関係でmega48を使っていますが、プログラムは少し修正しただけでmega48にしなけらばならない理由はありません。
プリスケーラ: 最近のデバイスは20MHzまで使えるようになりました。そのあたりの周波数まで表示したいので74HC4040バイナリカウンタを用いました。12段の
カウンタですが、4倍も8倍も変わらないだろうと思い8倍にしています。リセット端子もあるのでクロックを止めてバイナリデータを読みとれば分解能1Hzも可能と
思いますが今回は単純に8分の1の周波数をカウントすることにしています。
アンプ: 高周波アンプの知識がないので、アンバッファのインバータゲートがリニアアンプとして使えることを聞いたのを思い出し、74HCU04で増幅することに
しました。Webを探せば簡単に見つかるだろうと検索してみたのですが、簡単には見つけることができませんでした。数少ない報告の中で
CMOSインバータによるAFアンプの実験が丁寧な説明をしてくれています。
モトローラのCMOSは周波数特性が伸びていないのは知っていましたから、74HCU04では高い周波数まで使えるのではないかと期待してバラック実験を始めました。
帰還抵抗は広範囲な値が使えるようですが、高くすると信号が入ってから増幅が安定するまで時間がかかるようで、試行錯誤の末に回路図の値に決めました。2段、3段
にするとゲインはあがるのですが安定化に時間がかかるようで、実際の使用を考えると大きなゲインまでは必要なかろうと1段にしました。
周波数が低いときは手持ちの骨董品のオシロで増幅の様子がが見えるのですが、周波数が高くなると怪しくなります。さらにオシロをつなぐとゲインも安定度も影響されるようです。
最後はオシロをはずして試行錯誤で決めました。
結果的に入力12MHzでは1V入力で正常な表示をします。それ以上では少な目に表示されて不正確なのですが、入力を5Vp-pとすると手持ちの24MHz水晶発振器の値を読みとる
ことができました。ほとんど終わりになって、PHILIPSのデータシートからこのようなものを発見しました。 ここでは周波数特性は
5MHzのようですが実際はかなり伸びているようです。この計算によると5MHzでは10倍の利得があるようで、単純に0.5Vの入力で働くことになります。私は5Vの実験に
しか使わないので前のままでもいいのですが抵抗を挟んだりするときはゲインがある方が使いやすいと思います。
回路図はこのようなものです。入力回路の100μFコンデンサは大きすぎるようですが手持ちの関係で使ったものです。10μFでよいと思います。
ゲートに負電圧がかかるのをさけるためにダイオードを入れていますが、5Vを超える入力の対策としてダイオードでVccにクリップした方がいいでしょう。2回路のスイッチ
でMCUへの入力の切り替えを行い、同時にポートをGNDに接続してソフトウエアのための切り替えをしています。LCDの表示に余裕があるので(1/1)と(1/8)を表示しています。
今までは7桁表示でしたが、分周時には10MHzを超えるのでこのときは8桁としています。面倒なのとカンマ区切りがあるのでリーディングゼロサプレスはしていません。
関係ファイルはここにあります(fc48.lzh)。
RTC(maxim DS3231S)使用版 mega328
アマチュアが基準としていた秋月の12.8MHzTCXO(kyocera 18S-03A-4)の取り扱いがかなり前からなくなっています。今回、サンプルとして戴いたMAXIMのRTC DS3231Sはかなり精度が良い32.768kHzを発生していますのでこれを1秒の基準とした周波数カウンタを実験してみました。
はじめは、この32kHzをtiny2313でカウントして1秒を作り、4ビットカウンタ74HC161のゲートと使ってmega328pで16分周の信号をカウントして74HC161のカウント値と合算で測定することを目論みましたが1秒信号の伝達精度に問題があるらしくmega328pのカウント値に揺らぎが生じるため計画を変更しました。
測定の基礎条件を実験する目的から前置アンプや分周器は考えてなく、また出力もPC画面のteraterm表示に簡素化しています。 MCUにmega328pを使っているのは 48、88、168と価格の差が大きくなく特別の場合を除いて「大は小を兼ねる」ことと1種のMCUを使うことで煩雑さを避けるためです。
計画変更のため空のソケットが残っています。
調整後は右の基板を裏側に折り曲げて左に重ねます。
単純時計機能も使えるので最終的にはバックアップ電池を接続します。
周波数測定時には1608サイズの白色LEDが1秒周期で点滅します。
Vcc5Vで24MHz水晶はオーバースペックですが分周無しで8MHz程度まで測定できるようにクロックを高くしてみました。
RTCの32kHzをT1ピンで受けてタイマ/カウンタ1でカウントして1秒後に割り込みが発生するようにしています。
測定信号は直接T2ピン(プルアップと保護抵抗付き)で受けてタイマ/カウンタ0でカウントします。オーバーフローの回数を16ビット変数のインクリメントで計数します。 「この変数値×256+カウンタ0値」が1秒の計測値になります。
上記の1秒割り込みで周波数のカウント値を保存し、カウント関係をクリアして次のカウントに入ります。
前回のカウント値の保存が済むとフラグを立ててメインルーチンに知らせます。メインルーチンではこのフラグを見て測定値を計算し、結果をUARTに出力します。
TeraTermを前提にしていますので動作モードの選択メニューもTeraTermからとしています。TeraTermを起動してリセットスイッチを押すと
-- menu -- 1 周波数カウンタ 2 時刻表示 3 分未満四捨五入 4 時刻合わせ 24時制 4桁入力 5 Aging Offset |
f_cntr5_2313.c maximRTC(DS3231S)の1Hzパルス基準の周波数カウンタ 2011.09.04 im mcu=Tiny2313 clk=24MHz(Xtal) fuse= -fL11111111 -fH110111111 -fx00000001 Vcc=5V RTCの設定: #INT/SQW端子に1Hzを出力するためにSRAM 0Ehに0x00を書く。 SRAMのため、バッテリーバックアップが必要(Li一次電池) tiny2313では書けないのでmega328のI2Cで書いた。 AVRの接続: 1 RESET 10k pull up リセットsw→gnd | ISP 2 RXD UART 3 TXD UART 4 XTAL2 Xtal 5 XTAL1 Xtal 6 PD2/INT0 ←RTC #INT/SQW 7 PD3/INT1 ←TeraTermを立ち上げると1秒ごとに計測値が表示されます。上記の mega328版 と同じ結果が得られました。カウント入力8 PD4 9 PD5 ←カウント入力 10 GND 11 PD6 12 BP0 13 BP1 14 BP2 15 BP3 16 BP4 17 BP5/MOSI ISP 18 BP6/MISO ISP 19 BP7/SCK ISP 20 VCC 動作: 1秒パルスの立ち下がりで割り込み 割り込みの中でtimer1のカウントを変数に保存後timer1をクリア フラグを立ててメインへ通知 timer1のオーバーフローで割り込み 割り込みの中でオーバーフローカウンタ変数(16bit)のインクリメント メインルーチンで オーバーフロー変数の保存とクリア(1秒割り込みのcli対策) 保存オーバーフロー変数*65536+保存TCNT1変数 でカウントの計算 UARTへ出力
/* ************************************************************************************ main.c maximRTC(DS3234S)の1Hzパルス基準の周波数カウンタ 2011.09.10 im mcu=Tiny2313 clk=24MHz(Xtal) fuse= -fL11111111 -fH11011001 -fx00000001 (ff d9 ff) BOD=4.3V Vcc=5V RTCの設定: #INT/SQW端子に1Hzを出力するためにSRAM 0Ehに0x00を書く。 SPIバスのためtiny2313ソフトで設定できる。 back up電池を付けたので時刻設定もする。 (resetでメニューが表示される) AVRの接続: 1 RESET 10k pull up リセットsw→gnd | ISP 2 RXD UART 3 TXD UART 4 XTAL2 Xtal 5 XTAL1 Xtal 6 PD2/INT0 ←RTC #INT/SQW 7 PD3/INT1 8 PD4 9 PD5/T1 ←カウント入力 10 GND 11 PD6 12 BP0 SPI CS 13 BP1 SPI CLK 14 BP2 SPI DO 15 BP3 SPI DI 16 BP4 17 BP5/MOSI ISP 18 BP6/MISO ISP 19 BP7/SCK ISP 20 VCC 動作: 1秒パルスの立ち下がりで割り込み 割り込みの中でtimer1のカウントを変数に保存後timer1をクリア フラグを立ててメインへ通知 timer1のオーバーフローで割り込み 割り込みの中でオーバーフローカウンタ変数(16bit)のインクリメント メインルーチンで オーバーフロー変数の保存とクリア(1秒割り込みのcli対策) 保存オーバーフロー変数*65536+保存TCNT1変数 でカウントの計算 UARTへ出力 ***************************************************************************************/精度的にはDS3231と極めて近い測定値を得ることができました。約4MHzの測定でその差が 3Hz 程度と1ppm以下になっています。(参考)
/* SPI設定 portB使用 */ #define CS_H() PORTB |= 0x01 /* PB0 set rtc CS "high" */ #define CS_L() PORTB &= 0xFE /* PB0 set rtc CS "low" */ #define CK_H() PORTB |= 0x02 /* PB1 set rtc SCLK "high" */ #define CK_L() PORTB &= 0xFD /* PB1 set rtc SCLK "low" */ #define DO_H() PORTB |= 0x04 /* PB2 set rtc DI "high" */ #define DO_L() PORTB &= 0xFB /* PB2 set rtc DI "low" */ #define DI (PINB & 0x08) /* PB3 get rtc DO value (high:true, low:false) */1バイト通信のサブルーチンです。
/* ****************** SPI 1byte通信ルーチン ************************************ */ uint8_t xmit(uint8_t d){ /* 引数=送信データ 戻り値=受信データ */ uint8_t e=0,i; uint8_t b[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; CK_L(); /* アイドル時のクロックはL */ for(i=0;i<8;i++){ if(d & b[i]) DO_H(); else DO_L(); CK_H(); CK_L(); e<<=1; if(DI) e++; } return e; }メインルーチンで例えば時分秒をRTCから読み出すときは次のように書きます。
CS_L(); // チップセレクト xmit(0x00); // set read 読み取りはMSB=0(書き込みはmsb=1)、残り7ビットは内部アドレス ss=xmit(0x00); // read 00h mm=xmit(0x00); // read 01h hh=xmit(0x00); // read 02h CS_H();試行錯誤にかなりの時間を費やしましたが満足のできる結果となりました。
-- menu -- 1 周波数カウンタ 2 時刻合わせ 24時6桁入力 24時制で hhmmss を入力してください。 f= 3 803 340 Hz 16;46;47 f= 3 999 947 Hz 16;46;48 f= 3 999 948 Hz 16;46;49 f= 3 999 947 Hz 16;46;50メニュー 2 時刻合わせ を選んでも最初の数字に 0 1 2 以外を入力すると時刻設定をキャンセルしてカウンタが起動するようにしています。