ATtiny2313を使って、AVRマイコンのタイマー機能の実験をしました。タイマーを用いればマイコン内部で正確な時間を作り出すことができるとのことです。ちゃんと使いこなせればですが。
ATtiny2313は「Timer0」と「Timer1」という2つのタイマーを持っています。Timer0は8ビット、Timer1は16ビットのタイマーです。8ビットのタイマーというのは、目盛が8ビット(2の8乗)すなわち256まである時計、16ビットのタイマーは目盛が16ビット(2の16乗)すなわち65536まである時計です。アナログ時計の文字盤をイメージすればわかりやすいと思います。下の図のような感じです。
Timer0は針が図の位置から一定の速さで右回りに動いて「1, 2, 3, ・・・」と256まで数えます。針が1周して256になったらカウント数が0に戻り(オーバーフロー)、ふたたび「1, 2, 3, ・・・」と数えます。Timer1は1から65536まで数えることができます。1カウントあたり何秒になるかは、マイコンのクロック周波数、およびプログラム上での設定(分周比)によって変わってきます。
BASCOM-AVRのプログラムでは、1, 8, 64, 256, 1024の5通りの分周比を選択することができます。クロック周波数を1MHzとした場合、1カウントの周期やタイマーが1周するのにかかる時間は次のようになります。
分周比 | 1 | 8 | 64 | 256 | 1024 |
タイマー用クロック | 1MHz | 125kHz | 15.625kHz | 3.90625kHz | 0.9765625kHz |
1カウントの時間 | 1μS | 8μS | 64μS | 256μS | 1024μS |
1周する時間 | 256μS | 2,048μS | 16,384μS | 65,536μS | 262,144μS |
1秒あたりの カウント数 |
1,000,000 (3906周+64) | 125,000 (488周+72) |
15,625 (61周+9) | 3,906.25 (15周+66.25) |
976.5625 (3周+208.5625) |
分周比 | 1 | 8 | 64 | 256 | 1024 |
タイマー用クロック | 1MHz | 125kHz | 15.625kHz | 3.90625kHz | 0.9765625kHz |
1カウントの時間 | 1μS | 8μS | 64μS | 256μS | 1024μS |
1周する時間 | 65,536μS | 524,288μS | 4,194,304μS | 16,777,216μS | 67,108,864μS |
1秒あたりの カウント数 |
1,000,000 (15周+16,960) | 125,000 (1周+59,464) |
15,625 | 3,906.25 | 976.5625 |
Timer0の8分周を例に取ると、1MHz÷8=125kHzですからタイマーをカウントする際の基準となるクロック周波数は125kHzです。周期は1÷125kHz=8μSになりますから、8μS毎にカウントが1ずつアップします。Timer0は256カウントで1周するので、上のアナログ時計の例えで言うと針が1周するのにかかる時間は8μS×256=2,048μSです。1秒間では1,000,000μS÷8μS=125,000カウントですが、256カウント毎に0に戻りますので、1秒後には時計の針が488周して72を指します。
タイマー機能をテストするための回路を下に示します。マイコンICはATtiny2313-20PC、クロック源は3端子の水晶発振器JXO-5を用いました。周波数は1MHzです。水晶発振器をクロックにする場合はヒューズビットを「1110 0000」に書き換える必要があります。そのほかに、カウント数を表示するためにLCD・液晶ディスプレイを接続します。
最初に、Timer1のカウント数をLCDに表示するプログラムをやってみました。1カウントごとの表示は無理なので1秒ごとのカウント数を表示します。BASCOM-AVRがインストールされたパソコンで下記リンク「timer1a.bas」をクリックしてファイルをダウンロードすると、BASCOM-AVR IDEが自動的に立ち上がって画面内にプログラムが表示されます。
プログラムファイル timer1a.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Lcdpin=Pin, Db7=Portb.0, Db6=Portb.1 | LCDの接続設定。DB7=PB0, DB6=PB1。 | |
5 | Config Lcdpin=Pin, Db5=Portb.2, Db4=Portb.3 | LCDの接続設定。DB5=PB2, DB4=PB3。 | |
6 | Config Lcdpin=Pin, E=Portb.4, Rs=Portb.5 | LCDの接続設定。E=PB4, RST=PB5。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
9 | |||
10 | Config Timer1 = Timer , Prescale = 1 | Timer1をタイマーとして使用。分周比は1。 | |
11 | |||
12 | Do | Doループの範囲ここから。 | |
13 | Cls : Lcd Timer1 | LCDの表示を消去する。 | |
Timer1のカウント数を表示する。 | |||
14 | Waitms 985 | 985m秒間そのまま。 | |
15 | Loop | Doループの範囲ここまで。12行目へ戻る。 | |
16 | |||
17 | End | 終わり。 |
10行目「Config Timer1 = Timer , Prescale = 1」はTimer1の設定をするプログラムです。前半「Config Timer1 = Timer」はTimer1を「タイマー」として使用するという意味です。Timer1はタイマー以外にカウンターやコンパレータなどの機能を持っているので、それを区別するために記述します。後半の「Prescale = 1」は分周比を1にするという意味です。マイコンICのクロック周波数が1MHz、分周比が1なので、Timer1用のクロックも1MHzになります。14行目「Waitms 985」はDoループ1周が約1秒になるように調整しました。でもぴったりではありません。
このプログラムを分周比を変えて実行したときの結果(LCDの表示値)は下記のようになりました。カウント値の後のカッコ内の数字はオーバーフローの回数を表わしています。また最下行「Ct⁄s」はプログラム上での1秒(Doループ1周)あたりのカウント数で、5秒後の数値から4秒後の数値を引いたものです。
時間 | 分周比1 | 分周比8 | 分周比64 | 分周比256 | 分周比1024 |
0 | 5653(1) | 706(1) | 88(1) | 22(1) | 5(1) |
1 | 26588(15) | 60503(2) | 15374(1) | 3933(1) | 982(1) |
2 | 48830(30) | 55091(3) | 31442(1) | 7855(1) | 1961(1) |
3 | 5534(45) | 49680(4) | 47149(1) | 11777(1) | 2941(1) |
4 | 26468(60) | 44268(5) | 62857(1) | 15704(1) | 3922(1) |
5 | 48710(75) | 38856(6) | 13028(2) | 19630(1) | 4902(1) |
Ct⁄s | 1,005,282 (15周+22242) | 125,660 (1周+60124) |
15707 | 3926 | 980 |
Doループ1周が正確な1秒になっていないので、1秒あたりのカウント数は先の計算値とは違います。また表示の桁数によっても時間がずれてきます。そんなわけであまり役に立たないプログラムですが、Timer1が実際に数をカウントしているのだということだけはわかりました。
続いて、Timer1の設定直後のカウント数を表示するプログラムを試してみました。
プログラムファイル timer1b.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Lcdpin=Pin, Db7=Portb.0, Db6=Portb.1 | LCDの接続設定。DB7=PB0, DB6=PB1。 | |
5 | Config Lcdpin=Pin, Db5=Portb.2, Db4=Portb.3 | LCDの接続設定。DB5=PB2, DB4=PB3。 | |
6 | Config Lcdpin=Pin, E=Portb.4, Rs=Portb.5 | LCDの接続設定。E=PB4, RST=PB5。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
9 | |||
10 | Config Timer1 = Timer , Prescale = 1 | Timer1をタイマーとして使用。分周比は1。 | |
11 | Lcd Timer1 | Timer1のカウント数を表示する。 | |
12 | |||
13 | End | 終わり。 |
これを実行してみたところ、「8」と表示されました。つまり、タイマーを設定してから何もしていないのに8カウント進んだということになります。プログラムシミュレータで各行のサイクル数を調べてみた結果が下の表です。クロックは1MHzなので1サイクルが1μSです。また分周比は1ですからTimer1は1μS毎つまり1サイクル毎に1ずつカウントアップします。
プログラム | 経過サイクル数 | 各行の所要 サイクル数 |
10 Config Timer1 ... | 272504 | 4 |
11 Lcd Timer1 | 272508 | 4461 |
13 End | 276969 | − |
LCDの表示が8ということは、11行目の途中で表示されたということになります。Timer1はどの時点でスタートし、LCDへの表示はどの時点で行なわれるのでしょうか。
AVRマイコンのタイマーは(他のマイコンもそうだと思いますが)任意の位置でスタートさせたりストップさせたりできます。Timer1をスタートさせるときは「Start Timer1」と書きます。ストップさせたいときは「Stop Timer1」と書きます。また、0以外の値からカウントを始めることもできます。例えば「1」からカウントを始めたいときはいったんタイマーを止め、「Timer1=1」と書いてから再スタートさせます。
そこで、タイマー設定後すぐにタイマーをストップさせ、カウント数をLCDに表示させてみました。プログラムは下記の通りです。前半部分はこれまでと同じなのでタイマーの設定のところから記します。
プログラムファイル timer1c.bas
10 | Config Timer1 = Timer , Prescale = 1 | Timer1をタイマーとして使用。分周比は1。 | |
11 | Stop Timer1 | Timer1をストップ。 | |
12 | Lcd Timer1 | Timer1のカウント数を表示する。 | |
13 | |||
14 | End | − | 終わり。 |
これを実行すると、表示は「3」になりました。プログラムシミュレータで各行のサイクル数を調べた結果は下記の通りです。
プログラム | 経過サイクル数 | 各行の所要 サイクル数 |
10 Config Timer1 ... | 272504 | 4 |
11 Stop Timer1 | 272508 | 3 |
12 Lcd Timer1 | 272511 | 4461 |
14 End | 276972 | − |
表示が「3」ということは3サイクルで、これは11行目「Stop Timer1」の所要サイクル数と同じです。つまり、Timer1は10行目「Config Timer1 ...」の実行が終わった瞬間(272508サイクル目)にスタートし、11行目「Stop Timer1」の実行が終わった瞬間(272511サイクル目)にストップしたと考えてよさそうです。
次に、タイマーをいったん止めて「Timer1=0」にしてから再スタートさせ、すぐにまた止めてカウント数を表示させるプログラムをやってみました。
プログラムファイル timer1d.bas
10 | Config Timer1 = Timer , Prescale = 1 | Timer1をタイマーとして使用。分周比は1。 | |
11 | Stop Timer1 | Timer1をストップ。 | |
12 | Timer1 = 0 | Timer1のカウント数を「0」にする。 | |
13 | Start Timer1 | Timer1をスタート。 | |
14 | Stop Timer1 | Timer1をストップ。 | |
15 | Lcd Timer1 | Timer1のカウント数を表示する。 | |
16 | |||
17 | End | 終わり。 |
プログラム | 経過サイクル数 | 各行の所要 サイクル数 |
10 Config Timer1 ... | 272504 | 4 |
11 Stop Timer1 | 272508 | 3 |
12 Timer1 = 0 | 272511 | 6 |
13 Start Timer1 | 272517 | 2 |
14 Stop Timer1 | 272519 | 3 |
15 Lcd Timer1 | 272522 | 4461 |
17 End | 276983 | − |
結果、LCDの表示は同じく「3」でした。13行目「Start Timer1」に2サイクル要していますが、LCDの表示が「3」のままということは、Timer1は「Start Timer1」の実行が終わってからスタートする、だからこの行の所要時間はカウントに入らないのだと思われます。
ちなみに、12行目を「Timer1=100」に変えると表示は「103」に、「Timer1=1000」に変えると表示は「1003」になりました。また、マイナスの数を指定することも可能で、「Timer1=-3」と書いたら「0」が表示されました。この行の所要サイクル数は数値が何であっても同じ6サイクルでした。なお、表示とは関係ありませんが、15行目「Lcd Timer1」の所要サイクル数は、3桁の数字を表示するとき7068サイクル、4桁の数字を表示するとき9684サイクルと、文字数が多くなるにつれて時間がかかります。
上記と同じプログラムをTimer0でもやってみました。表示は「2」になりました。他の行もTimer1のときより所要サイクル数が少なくなりました。
プログラムファイル timer1e.bas
10 | Config Timer0 = Timer , Prescale = 1 | Timer0をタイマーとして使用。分周比は1。 | |
11 | Stop Timer0 | Timer0をストップ。 | |
12 | Timer0 = 0 | Timer0のカウント数を「0」にする。 | |
13 | Start Timer0 | Timer0をスタート。 | |
14 | Stop Timer0 | Timer0をストップ。 | |
15 | Lcd Timer0 | Timer0のカウント数を表示する。 | |
16 | |||
17 | End | 終わり。 |
プログラム | 経過サイクル数 | 各行の所要 サイクル数 |
10 Config Timer0 ... | 272504 | 2 |
11 Stop Timer0 | 272506 | 2 |
12 Timer0 = 0 | 272508 | 2 |
13 Start Timer0 | 272510 | 2 |
14 Stop Timer0 | 272512 | 2 |
15 Lcd Timer0 | 272514 | 4458 |
17 End | 276972 | − |
下記は、Timer1を「-3」からスタートさせ、1000μS秒後のカウント数をLCDに表示させるプログラムです。実行すると、思惑通り?「1000」と表示されました。なお、「Waitus 1000」のかわりに「Waitms 1」と書くと、カウント数は「1025」になります。
プログラムファイル timer1f.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Lcdpin=Pin, Db7=Portb.0, Db6=Portb.1 | LCDの接続設定。DB7=PB0, DB6=PB1。 | |
5 | Config Lcdpin=Pin, Db5=Portb.2, Db4=Portb.3 | LCDの接続設定。DB5=PB2, DB4=PB3。 | |
6 | Config Lcdpin=Pin, E=Portb.4, Rs=Portb.5 | LCDの接続設定。E=PB4, RST=PB5。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
9 | |||
10 | Config Timer1 = Timer , Prescale = 1 | Timer1をタイマーとして使用。分周比は1。 | |
11 | Stop Timer1 | Timer1をストップ。 | |
12 | Timer1 = -3 | Timer1のカウント数を「-3」にする。 | |
13 | Start Timer1 | Timer1をスタート。 | |
14 | Waitus 1000 | 1000μ秒間そのまま。 | |
15 | Stop Timer1 | Timer1をストップ。 | |
16 | Lcd "TIMER1 " ; Timer1 | LCDに「TIMER1」の文字とカウント数を表示。 | |
17 | |||
18 | End | 終わり。 |
下記のプログラムは、LCD画面上の表示が「1000, 2000, 3000, ...」というように1秒間に1000ずつ増えていくものです。といっても、実際にTimer1のカウントが1000ずつ増えているわけではなく、1000μS間カウントした後タイマーを止めてカウント数を表示し、1秒後にまた1000μS間だけタイマーのカウントを進めるという動作です。カウント時間を1000μSにするのに、16行目を「Waitus 997」にしたいのですが、「Waitus」での時間調整が4μS刻みでしかできないので、16行目は「Waitus 996」とし、21行目「Incr Timer1」でカウントを1進めています。「65000」を表示すると、次は「464, 1464, 2464, ...」となります。
プログラムファイル timer1g.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Lcdpin=Pin, Db7=Portb.0, Db6=Portb.1 | LCDの接続設定。DB7=PB0, DB6=PB1。 | |
5 | Config Lcdpin=Pin, Db5=Portb.2, Db4=Portb.3 | LCDの接続設定。DB5=PB2, DB4=PB3。 | |
6 | Config Lcdpin=Pin, E=Portb.4, Rs=Portb.5 | LCDの接続設定。E=PB4, RST=PB5。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
9 | |||
10 | Config Timer1 = Timer , Prescale = 1 | Timer1をタイマーとして使用。分周比は1。 | |
11 | Stop Timer1 | Timer1をストップ。 | |
12 | Timer1 = 1 | Timer1のカウント数を「1」にする。 | |
13 | |||
14 | Do | Doループの範囲ここから。 | |
15 | Start Timer1 | Timer1をスタート。 | |
16 | Waitus 996 | 996μ秒間そのまま。 | |
17 | Stop Timer1 | Timer1をストップ。 | |
18 | Cls | LCDの表示を消去する。 | |
19 | Lcd "TIMER1 " ; Timer1 | LCDに「TIMER1」の文字とカウント数を表示。 | |
20 | Wait 1 | 1秒間そのまま。 | |
21 | Incr Timer1 | Timer1のカウントを「1」進める。 | |
22 | Loop | Doループの範囲ここまで。 | |
23 | |||
24 | End | 終わり。 |