ATtiny1614で(裸の)周波数カウンタ
ATtiny1614 Frequency Meter:John BradnamPublished March 22, 2021
「A tiny frequency meter built using the ATtiny1614 microprocessor.」
とういうATtiny1614のタイマー「TCD」を使った周波数カウンタの
製作記事があったので、倣って作ってみました。
表示器は無しで、測定値の出力はシリアルで。
Arduino IDE環境で「内部のタイマーは使わない」に
しておかないと、タイマーの割り込みベクトルが衝突
しました。
※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水晶発振子、隣のピンの影響を受けるみたい
| 固定リンク
「ATtiny」カテゴリの記事
- 1Hzパルス発生回路改造(2026.03.12)
- 14ピンのATtiny3224のEVOUTB出力(2026.03.07)
- tinyAVRマイコンのタイマーAとtakeOverTCA0()(2026.02.22)
- ATtiny3224で裸の32bit周波数カウンタ #3(2026.02.20)
- 備忘録:1Hzパルス発生回路(2026.02.01)


コメント