Arduino Leonardo(CPUはATmega32U4)で、tone関数を使わずにBEEP音を出すプログラムを作ってみたいと思います。実験には株式会社CRETARIAが販売をしています二足歩行ロボットQumcumを利用することで、特に配線などをせずそのままUSBで接続してプログラミングを行います。
つくるもの
440Hzの音を1秒間ずつ鳴らしてみます。
処理はtoneもdelayも使わずに、タイマー割り込みと直接レジスターを制御するコードを書いてみます。プログラムでは、音を1秒間鳴らし、その後1秒間停止するというパターンを繰り返す動作のために、2つのタイマー(タイマー3とタイマー1)を使用して実現します。
コード
#include <avr/interrupt.h>
volatile bool soundOn = false; // 音をON/OFFするためのフラグ
void setup() {
// ピン12を出力として設定(PORTDの6番ビット)
DDRD |= (1 << 6);
noInterrupts(); // 割り込みを無効化
// タイマー3の設定(440Hz音を生成するためのタイマー)
TCCR3A = 0; // タイマー/カウンター3を正常モードで設定
TCCR3B = 0; // 初期化
TCNT3 = 0; // タイマー3のカウンタ値を0にリセット
OCR3A = 2272; // 比較一致Aの値を設定 (440Hzでトグル)
TCCR3B |= (1 << WGM32); // CTCモード
TCCR3B |= (1 << CS31); // プリスケーラを8に設定
TIMSK3 |= (1 << OCIE3A); // タイマー比較Aの割り込みを有効化
// タイマー1の設定(1秒音のON/OFFを制御するためのタイマー)
TCCR1A = 0; // タイマー/カウンター1を正常モードで設定
TCCR1B = 0; // 初期化
TCNT1 = 0; // タイマー1のカウンタ値を0にリセット
OCR1A = 15624; // 1秒間隔で割り込みが発生するように設定
TCCR1B |= (1 << WGM12); // CTCモード
TCCR1B |= (1 << CS12) | (1 << CS10); // プリスケーラを1024に設定
TIMSK1 |= (1 << OCIE1A); // タイマー比較Aの割り込みを有効化
interrupts(); // 割り込みを有効化
}
//440Hzを発信する割り込み
ISR(TIMER3_COMPA_vect) {
if (soundOn) {
// ピン12の状態を直接トグル (PORTDの6番ビット)
PORTD ^= (1 << 6);
}
}
//1秒の割り込み
ISR(TIMER1_COMPA_vect) {
// 音の状態をトグル
soundOn = !soundOn;
if (!soundOn) {
// 音を停止する時はピンをLOWに設定
PORTD &= ~(1 << 6);
}
}
void loop() {
// ここには何も書かない
}
説明
音の周波数を作るためと、1秒間隔の時間を作るために2つのタイマーを使って制御してみます。
タイマー3を使って440Hzを作ります
まず、CPUの動作周波数16MHzから440Hzのトグルを発生させるためにタイマー3をCTC(Clear Timer on Compare)モードに設定し、440HzでトグルするためにOCR3A(Output Compare Register 3A)にある値を設定します。
この値を得るために、プリスケーラ(デジタル電子回路においてクロック信号の周波数を分割するために用いられる装置や機能)を8に設定し値を求めていきましょう。
計算1.タイマークロック周波数を計算
プリスケーラを8に設定しましたので、タイマークロックは 16,000,000 Hz / 8 = 2,000,000 Hzと求めました。
計算2.OCR3Aの440Hzの値を計算する
出力周波数を440Hzとするため、1秒間に440回トグルさせる必要があります。このトグルのために、OCR3A(Output Compare Register 3A)にセットする値を求めます。トグルはHiとLowのエッジで構成されるので2倍(周期半分)となるため 、先ほど求めた 2,000,000 Hz / (440 Hz * 2)= 2272を算出しました。
これらの値をタイマー3に設定します。
// タイマー3の設定(音を生成するためのタイマー)
noInterrupts(); // 割り込みを無効化
TCCR3A = 0; // タイマー/カウンター3を正常モードで設定
TCCR3B = 0; // 初期化
TCNT3 = 0; // タイマー3のカウンタ値を0にリセット
OCR3A = 2272; // 比較一致Aの値を設定 (440Hzでトグル)
TCCR3B |= (1 << WGM32); // CTCモード
TCCR3B |= (1 << CS31); // プリスケーラを8に設定
TIMSK3 |= (1 << OCIE3A); // タイマー比較Aの割り込みを有効化
これにより、タイマーは約2272カウントごとに比較一致割り込みを発生させ、ピンの状態がトグルされるため、ピンは約440Hzの周波数でトグルされます。
結果、毎秒440回の割り込みが発生し、ISR(割り込みサービスルーチン)内でデジタルピン12の状態がトグルされ、soundOnフラグがtrueの場合に音がなります。
タイマー1を使って1秒間隔を作る
このタイマーもタイマー3と同じ方法で1秒の間隔を作ります。
今回は1秒間隔という長い時間の設定ですので、プリスケーラ1024という大きな数値により計算を進めていきます。
計算.タイマークロック周波数を決定する
タイマー1にプリスケーラを1024を適用し、そこから計算を行うと、タイマークロックは 16,000,000 Hz / 1024 = 15,625 Hzになります。ここで求めた数値は15625ですが、タイマーのカウントは0から始まるため、正確に1秒間隔で割り込みを発生させるには、OCR1Aには15624を設定するようにします。
// タイマー1の設定(音のON/OFFを制御するためのタイマー)
TCCR1A = 0; // タイマー/カウンター1を正常モードで設定
TCCR1B = 0; // 初期化
TCNT1 = 0; // タイマー1のカウンタ値を0にリセット
OCR1A = 15624; // 1秒間隔で割り込みが発生するように設定
TCCR1B |= (1 << WGM12); // CTCモード
TCCR1B |= (1 << CS12) | (1 << CS10); // プリスケーラを1024に設定
TIMSK1 |= (1 << OCIE1A); // タイマー比較Aの割り込みを有効化
これによって、タイマー1のISRでは、1秒ごとに処理されそのたびごとにsoundOnフラグをトグルし、音がOFFとONを切り替えます。