Timer1のPWM機能を使って、LEDの明るさをコントロールするプログラムを試してみました。マイコンICはATtiny2313を用いました。
プログラムのテスト用回路は下記の通りです。複雑そうに見えますが、パルス幅の測定用にもう1個ICを使っているためです。IC1がPWMプログラムを書き込むICで、IC2はパルス幅測定用のICです。
IC2には「パルス周期の測定(その2)」でやったパルス幅測定プログラム「pwidth2b.bas」を書き込んでおきます。IC1にはロータリーエンコーダとLEDをつなぎます。PWM出力は「OC1」という端子から出力されます。ATtiny2313の場合は15番ピン(PB3)がそれにあたります。ここにLEDとIC2の入力(INT0とINT1)を接続します。2個のICのクロック源はともに1.024MHzの水晶発振器です。クロック源を同じにしておけばプログラムの検証に好都合です。
ブレッドボード上の配線図と試作写真を下に掲げます。
BASCOM-AVRのマニュアルを見てPWMプログラムの書式だけはわかりましたが、具体的にどう書けばどういう動作になるのかがわかりません。とりあえず簡単なのをひとつ書いて動かしてみました。実質6行の短いプログラムです。
プログラムファイル pwm2a.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1024000 | クロック周波数を1.024MHzに設定。 | |
3 | |||
4 | Config Portb = Output | ポートBを出力に設定する。 | |
5 | Config Timer1=Pwm, Prescale=1024, Compare A Pwm=Clear Down, Pwm=10 | ||
Timer1をPWMとして使用する。 | |||
分周比1024, Clear Down, 10ビット。 | |||
6 | Pwm1a = 512 | 「Timer1=512」のとき出力を反転する。 | |
7 | |||
8 | End | 終わり。 |
上記のプログラムを実行すると、LEDが約2秒周期で点滅しました。1秒点灯、1秒消灯を繰り返します。パルス幅の測定結果は、「T1」つまり出力パルスが「1」レベルになっている時間が1024mS、「T0」つまり出力パルスが「0」レベルになっている時間が1022mSでした。下の図のような感じです。
5行目の長いプログラムがPWMの基本設定です。「Config Timer1 = Pwm」はTimer1をPWMとして使用するという意味で、「Prescale = 1024」はその分周比です。1.024MHzのクロックを1024分周すると1kHzですから、Timer1の1カウントあたりの時間は1mSになります。
次の「Compare A Pwm = Clear Down」というのはTimer1のカウントとPWM出力(OC1)の関係に関する設定で、「Clear Down」または「Clear Up」のどちらかを選択します。最後の「Pwm =10」はPWMの周期(ビット)に関する設定で、8, 9, 10のいずれかを選択します。これら2つはよくわからないので、後述のようにいろいろプログラムを変えて実験してみました。6行目「Pwm1a = 512」はPWMの「しきい値」を設定するもので、Timer1のカウントがこの値になったときに出力が反転します。
なお、5行目だけが横に長くて見づらいですが、分けて書くとエラーになりますので仕方ありません。また、「Compare A Pwm」や「Pwm1a」の「A」はAVRマイコンのPWM機能の種別を表わしています。と言ってもATtiny2313には「A」しかないようです。ICによっては「PWM A」と「PWM B」の2つを持っているものがあります。「Pwm1a」の「1」は「Timer1」の「1」と同じ意味だと思います。それから、このプログラムには「Do〜Loop」は必要ありません。「Do〜Loop」を書いても書かなくても動作は同じです。
まず、「Compare A Pwm = Clear Down」と「Compare A Pwm = Clear Up」の違いについて調べてみました。「Clear Down」と「Clear Up」のそれぞれの場合で、プログラム6行目を「Pwm1a = 512」、「Pwm1a = 256」、「Pwm1a = 768」としたときのT1とT0の時間を測定しました。分周比は1024、「Pwm」値は「10」で固定です。「Pwm1a」の値を256, 512, 768にしたのは、「Pwm=10」なので、10ビットすなわち1024に関係ある数字がいいだろうと思ったからです。結果は下記の通りです。
Compare A Pwm | Clear Down | Clear Up | ||
T1 | T0 | T1 | T0 | |
Pwm1a = 512 | 1024mS | 1022mS | 1022mS | 1024mS |
Pwm1a = 256 | 512mS | 1534mS | 1534mS | 512mS |
Pwm1a = 768 | 1536mS | 510mS | 510mS | 1536mS |
これを見てわかることは、まず1周期(T1+T0)はいずれも2046mSです。1024mSではなく、その2倍の2048mSとも少し違います。また、「Clear Down」と「Clear Up」では「T1」と「T0」の時間が逆転します。「Pwm1a」の値については、「Clear Down」の場合は「T1」の時間が「Pwm1a」値に比例し、「Clear Up」の場合は「T0」の時間が「Pwm1a」値に比例しています。
この結果をどう見たらいいのでしょうか。何かヒントはないかとネットで探したら「HERO's download」のATtiny2313のデータシートで下のような図を見つけました。
この図の意味やデータシートに書いてある説明は、私にはさっぱりわかりません。でも、Timer1のカウントがこういうふうになるのなら、上の実験結果を説明できそうです。なので強引にこの図に当てはめてみました。
何の根拠もありませんが、上の図が正しいと仮定して話を続けます。「Pwm=10」とした場合、Timer1は0からカウントを始め、1023までいったらそこで折り返して今度はカウントダウンします。したがって、1カウントが1mSなら1周期(T1+T0)は2046mSになります。
「Compare A Pwm = Clear Down」と書くと、Timer1がカウントアップしていって「Pwm1a」の設定値に達するとそこで出力が「1」から「0」に変わります。1023まで数えてカウントダウンしてくる際は「Pwm1a」値のところで出力が「0」から「1」に変わります。これの繰り返しでOC1端子には一定のデューティー比のパルス波が出力されます。「Compare A Pwm = Clear Up」と書いたときは出力反転のタイミングが逆になります。
「Pwm1a=512」のとき、「T1」と「T0」の時間が同じにならないのは、Timer1のカウントの最大値が1023だからです。0から512までは512カウント(512mS)ですが、512から1023までは511カウント(511mS)です。したがって「Clear Down」の場合は「T0」が「T1」より2mS短くなります。ちなみに「Clear Down, Pwm1a=511」にすると、T1=1022mS、T0=1024mSになります。
次に、「Pwm=8」,「Pwm=9」,「Pwm=10」の違いを調べました。「Pwm1a」の値は、それぞれの場合のTimer1カウントの「最大値+1」の半分としました。他の条件は、分周比1024および「Compare A Pwm = Clear Down」で同じです。「Pwm」値が1小さくなると周期が約半分になります。
Pwm | Pwm1a | T1 | T0 |
10 | 512 | 1024mS | 1022mS |
9 | 256 | 512mS | 510mS |
8 | 128 | 256mS | 254mS |
「Pwm」値と「Pwm1a」値を固定しておいてTimer1の分周比を変えると下記のようになります。細かい数字は計算で求めたものです。出力周波数は分周比に反比例します。
分周比 | T1 | T0 | 周波数 |
1024 | 1024mS | 1022mS | 0.49Hz |
256 | 256mS | 255.5mS | 1.96Hz |
64 | 64mS | 63.9mS | 7.82Hz |
8 | 8mS | 7.98mS | 62.6Hz |
1 | 1mS | 0.998mS | 500.5Hz |
LEDが1秒ごとにだんだん明るくなる動作を繰り返すプログラムです。「その1」の「pwm1a.bas」と同じ動作です。「Prescale = 1」、「Pwm=9」とし、「Pwm1a」の値を「0」から「102」「204」「306」「408」「510」と、102ずつ6段階で増やしていきます。
プログラムファイル pwm2b.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1024000 | クロック周波数を1.024MHzに設定。 | |
3 | |||
4 | Config Portb = Output | ポートBを出力に設定する。 | |
5 | Config Timer1=Pwm, Prescale=1, Compare A Pwm=Clear Down, Pwm=9 | ||
PWMの設定。分周比1, Clear Down, 9ビット。 | |||
6 | Dim P As Word | ワード型変数「P」を使用する。 | |
7 | |||
8 | Do | Doループここから。 | |
9 | For P = 0 To 510 Step 102 | Forループ。6回繰り返し。 | |
10 | Pwm1a = P | 変数「P」の値を「Pwm1a」の値にする。 | |
11 | Wait 1 | 1秒間そのまま。 | |
12 | Next P | Forループここまで。 | |
13 | Loop | Doループここまで。 | |
14 | |||
15 | End | 終わり。 |
ロータリーエンコーダのツマミを回すとLEDの明るさが変化するプログラムです。ロータリーエンコーダを右へ回すと1クリックごとに「Pwm1a」の値が102ずつ増えてLEDが明るさを増します。右へ回すと「Pwm1a」の値が102ずつ減ってLEDが暗くなります。
(追記 : 2008年2月15日)
下記プログラムのファイル名を間違えていたので訂正しました。ご指摘くださったNさん、ありがとうございます。
(追記ここまで)
プログラムファイル pwm2c.bas
1 | $regfile = "attiny2313.dat" | ATtiny2313を使用する。 | |
2 | $crystal = 1024000 | クロック周波数を1.024MHzに設定。 | |
3 | |||
4 | Config Timer1=Pwm, Prescale=1, Compare A Pwm=Clear Down, Pwm=9 | ||
PWMの設定。分周比1, Clear Down, 9ビット。 | |||
5 | Config Debounce = 1 | デバウンス時間を1ミリ秒とする。 | |
6 | Config Portb = Output | ポートBを出力に設定する。 | |
7 | Config Portd = Input | ポートDを入力に設定する。 | |
8 | Portd = 127 | PD0〜PD6をプルアップする。 | |
9 | |||
10 | Declare Sub Encode | サブルーチン「Encode」を使用する。 | |
11 | Dim P As Integer | インテジャー型変数「P」を使用する。 | |
12 | A Alias Pind.2 | PD2入力を「A」という名前にする。 | |
13 | B Alias Pind.3 | PD3入力を「B」という名前にする。 | |
14 | P = 306 | 変数「P」の初期値を「306」とする。 | |
15 | |||
16 | Do | Doループここから。 | |
17 | Debounce A , 0 , Encode , Sub | 「A(Pind.2)=0」になったら、 | |
サブルーチン「Encode」へジャンプ。 | |||
18 | Pwm1a = P | 変数「P」の値を「Pwm1a」の値にする。 | |
19 | Loop | Doループここまで。 | |
20 | |||
21 | End | メインプログラム終わり。 | |
22 | |||
23 | Sub Encode | サブルーチン「Encode」ここから。 | |
24 | If B = 1 Then | 条件分岐1。「B(Pind.3)=1」(右回転)ならば、 | |
25 | P = P + 102 | 変数「P」に102を加える。LEDが明るくなる。 | |
26 | Else | 「B(Pind.3)=0」(左回転)ならば、 | |
27 | P = P - 102 | 変数「P」から102を減じる。LEDが暗くなる。 | |
28 | End If | 条件分岐1ここまで。 | |
29 | If P > 510 Then P = 510 | 条件分岐2。「 P > 510」なら「P=510」とする。 | |
30 | If P < 0 Then P = 0 | 条件分岐3。「 P < 0」なら「P=0」とする。 | |
31 | End Sub | サブルーチン「Encode」ここまで。 |