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

BASCOM-AVR 音出し(その1)

 ATtiny26Lを使って、音出し(低周波発振回路)の実験をしました。スピーカーから「ピー」という音を出すプログラムです。

1. 音出しプログラムのテスト用回路

 下に今回の実験のためのマイコン回路を示します。PA0(20番ピン)に抵抗と圧電スピーカーを接続します。直列抵抗(R1)は1kΩにしました。これで十分な音量になります。圧電スピーカーの一端は電源のプラスにつないでも同じです。圧電スピーカーの代わりに普通の8Ωのスピーカーをつなぐこともできますが、少し音が小さくなります。

 出力周波数について少し厳密に調べてみたかったので、クロック源には3端子の水晶発振器を用いました。型番はJXO-05、周波数は1MHzです。水晶発振器を使用するときは、マイコンICのヒューズビットを「1110 0000」に書き換える必要があります。

 第1図

2. ポート出力を高速でオンオフする

 音出しプログラムといってすぐに思いつくのが、ポート出力を可聴周波数でオンオフする方法でしょう。LED点滅プログラムの「Wait」時間を短くするやり方です。「Waitus 500」で試してみました。「Waitus」は待ち時間をマイクロ秒単位で設定する命令です。

1 $regfile = "at26def.dat" ATtiny26Lを使用する。
2 $crystal = 1000000 クロック周波数を1MHzに設定。
3
4 Config Porta = Output ポートAを出力に設定。
5
6 Do Doループの範囲ここから。
7  Toggle Porta.0 PA0の出力を反転する。
8  Waitus 500 500μSそのまま。
9 Loop Doループの範囲ここまで。6行目へ戻る。
10
11 End 終わり。

 上記のプログラムを実行すると、スピーカーから「ピー」という音が出ます。周波数を測ってみると969Hzでした。PA0が500マイクロ秒(μS)毎に反転していれば周波数は1000Hzになるはずですが、そうならないのは、「Waitus 500」以外の部分のプログラムの実行にかかる時間を考慮していないからです。プログラムシミュレータで各行の所要サイクル数を調べてみました。

ループ
1回目
ループ
2回目
ループ
3回目
各行の所要
サイクル数
7 Toggle Porta.0 77212881804 14
8 Waitus 500 78613021818 500
9 Loop 128618022318 2
ループを1巡するのに必要なサイクル数516

 タイミングチャートで表わすと下記のようになります。

 第2図

 7行目「Toggle Porta.0」の実行に14サイクル、9行目「Loop」の実行に2サイクルかかります。したがってループを1周するのに516サイクル、クロックが1MHzなので時間に直すと516μSかかります。出力パルスの1周期はループ2周分ですから1032μS、周波数に直すと969Hzとなって、実測値と合います。

 「Toggle Porta.0」と「Loop」の所要サイクル数は「Waitus」の数値にかかわらず常に一定(合わせて16サイクル)でした。ということは、「Waitus」の数値を16減らしてやればいいことになります。出力を1000Hzにしたいときは、500−16=484ですから「Waitus 484」と書けばOKです。このように書き直したプログラムへのリンクを下に掲げておきます。実験の結果、出力周波数はぴったり1000Hzになりました。

 プログラムファイル afosc1a.bas

 周波数F(Hz)から「Waitus」の数値Wを求める式は下記の通りです。いくつかの周波数について「Waitus」の数値を計算した表も添えます。

F(Hz)100250500 100025005000
W49841984984 48418484

 上記の値はクロック周波数が1MHzのときのものです。クロック周波数が変わるとWの値も少し変わります。クロック周波数をC(MHz)とすると、Wの値は次の式で求められます。

 「Wait」や「Waitms」と違って、「Waitus」の後には数字しか入れられないようです。変数は使えませんでした。また、Wの値が1000以上になるときは「Waitms」も使えますが、「Waitus」と「Waitms」では所要サイクル数が違います。上のプログラムに「Waitus 1000」と書くと出力は492Hzですが、「Waitms 1」と書くと480Hzになります。サイクル数を調べた結果、「Waitms W」としたとき、この行の実行に「1005W+20」サイクルかかることがわかりました。

3. 時間を区切って音を出す

 決まった時間だけ音を出すプログラムです。「Do」ループ内に変数を入れてカウントし、時間が来たら「Loop Until 〜 」でループを抜けます。ここでは、1000Hzの音を1秒間出すプログラムを試してみました。

 最初に、下記のプログラムをシミュレーションしてサイクル数を測ってみました。先程の例と同じように、余分のサイクルを「Waitus」の数値から差し引けばよいと考えました。

1 $regfile = "at26def.dat" ATtiny26Lを使用する。
2 $crystal = 1000000 クロック周波数を1MHzに設定。
3
4 Config Porta = Output ポートAを出力に設定。
5 Dim N As Word ワード型変数「N」を使用する。
6
7 Do Doループの範囲ここから。
8  Toggle Porta.0 PA0の出力を反転する。
9  Waitus 500 500μSそのまま。
10  Incr N 変数「N」に「1」を加える。
11 Loop Until N = 2000 Doループの範囲ここまで。7行目へ戻る。
「N」の値が「2000」になったらループを抜ける。
12
13 End 終わり。
ループ
1回目
ループ
2回目
ループ
3回目
各行の所要
サイクル数
8 Toggle Porta.0 77213151858 14
9 Waitus 500 78613291872 500
10 Incr N 128618292372 18
11 Loop Until N=2000 130418472390 11
ループを1巡するのに必要なサイクル数543

 「Do」ループ内各行のサイクル数は上記の通りです。ループ1回あたりの所要サイクル数は543になりました。出力を1000Hzにするためにはループ1周を500サイクルにする必要があります。そこで9行目「Waitus 500」を「Waitus 457」にしてみました。

 ところが、再度シミュレーションしてみたところ、ループ1回あたりのサイクル数が503になりました。調べてみると「Waitus 457」の行に460サイクルかかっています。結果として出力周波数は1000Hzより若干低くなります。なぜ「Waitus」の数値とサイクル数が合わないのでしょう。「Waitus」に457付近のいろいろな数字を入れてサイクル数を測ってみました。

「Waitus」
の数値
「Waitus」行の
サイクル数
ループ1回あたりの
サイクル数
452452495
453456499
454456499
455456499
456456499
457460503
458460503
459460503
460460503
461464507

 「Waitus」行の所要サイクル数が「Waitus」の数値と一致するのは、452, 456, 460など、4の倍数のときだけです。それ以外の値のときは、より大きな数の中で直近の4の倍数と同じサイクル数になります。最初にやったプログラムでは数値がたまたま4の倍数だったのでこのことに気が付きませんでした。BASCOM-AVRのマニュアルには「Waitus命令では正確なタイミングは得られない」と書いてありますが、これ以上はどうにもならないのでしょうか。

 まあこれくらいの誤差は実用上はほとんど問題にならないんですけど、どうせならぴったりにならないものかとプログラムの書き方をいろいろ変えて実験しました。要は「Do」ループ中、「Waitus」以外の部分のサイクル数の合計が4の倍数になればいいわけです。その結果、下記のようなプログラムならうまくいくことがわかりました。

 プログラムファイル afosc1b.bas

1 $regfile = "at26def.dat" ATtiny26Lを使用する。
2 $crystal = 1000000 クロック周波数を1MHzに設定。
3
4 Config Porta = Output ポートAを出力に設定。
5 Dim N As Word ワード型変数「N」を使用する。
6 N = 1 変数「N」の初期値を「1」にする。
7
8 Do Doループの範囲ここから。
9  Toggle Porta.0 PA0の出力を反転する。
10  Waitus 460 460μSそのまま。
11  N = N + 1 変数「N」に「1」を加える。
12 Loop Until N > 2000 Doループの範囲ここまで。8行目へ戻る。
「N」の値が「2000」より大きくなったらループを抜ける。
13
14 End 終わり。

 「Incr N」を「N = N + 1」に変えます。また「N = 2000」を「N > 2000」に変えます。これで「Waitus」以外の部分のサイクル数の合計が40になるので、「Waitus」の数値を40減らしてやれば出力は切りのいい周波数になります。「N > 2000」に変えたことでループ回数が1回増えるので、変数「N」の初期値を「1」にしました(6行目)。各行のサイクル数は下記の通りです。

ループ
1回目
ループ
2回目
ループ
3回目
各行の所要
サイクル数
9 Toggle Porta.0 77912791779 14
10 Waitus 460 79312931793 460
11 N = N + 1 125317532253 14
12 Loop Until N>2000 126717672267 12
ループを1巡するのに必要なサイクル数500

 これでようやく、1000Hzの音を1秒間出すプログラムができました。でも、1秒を正確に測る手段がないので、本当に正しく動作するか確認していません。希望する出力周波数F(Hz)と音を出す時間T(S)から、「Waitus」の数値Wとループ回数Nを求める式は下記のようになります。

 いくつか具体的な数値例を計算してみました。

100Hz250Hz500Hz1000Hz 2500Hz5000Hz
0.1秒W=4960
N=20
W=1960
N=50
W=960
N=100
W=460
N=200
W=160
N=500
W=60
N=1000
0.25秒W=4960
N=50
W=1960
N=125
W=960
N=250
W=460
N=500
W=160
N=1250
W=60
N=2500
0.5秒W=4960
N=100
W=1960
N=250
W=960
N=500
W=460
N=1000
W=160
N=2500
W=60
N=5000
1秒W=4960
N=200
W=1960
N=500
W=960
N=1000
W=460
N=2000
W=160
N=5000
W=60
N=10000
2秒W=4960
N=400
W=1960
N=1000
W=960
N=2000
W=460
N=4000
W=160
N=10000
W=60
N=20000

 上の表はクロック周波数が1MHzのときのものです。クロック周波数が変わると数値も変わります。出力周波数がF(Hz)、音を出す時間がT(S)、クロック周波数がC(MHz)のとき、WおよびNの値は下記の式で求められます。

4. 断続音を発生させる

 1000Hzの音が1秒鳴って1秒止まるという動作を繰り返すプログラムです。1秒間の音出しプログラムをサブルーチンに入れ、メインプログラムで1秒おきに呼び出す形です。先程のプログラムをサブルーチンに入れるとまた周波数がずれます。メインプログラムの「Wait」命令の数値も調整が必要です。でも面倒くさいので、下記のプログラムでは大ざっぱに合わせるにとどめています。シミュレータ上では、周波数が998Hz、音が出ている時間が1.002秒、止まっている時間が1.000035秒という結果でした。

 プログラムファイル afosc1c.bas

1 $regfile = "at26def.dat" ATtiny26Lを使用する。
2 $crystal = 1000000 クロック周波数を1MHzに設定。
3
4 Config Porta = Output ポートAを出力に設定。
5 Declare Sub Afosc サブルーチン「Afosc」を使用する。
6
7 Do Doループの範囲ここから。
8  Call Afosc サブルーチン「Afosc」を呼び出して実行。
9  Waitms 995 995mSそのまま。
10 Loop Doループの範囲ここまで。7行目へ戻る。
11
12 End メインプログラム終わり。
13
14 Sub Afosc サブルーチン「Afosc」ここから。
15  Local N As Word サブルーチン内で変数「N」を使用する。
16  N = 1 変数「N」の初期値を「1」にする。
17  Do Doループの範囲ここから。
18  Toggle Porta.0 PA0の出力を反転する。
19  Waitus 452 452μSそのまま。
20  N = N + 1 変数「N」に「1」を加える。
21  Loop Until N > 2000 Doループの範囲ここまで。17行目へ戻る。
「N」の値が「2000」より大きくなったらループを抜ける。
22 End Sub サブルーチン「Afosc」ここまで。