Arduino高速操作テクニック: 標準関数を使わずにタイマー割込みとレジスタで音階を制御

UNO-ATmega328

Arduinoの標準的なライブラリ関数を使わずに、タイマー割込みと直接的なレジスタ操作を通じて、音の生成とタイミングの正確な管理を行いドレミファソラシドを1秒ごとに演奏するプログラムを作ってみます。

標準関数 tone()を使ったプログラム

まずは、一般的なArduinoの関数であるtoneを使ったソースコードです。このコードは、どのArduinoのボードでも実行が可能です。

int speakerPin = 9; // スピーカーを接続するピン
int notes[] = {262, 294, 330, 349, 392, 440, 494, 523}; // ドレミファソラシドの周波数 (Hz)
int noteDuration = 1000; // 各音の持続時間 (ミリ秒)

void setup() {
  // スピーカーピンを出力として設定
  pinMode(speakerPin, OUTPUT);
}

void loop() {
  // ドレミファソラシドの音を順に再生
  for (int i = 0; i < 8; i++) {
    tone(speakerPin, notes[i], noteDuration); // 音を出力
    delay(noteDuration + 100); // 指定した時間だけ待ってから次の音へ
  }

  // オプションでループを終了させる場合は以下のコメントを解除
  // while(true); // これ以上の音を鳴らさない
}

プログラムの説明

  • 変数定義: speakerPinはスピーカーが接続されるピン、notes配列にはドレミファソラシドの音階の周波数が格納されています。noteDurationは音が持続する時間を指定します。
  • setup()関数: スピーカーピンを出力としてセットアップします。
  • loop()関数: forループを使って、配列notesの中の各音階を順に再生します。tone()関数により指定されたピンから周波数に応じた音が出力され、その後delay()関数で音が1秒間再生された後に追加の小休止を挟みます。

このプログラムは簡単に理解できるため、Arduino初心者にもおすすめですが、delay()の使用によりその間プログラムの他の処理がブロックされるため、複数のタスクを同時に実行する場合には適していません。

tone関数

tone(pin, frequency)
tone(pin, frequency, duration)

Arduinoのtone()関数は、指定したピンから特定の周波数の正方形波を生成するために使用される関数です。これにより、簡単にビープ音や他の音階を生成することができます。

  • pin: 出力ピンの番号。PWMが可能なピンを使用する必要があります。
  • frequency: 生成する音の周波数(単位はヘルツ)。この関数で設定できる周波数の範囲は、通常は31Hzから約65535Hzまでです。
  • duration: オプションのパラメータで、音を鳴らす持続時間をミリ秒単位で指定します。このパラメータを設定すると、指定した時間が経過すると自動的に音が停止します。

音階と周波数表

Octave C C# D D# E F F# G G# A A# B
0 16 18 18 20 20 22 24 24 26 28 30 30
1 32 34 36 38 40 44 46 48 52 54 58 62
2 66 70 74 78 82 88 92 98 104 110 116 124
3 130 138 146 156 164 174 186 196 208 220 234 246
4 262 276 294 312 330 350 370 392 416 440 466 494
5 524 554 588 622 660 698 740 784 832 880 932 988
6 1046 1108 1174 1244 1318 1396 1480 1568 1662 1760 1864 1976
7 2092 2218 2348 2488 2636 2794 2960 3136 3324 3520 3728 3952
8 4186 4436 4698 4978 5274 5588 5920 6272 6644 7040 7460 7902
9 8372 8870 9398 9956 10548 11176 11840 12544 13290 14080 14916 15804
  • 四捨五入して偶数に調整したたオリジナルの値とは少し異なる可能性があります。

標準関数を使わないプログラム

次に、ArduinoのtonenoTone関数の代わりにタイマー割込みとレジスタ操作を用いて、ドレミファソラシドの音階を1秒ごとに切り替えるものです。このプログラムでは、Arduinoのタイマー1(16ビットタイマー)を使用してピンの状態をトグルし、音を生成します。また、プログラム内ではdelayを使用せず、タイマー割込みによって時間の経過を管理します。

#include <avr/io.h>
#include <avr/interrupt.h>

volatile unsigned long timer1_millis;
volatile int currentNote = 0;
const int speakerPin = 9; // PWM出力ピン
const int notes[] = {262, 294, 330, 349, 392, 440, 494, 523}; // ドレミファソラシドの周波数

ISR(TIMER1_COMPA_vect) {
  timer1_millis++; // タイマー割込み毎にミリ秒をカウントアップ
}

void setup() {
  pinMode(speakerPin, OUTPUT);

  // タイマー設定 (CTCモード、プリスケーラ64)
  TCCR1A = 0;
  TCCR1B = 0;
  TCCR1B |= (1 << WGM12) | (1 << CS11) | (1 << CS10); // CTC mode and 64 prescaler
  OCR1A = 249; // 1ミリ秒ごとに割込み (16MHz / 64 / 1000)
  TIMSK1 |= (1 << OCIE1A); // タイマー割込みを有効化

  sei(); // 割込みを許可
  timer1_millis = 0; // タイマー用のカウンタを0に
}

void loop() {
  if (timer1_millis >= 1000) { // 1秒ごとに実行
    timer1_millis = 0; // カウンタリセット
    playTone(notes[currentNote], 1000); // 現在の音を1秒間再生
    currentNote = (currentNote + 1) % 8; // 次の音へ
  }
}

void playTone(int frequency, int duration) {
  long toggleCount = 2 * frequency * duration / 1000;
  int toggleDelay = 1000000 / (frequency * 2); // toggle every half period

  for (long i = 0; i < toggleCount; i++) {
    PORTB ^= (1 << PORTB1); // ピンの状態をトグル (ピン9)
    _delay_us(toggleDelay); // 半周期待つ
  }
}

プログラムの詳細説明

  • タイマー割込みの設定: タイマー1をCTCモードで設定し、1ミリ秒ごとにTIMER1_COMPA_vect割込みを発生させています。これにより、timer1_millis変数が1ミリ秒ごとにインクリメントされます。
  • 音の再生: playTone関数では、指定された周波数で正確な期間音を再生します。これはピンの状態をトグルすることで実現し、そのためのディレイは_delay_usを使用して精密に管理します。
  • 音階の管理: loop関数では、timer1_millisが1000ミリ秒に達するごとに音を切り替えています。音階は配列notesに保存されており、ドレミファソラシドの周波数が格納されています。

このプログラムは、Arduinoの標準的なライブラリ関数を使わずに、タイマー割込みと直接的なレジスタ操作を通じて、音の生成とタイミングの正確な管理を行っています。これにより、より効率的でタイムクリティカルなアプリケーションに適した制御が可能となります。

toneを使わない処理のメリット

tone関数を使わずに直接タイマー割込みとレジスタ操作を用いる方法と比較した場合、以下のメリットが考えられます:

1. 高速な実行とリアルタイム性

直接タイマー割込みとレジスタを操作する方法は、関数呼び出しのオーバーヘッドが少なく、より高速に実行できます。これにより、リアルタイム性が要求されるアプリケーションでの使用に適しています。tone関数よりも細かいタイミング制御が可能で、割込みレイテンシーを最小限に抑えることができます。

2. リソースの節約

tone関数は内部でタイマーを使用しますが、自分でタイマーを制御することで、使用するタイマーの数や方法を最適化でき、限られたリソースをより効果的に利用することが可能です。特に複数のタスクを同時に処理する必要がある場合に有効です。

3. 柔軟な制御

直接レジスタを操作することで、tone関数ではアクセスできないタイマーの特定の機能や設定を利用できるようになります。例えば、異なるプリスケーラの設定やタイマーの特定のモードを活用することが可能です。これにより、より複雑な音のパターンやタイミングの制御が可能になります。

4. 複数のピンでの同時音出力の容易化

tone関数は一度に一つのピンにのみ音を出力できますが、直接レジスタ操作による方法では、複数のピンに対して独立した制御を同時に行うことが可能です。これにより、より複雑な音楽や効果を生成することができます。

5. パワーマネジメントの最適化

直接レジスタ操作による方法では、必要な時だけタイマーを有効にし、不要な時は無効化することで消費電力を節約することができます。これは、特にバッテリー駆動のデバイスにとって重要です。

注意点

ただし、これらのメリットを享受するためには、マイクロコントローラの内部構造や動作について深い理解が必要です。間違ったレジスタ設定はシステムの不安定な動作や予期せぬ副作用を引き起こす可能性があります。また、tone関数を使った方が開発はずっと簡単で、多くの場合において十分な性能を提供します。

タイトルとURLをコピーしました