ブザーを叩くとLEDが点滅を始め、設定した時間が経過すると、メロディが流れる。
メロディは、MML(Music Macro Language)風の書き方 KMML ( Kaimu Music Macro Language )で入力できる。
マイコン PIC12F1822-I/P
発光ダイオードLED Φ5mm
カーボン抵抗器 150Ω 、10kΩ
圧電スピーカー(圧電サウンダ)(13mm)PKM13EPYH4
ミニブレッドボードBBー601
電池ボックス(単3型×2本用、バッテリスナップタイプ)
バッテリスナップ
開発環境:MPLAB XIDE, Cコンパイラ:XC8 Compiler
マイコンプログラマ: PICkit3
ブザーを叩くと、LEDが点滅を始める。
設定した時間(TIME_LIMIT)が経過すると、メロディが流れる。
メロディはKMML ( Kaimu Music Macro Language ) の文字列で指定できる。
コマンド
O* オクターブの指定(次に指定するまで保持) *:0,1,...
# 半音上がる(一音のみ)
b 半音下がる(一音のみ)
CDEFGAB ... 音階の指定
C* ... 指定した音階を*の長さだけ鳴らす *:1,2,4,8,@ (全、二分、四分、八分、16分)
R* ... 休符 *は1,2,4,8,@ (全、二分、四分、八分、16分)
/*
* File: mml-timer-shock-12f1822.c
* Author: kaimu
*
* Created on February 17, 2018, 8:50 AM
*/
/*
* Input
* AN2 [PIN5] : Analog Input
* Output
* RA5 [PIN2] : LED (1...OFF, 0...ON)
* CCP1 (RA2) [PIN5] : piezo buzzer
*/
#include <xc.h>
#include <stdint.h>
// タイマーのアラーム・メロディ
//static const char mml_melody[] = "O1R2C4D4E2D4C4R@C4D4E4D4C4D2R@"; // チャルメラ
//static const char mml_melody[] = "O1D8C8 O0F4O1F4F4D8C8 O0F4O1F4F4D8C8 O0F4O1F4O0D4O1F4 O0C4O1E4E4D8C8 \
O0C4O1E4E4D8C8 O0C4O1E4E4D8C8 O0C4O1E4O0D4O1E4 O0F4O1F4F4R4"; //猫ふんじゃった
//static const char mml_melody[] = "O0D8G8 G2B8A8G8 B2B8A8 G2E4 D2D8G8 \
G2B8A8G8 B2A8B8O1D1 D2O0B8O1D8 \
D2O0B8A8G8 B2B8A8 G2E4 D2D8G8 \
G2B8A8G8 B2A4 G2G2R4"; // アメイジング・グレイス
//static const char mml_melody[] = "O0F8G8A8bB8 A8G8F8R8 A8bB8O1C8D8 C8O0bB8A8R8 \
F8R8F8R8 F8R8F8R8 F@F@G@G@A@A@bB@bB@ A8G8F8R8"; // カエルの合唱
static const char mml_melody[] = "O1C4E4D4O0G2 O1C4D4E4C2 \
E4C4D4 O0G2 G4O1D4E4C4 R8"; // ウエストミンスターの鐘 Westminster Quarters
// タイマーの動作音
static const char mml_boot[] = "O0C8D8E8R8"; // 起動時
static const char mml_timer_start[] = "R8O0A4O1A4R@"; // タイマーの開始時
static const char mml_every_5sec[] = "O0A@R@"; // 5秒ごと
// タイマーの設定時間[s]
#define TIME_LIMIT 180 // LEDが点灯している時間(タイマの秒数)
#define MELODY_COUNT 2 // 時間経過後に鳴らすメロディの繰り返し回数
// LED点滅設定
#define LED_BLINK_PERIOD_MIN 50 // LEDが点滅する周期の最小値(20-100)
// I/Oポートの定義 (LEDを接続するポートが変わった時はここを変更する)
#define PORT_LED_1 RA5
#define TRISA_LED_1 TRISA5
#define PORT_ADC ANSA2
#define PORT_BUZ RA2
#define TRISA_BUZ TRISA2
#define TRISA_ADC TRISA2
#define PORT_NO_ADC 0x02
// PIC micro controller Configuration 1
#pragma config FOSC = INTOSC // 内部クロック使用。
#pragma config WDTE = OFF // ウォッチドッグタイマなし。
#pragma config PWRTE = ON // 64ms後にプログラム開始。
#pragma config MCLRE = OFF // 外部リセットなし。RA3は入力ピン。
#pragma config CP = OFF // コード保護なし。
#pragma config CPD = OFF // データ保護なし。
#pragma config BOREN = ON // 電源電圧低下時のリセットあり。
#pragma config CLKOUTEN = OFF // クロック出力不可。
#pragma config IESO = OFF // 内部外部クロックなし。
#pragma config FCMEN = OFF // FCMなし。
// 動作設定その2
// Configuration 2
#pragma config WRT = OFF // 書き込み保護なし。
#pragma config PLLEN = OFF // PLLなし。
#pragma config STVREN = ON // スタックオーバー/アンダーフローでリセット。
#pragma config BORV = LO // 電源電圧低下監視閾値はLowモード
#pragma config LVP = OFF // 低電圧プログラムなし。
#define ledOn() (PORT_LED_1 = 0)
#define ledOff() (PORT_LED_1 = 1)
#define led() (PORT_LED_1 ^= 1)
void setBuzOutputMode(void) {
TRISA_BUZ = 0; // Buzzer Port OUTPUT mode
PORT_ADC = 0; // ADC port as Digital port
}
void setBuzInputMode(void) {
TRISA_BUZ = 1; // Buzzer Port INPUT mode
PORT_ADC = 1; // ADC port as Analog port
}
void wait(volatile uint32_t t)
// Delay
{
while (t-- > 0) NOP();
}
void initIo(void) {
//OSCCON = 0b01111010; // PLL disable, 16MHz internal clock
// OSCCON = 0b01100010; // PLL disable, 8MHz internal clock
OSCCON = 0b01101010; // PLL disable, 4MHz internal clock
//OSCCON = 0b01011010; // PLL disable, 1MHz internal clock
//OSCCON = 0b00111010; // PLL disable, 500kHz internal clock
INTEDG = 0; // Interrupt on falling edge of RB0/INT pin
INTE = 1; // External Interrupt Enable
GIE = 1; //Interrupt Enable
ANSELA = 0b00000001; //RA0 Analog port, others digital port
CM1CON0 = 0x00; //Comparator disable
TRISA = 0b00001001; //I/O Direction. RA1-2,4-5 : output, RA0,3 : Input
LATA = 0b00000000; // PORTA Latch Settings
}
void initPwm(void) {
//Timer2 settings for PWM standard mode
PR2 = 0; // PWM Period = [(PR2) +1 ]*4*(1/FOSC)*(Timer2 Prescale value)
T2CON = 0b00000010; //00 = Prescaler is 1, 01 = Prescaler is 4,
// 10 = Prescaler is 16, 11 = Prescaler is 64
APFCON &= ~(1 << CCP1SEL); //CCP1 function is on RA2
//CCP1SEL = 1;//CCP1 function is on RA5
CCP1CON = 0b00001100; //PWM mode: P1A, P1C active-high; P1B, P1D active-high
CCPR1L = 64; // PWM Duty Cycle (CCPR1L:CCP1CON<5:4>)*(1/FOSC)*(TMR2 Prescale Value)
//Start PWM
TMR2 = 0;
TMR2ON = 1;
}
// A/D変換関数
// A/D conversion functions
void initAdc(void) {
PORT_ADC = 1;
// PORT_ADCで定義されたポートをアナログポートにする。Analog port (see PORT_ADC def.) setting
TRISA_ADC = 1; // A/D変換端子を入力モードにする。
ADCON0 = 0b00000001 | (PORT_NO_ADC << 2);
//AD変換を有効化。0:unimp, xxxxx:analog ch, 0:GOnDone, 1:ADC enable
ADCON1 = 0b01000000;
//0:(ADFM=0)Left justified (10bit res.), 100:Fosc/4 ,0:Reserved, 00:AVDD=VREF
CM1CON0 = 0x00; //コンパレータを無効化。Comparator disable
}
uint16_t getAdc(void)
// A/D変換した値を得る。
// 戻り値:
// A/D 変換されたアナログ信号の大きさ(0〜1023)
{
uint16_t adcvalue;
//ADC
wait(10);
GO_nDONE = 1;
while (GO_nDONE);
adcvalue = ADRESH << 2 | ADRESL >> 6;
return adcvalue;
}
#define SHOCK_THRESHOLD 60 // 衝撃の閾値 (0 ~ 1023)
#define SHOCKED 1
#define NO_SHOCK 0
uint8_t isShock(void)
// Shock sensor detection
// return value: SHOCKED ... shocked
// NO_SHOCK ... no
{
uint16_t adcvalue;
uint8_t result;
setBuzInputMode();
adcvalue = getAdc();
if (adcvalue > SHOCK_THRESHOLD) {
result = SHOCKED;
} else {
result = NO_SHOCK;
}
return result;
}
void waitUntilBuz(void)
//ブザーの振動がおさまるまで待つ
{
uint8_t silentCounter; // ブザーが振動していない回数
for (silentCounter = 0; silentCounter < 5; silentCounter++) {
if (isShock()) {
silentCounter--; //ブザが振動していたら、振動していない回数を減ずる
}
}
}
volatile uint32_t TimerCounter; // Counter of Timer Interrupt
#define T0UNIT 15UL // TimerCounter at 1 sec.
void initTimer0(void)
//Initialize Timer 0
// count up every 256*256us = 65536us = 65.536ms (clock 4/4=1MHz, prescaler 1:256
// if you want t[s] timer, TimerCount <= t / 0.065536
{
TMR0IF = 0; // interrupt flag clear
TimerCounter = 0; // Timer Interrupt Counter clear
OPTION_REG = 0b11010111; // Timer0 Internal clock, Prescaler 1:256
// Timer0 Interrupt occured every 65.536ms
TMR0IE = 1; // enable Timer0 interrupt
GIE = 1; // enable interrupt
}
void initTimer1(void) {
// Initialize TIMER1
TMR1CS1 = 0;
TMR1CS0 = 0; // Internal clock (Fosc/4)
// T1CKPS1 = 1; T1CKPS0 = 1;// prescaler 1:8
T1CKPS1 = 0;
T1CKPS0 = 0; // prescaler 1:1
TMR1H = 0xff;
TMR1L = 0x00; // Clear Timer1 counter
TMR1ON = 1; // Enable Timer1
TMR1IE = 1; // Timer1 Intterupt Enable
PEIE = 1;
GIE = 1;
}
volatile int32_t PWMCounter = 0, PWMPeriod = 0, PWMCounterOn2Off = 0; // PWM Control Parameters
volatile int32_t dpwm = 1; // LEDの明るさをPWM制御するためのカウンタの増分
void interrupt intr(void)
// Interrupt processing function
{
if (INTF == 1) {
INTF = 0;
}
if (TMR0IF == 1) {
TMR0IF = 0; //Clear TMR0 Interrupt flag
TimerCounter++;
}
if (TMR1IF) {
TMR1IF = 0; // Clear Timer0 Interrupt Flag
TMR1H = 0xff;
TMR1L = 0x00; // Clear Timer1 counter
if (PWMPeriod > 0) {
if (PWMCounter < PWMCounterOn2Off) {
ledOn();
} else {
ledOff();
}
PWMCounter++;
if (PWMCounter >= PWMPeriod) {
// PWMの1周期が完了したら、
PWMCounter = 0;
PWMCounterOn2Off += dpwm;
if (PWMCounterOn2Off >= PWMPeriod) {
dpwm = -1; // 徐々に暗くするPWMに変更
} else if (PWMCounterOn2Off <= 0) {
dpwm = 1; // 徐々に明るくするPWMに変更
}
}
} else {
ledOff();
}
}
}
void pwmLedOff(void) {
//PWMCounter = 0, PWMPeriod = 0, PWMCounterOn2Off=0; // PWMの設定(消灯)
TMR1ON = 0; // Timer1 Interrupt Disable
}
void pwmLedOn(int32_t period) {
PWMCounter = 0;
PWMPeriod = period;
PWMCounterOn2Off = 1; // PWMの設定
TMR1ON = 1; // Timer1 Interrupt Enable
}
void pwmLedSetPeriod(int32_t period) {
PWMPeriod = period; // PWM周期の設定
}
void playFreq(uint8_t octIndex, uint8_t modIndex, uint8_t pitchIndex, uint8_t length)
// 引数で指定されたオクターブ、調、音階、長さの音をPWMで出力する。
// arguments modIndex :調:0,1, octIndex :オクターブ:0,1 , pitchIndex :音高:0~6,
// length : 音の長さ
#define N_OCT 2 // オクターブの種類
#define N_MOD 3 // 調の種別
#define N_PITCH 7 // 音階の種類
{
// 周波数に対応したPWMのパラメータ
// PITCH order -> ABCDEFG
static const uint8_t freq[N_OCT][N_MOD][N_PITCH] =
{
141, 126, 238, 212, 189, 178, 158, // ABCDEFG (oct. 4)
133, 126, 224, 200, 189, 168, 149, //#ABCDEFG (oct. 4)
149, 133, 238, 224, 200, 178, 168, //bABCDEFG (oct. 4)
70, 62, 118, 105, 94, 88, 79, // ABCDEFG (oct. 5)
112, 99, 94, 83, 74, 66, 62, //#ABCDEFG (oct.5))
74, 66, 62, 112, 99, 94, 83 //bABCDEFG (oct.5)
};
static const uint8_t maxLength = 16; // 音の最大の長さ
setBuzOutputMode();
if (octIndex < N_OCT && modIndex < N_MOD && pitchIndex < N_PITCH) {
PR2 = freq[octIndex][modIndex][pitchIndex];
wait((uint32_t) (maxLength / length) * 3000); // 音の長さだけ待つ
}
PR2 = 0; // Stop Sound
}
void playWait(uint8_t length) {
const uint8_t maxLength = 16; // 音の最大の長さ
PR2 = 0;
wait((uint32_t) (maxLength / length) * 3000); // 音の長さだけ待つ
}
uint8_t getMmlLength(char mmlchar) {
uint8_t length;
if (mmlchar == '@') {
length = 16;
} else {
length = (uint8_t) mmlchar - '0';
}
return length;
}
void playMmlString(char *melodyString) {
// play Music Macro Language
// example "O1C4D8E@F2R8G4#D4R8"
// C4 ... pitch name C , length 1/4
// pitch name : C, D, E, F, G, A, B
// octave : O0, O1
// mode : #, b
// rest : R
// length 1, 2, 4, 8, @ (1/1 , 1/2, 1/4, 1/8, @ is 1/16)
// MML文字列を再生
uint8_t index = 0;
static volatile uint8_t octIndex = 0, modIndex = 0, pitchIndex = 0, length;
while (*(melodyString + index) != '\0') {
switch (*(melodyString + index)) {
case 'O':
index++;
octIndex = *(melodyString + index) - '0';
break;
case '#':
modIndex = 1;
index++;
pitchIndex = *(melodyString + index) - 'A';
index++;
length = getMmlLength(*(melodyString + index));
playFreq(octIndex, modIndex, pitchIndex, length);
modIndex = 0;
break;
case 'b':
modIndex = 2;
index++;
pitchIndex = *(melodyString + index) - 'A';
index++;
length = getMmlLength(*(melodyString + index));
playFreq(octIndex, modIndex, pitchIndex, length);
modIndex = 0;
break;
case 'R':
index++;
length = getMmlLength(*(melodyString + index));
playWait(length);
break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
modIndex = 0;
pitchIndex = *(melodyString + index) - 'A';
index++;
length = getMmlLength(*(melodyString + index));
playFreq(octIndex, modIndex, pitchIndex, length);
default:
playWait(0);
}
index++;
}
}
void main(void) {
int32_t pwmLedBlinkPeriod; // LEDのPWM制御の速度
uint32_t timeLimitCount; // 時間切れまでのカウント数
uint32_t i;
//Initialization
initIo();
initPwm();
initAdc();
initTimer0();
initTimer1();
ledOff();
pwmLedOff(); // PWMによるLED点滅を停止
playMmlString(mml_boot);
waitUntilBuz(); // ブザーの振動がおさまるまで待つ
while (1) {
if (isShock()) {
waitUntilBuz(); // ブザーの振動がおさまるまで待つ
playMmlString(mml_timer_start); // 衝撃を検出したときの音
waitUntilBuz(); // ブザーの振動がおさまるまで待つ
pwmLedBlinkPeriod = TIME_LIMIT; // 点滅間隔の初期化
pwmLedOn(pwmLedBlinkPeriod); // Start PWM (LED点灯)
TimerCounter = 0; // タイマのリセット。計時開始。
timeLimitCount = T0UNIT * TIME_LIMIT;
while (TimerCounter < timeLimitCount) {
//制限時間になるまでの処理
if (TimerCounter % (T0UNIT * 5UL) == 0) {
for (i = 0;
i < (timeLimitCount - TimerCounter) / (T0UNIT * 60UL) + 1UL;
i++) {
// 残り分数+1の回数だけ音を5秒ごとにならす
playMmlString(mml_every_5sec);
}
led();
pwmLedBlinkPeriod -= 5; // LED点滅速度を徐々に速くする
if (pwmLedBlinkPeriod <= LED_BLINK_PERIOD_MIN) {
pwmLedBlinkPeriod = LED_BLINK_PERIOD_MIN;
} // LED点滅速度が速すぎるときは最速に設定
pwmLedSetPeriod(pwmLedBlinkPeriod); // LED点滅速度の設定
}
}
for (i = 0; i < MELODY_COUNT; i++) {
playMmlString(mml_melody); //警報音を鳴らす。
}
waitUntilBuz(); // ブザーの振動がおさまるまで待つ
pwmLedOff(); // PWMの設定(消灯)
ledOff();
}
}
}