- 追加された行はこの色です。
- 削除された行はこの色です。
#norelated
#contents
* 電子オルゴールサンプル [#u83badf4]
スピーカーを繋いで、タイマーによる周波数出力で音楽を演奏させる。~
色々考えてみたけど、和音は一筋縄ではいかなさそうなので、とりあえず単音で。~
** 回路図 [#xe36eef5]
#ref(BeepTest.png,left,nowrap,電子オルゴールサンプル)
基本的にはスピーカーのみの接続です。~
[[部品購入1回目]]で、秋月で買った圧電スピーカーね。~
後で、RTCと組み合わせたり、他のセンサと組み合わせることによって、演奏のトリガを作っても面白いかも。~
** ソースコード [#x29c38ec]
いつものライブラリ。~
今回はユーティリティ(delay)のみ。~
&ref(util.h,left,nowrap,ユーティリティヘッダ);&ref(util.c,left,nowrap,ユーティリティソース);~
まずは、音階の周波数定義と、音符の長さ定義用のファイルを、「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;
}
** 基本動作 [#k21d9ac5]
圧電スピーカーは音が甲高くて耳につきますね……。~
超小型スピーカーの方がよかったかも。~