Timer1のCompare割込みについて実験しました。Timer1のカウントが設定値に達するたびに割り込みが起きます。ここでは1秒ごとに割り込みを発生させて時刻を表示するプログラムを作りました。1秒ごとに割り込みを起こすのなら、何もCompare割込みでなくても普通のオーバーフロー割込みでいいんですが、他に適当な応用例を思いつかないのでここはとりあえず時計にしました。
プログラムのテスト用の回路は下記の通りです。ATtiny2313に表示用のLCDユニットと時刻合わせ用の3つの押しボタンスイッチをつなぎます。クロックは水晶発振器の1.024MHz (ヒューズビットは「1110 0000」) です。LCD画面の表示は「00h00m00s」という形になります。圧電スピーカーはプログラムの動作とは関係ありませんが、OC1端子につないでおけば1秒ごとに「チッ、チッ、チッ、・・・」と鳴るので昔の置時計のような雰囲気が出ます。
まずは普通に時刻だけを表示するプログラムを作りました。Timer1-Compareで1秒ごとに割り込みを発生させ、LCD画面で秒数のカウントを進めます。「秒(s)」が60になると「分(m)」のカウントが1増え、「分(m)」が60になると「時(h)」のカウントが1増えます。「11h59m59s」から1秒たつとすべての数字が「00」に戻ります。
プログラムファイル compare3a.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1024000 | クロック周波数を1.024MHzに設定。 | |
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.4 | LCDの接続設定。DB5=PB2, DB4=PB4。 | |
6 | Config Lcdpin=Pin, E=Portb.5, Rs=Portb.6 | LCDの接続設定。E=PB5, RST=PB6。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
9 | Config Timer1 = Timer , Prescale = 1024 , Compare A = Toggle , Clear Timer = 1 | ||
Timer1をタイマーとして使用。分周比は1024。 | |||
比較A出力はToggle。Clear Timerは1。 | |||
10 | Compare1a = 999 | 比較値を「999」とする。 | |
11 | Config Portb = Output | ポートBを出力に設定する。 | |
12 | |||
13 | On Compare1a Incr_s | Compare1a割込みが起きたら「Incr_s」を実行する。 | |
14 | Enable Interrupts | 割り込み全般を許可する。 | |
15 | Enable Compare1a | Compare1a割込みを許可する。 | |
16 | Dim Flag As Bit | ビット型変数「Flag」を使用する。 | |
17 | Dim S1 As Byte | バイト型変数「S1」を使用する。 | |
18 | Dim M1 As Byte | バイト型変数「M1」を使用する。 | |
19 | Dim H1 As Byte | バイト型変数「H1」を使用する。 | |
20 | Dim S2 As String * 2 | 2文字の変数「S2」を使用する。 | |
21 | Dim M2 As String * 2 | 2文字の変数「M2」を使用する。 | |
22 | Dim H2 As String * 2 | 2文字の変数「H2」を使用する。 | |
23 | |||
24 | Do | Doループの範囲ここから。 | |
25 | If Flag = 1 Then | 条件分岐1。「Flag」が「1」になったら、 | |
26 | Incr S1 | 変数「S1」の値を1増やす。 | |
27 | If S1 = 60 Then | 条件分岐2A。「S1」が60になったら、 | |
28 | S1 = 0 : Incr M1 | 「S1」を0にして「M1」を1増やす。 | |
29 | End If | 条件分岐2Aここまで。 | |
30 | If M1 = 60 Then | 条件分岐2B。「M1」が60になったら、 | |
31 | M1 = 0 : Incr H1 | 「M1」を0にして「H1」を1増やす。 | |
32 | End If | 条件分岐2Bここまで。 | |
33 | If H1 = 12 Then H1 = 0 | 条件分岐2C。「H1」が12になったら「H1」を0にする。 | |
34 | S2 = Str(s1) | 「S1」を文字変数「S2」に変換する。 | |
35 | S2 = Format(s2 , "00") | 「S2」を「00」の形にする。 | |
36 | M2 = Str(m1) | 「M1」を文字変数「M2」に変換する。 | |
37 | M2 = Format(m2 , "00") | 「M2」を「00」の形にする。 | |
38 | H2 = Str(h1) | 「H1」を文字変数「H2」に変換する。 | |
39 | H2 = Format(h2 , "00") | 「H2」を「00」の形にする。 | |
40 | Cls | LCDの表示を消去する。 | |
41 | Lcd "TIME " ; H2 ; "h" | LCDに「TIME」の文字と「H2」の値を表示する。 | |
42 | Lcd M2 ; "m" ; S2 ; "s" | LCDに「M2」と「S2」の値を表示する。 | |
43 | Flag = 0 | 変数「Flag」の値を「0」に戻す。 | |
44 | End If | 条件分岐1ここまで。 | |
45 | Loop | Doループの範囲ここまで。 | |
46 | |||
47 | End | メインプログラム終わり。 | |
48 | |||
49 | Incr_s: | サブルーチン「Incr_s」ここから。 | |
50 | Flag = 1 | 「Flag」の値を「1」にする。 | |
51 | Return | サブルーチン「Incr_s」ここまで。 |
マイコンICのクロック周波数は1.024MHz、Timer1の分周比は1024なので、Timer1の1カウントは1mSです。Compare割込みは「Compare1a値+1」ミリ秒ごとに起きるので、10行目でCompare1a値(比較値)を「999」に設定しました。これで1秒ごとに割り込みが起きます。割り込みが起きると49行目からのサブルーチン「Incr_s」で変数「Flag」の値が「0」から「1」に変わります。
「Flag」が「1」になると、24行目からのメインプログラムが実行されます。ここではまず秒数のカウントをアップし、必要ならば分と時のカウントも進めます。続いて各変数値を2桁の文字変数に変換してLCD画面に表示します。この部分は「LCDの表示(その5)」の「lcd5c.bas」と同じです。表示が終わったら変数「Flag」の値を「0」に戻します。
数時間動作させて本物の時計と比べてみましたが、ほぼ正確に計時しているように見えます。ただ、プログラムシミュレータで調べると、割り込みプログラム「Incr_s」の実行に120サイクルを要します。クロックが1.024MHzですから時間にして約117μSですが、毎秒この分だけ遅れが出るような気もします。24時間で10秒ほどの遅れです。しかし、誤差がこのくらいになると水晶発振器の精度自体も問題にしなければならないので、検証は難しいと思います。
時計として実用性を持たせるため、スイッチで時刻を合わせるプログラムを考えてみました。上に示した回路図のように、ICのINT0に「秒」合わせ、INT1に「分」合わせ、PB4に「時」合わせ用の押しボタンスイッチをつなぎます。
プログラムファイル compare3b.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1024000 | クロック周波数を1.024MHzに設定。 | |
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.4 | LCDの接続設定。DB5=PB2, DB4=PB4。 | |
6 | Config Lcdpin=Pin, E=Portb.5, Rs=Portb.6 | LCDの接続設定。E=PB5, RST=PB6。 | |
7 | Config Lcd = 16 * 2 | 16文字×2行表示のLCDを使用する。 | |
8 | Cls : Cursor Off | LCDの表示を消去。カーソルを消去。 | |
9 | Config Timer1 = Timer , Prescale = 1024 , Compare A = Toggle , Clear Timer = 1 | ||
Timer1をタイマーとして使用。分周比は1024。 | |||
比較A出力はToggle。Clear Timerは1。 | |||
10 | Compare1a = 999 | 比較値を「999」とする。 | |
11 | Config Portb = Output | ポートBを出力に設定する。 | |
12 | Config Portd = Input | ポートDを入力に設定する。 | |
13 | Portd = 127 | PD0〜PD6をプルアップする。 | |
14 | |||
15 | On Compare1a Incr_s | Compare1a割込みが起きたら「Incr_s」を実行する。 | |
16 | On Int0 Adj_s | INT0割込みが起きたら「Adj_s」を実行する。 | |
17 | On Int1 Adj_m | INT1割込みが起きたら「Adj_m」を実行する。 | |
18 | Declare Sub Adj_h | サブルーチン「Adj_h」を使用する。 | |
19 | Declare Sub Lcdisplay | サブルーチン「Lcdisplay」を使用する。 | |
20 | Enable Interrupts | 割り込み全般を許可する。 | |
21 | Enable Compare1a | Compare1a割込みを許可する。 | |
22 | Enable INT0 | INT0割込みを許可する。 | |
23 | Enable INT1 | INT1割込みを許可する。 | |
24 | Dim Flag As Bit | ビット型変数「Flag」を使用する。 | |
25 | Dim S1 As Byte | バイト型変数「S1」を使用する。 | |
26 | Dim M1 As Byte | バイト型変数「M1」を使用する。 | |
27 | Dim H1 As Byte | バイト型変数「H1」を使用する。 | |
28 | Dim S2 As String * 2 | 2文字の変数「S2」を使用する。 | |
29 | Dim M2 As String * 2 | 2文字の変数「M2」を使用する。 | |
30 | Dim H2 As String * 2 | 2文字の変数「H2」を使用する。 | |
31 | |||
32 | Do | Doループの範囲ここから。 | |
33 | If Flag = 1 Then | 条件分岐。「Flag」が「1」になったら、 | |
34 | Incr S1 | 変数「S1」の値を1増やす。 | |
35 | Call Lcdisplay | サブルーチン「Lcdisplay」へジャンプ。 | |
36 | Flag = 0 | 変数「Flag」の値を「0」に戻す。 | |
37 | End If | 条件分岐ここまで。 | |
38 | Debounce Pind.4 , 0 , Adj_h , Sub | 「Pind.4=0」になったら「Adj_h」を実行する。 | |
39 | Loop | Doループの範囲ここまで。 | |
40 | |||
41 | End | メインプログラム終わり。 | |
42 | |||
43 | Incr_s: | サブルーチン「Incr_s」ここから。 | |
(Compare1a割込みプログラム。) | |||
44 | Flag = 1 | 「Flag」の値を「1」にする。 | |
45 | Return | サブルーチン「Incr_s」ここまで。 | |
46 | |||
47 | Adj_s: | サブルーチン「Adj_s」ここから。 | |
(INT0割込みプログラム。「秒」合わせ。) | |||
48 | S1 = 0 | 「S1」を0にする。 | |
49 | Timer1 = 0 | Timer1のカウントを0にする。 | |
50 | Call Lcdisplay | サブルーチン「Lcdisplay」へジャンプ。 | |
51 | Waitms 250 | 250ミリ秒そのまま。 | |
52 | Return | サブルーチン「Adj_s」ここまで。 | |
53 | |||
54 | Adj_m: | サブルーチン「Adj_m」ここから。 | |
(INT1割込みプログラム。「分」合わせ。) | |||
55 | Incr M1 | 「M1」を1増やす。 | |
56 | Call Lcdisplay | サブルーチン「Lcdisplay」へジャンプ。 | |
57 | Waitms 250 | 250ミリ秒そのまま。 | |
58 | Return | サブルーチン「Adj_m」ここまで。 | |
59 | |||
60 | Sub Adj_h | サブルーチン「Adj_h」ここから。 | |
(入力スイッチプログラム。「時」合わせ。) | |||
61 | Incr H1 | 「H1」を1増やす。 | |
62 | Call Lcdisplay | サブルーチン「Lcdisplay」へジャンプ。 | |
63 | End Sub | サブルーチン「Adj_h」ここまで。 | |
64 | |||
65 | Sub Lcdisplay | サブルーチン「Lcdisplay」ここから。 | |
(カウントの繰上げとLCDへの表示。) | |||
66 | If S1 = 60 Then | 条件分岐1。「S1」が60になったら、 | |
67 | S1 = 0 : Incr M1 | 「S1」を0にして「M1」を1増やす。 | |
68 | End If | 条件分岐1ここまで。 | |
69 | If M1 = 60 Then | 条件分岐2。「M1」が60になったら、 | |
70 | M1 = 0 : Incr H1 | 「M1」を0にして「H1」を1増やす。 | |
71 | End If | 条件分岐2ここまで。 | |
72 | If H1 = 12 Then H1 = 0 | 条件分岐3。「H1」が12になったら「H1」を0にする。 | |
73 | S2 = Str(s1) | 「S1」を文字変数「S2」に変換する。 | |
74 | S2 = Format(s2 , "00") | 「S2」を「00」の形にする。 | |
75 | M2 = Str(m1) | 「M1」を文字変数「M2」に変換する。 | |
76 | M2 = Format(m2 , "00") | 「M2」を「00」の形にする。 | |
77 | H2 = Str(h1) | 「H1」を文字変数「H2」に変換する。 | |
78 | H2 = Format(h2 , "00") | 「H2」を「00」の形にする。 | |
79 | Cls | LCDの表示を消去する。 | |
80 | Lcd "TIME " ; H2 ; "h" | LCDに「TIME」の文字と「H2」の値を表示する。 | |
81 | Lcd M2 ; "m" ; S2 ; "s" | LCDに「M2」と「S2」の値を表示する。 | |
82 | End Sub | サブルーチン「Lcdisplay」ここまで。 |
先の時計プログラムに時分秒を合わせるプログラムが加わるので、ずいぶん長くなってしまいました。前のプログラムでは秒数のカウントアップとLCDへの表示をメインプログラムに入れましたが、今回は独立したサブルーチンで処理しています。これは、時刻合わせスイッチの動作をその都度LCD画面で表示したかったからです。スイッチが押されるたび65行目からのサブルーチン「Lcdisplay」が呼び出されてLCD画面の数字が動きます。これをメインプログラムに入れると、表示が1秒おきにしか変わらないのでスムーズな時刻あわせができません。
47行目のサブルーチン「Adj_s」は「秒」を合わせるプログラムです。スイッチS1をチョンと押すとINT0割込みが起きて変数「S1(秒)」の値が0になります。同時にTimer1のカウントもクリアされます。スイッチS1を長押しすると、押している間じゅう「秒」は0のままです。ラジオの時報などで時刻を合わせるときは、正時少し前にスイッチS1を押してスタンバイし、時報が鳴ると同時に押していたスイッチを放せばOKです。
54行目のサブルーチン「Adj_m」は「分」を合わせるプログラムです。スイッチS2をチョンと押すとINT1割込みが起きて変数「M1(分)」の値が1増えます。スイッチを長押しすると250ミリ秒ごとに連続してカウントアップします。
60行目のサブルーチン「Adj_h」は「時」を合わせるプログラムです。外部割込みは2つしかないので、スイッチS3の状態はメインプログラム内で監視しています。スイッチが押されると変数「H1(時)」の値が1増えます。「分」のときと違って、スイッチの長押しには反応しません。数字をいくつも進めるときはその回数分だけ押し直す必要があります。まあ、最大でも11回ですからいいでしょう。