メインページ/Electronics/AVR/Timer 16bit

提供:MwWiki

移動: 案内, 検索

目次

[非表示]

[編集] 16ビットタイマー

8ビットタイマーを使えるようになったので、ついでに16ビットタイマーについても動作確認します。

16ビットタイマーは8ビットタイマーが単に16ビットに拡張、つまり分解能が256倍になったもの、程度の認識でしたが、モードや使うレジスタの違い、それにオマケ機能が追加されています。

[編集] モード

ATMega88 のデータシートの表16-5にモードの一覧があります。高速なタイマを試したいと思ったので、ここでは「高速PWM動作」を使います。「高速PWM動作」には、TOP値の設定にICR1レジスタを使うものとOCR1Aレジスタを使うものの2種類があります。最初、OCR1Aを使って試していたのですが、思ったとおりに動作しなかったため、ICR1 を使うモード (WGM13=WGM12=WGM11=1)を使ったらうまくいきましたので、これを使います。こちらのページを参考にさせていただきました。

[編集] プログラム

高速PWM動作を使うぶんには、それほど8ビットと違いはありませんが、16ビットの方がフレキシブルになっています。

  • 8ビットではTOP値固定でしか使えませんでしたが、16ビットでは指定可能。
  • 2本の出力可

以下は、PB1 にPWMを出力するテストプログラム

#define	F_CPU	1000000UL	//1MHz
#include <util/delay.h>
#include <avr/io.h>
int main(void) {
	//OC1A (PB1) にタイマー出力
	DDRB |=_BV(PB1);
	PORTB |= _BV(PB1);

	//タイマー1設定
	TCCR1B = 0; //停止 (クロック分周設定BIT=0)
	TCCR1A |= _BV(WGM11); //16ビット高速PWMモード (ICR1)
	TCCR1B |= _BV(WGM13)|_BV(WGM12); //16ビット高速PWMモード
	TCCR1A |= _BV(COM1A1); //非反転動作、OC1Aピンへ出力

	ICR1=0xFFFF;//TOP値 (8bitタイマーとレジスタが異なる)
	OCR1A=0x7FFF; //一致値 Duty = ICR1/OCR1A

	//Freq = 1MHz / (分周) x (1+TOP)
	//1000000/65536=15.26Hz

	_delay_ms(5000);
	TCCR1B |= _BV(CS10); //1分周、タイマースタート

	while(1) {
	}

	return 0;
}

なお、タイマーの開始・停止はTCCR1B のクロック分周値の設定を0とそれ以外にして行うのが普通のようです。

[編集] 出力

今日、テクトロの4万円オシロが届いたのでさっそくキャプチャしてみました。

Pwm 16bit1.png

USBメモリ経由の場合は、画面表示がそのままBMPにセーブされました。今まで秋月のPicoScopeでやっていて、PC画面をキャプチャする方に慣れていましたので、USBメモリの抜き差しはちょっと面倒です。でも、本物のオシロは操作性がやはりいいです。

周波数範囲は、クロック1MHzで使う場合は FREQ = (1MHz / (分周) x (1+TOP)) より

15.26Hz (TOP値0xFFFF、OCR1A=0x7FFF)

333.34kHz (TOP値2、OCR1A=1)

となります。

[編集] 16ビットタイマーのおまけ機能

インプット・キャプチャ・ユニット(捕獲入力)という機能があります。これは、ICP1ピンに入力があると、タイマがカウントを始め、再度入力があるとカウントが終る機能です。つまり、何かが起こっている間の時間を計測するのに非常に都合がよい機能で、8ビットには無い、すばらしい仕組みがあったのです。

この機能を試すにあたって、問題が発生しました。LCDデバッグボードを作ってあったのですが、LCDとのデータ接続に使うポートをPORTBに固定してしまったため、ICP1 ピン(PB0)を入力として使えない、という事態になりました。

ATMEGA88 LCD pin.JPG ICP1 はLCD用(緑)に使ってしまっていた。

LCDを付けたデバッグボード。LCDとの接続ポートは選択できるように作っておけばよかった。。

Lcd debug1s.jpg 

ICP1の他、捕獲入力元としてはアナログコンパレータというのが使えるもようです。したがって、まずはアナログコンパレータを先に学習する必要が発生しました。こちらを御覧ください。

アナログコンパレータの使い方がわかったので、インプット・キャプチャを試します。これも、たまたま同じ本(AVRリファレンス・ブック)にサンプルがそのまま出ていましたので、それを参考にしています。ただし、ICP1 入力でなく、ADC入力を使うようにしました。

プログラム

//ATMega88
//16bit タイマーのインプットキャプチャ(捕獲入力)のテスト
//値はLCDに表示
#include	<avr/interrupt.h>
#include <avr/io.h>
#include	"lcdlib.h"		//F_CPU定義と<util/delay.h>を含む

uint16_t current_value, temp_value, value;

//得られたタイマー値を表示する
//引数:	ICR1
void	lcdPutValue(uint8_t x, uint8_t y, int16_t dValue)
{
	lcdSetPos(x, y);
	lcdPutStr("       ");	//7桁
	
	//負数の剰余算は結果が負数で求まるため、最初に正数にしてしまう
	if (dValue < 0)
	{
		lcdPutChar('-');
		dValue = -dValue;
	}
	
	lcdSetPos(x, y);
	lcdPutUInt(dValue);
}


void timer1_init(void) {
	TCCR1B = 0; //停止 (クロック選択BIT=0)
	TCNT1H = 0xFF; TCNT1L = 0xFF;
	TCCR1A = 0; //標準動作
//	TCCR1B |=_BV(CS11); //8分周 FREQ=125kHz。(まだスタートしない。)
	TCCR1B |=_BV(ICNC1)|_BV(ICES1); //ノイズキャンセラ使用、立ち上がりエッジをキャプチャ
}

ISR(TIMER1_CAPT_vect) {
	current_value = ICR1L; //タイマー値を取得
	current_value |= (int)ICR1H << 8;
	value = current_value - temp_value;

	temp_value = current_value;
	lcdPutValue(0,1, value);
}

ISR(TIMER1_OVF_vect) {
	//タイマー1がオーバーフローしたとき temp_value をクリア
	temp_value=0x0000;
}

void portInit(void) {
	//AIN1入力
	DDRD&=~_BV(PD7); PORTD&=~_BV(PD7);
	DDRC=0;PORTC=0;//ADCポートも初期化必要とのこと
}

void comparator_init(void) {
	ACSR |= _BV(ACBG)|_BV(ACIC); //内部基準電圧(Bandgap)選択、入力キャプチャ許可
	ACSR |=_BV(ACD); //初期状態はACオフ
}

void init(void) {
	cli();
	portInit();
	lcdInit();	//LCD初期化
	comparator_init();
	timer1_init();

	TIMSK1 |= _BV(ICIE1)|_BV(TOIE1); //タイマ捕獲割り込み許可、オーバーフロー割り込み許可
	sei();
}

int main(void) {
	_delay_ms(1000);
	init();
	temp_value=current_value=0;
	ACSR &=~_BV(ACD); //比較器ON
	TCCR1B |= _BV(CS11); //8分周、タイマースタート
	lcdSetPos(0, 0); lcdPutStr("Start!");

	while(1) {
	}

	return 0;
}

これで、試しに発振器からPD7に 50Hz を入力すると、だいたい 2544 近辺の値が表示されました。クロックは 1MHz / 8 分周 = 125kHz ですから、125kHz / 2544 = 49.1Hz となります。

続きは商用周波数カウンタ