ATtiny26Lを使って、音出し(低周波発振回路)の実験をしました。スピーカーから「ピー」という音を出すプログラムです。
下に今回の実験のためのマイコン回路を示します。PA0(20番ピン)に抵抗と圧電スピーカーを接続します。直列抵抗(R1)は1kΩにしました。これで十分な音量になります。圧電スピーカーの一端は電源のプラスにつないでも同じです。圧電スピーカーの代わりに普通の8Ωのスピーカーをつなぐこともできますが、少し音が小さくなります。
出力周波数について少し厳密に調べてみたかったので、クロック源には3端子の水晶発振器を用いました。型番はJXO-05、周波数は1MHzです。水晶発振器を使用するときは、マイコンICのヒューズビットを「1110 0000」に書き換える必要があります。
音出しプログラムといってすぐに思いつくのが、ポート出力を可聴周波数でオンオフする方法でしょう。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 | 772 | 1288 | 1804 | 14 |
8 Waitus 500 | 786 | 1302 | 1818 | 500 |
9 Loop | 1286 | 1802 | 2318 | 2 |
ループを1巡するのに必要なサイクル数 | 516 |
タイミングチャートで表わすと下記のようになります。
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) | 100 | 250 | 500 | 1000 | 2500 | 5000 |
W | 4984 | 1984 | 984 | 484 | 184 | 84 |
上記の値はクロック周波数が1MHzのときのものです。クロック周波数が変わるとWの値も少し変わります。クロック周波数をC(MHz)とすると、Wの値は次の式で求められます。
「Wait」や「Waitms」と違って、「Waitus」の後には数字しか入れられないようです。変数は使えませんでした。また、Wの値が1000以上になるときは「Waitms」も使えますが、「Waitus」と「Waitms」では所要サイクル数が違います。上のプログラムに「Waitus 1000」と書くと出力は492Hzですが、「Waitms 1」と書くと480Hzになります。サイクル数を調べた結果、「Waitms W」としたとき、この行の実行に「1005W+20」サイクルかかることがわかりました。
決まった時間だけ音を出すプログラムです。「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 | 772 | 1315 | 1858 | 14 |
9 Waitus 500 | 786 | 1329 | 1872 | 500 |
10 Incr N | 1286 | 1829 | 2372 | 18 |
11 Loop Until N=2000 | 1304 | 1847 | 2390 | 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回あたりの サイクル数 |
452 | 452 | 495 |
453 | 456 | 499 |
454 | 456 | 499 |
455 | 456 | 499 |
456 | 456 | 499 |
457 | 460 | 503 |
458 | 460 | 503 |
459 | 460 | 503 |
460 | 460 | 503 |
461 | 464 | 507 |
「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 | 779 | 1279 | 1779 | 14 |
10 Waitus 460 | 793 | 1293 | 1793 | 460 |
11 N = N + 1 | 1253 | 1753 | 2253 | 14 |
12 Loop Until N>2000 | 1267 | 1767 | 2267 | 12 |
ループを1巡するのに必要なサイクル数 | 500 |
これでようやく、1000Hzの音を1秒間出すプログラムができました。でも、1秒を正確に測る手段がないので、本当に正しく動作するか確認していません。希望する出力周波数F(Hz)と音を出す時間T(S)から、「Waitus」の数値Wとループ回数Nを求める式は下記のようになります。
いくつか具体的な数値例を計算してみました。
100Hz | 250Hz | 500Hz | 1000Hz | 2500Hz | 5000Hz | |
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の値は下記の式で求められます。
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」ここまで。 |