---
---
PWM を使用してオルゴール調の音色で曲を演奏します。 PIC16F1459のオルゴールプログラムをPIC12F1822に書きなおしたものです。このオルゴールは、基本的な動作を確認するために1つの音しか発生させていませんが、この発音方法を応用し、4音同時に演奏するプログラムも次のページに掲載しました。また、このプログラムでは、PICの特徴を生かし、演奏をしていないときはスリープ状態にし消費電力を抑えたため乾電池2本で、電池寿命を忘れるほど長く持ちます。小さな箱に組み込んで使用するのに最適です。
*Note*
このプログラムで使用する楽譜データは、 [オルゴールデーター作成 Windows アプリ] を利用して、作ることができます。
オルガン プログラム CB03_Orgel.zip |
<プログラム> main.c
/* File: main.c 1
* gakufu は、16 bit 単位。
* 最初にテンポ(1分間に演奏する四分音符の数)が記録されている。
* それ以降は音符 MSB 8 bitが音の高さ。 半音単位で、中央のドが 60(0x3C)
* LSB 8 bitが音の長さ
* 2分音符:10、 4分音符:08、 8分音符:04、 16分音符:02
* 付点2分音符:18、付点4分音符:0C、付点8分音符:06、付点16分音符:03
* | 7 6 5 4 3 2 1 0| 7 6 5 4 3 2 1 0 |
* | 高さ | 長さ |
* Language: MPLABX XC8
* Target: PIC12F1822
*/
// 音が減衰する程度 指定値(ms)で1/16減少
#define EnvelopeConst 50
// 音符と音符の間の無音時間(ms)
#define Silent 30
#include "xc.h"
#include "gakufu.h"
#define _XTAL_FREQ 8000000
#define SW RA5
#define SPK LATA2
#define TRIS_SPK TRISA2
#define TRIS_LED TRISA4
#define LED LATA4
#define waitStart 0
#define getGakufu 1
#define getOnpu 2
#define onpuStart 3
#define onpuPlay 4
#define onpuEnd 5
#define gkfEnd 6
#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF
#pragma config CPD = OFF, BOREN = ON, CLKOUTEN = OFF, IESO = OFF, FCMEN = OFF
#pragma config WRT = OFF, PLLEN = OFF, STVREN = ON, BORV = LO, LVP = OFF
// 音の高さを決める定数 for 8MHz Fosc PWM Freq 15.625KHz
const unsigned int dW_tbl[] = {
// Do, Do#, Re, Re#, Mi, Fa, Fa#, So, So#, Ra, Ra#, Si,
0,0x112,0x122,0x133,0x146,0x159,0x16E,0x183,0x19B,0x1B3,0x1CD,0x1E8,0x205, //12
0x224,0x245,0x267,0x28C,0x2B3,0x2DC,0x307,0x336,0x366,0x39A,0x3D1,0x40B,
0x449,0x48A,0x4CF,0x518,0x566,0x5B8,0x60F,0x66C,0x6CD,0x735,0x7A3,0x817,
0x892,0x915,0x99F,0xA31,0xACD,0xB71,0xC1F,0xCD8,0xD9B,0xE6A,0xF46,0x102E,
0x1125,0x122A,0x133E,0x1463,0x159A,0x16E3,0x183F,0x19B0,0x1B37,0x1CD5,0x1E8C,0x205D,
0x224A,0x2454,0x267D,0x28C7,0x2B34,0x2DC6,0x307E,0x3361,0x366F,0x39AB,0x3D19,0x40BB
}; // 0x892(index 37) が中心のド(60 0x3E) ,0xD9B はラ (440Hz)
unsigned int accWave,incWave;
unsigned char amp,wave;
void main(void) {
char SWdown=0; // SW が押下のフラグ
char SWnotRdy=0; // SW チャタリング防止
char state = 0; // 制御ステート
unsigned int pOnp = 0; // 音符位置
unsigned int Onp; // 音符
unsigned char tempo; // テンポ
unsigned char tPlay; // 演奏長さ
char AmpCnt = 25; // 振幅調整
char TmpCnt = 65; // テンポ調整
char TmpFlg = 0; // テンポフラグ
char SltCnt = 20; // 無音時間
char SltFlg = 0; // 無音フラグ
char ampNext; // 次音符音量
OSCCON = 0b01110000; // 内部クロック 8Mhz
ANSELA = 0; // 全てデジタルIO
LATA = 0;
TRIS_SPK = 0; // SPK RA2 音声出力
SPK = 0; // SPK 出力「0]
TRIS_LED = 0; // LED RC2 出力
LED = 0; // LED 消灯
nWPUEN = 0; // 弱プルアップ 有効
// Timer1 の設定 ------------------------------------
T1CON = 0b01000001; // Fosc ps 1/1 TMR1ON
TMR1 = 0xE100; // +7936 (0.992ms) で Carry
TMR1IF = 0; // Timer1フラグクリア
// PWM Timer2 の設定 ------------------------------------
T2CON = 0b00000100; // Timer2 ON PreS 1/1
PR2 = 0x7F; // 繰返し周波数 15.625KHz
TMR2IF = 0; // Timer2フラグクリア
TMR2IE = 1; // Timer2割込み有効
PEIE = 1; // ペリフェラル割込み有効
while(1){
//------------ 1ms 毎のタイミング処理 ----------------------
if(TMR1IF){ // 1ms 毎に実施
TMR1H = 0xE1; // 次の 1ms を設定
TMR1IF = 0; // フラグクリア
//------- SWチャタリング防止 1msループ内 ----------------
if(SWnotRdy){ // SWupの確認中なら
if(SW)SWnotRdy--; // SWupの確認をー1
else SWnotRdy = 5; // SWupの確認初期値
}else if(!SW){ // SWupの確認済みでDownなら
SWdown=1; // SWが押されたマーク
SWnotRdy = 5; // SWupの確認待ちに
}
//------- 振幅調整 50ms -----------------------------------
if(AmpCnt-- == 1){
AmpCnt = EnvelopeConst; // 音量を 50ms毎に
amp = (int)amp * 15/16; // 音量を -6% する
}
//------- テンポ調整 -----------------------------------
if(TmpCnt-- == 1){
TmpCnt = tempo; // テンポ毎に
TmpFlg = 1; // テンポフラグをセット
}
//------- Silent時間 -----------------------------------
if(SltCnt-- == 1){ // 消音時間になれば
SltFlg = 1; // 消音フラグをセット
}
}
//------------------- 主フロー制御 ---------------
switch(state){
case waitStart: // ====== 演奏開始待ち ======
if(SWnotRdy)break; // SW がOFFのこと
SWdown=0; // SWdown リセット
CCP1CON = 0; // PWM OFF
T1CON = 0; // Timer1 OFF
GIE = 0; // 割込み禁止
IOCAN5 = 1; // SW ONを待つ
IOCAF = 0; // SW フラグリセット
IOCIE = 1; // SW SLEEP再起動 ON
NOP(); // -------------------------------------
SLEEP(); // この間 スリープ
NOP(); // -------------------------------------
IOCIE = 0; // SW SLEEP再起動禁止
CCP1CON = 0b00111100; // シングルPWM 正論理出力
T1CON = 0b01000001; // Fosc ps 1/1 TMR1ON
SWdown = 0; // SW 初期化
SWnotRdy = 5; //
GIE = 1; // 割込み有効
state = getGakufu;
break;
case getGakufu: // ====== 楽譜の設定 ======
if(p1gkf[pOnp]==0xFFFF)pOnp=0; // 楽譜終了なら先頭に
tempo = 7500/p1gkf[pOnp++]; // 楽譜のテンポ取得
TmpCnt = tempo; // テンポセット
TmpFlg = 0; // フラグりセット
state = getOnpu;
break;
case getOnpu: // ====== 音符の設定 ======
Onp = p1gkf[pOnp++]; // 音符を取得
if(Onp == 0xFFFF){ // 曲の終了なら
state = gkfEnd; // 抜ける
break;
}
tPlay = Onp & 0x00FF; // 音符の長さ取得
Onp = Onp >> 8; // 音高を取り出す
if(Onp){ // 音符なら
incWave = dW_tbl[Onp - 23]; // 音高を繰返し時間に変換
ampNext = 0x7F; // 音量最大
}else{ // 休符なら
incWave = 0; // 繰返し なし
ampNext = 0; // 音量OFF
}
state = onpuStart;
break;
case onpuStart: // ====== 音符の演奏開始 ======
if(TmpFlg){ // テンポ待ち
TmpFlg = 0; // テンポフラグリセット
amp = ampNext; // 音量最大
if(amp)LED=1; // 休符でなければLED点灯
AmpCnt = EnvelopeConst; // 音量減少リセット
state = onpuPlay;
}
break;
case onpuPlay: // ====== 音符の演奏継続 ======
if(TmpFlg){ // テンポ待ち
TmpFlg = 0; // テンポフラグリセット
if(tPlay-- == 2){ // 音符終了なら
SltCnt = tempo - Silent; // 消音時間をセットし
SltFlg = 0; // 消音フラグをリセット
LED=0; // LED消灯
state = onpuEnd;
}
if(SWdown){ // SWが押されたなら
SWdown=0; // SW 処理済み明示
state = gkfEnd; // 終了処理へ
}
}
break;
case onpuEnd: // ====== 音符の演奏終了 ======
if(SltFlg){ // 時間を待つ
amp = 0; // 音量OFF
state = getOnpu; // 次の音符取得へ
}
break;
case gkfEnd: // ====== 楽譜の演奏終了 ======
LED=0; // LED消灯
pOnp--; // 曲終了まで進め
while(p1gkf[pOnp++] != 0xFFFF);
state = waitStart; // 演奏開始待ちに
break;
}
} // while
}
// 割込み処置 -----------------------------------------------------
void interrupt isr(void){
if(TMR2IF){ // Timer2 割込みを確認
TMR2IF = 0; // Timer2 フラグ リセット
accWave += incWave; // 周期計算
if(STATUSbits.CARRY)wave ^= 1; // キャリが出れば、波形反転
if(wave)CCPR1L = amp; // 波形ONなら、 出力あり
else CCPR1L = 0; // OFFなら、出力なし
}
}
<プログラム> gakufu.h
const unsigned int p1gkf[] = {
84, // 夕焼け小焼け
0x4304,0x4304,0x4304,0x4504,0x4304,0x4304,0x4304,0x4004,
0x3C04,0x3C04,0x3E04,0x4004,0x3E0C,0x0004,0x4008,0x4004,
0x4304,0x4504,0x4804,0x4804,0x4504,0x4304,0x4304,0x4504,
0x4304,0x480C,0x0004,0x4806,0x4A02,0x4804,0x4504,0x4804,
0x4804,0x4304,0x4304,0x4504,0x4304,0x4504,0x4304,0x400C,
0x0004,0x4304,0x4004,0x3E04,0x3C04,0x3E04,0x3E04,0x3C04,
0x3E04,0x4004,0x4304,0x4504,0x4304,0x480C,0x0004,
0xFFFF,
92, // フランスの古い歌
0x3E04,0x4304,0x4504,0x4604,0x4804,0x4A0C,0x4A04,0x4804,
0x4A04,0x4B04,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,
0x4804,0x4A04,0x4B02,0x4A02,0x4804,0x4604,0x450E,0x4302,
0x430C,0x3E04,0x4304,0x4504,0x4604,0x4804,0x4A0C,0x4A04,
0x4804,0x4A04,0x4B04,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,
0x4B04,0x4804,0x4A04,0x4B02,0x4A02,0x4804,0x4604,0x450E,
0x4302,0x4310,0x4308,0x4304,0x4504,0x460C,0x4604,0x4808,
0x4808,0x450C,0x4504,0x4A0C,0x4A04,0x4B04,0x4D02,0x4B02,
0x4A04,0x4804,0x4608,0x4504,0x4304,0x450C,0x3E04,0x4304,
0x4504,0x4604,0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,
0x4804,0x4A0C,0x4A04,0x4804,0x4A04,0x4B04,0x4804,0x4A04,
0x4B02,0x4A02,0x4804,0x4604,0x450E,0x4302,0x4310,
0xFFFF,
92, // かたつむり
0x4506,0x4502,0x4504,0x4204,0x3E06,0x3E02,0x3E04,0x4004,
0x4206,0x4202,0x4004,0x3E04,0x4008,0x0008,0x4206,0x4302,
0x4504,0x4704,0x4506,0x4502,0x4504,0x4204,0x4006,0x4002,
0x3E04,0x4004,0x4208,0x0008,0x4504,0x4A04,0x4A04,0x4504,
0x4204,0x4504,0x4504,0x4204,0x3E04,0x4204,0x4206,0x4002,
0x3E08,0x0008,
0xFFFF,
0xFFFF,
};