こんばんは、Reveです。
思ったより長くなりそう(汗)なので前回に引き続き、WAVファイルの「ダヨーン」音声をArduinoで再生する電子工作を取り上げてみます。今回は、プログラム実装の詳細について書いていきます。
【デモ動画】
前と同じry(相変わらず、音量は小さいですorz)
【プログラムの解説】
プログラム全体については、前回の記事を参照していただければと思います。
Arduinoで行っている処理は主に下の通りです。
・タイマー割り込み
・PWM
・SPI(SDカードの読込)
では、詳細についてArduinoの機能などを確かめながら見ていきたいと思います。
・タイマー割り込み
そもそも、マイコンは発振子という部品により一定のタイミングで動作します(メトロノームか置時計の振り子を想像するとわかりやすいかも)。
タイマーはマイコンの動作ごとにその回数をカウントしていき、一定の回数を経過したらあらかじめ登録したイベントを実行することで決められた時間での処理を実現するわけです。
マイコンの中にはいくつかタイマーが用意されているのですが、今回はTimer2を使います。
タイマーを使う際にはイベントや実行するタイミングを設定するわけですが、今回はマイコン(というかコンピュータ)の特性である桁あふれを利用し、これが発生するごとにイベントを実行する手法を採ります。
桁あふれとは、数値が変数(の型)で扱える最大の数を超える現象(ダイヤル式のロックで、暗証番号が決められた範囲でしか設定できないようなもの)です。
タイマーも決められた数までしかカウントできず、それ以上は桁あふれを起こしてしまいますが、今回はそれが起きた瞬間を狙ってイベントを発生させています。
使っているTimer2は8bitのカウンタを持つので 0~255 まで数えられます。
そのため、イベントが起きる周期はクロック周波数が8MHzとして
(イベントの発生間隔): 1 / (8000000 / 256) = 0.000032[秒] = 32[マイクロ秒]
つまり、32マイクロ秒(マイクロは1の百万分の一)毎に処理が行われるわけです。
で、肝心のイベントはプログラムの下のほうにあるISRとついた関数に当たります。
このISRはユーザーが指定した現象(一定時間の経過やスイッチ入力など)が発生することで実行される処理を実装するためのもので、今回は桁あふれのフラグ(TIMER2_OVF_vect)を引数に入れることで桁あふれのイベントとしています。
ISR関数の中では、SDカードから読み込んだ音データをPWMで再生する処理、そして音データのバッファの最後まで到達したら次のバッファを読み込ませるか、あるいは再生終了のためにタイマーを止める処理を実装しています。
タイマーの実行や停止は、TIMSK2レジスタのTOIE2という場所で管理していますが、ここを0にすることで実現します。
なお、タイマーの開始はloop関数の中でTOIE2を1にすることで実行します。
・PWM
こちらもレジスタをいじって初期設定や出力を行っています。
ここでPWMの原理を考えてみると、PWMは疑似的なアナログ出力を、高速でONとOFFの出力を繰り返すこと(スイッチング)で実現します。このONとOFFの割合を調整することで出力の強さを切り替えられます。
ここでONとOFFを切り替えるタイミングや速さをレジスタで設定するのですが、今回はなるべく短い時間でPWM出力を切り替えられるよう、分周なしの高速PWMモードで使用します。
PWMの初期設定はTCCR2AとTCCR2Bで調整します。
この辺りのレジスタは2つ合わせて一つの機能を設定したりと少しややこしい部分もあるのですが、つまりレジスタ内の以下の部分を設定しています。
・COM2B0, 1: PWMの出力設定
・WGM20 ~ 2:PWMモードの設定
・CS20 ~ 2:分周
PWMの出力というのは、簡単に言うと ON->OFF->ON->… か OFF->ON->OFF->… のどちらで設定するかということですが、ここは前者を選んでいます。
PWMモードは高速PWMや位相基準PWMといった種類があるのですが、今回は常に同じタイミングでONとOFFを切り替える高速PWMモードを選びます。
分周はONとOFFを合わせた時間の周期を長くするためのもので、例えると、時計で60秒で1分、60分で1時間など、測る時間の単位を変える様なものです。
実は、PWMも前述したタイマーを利用しているのですが、タイマー割り込みと同じTimer2を利用しています。PWM出力調整は、Timer2のオーバーフロー(桁あふれ)を狙ってタイマー割り込みによって行います。前回の記事でも述べたように、タイマー割り込みは31.25kHzで動作するため、PWMの周期もそれに合わせて分周なし(つまり最速)にしています。
また、PWM出力の強さについてはOCR2Bに0 ~ 255の値を入れることで調整できますが、ここに音のデータを入れることでスピーカーでの音声再生を実現します。
・SPI(SDカードの読込)
SDカードの読み込みはライブラリを使うのですが、そこではSPI通信によりデータの読み書きを行います。
SPI通信は直接使うわけではないので省略しますが、SDライブラリを見てみましょう。
まず、ライブラリの読み込みは #include
SDライブラリを使うために、setup関数で初期化(SD.begin(10))をする必要があります。数値の10は出力ピンを指定しています。
続いて、ファイルを開くときはFile dataFile = SD.open(“ファイル名”)というように、Fileインスタンスに指定のファイルをあたえますが、ここではファイルを見つけるだけのようなものです。
実際に中のデータを読み込む際はread関数を使いますが、引数にデータサイズとデータ格納用の配列を入れることで、データを指定のサイズ分だけまとめて読み込めます。
処理が終了したら、close関数で開いたデータファイルを閉じます。
【結論】
と、ここまで長々とプログラムの解説をしてみました(が、その割にわかりづらかったかと思いますorz)。
レジスタというものとその使い方に慣れていないと難しく感じるかもしれませんが、わかるとArduinoのデフォルトではできない独自の使い方ができたり、AVRマイコンのプログラミングの練習にもなると思いますので、挑戦してみてはいかがでしょうか。
【参考】
(放課後マイコンクラブ様)
SDカードのWAVファイル再生する。 [Arduino]
(うしこblog様)
AVRでのタイマとPWMの使い方
コメント