2015年1月25日 星期日

WEEK12

學習如何用Arduino按鈕來作發出聲音或亮燈

這是一個在網路上的實例教學:有四個按鈕對應四個LED燈,程式會亂數點亮LED,要記住順序依序按下那四個按鈕,若對了就會發出成功的音效,錯了則發出失敗的音效,然後在繼續下一次。

接線如下:
1.從Arduino板的GND接到麵包板
2.Arduino腳位5接開關右上腳,開關佐下腳接地
3.Arduino腳為13接LED長腳,LED短腳接GND



完整電路圖如下


從Arduino板的GND接到麵包板。
先差上四個瞬時型開關,左下腳接地。右上腳,從左到右,接到5、4、3、2。


然後開關的左上腳接LED的短腳,LED的長腳接到100 ohm的電阻。


再然後,從左到右,電阻的另一腳,接到Arduino板的腳位11、10、9、8。



聲音的部分:從Arduino板的腳位Ao接到1k ohm可變電阻的左腳,中腳接蜂鳴器的紅線,蜂鳴器的黑線接地,將以這個可變電組調整音量。

程式碼以功能區塊劃分說明。

#define TONE_PIN A0 // 定義蜂鳴器的腳位
#define NUMBER 4 // 共有四個LED、四個開關

// 定義開關與LED的腳位
const int switches[NUMBER] = {5, 4, 3, 2};
const int leds[NUMBER] = {11, 10, 9, 8};

// 定義各LED對應的音符頻率
const int notes[NUMBER] = {
    NOTE_C4, NOTE_D4, NOTE_E4,NOTE_F4,
};

// 使用狀態機的概念,要定義各種狀態
typedef enum{
    STATE_START, // 重新開始遊戲
    STATE_QUESTION, // 閃爍LED,給出問題
    STATE_ANSWER, // 等待使用者按下開關,回答問題
    STATE_CORRECT, // 正確,播放恭喜的音效,重置
    STATE_WRONG, // 錯誤,播放可惜的音效,重置
    STATE_MAX,
} State;

// 定義播放音效的資料
Melody melodys[MELODY_MAX] = {
    {noteStart, noteDurationsStart, 13},
    {noteCorrect, noteDurationsCorrect, 6},
    {noteWrong, noteDurationsWrong, 8},
};

// 以tone()播放音效
void playtone(int *note, int *noteDurations, int num){
  for(int thisNote = 0; thisNote < num; thisNote++){
    int noteDuration = 3000 / noteDurations[thisNote];
    tone(TONE_PIN, note[thisNote], noteDuration);

    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
  }
}

// 程式裡有三種音效,開始遊戲、回答正確、回答錯誤
typedef enum{
    MELODY_START,
    MELODY_CORRECT,
    MELODY_WRONG,
    MELODY_MAX,
} Melody_Enum;
// 把playtone()包一層,用起來比較簡單
void playMelody(Melody_Enum me){
    playtone(melodys[me].note, melodys[me].duration, melodys[me].number);
}

// 初始化,開關的腳位,開啟上拉電阻,所以預設狀態會是HIGH,
// 之後讀取開關狀態時,若讀到LOW,代表按下按鍵
void setup(){
    for(int i = 0; i < NUMBER; i++){
        pinMode(switches[i], INPUT);
        digitalWrite(switches[i], HIGH);
        
        pinMode(leds[i], OUTPUT);
        digitalWrite(leds[i], LOW);
    }
    
    // 亂數,將以亂數製作問題
    randomSeed(analogRead(A1));
}

// 只播放一個音符的函式,秀出問題時可用,
// 使用者回答時也可用。
void playOneTone(int note, float delayScale){
    int noteDuration = 3000 / 8;
    tone(TONE_PIN, note, noteDuration);
    
    int pauseBetweenNotes = noteDuration * delayScale;
    delay(pauseBetweenNotes);

}

// 準備好問題後,放出音效、閃爍LED 
void playQuestionsTone(){
    for(int i = 0; i < q_num; i++){
        digitalWrite(leds[questions[i]], HIGH);
        playOneTone(notes[questions[i]], 1.3);
        digitalWrite(leds[questions[i]], LOW);
    }


// 檢查問題跟使用者的回答是不是一樣
boolean check(){
    for(int i = 0; i < q_num; i++){
        if(questions[i] != answers[i]){
            return false;
        }
    }
    return true;
}

// 重點
void loop(){
    switch(state){ // 以state記錄目前狀態,根據狀態作事
        case STATE_START:{ // 開始遊戲,播放開始音效,進入問題狀態
            reset();
            playMelody(MELODY_START);
            state = STATE_QUESTION;
            break;
        }
        
        case STATE_QUESTION:{ // 問題狀態,以亂數製作問題
            questions = (int *)(malloc(sizeof(int) * q_num));
            answers = (int *)(malloc(sizeof(int) * q_num));
            for(int i = 0; i < q_num; i++){
                questions[i] = random(0, NUMBER);
            }
            answer_num = 0;
            playQuestionsTone(); // 顯示問題
            lastClickTime = millis(); // 記錄目前時間
            state = STATE_ANSWER;
            break;
        }
        
        case STATE_ANSWER:{
            // 看看使用者多久沒按開關了,超過10就宣布失敗
            const unsigned long nowTime = millis();
            if(nowTime >= lastClickTime + 10000UL){
                state = STATE_WRONG;
                break;
            }
            
            // 讀取每個開關的狀態
            for(int i = 0; i < NUMBER; i++){
                int ss = digitalRead(switches[i]);
                if(ss == LOW){
                    digitalWrite(leds[i], HIGH);
                    lastClickTime = nowTime;
                    answers[answer_num] = i; // 把使用者的輸入答案存起來
                    answer_num++;
                    playOneTone(notes[i], 1);
                    digitalWrite(leds[i], LOW);
                    delay(200);
                    break;
                }
                
            }
            
            if(answer_num >= q_num){ // 判斷是不是已經回答完了
                // 檢查回答正不正確
                state = check() ? STATE_CORRECT : STATE_WRONG;
            }
            break;
        }
        
        case STATE_CORRECT:{ // 回答正確,恭喜
            q_num++;
            playMelody(MELODY_CORRECT);
            delay(2000);
            state = STATE_START;
            break;
        }
        
        case STATE_WRONG:{ // 回答錯誤,可惜
            playMelody(MELODY_WRONG);
            delay(2000);
            state = STATE_START;
            break;
        }
        
        default:{
            state = STATE_START;
            break;
        }
    }
}


實際操作影片網址:https://www.youtube.com/watch?v=ZDvK9AbC1eY#action=share
資料來源 網址:http://yehnan.blogspot.tw/2012/02/arduinosimon-says.html

沒有留言:

張貼留言