電子オルゴールサンプル †スピーカーを繋いで、タイマーによる周波数出力で音楽を演奏させる。 回路図 †基本的にはスピーカーのみの接続です。 ソースコード †いつものライブラリ。 まずは、音階の周波数定義と、音符の長さ定義用のファイルを、「music_def.h」として作成します。 #ifndef MUSIC_DEF_H_
#define MUSIC_DEF_H_
typedef enum _tagNOTE_HZ{ // 音階リスト
NOTE_HZ_NONE,
NOTE_HZ_C3,
NOTE_HZ_C3S,
NOTE_HZ_D3,
NOTE_HZ_D3S,
NOTE_HZ_E3,
NOTE_HZ_F3,
NOTE_HZ_F3S,
NOTE_HZ_G3,
NOTE_HZ_G3S,
NOTE_HZ_A3,
NOTE_HZ_A3S,
NOTE_HZ_B3,
NOTE_HZ_C4,
NOTE_HZ_C4S,
NOTE_HZ_D4,
} NOTE_HZ;
typedef enum _tagNOTE_LEN{ // 音符リスト
NOTE_LEN_ZEN,
NOTE_LEN_2BU,
NOTE_LEN_2_BU,
NOTE_LEN_4BU,
NOTE_LEN_4_BU,
NOTE_LEN_8BU,
NOTE_LEN_16BU,
} NOTE_LEN;
typedef struct _tagNOTE{ // 音符データ構造体
uint8_t hz;
uint8_t len;
} NOTE;
uint8_t note_hz[] = { // 音階周波数リスト(CPUクロック1MHz/プリスケーラ8)
0, // 休符
( 1000000 / 2 / 8 ) / 261.62, // C3
( 1000000 / 2 / 8 ) / 277.18, // C3#
( 1000000 / 2 / 8 ) / 293.66, // D3
( 1000000 / 2 / 8 ) / 311.12, // D3#
( 1000000 / 2 / 8 ) / 329.62, // E3
( 1000000 / 2 / 8 ) / 349.22, // F3
( 1000000 / 2 / 8 ) / 369.99, // F3#
( 1000000 / 2 / 8 ) / 391.99, // G3
( 1000000 / 2 / 8 ) / 415.30, // G3#
( 1000000 / 2 / 8 ) / 440.00, // A3
( 1000000 / 2 / 8 ) / 466.16, // A3#
( 1000000 / 2 / 8 ) / 493.88, // B3
( 1000000 / 2 / 8 ) / 523.25, // C4
( 1000000 / 2 / 8 ) / 554.36, // C4#
( 1000000 / 2 / 8 ) / 587.32, // D4
};
uint16_t note_len[] = { // 音符長リスト
60000 / BPM * 4, // 全音符
60000 / BPM * 2, // 二分音符
60000 / BPM * 3, // 附点二分音符
60000 / BPM, // 四分音符
60000 / BPM * 1.5, // 附点四分音符
60000 / BPM / 2, // 八分音符
60000 / BPM / 4, // 十六分音符
};
#endif /* MUSIC_DEF_H_ */
見ての通り、音階リストの列挙型と音階周波数リストが、音符リストの列挙型と音符長リストが、それぞれ対応しています。 周波数リストは、CPUの周波数と、実際の音階周波数を元に算出している、8bitタイマ/カウンタの比較レジスタに設定する値です。 例えば、A3「ラ」の音の周波数は440Hzなので、ピンのON/OFF1セットの回数を、1秒間に440回のサイクルで行ってあげる必要があります。 8bitタイマは、基本的には、CPUのクロックが0〜最大255回までをカウントして、あるタイミングで出力のLow/Highを切り替えるものなので、1136回もカウントはできません。 あと、音符長リストには、BPMという定数を使った計算をしています。 次に、楽譜ファイル。 #ifndef DOREMI_H_
#define DOREMI_H_
NOTE score[] ={ // ドレミの歌
{ NOTE_HZ_C3, NOTE_LEN_4_BU },
{ NOTE_HZ_D3, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_4_BU },
{ NOTE_HZ_C3, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_4BU },
{ NOTE_HZ_C3, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4_BU },
{ NOTE_HZ_NONE, NOTE_LEN_8BU },
{ NOTE_HZ_D3, NOTE_LEN_4_BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_D3, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_2_BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4_BU },
{ NOTE_HZ_F3, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_4_BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_4_BU },
{ NOTE_HZ_NONE, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_4_BU },
{ NOTE_HZ_G3, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_2_BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_4_BU },
{ NOTE_HZ_C3, NOTE_LEN_8BU },
{ NOTE_HZ_D3, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_2_BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_A3, NOTE_LEN_4_BU },
{ NOTE_HZ_D3, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_F3S, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_8BU },
{ NOTE_HZ_B3, NOTE_LEN_2_BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_B3, NOTE_LEN_4_BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_F3S, NOTE_LEN_8BU },
{ NOTE_HZ_G3S, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_8BU },
{ NOTE_HZ_B3, NOTE_LEN_8BU },
{ NOTE_HZ_C4, NOTE_LEN_2BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_B3, NOTE_LEN_8BU },
{ NOTE_HZ_A3S, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_4BU },
{ NOTE_HZ_F3, NOTE_LEN_4BU },
{ NOTE_HZ_B3, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_4BU },
{ NOTE_HZ_C4, NOTE_LEN_2_BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_C3, NOTE_LEN_4BU },
{ NOTE_HZ_D3, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4BU },
{ NOTE_HZ_F3, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_4BU },
{ NOTE_HZ_A3, NOTE_LEN_4BU },
{ NOTE_HZ_B3, NOTE_LEN_4BU },
{ NOTE_HZ_C4, NOTE_LEN_4BU },
{ NOTE_HZ_C4, NOTE_LEN_4BU },
{ NOTE_HZ_B3, NOTE_LEN_4BU },
{ NOTE_HZ_A3, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_4BU },
{ NOTE_HZ_F3, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4BU },
{ NOTE_HZ_D3, NOTE_LEN_4BU },
{ NOTE_HZ_C3, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_E3, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_4BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_D3, NOTE_LEN_4BU },
{ NOTE_HZ_F3, NOTE_LEN_4BU },
{ NOTE_HZ_F3, NOTE_LEN_4BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_A3, NOTE_LEN_4BU },
{ NOTE_HZ_B3, NOTE_LEN_4BU },
{ NOTE_HZ_B3, NOTE_LEN_4BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_C3, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_NONE, NOTE_LEN_8BU },
{ NOTE_HZ_E3, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_8BU },
{ NOTE_HZ_NONE, NOTE_LEN_8BU },
{ NOTE_HZ_D3, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_8BU },
{ NOTE_HZ_F3, NOTE_LEN_8BU },
{ NOTE_HZ_NONE, NOTE_LEN_8BU },
{ NOTE_HZ_A3, NOTE_LEN_8BU },
{ NOTE_HZ_B3, NOTE_LEN_8BU },
{ NOTE_HZ_B3, NOTE_LEN_8BU },
{ NOTE_HZ_NONE, NOTE_LEN_8BU },
{ NOTE_HZ_G3, NOTE_LEN_2BU },
{ NOTE_HZ_C3, NOTE_LEN_2BU },
{ NOTE_HZ_A3, NOTE_LEN_2BU },
{ NOTE_HZ_F3, NOTE_LEN_2BU },
{ NOTE_HZ_E3, NOTE_LEN_2BU },
{ NOTE_HZ_C3, NOTE_LEN_2BU },
{ NOTE_HZ_D3, NOTE_LEN_2_BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
{ NOTE_HZ_G3, NOTE_LEN_2BU },
{ NOTE_HZ_C3, NOTE_LEN_2BU },
{ NOTE_HZ_A3, NOTE_LEN_2BU },
{ NOTE_HZ_B3, NOTE_LEN_2BU },
{ NOTE_HZ_C4, NOTE_LEN_2BU },
{ NOTE_HZ_D4, NOTE_LEN_2BU },
{ NOTE_HZ_C4, NOTE_LEN_2_BU },
{ NOTE_HZ_NONE, NOTE_LEN_4BU },
};
#endif /* DOREMI_H_ */
そしてメイン関数。 #include <avr/io.h>
#include "util.h"
#define BPM 140 // テンポ設定
#include "music_def.h"
#include "doremi.h"
int main( void )
{
uint8_t i = 0; // ループ変数
DDRD = 0b01000000; // PD6(OC0A)を出力に設定
PORTD = 0b00000000; // ポートDをLowに初期化
while( 1 ){
for( i = 0; i < sizeof( score ) / sizeof( NOTE ); i++ ){
if( NOTE_HZ_NONE == score[i].hz ){
TCCR0A = 0b00000000; // タイマー停止
} else {
OCR0A = note_hz[score[i].hz]; // 周波数設定
TCCR0A = 0b01000010; // 比較一致トグル出力
TCCR0B = 0b00000010; // 前置分周8
}
delay_ms( note_len[score[i].len] );
}
}
return 0;
}
BPM(テンポ)の定義をしてからmusic_def.hをincludeすることで、先程の音符長の値が決定します。 タイマの出力をピンに反映させるためには、ポート方向レジスタを、出力に設定する必要があります。 無限ループ内に曲のループがあり、音符データ構造体を一つずつ処理しています。 基本動作 †基本、楽譜通りに鳴らしているだけですが、同じ音が複数続いた場合、繋がって聞こえてしまうという問題があります。 あと、もうひとつ、比較レジスタ値を途中で変えていますが、これを変えたタイミングで、既にカウンタが後から設定した値を超えていると、一致するタイミングが無くて、一旦最大値(255)までカウントしてしまってから次のサイクルに入ってしまうという問題があります。 圧電スピーカーは音が甲高くて耳につきますね……。 |