スピーカーを繋いで、タイマーによる周波数出力で音楽を演奏させる。
色々考えてみたけど、和音は一筋縄ではいかなさそうなので、とりあえず単音で。
基本的にはスピーカーのみの接続です。
部品購入1回目で、秋月で買った圧電スピーカーね。
後で、RTCと組み合わせたり、他のセンサと組み合わせることによって、演奏のトリガを作っても面白いかも。
いつものライブラリ。
今回はユーティリティ(delay)のみ。
まずは、音階の周波数定義と、音符の長さ定義用のファイルを、「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タイマ/カウンタの比較レジスタに設定する値です。
今回、音をスピーカーから出力するために、8bitタイマの比較一致のトグル出力を使用します。
8bitタイマが0〜比較レジスタに格納した値までカウントを行い、カウンタが比較レジスタに格納した値に達した時点で、ピンのLow/Highを切り替えます。
例えば、A3「ラ」の音の周波数は440Hzなので、ピンのON/OFF1セットの回数を、1秒間に440回のサイクルで行ってあげる必要があります。
ということは、ON/OFFの切り替えが実質2回の処理なので、1秒間に880回のサイクルでONとOFFを繰り返せばいいことになりますが、CPUの1MHzを880回に分けると、1回のON/OFFの切り替えのタイミングは、CPUのクロックが約1136回の時点となります。
8bitタイマは、基本的には、CPUのクロックが0〜最大255回までをカウントして、あるタイミングで出力のLow/Highを切り替えるものなので、1136回もカウントはできません。
というわけで、タイマ機能の前置分周を8に設定し、CPUクロックが8毎にタイマカウンタが1上がるように設定します。
こうすれば、1136/8で142回となるので、8bitタイマの比較レジスタに142を入れ、タイマをスタートし、カウンタが142となった時点で出力のLow/Highが切り替わるという動作が続けば、その出力ピンに繋いでいるスピーカーからは、440Hzの「ラ」の音が出ることになります。
まぁ、16bitタイマを使えば、前置分周とかは考えなくてもいいような気もしますが……。
あと、音符長リストには、BPMという定数を使った計算をしています。
これは、後でメインソースの方で定義しますが、曲のテンポです。
1分間に四分音符が何個かという単位なので、それをミリ秒単位に変換しています。
次に、楽譜ファイル。
サンプルで作ってみたのは、「ドレミの歌」。
music_def.hで定義した音符データ構造体の配列に、音階と音符のペアで、楽譜を記述しています。
#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 { TCCR0A = 0b01000010; // 比較一致トグル出力 TCCR0B = 0b00000010; // 前置分周8 OCR0A = note_hz[score[i].hz]; // 周波数設定 } delay_ms( note_len[score[i].len] ); } } return 0; }
圧電スピーカーは音が甲高くて耳につきますね……。
超小型スピーカーの方がよかったかも。