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だろうと思いこむ。 デバイスの最高周波数より高い周波数のクロックを入力する場合、前置分周器で押さえてやったりするみたいね。 というわけで、ヒューズビットのCKDIV8を立てて、Frequencyを8000000Hzにしてもちゃんと動きました。 AVRWikiにあるように、_delay_ms関数は、システムクロック数によって、待てる時間が異なり、しかもその時間は結構短い。 本当は、1ms指定じゃなくもっと大きい数で少ない回数回す方が、誤差は少ないはず。 _delay_ms関数は結局、周波数の定数F_CPUを参照して、それをもとにループ回数を決め、ループを回しているだけらしい。 あと、ふと思ったけど、これって決め打ちでループ回してるだけだから、間に割り込みとか入ってそれが時間を食うと、その分が丸々誤差になるんですね。 タイマを使ってみようかとも思ったけど、分周比との兼ね合いを考えると、あらかじめ待つ時間がわかってるような場合じゃないと、使いにくい。 タイマ †とかいいつつ、前置分周なしの8bitタイマ0使用のバージョンを作ってみた。 #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だとは思わなかったよ! 実際はやっぱり誤差があるはず。 後は、電力の節約とかを考えると、入力の時間によって、前置分周を可変でタイマ開始してやるとか出来ればいいんだろうな。 |