« 大阪市立図書館のシステムがダウンだと | トップページ | ATtiny1614で周波数カウンタ:キャプチャタイミング »

2025年10月 3日 (金)

ATtiny1614で(裸の)周波数カウンタ

ATtiny1614 Frequency Meter:John BradnamPublished March 22, 2021
「A tiny frequency meter built using the ATtiny1614 microprocessor.」
とういうATtiny1614のタイマー「TCD」を使った周波数カウンタの
製作記事があったので、倣って作ってみました。
表示器は無しで、測定値の出力はシリアルで。

Fc41

Arduino IDE環境で「内部のタイマーは使わない」に
しておかないと、タイマーの割り込みベクトルが衝突
しました。
Fc42
 ※AVR1シリーズだと、デフォルトのタイマーがTCD0
  ということですので、これが衝突原因です。
  delayやmillisなどタイマー系の命令を使いたい
  ときはTCAあるいはTCBを使うようにすればOKかと。

こんな具合に、ゲート時間の1秒ごと、シリアル
で測定周波数を出力します。

 ATtiny1614 Frq cnt ←タイトル
 F_CPU:20000000Hz ←動作周波数
 990.547kHz
 1000.015kHz  ←1MHzパルス入力
 1000.014kHz
 1000.014kHz
 0.000kHz  ←パルスオフ
 0.000kHz  1.5秒周期で0Hzを表示
 0.000kHz
 2.767kHz  ←1MHzパルス再接続
 267.531kHz
 1000.014kHz
 1000.014kHz
  :

タイマーTCDのキャプチャタイミングは計測する
クロックに同期します。
そのせいでクロックがないとキャプチャが行われず、
表示が止まってしまいます。
そこで、1.5秒内にキャプチャできなかったときは
クロック無しとして「0.000kHz」を出力するように
してあります。

制御スケッチです。 (ちょっと長い)
※10-03のスケッチを変更
 TCD0の2つの割り込み、オーバーフローとキャプチャが
 同時に入ったとき、オーバーフローが優先されるので
 計測カウント値が「+4096」されてしまう。
   4.096kHzを入れてるのに8.192kHになってしまう。
 そこでOVF処理でCAPをチェックしてCAPがあるときは
 cnt_ovfを+1しないようにした。
   ATmega328PではOVFよりCAPのほうが優先される。
   ATtiny1614とはちょっと違うので注意。

/*****  ATtiny1614を使った周波数カウンタ  *****/
// その叩き台 計測値はシリアル出力
// "tiny1614_fcnt_td0.ino" 2025-10-03 JH3DBO
// "tiny1614_fcnt_td0a.ino" 10-05
// TCD0のイベント:キャプチャ割り込みと
// オーバーフロー割り込みの部分を修正
// 同時ならオーバーフローが優先させる
// PA3 13pin 測定クロック入力
// PA1 11pin シリアル出力 9600BPS
// PA2 12pin RTC 1Hzパルス出力 キャプチャタイミング
// PB0 9pin TCD0オーバーフロータイミング チェック出力
// PB1 8pin TCD0キャプチャタイミング チェック出力
// PB2(7),PB3(6) 32.768kHz時計用水晶を接続
// クロックが無いとキャプチャデータが上がってこない
// PITを使って1.5秒間をチェックしてキャプチャがないと0Hzと表示
// タイトル
const char pgm_ttl[] = "Test ATtiny1614 Frq cnt (2025-10-05)\r\n";
/***** カウントデータ *****/
volatile uint32_t cnt_cap; // カウントキャプチャ値(32bit)
volatile uint16_t cnt_ovf; // TCD0は12bitなので上位桁を
// オーバーフロー値として累積カウント
volatile byte f_cap; // キャプチャ完了フラグ
/***** 文字出力バッファ *****/
char str_bff[40]; // 40文字 終端のnull含めて
// longは10桁
/***** シリアル出力 *****/
void tx(char c)
{
while(!(USART0.STATUS & (1 << 5))); // DREIF 送信できるまで待つ
USART0.TXDATAL = c; // 1文字出力
}
/***** シリアル文字列出力 *****/
void txstr(const char *s)
{
while(*s != '\0'){ // Nullまで
tx(*s); // 1文字出力
s++; // 次文字
}
}
/***** 書式付シリアル出力 *****/
void txprintf(const char *s, ...)
{
va_list vp;
va_start(vp,s);
vsnprintf(str_bff, sizeof str_bff, s, vp);
txstr(str_bff);
va_end(vp);
}
/***** RTC,PIT busy待ち *****/
// 両方とも0になるのを待つ
void rtcbusy(void) {
while((RTC.STATUS & 0x0F) || // RTC busy ?
(RTC.PITSTATUS & 1)); // PIT busy ?
}
/***** TCD0 オーバーフロー割り込み *****/
// カウント値の12bit以上のMSB側を計数
// OVFとCAPが同時ならOVFが優先されるので
// CAPフラグがオンなら+1しない
// PB0でチェック
ISR(TCD0_OVF_vect)
{
PORTB.OUTSET = (1 << 0); // PB0 H 9pin
TCD0.INTFLAGS = 1; // TCD0オーバーフローフラグをクリア
if(!(TCD0.INTFLAGS & (1 << 3))){ // CAPフラグonなら+1しない
cnt_ovf++; // MSB側カウント値を+1
}
PORTB.OUTCLR = (1 << 0); // PB0 L
}
/***** TCD0 トリガ割り込み *****/
// トリガB割り込みでカウント値をキャプチャ
// 上位のオーバーフローカウント値を加算して総クロック数を計算
// カウントクリアのタイミングで1 clk少なくなるのを+1して補正
// PB1でチェック
ISR(TCD0_TRIG_vect)
{
static uint16_t d;
PORTB.OUTSET = (1 << 1); // PB1 H 8pin
TCD0.INTFLAGS = (1 << 3); // TCD0トリガB割り込みフラグをクリア
d = TCD0.CAPTUREB & 0x0FFF; // 12bitキャプチャデータ
cnt_cap = ((uint32_t)cnt_ovf << 12) + (uint32_t)d; // 32bitで加算
cnt_cap += 1; // クリアタイミングの1 clkを補正
cnt_ovf = 0; // 結果が出たのでMSB値をクリア
f_cap = 1; // キャプチャok
TCD0.INTFLAGS = 1; // TCD0オーバーフローフラグをクリア
PORTB.OUTCLR = (1 << 1); // PB1 L
}
/***** SETUP *****/
void setup() {
cli(); // 割込禁止
PORTA.OUT = 0b00000010;
PORTA.DIR = 0b11110110;
// |||||||+---- PA0 10pin UPDI
// ||||||+----- PA1 11pin TXD
// |||||+------ PA2 12pin EVOUT0 (!!!)
// ||||+------- PA3 13pin 測定クロック入力 ←
// |||+-------- PA4 2pin
// ||+--------- PA5 3pin
// |+---------- PA6 4pin
// +----------- PA7 5pin
PORTB.DIR = 0b00000011;
// |||+---- PB0 9pin out (!!!)
// ||+----- PB1 8pin out (!!!)
// |+------ PB2 7pin TOSC2 32.768kHz
// +------- PB3 6pin TOSC1 XTAL
// プルアップ
PORTA_PIN3CTRL = PORT_PULLUPEN_bm; // PA3 pull up
// 代替ポート
PORTMUX.CTRLA = 0b00000001; // 代替ポート
// || ||+--- EVOUT0 PA2 イベント出力
// || |+---- EVOUT1 PB2
// || +----- EVOUT2 PC2
// |+------- LUT0out PB4
// +-------- LUT1out PC1
PORTMUX.CTRLB = 0b00000001; // 代替ポート
// | +-- USART0 TXをPA1に
// +---- SPIO
// 32.768kHz XTAL
// _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, 0x00); // XTALオフ
// while(CLKCTRL.MCLKSTATUS & (1 << 6)); // XOSC32KSの0を待つ
// リセット起動なら↑の手順は不要 いきなりオン↓でOK
_PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, 0b00000001); // 発振イネーブル
// || ||+-- ENABLE
// || |+--- RUNSTBY
// || +---- SEL
// ++------ CSUT 1kサイクル
// CSUT 01で16kサイクル、10で32k、11で64kサイクルの起動時間待ち
// RTC
rtcbusy(); // RTC,PIT ビジー待ち
RTC.CLKSEL = 0b00000010;
// ++---- XOSC32K 32.768kHzHz水晶
RTC.CTRLA = 0b00101001;
// ||||| +---- RTCEN RTC周辺機能許可
// |++++------- CLK_RTC 1/32 →1024Hz
// +----------- RUNSTBY
RTC.PER = 1024 - 1; // 1/1024 →1Hz
RTC.PITCTRLA = 0b01010001;
// |||| +-- PITEN PIT有効
// ++++----- PERIOD 32.768kHz/2048 →16Hz
// タイマーD ※データシート 22.3.2.6参
TCD0.CMPBCLR = 4096 - 1; // カウント最大値
TCD0.CTRLB = 0b00000000; // 波形制御
// ++--- WGMODE 1ランプモード
TCD0.INPUTCTRLB = 0b00001000; // 入力制御
// ++++--- EDGETTRIG キャプチャとクリア
TCD0.EVCTRLB = 0b10000101; // イベント制御
// || | | +--- トリガイベント入力有効
// || | +----- イベントはキャプチャをトリガ
// || +------- イベントは↓エッジ
// ++--------- 非同期イベントで
TCD0.INTCTRL = 0b00001001; // 割り込み
// || +--- オーバーフロー割り込みon
// |+----- トリガA
// +------ トリガB キャプチャ割り込みon
while(!(TCD0.STATUS & 1)); // ENRDY イネーブルレディを待つ
TCD0.CTRLA = 0b01000001; // 制御開始
// ||||||+---- ENABLE
// ||||++----- SYNC分周比 1/1
// ||++------- プリスケーラ1/1
// ++--------- 外部クロック PA3を入力
// イベント
EVSYS.ASYNCCH1 = 0x08; // RTCの1秒オーバーフロー
EVSYS.ASYNCUSER7 = 0x04; // TCD0のイベント1でキャプチャを実行
EVSYS.ASYNCUSER8 = 0x04; // チェックのためEVOUT0 (PA2)にも出力
// シリアル
// 9600BPSでシリアル出力 受信はなし
USART0.CTRLB = 0b01000000;
// || ||||+-- MPCE
// || ||++--- RXMODE
// || |+----- ODME
// || +------ SFDEN
// |+-------- TXEN 送信有効
// +--------- RXEN 受信はしない
USART0.CTRLC = 0b00000011;
// |||||+++-- CHSIZE 8BIT
// ||||+----- SBMODE 1stop
// ||++------ PMODE Parity無し
// ++-------- CMODE 非同期USART
USART0.BAUD = (4L * F_CPU) / 9600L; // 20MHzなら8333
sei(); // 割込有効
}
/***** LOOP *****/
// 1秒周期のキャプチャを待ってシリアル出力
// 1234.567kHz
// 1.5秒以上パルスが来ないと0.000kHzを出力
void loop() {
uint32_t d; // 周波数データ
static byte f_disp; // 周波数表示(出力)フラグ
static byte pit_cnt; // 16Hz PITカウンタ
// 1.5秒経過(24カウント)で0Hz出力
txstr(pgm_ttl); // タイトル出力
txprintf("F_CPU:%luHz\r\n", F_CPU); // 動作周波数
while(!(CLKCTRL.MCLKSTATUS & (1 << 6))); // XOSC32KSの1を待つ
//while(!(TCD0.STATUS & 1)); // ENRDY イネーブルレディを待つ
//TCD0.CTRLE = (1 << 2); // TCD0再スタート (clkが必要)
cli(); // 割り込み禁止で
TCD0.INTFLAGS = 1; // TCD0オーバーフローフラグをクリア
TCD0.INTFLAGS = (1 << 3); // TCD0トリガB割り込みフラグをクリア
f_cap = 0; // キャプチャフラグをクリア
cnt_ovf = 0; // オーバーフローカウンタクリア
sei(); // 割込有効に
// loop
while(1){
if(f_cap){ // キャプチャした?
f_cap = 0; // キャプチャフラグをクリア
cli(); // 割り込み禁止で
d = cnt_cap; // キャプチャデータを読み出し
sei(); // 割込有効に
f_disp = 1; // 周波数確定 表示フラグをオン
pit_cnt = 0; // PITカウンタをゼロに 24回で0Hz表示
}
if(RTC.PITINTFLAGS & 1){ // PIT 16Hz?
RTC.PITINTFLAGS = 1;
pit_cnt++; // PITカウンタ +1
if(pit_cnt >= 24){ // 24/16サイクル=1.5秒経過
cli(); // 割り込み禁止で
cnt_ovf = 0; // オバーフローカウンタクリア
sei(); // 割込有効に
d = 0; // カウント値ゼロで
f_disp = 1; // 表示指令
pit_cnt = 0; // PITカウンタクリア
}
}
if(f_disp){ // 周波数表示指令?
f_disp = 0; // 表示フラグクリア
txprintf("%5lu.%03lukHz\r\n", // シリアル出力
d / 1000L, d %1000L);
}
}
}

ATtiny1614のレジスタへの書き込み、「わけわかめ」の
信号の英文名称を記述するのではなく、データシートを
見ればわかる「ビット位置」を使って書いています。
信号名称を使うと、
 「使いたいんは何ちゅう名前やねん」
 「それはどこに書いたんねん」
 「そいつの値はなんぼやねん」
 「なにやねんそれ」
 「えらい長い名前やなぁ」
を検索するのがうっとうしく、結局はデータシートを
見なけりゃならないので、8bitなら「0b00000001」
という記述でエエやんっと、独自路線で仕上げました。

もう一つ。
タイマーTCD、キャプチャと同時にゼロクリアしてくれるので
周波数カウンタの用途に便利なんですが、このゼロクリアの
タイミングも入力クロックで刻まれているようです。
そのため、周波数が低いと(10Hz以下)、ゲートとなる
RTCからの1Hzクロックとの差で、ミスが発生します。
  低めの周波数値が出る
キャプチャしてからクリアという流れがクロックで刻まれ
るのでカウントをミスるようです。

20MHz動作で8MHzは数えていました。

2019年12月20日:Arduino UNOで周波数カウンタ
2021年1月15日:アナデバの電力計用IC、arduinoで周波数(周期)を計る
のようにキャプチャ機能だけを使ってカウントする方が良い
かもしれません。

Arduino UNO R3のATmega328pとはちょっと違う動き
でした。

※関連
2021年1月15日:アナデバの電力計用IC、arduinoで周波数(周期)を計る
2025年5月16日:Arduino UNO R3で周波数を計る

※続き
2025年10月4日:ATtiny1614で周波数カウンタ:キャプチャタイミング

※注意
2025年10月6日:ATtiny1614につないだ32kHz水晶発振子、隣のピンの影響を受けるみたい

|

« 大阪市立図書館のシステムがダウンだと | トップページ | ATtiny1614で周波数カウンタ:キャプチャタイミング »

ATtiny」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« 大阪市立図書館のシステムがダウンだと | トップページ | ATtiny1614で周波数カウンタ:キャプチャタイミング »