ATtiny2313を使って、タイマー割り込み(オーバーフロー割り込み)の実験をしました。外部割込みと同じく、わかったようなわからないような感じです。
プログラムのテスト用の回路は下記の通りです。マイコンICのPB0(12番ピン)にLEDと圧電スピーカーをつなぎます。この回路で、割り込みが起きるたびにPB0の出力が反転するプログラムを試してみようと思います。出力周波数が数Hz以下のときはLEDの点滅によって、それ以上のときは圧電スピーカーから音が出るので動作の確認ができます。PB1(13番ピン)にもLEDをつないでおきます。
外部割込みは入力端子の状態の変化によって割り込みが発生しましたが、タイマー割り込みはIC内部のタイマーが1周することによって割り込みが起きます。Timer0の場合はカウント数が256に達した時点で割り込みが起きます。そのためオーバーフロー割り込みとよばれます。タイマーをストップさせなければ一定時間ごとにオーバーフローが生じますので、そのたびに割り込みが発生します。
最初に、Timer0のカウントが256になるたびにPB0の出力が反転するプログラムをやってみます。割り込み関係のプログラムの書き方は外部割込みのときと同じです。
プログラムファイル timer2a.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Portb = Output | ポートBを出力に設定する。 | |
5 | Config Timer0 = Timer , Prescale = 1024 | Timer0をタイマーとして使用。分周比は1024。 | |
6 | On Timer0 Timer0_int | 割り込みが起きたら「Timer0_int」を実行する。 | |
7 | Enable Interrupts | 割り込み全般を許可する。 | |
8 | Enable Timer0 | Timer0割り込みを許可する。 | |
9 | |||
10 | Do | Doループの範囲ここから。 | |
11 | Toggle Portb.1 | PB1の出力を反転する。 | |
12 | Waitms 500 | 500ミリ秒間そのまま。 | |
13 | Loop | Doループの範囲ここまで。10行目へ戻る。 | |
14 | |||
15 | End | メインプログラム終わり。 | |
16 | |||
17 | Timer0_int: | 割り込みプログラム「Timer0_int」ここから。 | |
18 | Toggle Portb.0 | PB0の出力を反転する。 | |
19 | Return | 割り込みプログラム「Timer0_int」ここまで。 |
5行目「Config Timer0 = Timer , Prescale = 1024」はTimer0の設定に関する記述です。分周比が1024なので、Timer0のカウント周期は1024μS、したがってその256倍の262,144μSごとに割り込みが起こります。6行目「On Timer0 Timer0_int」は、割り込みが発生したら「Timer0_int」という名前のサブルーチンへ飛ぶという意味です。17〜19行目の割り込みルーチン「Timer0_int」ではPB0の出力を反転させる動作をしています。また、プログラムの性質上Do〜Loopではさまれたメインプログラムが必要なので、PB1の出力を0.5秒ごとに反転させるプログラムを書きました。
このプログラムを実行すると、PB0に接続したLED0が1秒間に約2回点滅しました。プログラムがスタートして262,144μS後に最初の割り込みが起こり、PB0出力が「0」から「1」に変わるのでLED0は点灯します。524,288μS後に2回目の割り込みがおきるとPB0出力は「0」に戻り、LED0は消灯します。割り込みが起きるたび、圧電スピーカーからは「ポツ」という音が出ます。このようすを図で示すと下のようになります。
分周比(Prescale値)を「256」にすると、割り込みが発生する周期が短くなるので、LEDは早い点滅に変わります。周波数でいうと約8Hzです。分周比「64」では出力周波数31Hz、「8」では244Hz、「1」では1953Hzでした。割り込み2回分の時間の逆数が周波数になります。あまり細かい測定はできませんが、だいたい計算値と合っているようです。
タイマー割り込みに限らず、割り込みプログラムの実行中は新たな割り込みは起きません。つまり、「Timer0_int」の実行中にTimer0のカウントが256になっても無視されます。したがって、割り込みプログラムは次の割り込みが起きるまでに終わるくらいの長さでないと正しい動作ができなくなります。試しに上のプログラムで分周比を「1」とし、「Timer0_int」内に「Cls」という命令を追加してみます。「Cls」の実行には5000μS以上かかります。結果は、出力周波数が1953Hzから86Hzと大幅に下がってしまいました。分周比が「1024」や「256」のときは、割り込みプログラムの処理に多少時間がかかっても大丈夫です。
メインプログラムではPB1(LED1)を1秒間隔で点滅させていますが、分周比が小さくなるにつれてLED1の点滅がゆっくりになります。分周比が小さいと割り込みが頻繁に起こり、そのたびにメインプログラムの実行が中断されるためではないかと思われます。
次に、PB0出力の反転をメインプログラム内で行なうプログラムを実験しました。割り込みルーチン内ではビット型変数「Flag」の値を「1」に変える処理だけをします。このプログラムは上記「timer2a.bas」とまったく同じ動作になります。最初、14行目「Flag=0」を条件分岐プログラムの外に書いていたため、うまく動かなくてずいぶん悩みました。
プログラムファイル timer2b.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Portb = Output | ポートBを出力に設定する。 | |
5 | Config Timer0 = Timer , Prescale = 1024 | Timer0をタイマーとして使用。分周比は1024。 | |
6 | Dim Flag As Bit | ビット型変数「Flag」を使用する。 | |
7 | On Timer0 Timer0_int | 割り込みが起きたら「Timer0_int」を実行する。 | |
8 | Enable Interrupts | 割り込み全般を許可する。 | |
9 | Enable Timer0 | Timer0割り込みを許可する。 | |
10 | |||
11 | Do | Doループの範囲ここから。 | |
12 | If Flag = 1 Then | 条件分岐。「Flag=1」ならば、 | |
13 | Toggle Portb.0 | PB0の出力を反転する。 | |
14 | Flag = 0 | 変数「Flag」の値を「0」にする。 | |
15 | End If | 条件分岐プログラムここまで。 | |
16 | Loop | Doループの範囲ここまで。11行目へ戻る。 | |
17 | |||
18 | End | メインプログラム終わり。 | |
19 | |||
20 | Timer0_int: | 割り込みプログラム「Timer0_int」ここから。 | |
21 | Flag = 1 | 変数「Flag」の値を「1」にする。 | |
22 | Return | 割り込みプログラム「Timer0_int」ここまで。 |
Timer0とTimer1はそれぞれ独立したタイマーなので、2つを同時に使うことができます。ここでは、Timer0割り込みで1953Hzの音(ピー音)を出し、Timer1割り込みでその音を約4秒後に止めるというプログラムをやってみます。
プログラムファイル timer2c.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Portb = Output | ポートBを出力に設定する。 | |
5 | Config Timer0 = Timer , Prescale = 1 | Timer0をタイマーとして使用。分周比は1。 | |
6 | Config Timer1 = Timer , Prescale = 64 | Timer1をタイマーとして使用。分周比は64。 | |
7 | Dim Flag0 As Bit | ビット型変数「Flag0」を使用する。 | |
8 | Dim Flag1 As Bit | ビット型変数「Flag1」を使用する。 | |
9 | On Timer0 Timer0_int | Timer0割り込み時は「Timer0_int」を実行。 | |
10 | On Timer1 Timer1_int | Timer1割り込み時は「Timer1_int」を実行。 | |
11 | Enable Interrupts | 割り込み全般を許可する。 | |
12 | Enable Timer0 | Timer0割り込みを許可する。 | |
13 | Enable Timer1 | Timer1割り込みを許可する。 | |
14 | |||
15 | Do | Doループの範囲ここから。 | |
16 | If Flag0 = 1 Then | 条件分岐。「Flag0=1」ならば、 | |
17 | Toggle Portb.0 | PB0の出力を反転する。 | |
18 | Flag0 = 0 | 変数「Flag0」の値を「0」にする。 | |
19 | End If | 条件分岐プログラムここまで。 | |
20 | Loop Until Flag1 = 1 | Doループの範囲ここまで。15行目へ戻る。 | |
ただし「Flag1=1」になったらループを抜ける。 | |||
21 | |||
22 | End | メインプログラム終わり。 | |
23 | |||
24 | Timer0_int: | 割り込みプログラム「Timer0_int」ここから。 | |
25 | Flag0 = 1 | 変数「Flag0」の値を「1」にする。 | |
26 | Return | 割り込みプログラム「Timer0_int」ここまで。 | |
27 | |||
28 | Timer1_int: | 割り込みプログラム「Timer1_int」ここから。 | |
29 | Flag1 = 1 | 変数「Flag1」の値を「1」にする。 | |
30 | Return | 割り込みプログラム「Timer1_int」ここまで。 |
Timer0は分周比が1なので262,144μSごとに割り込みが起こります。割り込みが起こると「Timer0_int」が実行されて変数「Flag0」の値が「1」に変わります。これによりメインプログラム内でPB0出力が反転し、圧電スピーカーから1953Hzの音が出ます。一方Timer1は分周比が64なので約4.2秒後に割り込みが起こります。変数「Flag1」が「1」になると、20行目「Loop Until Flag1 = 1」により、Doループを抜けて音が止まります。
次に、ピー音を約0.5秒ごとに断続させるプログラムを作りました。割り込みを許可するときは「Enable 〜」と書きますが、プログラムの途中で割り込みを一時禁止することも可能で、そのときは「Disable 〜」と書きます。条件分岐プログラムの中でこれを使って、Timer0割り込みを禁止したり許可したりします。下記のプログラムでは524,288μSごとにTimer1割り込みが生じ、そのたびに変数「Flag」の値が反転します。「Flag=0」のときはTimer0割り込みが許可されるのでスピーカーが「ピー」と鳴り、「Flag=1」になると割り込みが禁止されるので音が止まります。
プログラムファイル timer2d.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1000000 | クロック周波数を1MHzに設定。 | |
3 | |||
4 | Config Portb = Output | ポートBを出力に設定する。 | |
5 | Config Timer0 = Timer , Prescale = 1 | Timer0をタイマーとして使用。分周比は1。 | |
6 | Config Timer1 = Timer , Prescale = 8 | Timer1をタイマーとして使用。分周比は8。 | |
7 | Dim Flag As Bit | ビット型変数「Flag」を使用する。 | |
8 | On Timer0 Timer0_int | Timer0割り込み時は「Timer0_int」を実行。 | |
9 | On Timer1 Timer1_int | Timer1割り込み時は「Timer1_int」を実行。 | |
10 | Enable Interrupts | 割り込み全般を許可する。 | |
11 | Enable Timer0 | Timer0割り込みを許可する。 | |
12 | Enable Timer1 | Timer1割り込みを許可する。 | |
13 | |||
14 | Do | Doループの範囲ここから。 | |
15 | If Flag = 1 Then | 条件分岐。「Flag=1」ならば、 | |
16 | Disable Timer0 | Timer0割り込みを禁止する。 | |
17 | Else | 「Flag=0」ならば、 | |
18 | Enable Timer0 | Timer0割り込みを許可する。 | |
19 | End If | 条件分岐プログラムここまで。 | |
20 | Loop | Doループの範囲ここまで。14行目へ戻る。 | |
21 | |||
22 | End | メインプログラム終わり。 | |
23 | |||
24 | Timer0_int: | 割り込みプログラム「Timer0_int」ここから。 | |
25 | Toggle Portb.0 | PB0の出力を反転する。 | |
26 | Return | 割り込みプログラム「Timer0_int」ここまで。 | |
27 | |||
28 | Timer1_int: | 割り込みプログラム「Timer1_int」ここから。 | |
29 | Toggle Flag | 変数「Flag」の値を反転する。 | |
30 | Return | 割り込みプログラム「Timer1_int」ここまで。 |
LCDの上の行にTimer1のカウント値、下の行にオーバーフロー回数つまりTimer1が何周したかを表示するプログラムを作りました。「タイマー機能(その1)」の「timer1g.bas」にオーバーフロー回数の表示を追加したものです。マイコン回路は「タイマー機能(その1)」で使用したものと同じです。
プログラムファイル timer2e.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 | Config Timer1 = Timer , Prescale = 1 | Timer1をタイマーとして使用。分周比は1。 | |
9 | Dim Over As Byte | バイト型変数「Over」を使用する。 | |
10 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
11 | |||
12 | On Timer1 Timer1_int | 割り込みが起きたら「Timer1_int」を実行する。 | |
13 | Enable Interrupts | 割り込み全般を許可する。 | |
14 | Enable Timer1 | Timer1割り込みを許可する。 | |
15 | Stop Timer1 | Timer1をストップ。 | |
16 | Timer1 = 1 | Timer1のカウント数を「1」にする。 | |
17 | |||
18 | Do | Doループの範囲ここから。 | |
19 | Start Timer1 | Timer1をスタート。 | |
20 | Waitus 996 | 996μ秒間そのまま。 | |
21 | Stop Timer1 | Timer1をストップ。 | |
22 | Cls | LCDの表示を消去する。 | |
23 | Lcd "TIMER1 " ; Timer1 | LCDに「TIMER1」の文字とカウント数を表示。 | |
24 | Lowerline | LCDの下の行に、 | |
25 | Lcd "OVERFL " ; Over | 「OVERFL」の文字と変数「Over」の値を表示。 | |
26 | Wait 1 | 1秒間そのまま。 | |
27 | Incr Timer1 | Timer1のカウントを「1」進める。 | |
28 | Loop | Doループの範囲ここまで。 | |
29 | |||
30 | End | メインプログラム終わり。 | |
31 | |||
32 | Timer1_int: | 割り込みプログラム「Timer1_int」ここから。 | |
33 | Incr Over | 変数「Over」の値に「1」を加える。 | |
34 | Return | 割り込みプログラム「Timer1_int」ここまで。 |
Timer1割り込みが起きるたび、変数「Over」の値を1ずつ加算します。これがオーバーフローの表示値になります。「timer1g.bas」と同じく、実際のカウント値をそのまま表示しているわけではありませんが、Timer1のカウントのようすをイメージするには便利かと思います。なお、タイマーが1周した後のカウント数は「585, 1585, 2585, ...」となります。「timer1g.bas」のときより120カウントだけ多いのは、割り込みプログラム「Timer1_int」の実行に120サイクル(120μS)要するためです。