ブレッドボードラジオAVRマイコン入門

BASCOM-AVR タイマー1比較(その3)

 Timer1のCompare割込みについて実験しました。Timer1のカウントが設定値に達するたびに割り込みが起きます。ここでは1秒ごとに割り込みを発生させて時刻を表示するプログラムを作りました。1秒ごとに割り込みを起こすのなら、何もCompare割込みでなくても普通のオーバーフロー割込みでいいんですが、他に適当な応用例を思いつかないのでここはとりあえず時計にしました。

1. 実験回路

 プログラムのテスト用の回路は下記の通りです。ATtiny2313に表示用のLCDユニットと時刻合わせ用の3つの押しボタンスイッチをつなぎます。クロックは水晶発振器の1.024MHz (ヒューズビットは「1110 0000」) です。LCD画面の表示は「00h00m00s」という形になります。圧電スピーカーはプログラムの動作とは関係ありませんが、OC1端子につないでおけば1秒ごとに「チッ、チッ、チッ、・・・」と鳴るので昔の置時計のような雰囲気が出ます。

 図1

2. 時刻の表示

 まずは普通に時刻だけを表示するプログラムを作りました。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秒ほどの遅れです。しかし、誤差がこのくらいになると水晶発振器の精度自体も問題にしなければならないので、検証は難しいと思います。

3. スイッチで時刻を合わせる

 時計として実用性を持たせるため、スイッチで時刻を合わせるプログラムを考えてみました。上に示した回路図のように、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回ですからいいでしょう。