初めに
Arduinoで音を出そうとしたとき、Tone()関数を使うと思います。
しかし、この関数は1つの音しか出すことができません。
そこで数回に分けて、ライブラリ等を使用しないで複数の音を出力する方法を示したいと思います。
原理
Tone()関数は、Arduinoデバイスのタイマを使用し音を出しています。
タイマのカウント数を変更することで、オーバーフローする周期を変更しています。
Arduinoでの実装の詳細はこちらを参考にしています
Arduino 日本語リファレンス Tone(pin,freaquency)
Arduino 内部構造 tone()
周波数の計算方法
Freaq[Hz]=\frac{f_{clk_{I/O}}}{2 \times Count\times prescalar}=\frac{16[MHz]}{2\times (0~255)\times (2,8,32,64,128,256,1024)}\\
実際には
Count = OCRXA (X=0,1,2)\\
となります。
試しに、TIMER2を使ってTone()もどきを作ってみましょう
#define DIV_1 1 //分周無し
#define DIV_8 2 //8分周
#define DIV_32 3 //32分周
#define DIV_64 4 //64分周
#define DIV_128 5 //128分周
#define DIV_256 6 //256分周
#define DIV_1024 7 //1024分周
#define WGM21 0b10 //タイマ2 動作モード設定
#define TOIE2 0b10 //割り込み許可
uint8_t output_pin = 13;
void setup() {
//初期化関数
pinMode(output_pin, OUTPUT); //13を出力に設定
//タイマの設定
TCCR2A = WGM21;//CTCモード 比較一致OCR2A
TCCR2B = DIV_128;
OCR2A = 141;//440Hz
TIMSK2 = TOIE2;
sei();//割り込み許可
}
void loop() {
}
ISR(TIMER2_COMPA_vect) {
//割り込み時に実行される関数
digitalWrite(output_pin, !digitalRead(output_pin));
}
さて、こちらのコードをArduino(UNO,MEGA)に書き込むとD13ピンから矩形波が440Hz(A4:ラ)で出力されます。
オシロスコープがある方は、波形を確認してみてください。
ない方は、1kΩくらいの抵抗を付けてスピーカーにつなげてみてください。
絶対音感がある方ならそこで、音階がわかると思います。
ない方は、スマートフォンのチューニングアプリで確認してみてください。(おすすめ)
###計算式から結果を検討する
式に今回の条件を代入すると、
Freaq[Hz]=\frac{f_{clk_{I/O}}}
{2 \times Count\times prescalar}=\frac{16[MHz]}{2 \times (141+1)\times 128}=440.14[Hz] \\
となります。出力結果は計算通りですね。
注記
141に1を足しているのは、0もカウントとされるためです。
配列の1番目が[0]になるようなものです
任意の周波数fを出力するOCR2A出したい場合は、式を変形して
\begin{align}
Freaq[Hz]&=\frac{f_{clk_{I/O}}}
{2 \times Count\times (OCR2A+1)} \\
Freaq[Hz]\times (OCR2A+1)&=\frac{f_{clk_{I/O}} }
{2 \times Count\times (OCR2A+1)}\times (OCR2A+1) \\
Freaq[Hz]\times (OCR2A+1)&=\frac{f_{clk_{I/O}} }
{2 \times Count}\\
Freaq[Hz]\times (OCR2A+1) \div Freaq[Hz]&=\frac{f_{clk_{I/O}} }
{2 \times Count}\div Freaq[Hz]\\
OCR2A+1&=\frac{f_{clk_{I/O}} }
{2 \times Count \times Freaq[Hz]}\\
OCR2A+1&=\frac{f_{clk_{I/O}} }
{2 \times Count \times Freaq[Hz]}-1\\
\end{align}
となります。
余談
マークダウン書式の練習で細かく書いています。
先ほどの、440Hzの時は
\begin{align}
OCR2A&=\frac{f_{clk_{I/O}} }
{2 \times Count \times Freaq[Hz]}-1\\
OCR2A&=\frac{{16 \times 10^6 } }
{2 \times 128 \times 440[Hz]}-1\\
OCR2A&=142-1\\
&=141\\
\end{align}
となります。ちなみに、このタイマの使い方をCTCモードといいます
大変参考になるページ
始める電子回路様
http://startelc.com/AVR/Avr_100timrMemo.html
本記事では、AVRのレジスタの説明は行わない予定ですので他サイト様をご参照ください
余談
タイマ割り込みで有名な
MsTimer2もこの方法で割り込み周期を計算しています。
ISR内
ISR(ベクタ名)は、各割り込みが起きたときに実行される関数です。
今回は、タイマ2のOCR2Aコンペアマッチなので、TIMER2_COMPA_vectとしています。
digitalWrite(output_pin, !digitalRead(output_pin));
ここでは、D13の状態を確認しそれを反転(!)しています。
省略せずに書くと
int pin_state = digitalRead(output_pin);
pin_state = !pin_state;
digitalWrite(output_pin,pin_state);
となります。
まとめ
その1では、Tone()関数を自前で作ってみるところでおしまいにしようとおもいます。
OCR2Aや分周数を変更して、出力される周波数を変更するなど遊んでみてください。
その2では、和音の出力に入りたいと思います。