LEDサンプル

回路図

やっぱり最初はこれよね。
LEDをPD0〜PD7に接続。

LEDサンプル

本当は抵抗が必要なはずだが、まぁいい。
とりあえずは光ればいい。

ソースコード

以下、ソースコード。

#include <avr/io.h>
#include <util/delay.h>

void delay_ms( int time )
{
	// 指定ms分ループ
	while( time-- ){
		_delay_ms( 1 );
	}
}

int main( void )
{
	DDRD	= 0b11111111;	// ポートDの方向レジスタを、全て出力に
	PORTD	= 0b00000000;	// ポートDの出力レジスタを、全てLowに

	char count	= 0;	// 0〜7まで
	char binc	= 1;	// インクリメントモードかデクリメントモードか

	while( 1 ){
		PORTD = ( 1 << count );	// ポートDのカウント数位置の出力レジスタのみをHighに
		if( binc ){	// インクリメントモード
			count++;
			if( 7 <= count ){	// 端までいったのでデクリメントモードへ
				binc = 0;
			}
		} else {	// デクリメントモード
			count--;
			if( 0 >= count ){	// 最初まで戻ったのでインクリメントモードへ
				binc = 1;
			}
		}
		delay_ms( 50 );	// 50ms待ち
	}

	return 0;
}

_delay_ms

ATmega168Pは、内蔵RC発信器が8MHzとなっていたので、普通にCPUのFrequencyも8MHzだろうと思いこむ。
AVRStudioのProject Optionで、Frequencyを8000000Hzに設定して、F_CPUのdefineを設定したんだけど、実際やってみると、delay_msの待ちが余りに遅い。
具体的に言うと、8倍くらい。
データシートを読み返してみると、ヒューズビットにCKDIV8とかいうのがあって、こいつが0だと、実際には8で割った数がシステムクロックとなるらしい。
(システムクロック前置分周器:目的としては、消費電力節約用?)
ってんで、Frequencyを1000000Hzに設定しなおして、事なきを得る。

デバイスの最高周波数より高い周波数のクロックを入力する場合、前置分周器で押さえてやったりするみたいね。

というわけで、ヒューズビットのCKDIV8を立てて、Frequencyを8000000Hzにしてもちゃんと動きました。
この辺は、電源とかの兼ね合いで変えるべきかね。

AVRWikiにあるように、_delay_ms関数は、システムクロック数によって、待てる時間が異なり、しかもその時間は結構短い。
なので、1ms指定で指定ms分ループをしている。

本当は、1ms指定じゃなくもっと大きい数で少ない回数回す方が、誤差は少ないはず。
そのシステムクロックで回せる最大数以上だったら最大数、未満だったらその値を指定するのが一番いいような気はする。
しかし、未満で変数を渡す場合は、以下の問題がある。

_delay_ms関数は結局、周波数の定数F_CPUを参照して、それをもとにループ回数を決め、ループを回しているだけらしい。
このため、定数を_delay_msに渡す場合は、コンパイラが最適化し、ループ回数が決定している。
けど、変数を渡すようなコードを書くと、doubleに変換した引数とF_CPUの計算ルーチンが入るため、コードが膨らむ。
手軽に使えるけど、色々考えなければいけないようだ。

あと、ふと思ったけど、これって決め打ちでループ回してるだけだから、間に割り込みとか入ってそれが時間を食うと、その分が丸々誤差になるんですね。
びみょい。

タイマを使ってみようかとも思ったけど、分周比との兼ね合いを考えると、あらかじめ待つ時間がわかってるような場合じゃないと、使いにくい。
結局、今のやり方が、とりあえずは一番お手頃な感じ。


タイマ

とかいいつつ、前置分周なしの8bitタイマ0使用のバージョンを作ってみた。
以下、ソースコード。
main関数は変わってない。

#include <util/delay.h>はいらなくなるので削除。

void delay_ms( int32_t time )
{
	int32_t waithz = ( F_CPU / 1000 ) * time;	// 指定ms数で待つクロック数を算出

	if( 0 >= time ) return;

	TCCR0A	= 0;	// タイマ/カウンタ0制御レジスタAを標準動作に設定

	while( 0 < waithz ){
		if( 256 < waithz ){
			TCNT0 = 0;				// 待ちクロックが256よりも大きければ、カウンタを0に設定
		} else {
			TCNT0 = 256 - waithz;	// 256以内なら、カウンタを256-残り数に設定
		}
		waithz -= 256;
		// 一周して0xFF→0になる瞬間に捕まえる
		TCCR0B	= 1;	// タイマ/カウンタ0制御レジスタBを前置分周なしで開始に設定
		while( 1 ){
			if( bit_is_set( TIFR0, TOV0 )){
				TCCR0B	= 0;			// タイマ/カウンタ0制御レジスタBを動作停止に設定
				TIFR0	= _BV( TOV0 );	// オーバーフローフラグを落とす(なぜか、1を書き込むと0になるらしい……)
				break;
			}
		}
	}
}

すっかり頭から抜け落ちてたというか、普段32bitプログラムに慣れているせいで、intが16bitだとは思わなかったよ!
最初組んだら、waithzがオーバーフローしてて0以上だったらのループに入らないでやんの。
しばらく悩んでもーた。

実際はやっぱり誤差があるはず。
ループごとにタイマ停止するから、次のタイマ開始までの処理分余計にかかるし、フラグが立ってからタイマ停止の間にも数クロック消費するし、前置分周なしの時は、タイマ開始の瞬間に、カウンタが1増えるらしいし、積もり積もればそれなりの誤差になるんだろうな。

後は、電力の節約とかを考えると、入力の時間によって、前置分周を可変でタイマ開始してやるとか出来ればいいんだろうな。
しかし、タイマ化するだけで結構プログラムサイズは増えるのであった。
(書き込みサイズが、変更前233bytes、変更後353bytes)
こんな使い方だったら、やっぱり_delay_msで充分なのかも。
とりあえず、タイマの勉強にはなったけど、やっぱりタイマは割り込みで使うべきかな。


添付ファイル: fileLEDTest.png 1454件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-09-10 (木) 00:01:51 (5334d)