2012/02/27

Arduino練習:Simon Says請你跟我這樣做

我看了這篇在Instructables的文章Arduino Simon Says,這邊有他完成後的影片

Simon Says這個遊戲是這麼玩的,有四個按鈕對應四個LED,程式會亂數點亮LED,你要記住順序,然後依序按下那四個按鈕,若對了就會發出成功的音效,錯了則發出失敗的音效,然後再繼續下一次。

電路很簡單,但程式落落長,讀別人的code很痛苦,所以我決定自己寫寫看。

首先練習一下Arduino板內建的上拉電阻(pullup resistor),如果你已經知道這是什麼,可以跳過。

Arduino板子上的數位腳位(digital pin),以pinMode()設定為INPUT模式時,若該腳位沒有連接到任何東西,那麼以digitalRead()讀取時,會讀到空氣、環境的值,也就是說是亂數,通常我們不希望如此,我們希望該腳位能有個明確的值,HIGH或LOW。之前的Arduino練習:以開關切換LED明滅狀態,也是如此,開關的一邊接到5V,另一邊則接地,這麼一來,不是HIGH就是LOW,不會讀到亂數。

接線如下:

從Arduino板的GND接到麵包板。 
Arduino腳位5接開關右上腳,開關左下腳接地。
Arduino腳位13接LED長腳,LED短腳接GND。



按下開關的話,開關左右兩邊連接在一起,所以腳位3會讀到LOW,而在不按下開關的情況,我們希望讀到HIGH,但是開關右下腳並沒有連接5V啊?可以利用Arduino板子內建的上拉電阻,以程式控制,如下:

void setup(){
    Serial.begin(115200);
    pinMode(13, OUTPUT); // LED,平常點亮,按下開關則熄滅
    pinMode(5, INPUT); // 接到開關
    digitalWrite(5, HIGH); // 開啟上拉電阻
}

void loop(){
    int status = digitalRead(5); // 讀取開關狀態
    digitalWrite(13, status); // 根據開關狀態控制LED明滅
    Serial.println(status);
}

其中,當腳位5設為INPUT模式時,可用digitalWrite(5, HIGH)開啟上拉電阻,如此一來,在平常時(不按下開關),就會讀到HIGH。

執行程式後,開啟序列埠監控視窗,平常會看到1,若按下開關,則為0。


好,接下來,要開始製作Simon Says。

電路圖(Fritzing格式)與程式原始碼,可在此下載

完整的電路圖如下,接下來會一部份一部份慢慢接。


從Arduino板的GND接到麵包板。

先插上四個瞬時型開關,左下腳接地。右上腳,從左到右,接到腳位5、4、3、2。


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


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


聲音的部份。從Arduino板的腳位A0接到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()播放音效,這裡就不解說了。
// 用法請看Arduino練習:loudspeaker揚聲器
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;
        }
    }
}

完成後的影片如下:



參考資料:


104 comments:

  1. shin chen16/8/13 14:48

    您好,我一直無法完整建立SimonSays.cpp的檔案,不知道是否方便提供一下~txs^^

    ReplyDelete
    Replies
    1. 在這裡
      https://github.com/yehnan/arduino_practices/tree/master/SimonSays

      Delete
  2. shin chen16/8/13 15:27

    恩~我也有參考此處的資料了,但libraries裡還缺少SimonSays.cpp的檔案,因此一直無法完成,是哪個環節有loss掉了嗎?

    ReplyDelete
    Replies
    1. 不需要SimonSays.cpp啊,

      程式主體都在SimonSays.ino裡,
      另外需要SimonSays.h與pitches.h作為輔助,

      SimonSays.fzz是電路圖,可刪除,
      SimonSays_pullup_resistor.ino也不需要,可刪除。

      你是用Arduino IDE嗎?

      Delete
  3. shin chen16/8/13 15:35

    yes..........耶~~所以我是多放了一個cpp的程式,我把他刪掉就可以運作了~~txs....因為看範例裡面都有放cpp因此有樣學樣~~哈哈~~~感恩^^

    ReplyDelete
  4. 請問一下 我把SimonSays.ino放進去 會有錯誤 SimonSays.h pitches.h 是要一起放進去嗎?

    ReplyDelete
    Replies
    1. 三支檔案都要放在子目錄SimonSays裡。

      Delete
  5. 我全部放一起也會有問題 是有先後順序的差別嗎 還是有要放特定的地方呢??

    ReplyDelete
    Replies
    1. 請問什麼問題?錯誤訊息?

      Delete
  6. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. 不懂你的意思。

      我這一篇裡的電路與程式,只要一直亂按開關,就會被判定為錯誤,然後就會重新出題。

      Delete
    2. This comment has been removed by the author.

      Delete
    3. 你說的迴圈是loop()這支函式?

      就直接return,然後Arduino就會再次執行loop()了。應該說Arduino(微控制器)會不斷地執行loop()這支函式。

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  9. This comment has been removed by the author.

    ReplyDelete
  10. 改寫setup與loop的部份,大概如下。我沒有組裝電路,雖然程式可以編譯成功,但也不知道是否正確。請當做參考。

    unsigned int number;
    unsigned long timePrevious;
    unsigned long lastDebounceTime;
    #define DEBOUNCE_DELAY 200
    void setup() {
    pinMode(A, OUTPUT);
    pinMode(B, OUTPUT);
    pinMode(C, OUTPUT);
    pinMode(D, OUTPUT);
    pinMode(E, OUTPUT);
    pinMode(F_PIN, OUTPUT);
    pinMode(G, OUTPUT);
    pinMode(CA1, OUTPUT);
    pinMode(CA2, OUTPUT);
    pinMode(CA3, OUTPUT);
    pinMode(CA4, OUTPUT);

    number = 9999;
    timePrevious = millis();
    }

    void displayNumber(){
    pickDigit(1);
    pickNumber(number%10);
    delay(delay_time);
    //
    pickDigit(2);
    pickNumber((number/10)%10);
    delay(delay_time);
    //
    pickDigit(3);
    pickNumber((number/100)%10);
    delay(delay_time);
    //
    pickDigit(4);
    pickNumber((number/1000)%10);
    delay(delay_time);
    }
    void loop(){
    const unsigned long t = millis();
    if(t - timePrevious >= 1000){
    timePrevious = t;
    number--;
    }

    if(digitalRead(buttonPin) == HIGH && (t - lastDebounceTime) > DEBOUNCE_DELAY){
    lastDebounceTime = t;
    number = 9999;
    }
    displayNumber();
    }

    ReplyDelete
    Replies
    1. 以全域變數number記錄想要顯示的數字(9999~0000),在loop裡不斷地呼叫displayNumber刷新顯示數字。

      呼叫millis()並比對上次記錄的時間timePrevious,超過一秒後就將number減一倒數。

      另一個lastDebounceTime則用來解決機械開關的bounce問題,被按下的話就number = 9999重置。

      Delete
    2. 謝謝可以了
      我可以麻煩你在程式旁邊註解嗎
      對程式不是很了解

      Delete
  11. 我把number改成10 它跳到5535
    要怎麼樣才能停在0

    ReplyDelete
    Replies
    1. 因為0再減一的話,會變成65535。

      在number--;的附近作判斷,若已經到0了,就不要再改變了。

      至於註解的話,就請您閱讀原始程式碼。

      Delete
    2. 在number--;的附近作判斷 ??
      不太懂
      抱歉我程式不太行

      Delete
    3. 嗯,可改寫number--;,
      用if判斷number是否大於0,若大於0就--,若等於0,就不作事。
      這樣就可以在到0時,不再改變。

      Delete
  12. 想請教如規定亂數不重複應如何分析。

    ReplyDelete
    Replies
    1. 我程式碼裡有使用Arduino內建的亂數函式randomSeed與random。

      至於若想要分析亂數夠不夠亂,這我不太熟,請找本書研究一下吧。

      Delete
    2. 我意思是如有50顆LED,上次如抽中(2,4,6)下次的亂數不會重複(2,4,6),網上也有用c分析,但看不明白。

      Delete
    3. 如果是不想重複的話,記錄上次的數字,下次產生亂數時若重複,再取下一個亂數。

      如果是想分析上次下次會不會重複,這我不熟,請找本書研究一下吧。

      Delete
  13. 煩請再賜教,我嘗試在case段落加一個Go On Button, 不知什麼問題,在while裡Go On Button總是感測不到的?
    case STATE_CORRECT:{
    q_num++;
    playMelody(MELODY_CORRECT);
    delay(2000);
    // Go On Button.........
    boolean gn = digitalRead(GoOnButton);
    while(gn == 1){ //按鈕沒有按下,led在閃
    digitalWrite(GoOnLed, HIGH);
    delay(100);
    digitalWrite(GoOnLed, LOW);
    delay(100);
    }
    state = STATE_START;
    break;

    ReplyDelete
    Replies
    1. 因為boolean gn = digitalRead(GoOnButton)之前有個delay(2000),
      所以程式停在那邊(為了讓音樂完整播放),
      2000毫秒後才會執行digitalRead(GoOnButton),但一瞬即過,
      人類幾乎不可能在那一瞬間剛好按下按鈕。

      想要達到你想要的功能的話,可以再加入一個狀態STATE_XXX,然侯改寫程式。

      Delete
    2. 另外,
      boolean gn = digitalRead(GoOnButton);
      while(gn == 1){ //按鈕沒有按下,led在閃
      digitalWrite(GoOnLed, HIGH);
      delay(100);
      digitalWrite(GoOnLed, LOW);
      delay(100);

      如果gn==1成立,那麼就會進入while迴圈,而這是個無窮迴圈。

      Delete
    3. 就是希望按鍵不動gb==1閃, 按下gb==0就繼續

      Delete
    4. 賜教,如何優化較好

      Delete
    5. 我的程式裡,原本當玩家對了或錯了,都會先播放一段音樂,然後進入狀態STATE_START重新開始遊戲。
      你現在可再加入一個狀態,當玩家對了或錯了,播放音樂完後,就進入新狀態,
      在新狀態裡,持續讀取開關,若gb==1就讓LED閃爍,若gb==0,就進入STATE_START重新開始遊戲。

      Delete
  14. 請問一下,用一個開關控制LED亮滅,輸入是隨機0或1 要怎麼改寫呢 謝謝!

    ReplyDelete
    Replies
    1. 使用Arduino內建函式randomSeed(seed)和random(),

      用法與API請參考官方參考文件
      http://arduino.cc/en/Reference/RandomSeed
      http://arduino.cc/en/Reference/Random

      譬如random(2)會回傳0或1

      Delete
  15. 版大你好,很高興看到你的blog分享,我是arduino新手,

    目前我修改了ㄧ些程式碼,使用IR遙控器與蜂鳴器結合,利用遙控器上的數字鍵取代麵包版上的實體按鍵,但是我使用遙控器按壓出來的聲音跟正常聲音不一樣,想請問這是甚麼問題呢?

    我的程式碼如下:

    #include

    int RECV_PIN = 11;
    int LED = 10;

    int speakerPin = 13;
    int val = 0;

    IRrecv irrecv(RECV_PIN);
    decode_results results;

    int length = 1; // the number of notes
    char notes[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' }; // a space represents a rest
    int beats[] = { 1 };
    int tempo = 300;

    void playTone(int tone, int duration) {
    for (long i = 0; i < duration * 100L; i += tone * 2) {
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
    }
    }

    void playNote(char note, int duration) {
    char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
    int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };
    // play the tone corresponding to the note name
    for (int i = 0; i < 8; i++) {
    if (names[i] == note) {
    playTone(tones[i], duration);
    }
    }
    }

    void setup()
    {
    pinMode(LED,OUTPUT);
    pinMode(speakerPin,OUTPUT);
    Serial.begin(9600);
    irrecv.enableIRIn();
    }

    void loop() {

    if (irrecv.decode(&results))
    {
    irrecv.resume();
    irdisplay(results.value);
    }

    }

    void irdisplay(unsigned long value)
    {
    switch(value){
    case 0xFFA25D:
    digitalWrite(LED, LOW);
    digitalWrite(speakerPin, LOW);
    break;
    case 0xFF629D:
    digitalWrite(speakerPin, HIGH);
    break;
    case 0xFFE21D:
    digitalWrite(LED, HIGH);
    break;

    case 0xFF30CF: //1-Do
    playNote(notes[0], 300);
    break;

    case 0xFF18E7://2-Re
    playNote(notes[1], 300);
    break;

    case 0xFF7A85://3-Mi
    playNote(notes[2], 300);
    break;

    case 0xFF10EF://4-Fa
    playNote(notes[3], 300);
    break;

    case 0xFF38C7://5-Sol
    playNote(notes[4], 300);
    break;
    }
    }


    感謝版大

    ReplyDelete
    Replies
    1. 看不出有何問題。
      原本的程式?聲音有何不同?

      Delete
    2. 應該說傳統arduino piano的方式是利用button按壓然後給予蜂鳴器音高聲音如:https://www.youtube.com/watch?v=1F2DSQc5OJA
      但我現在使用遙控器按壓聲音很沉悶沒這麼響亮,音高不是很準確,想請問有可能是什麼原因造成的嗎?

      Delete
    3. 或許是蜂鳴器沒得到夠大的電流吧。換個腳位試試看。

      Delete
    4. 好的,謝謝你的提醒,
      但我換腳位還是一樣結果,還有什麼方法嗎?

      謝謝

      Delete
    5. 看不出來。
      你也沒有使用tone,應該也不會跟IRremote使用的計時器衝突,嗯,不清楚。

      Delete
    6. 抱歉,你說的tone是指playTone這個方法嗎?

      聲音一直出不來讓我想破頭還是無解Orz

      Delete
    7. 不是,我說的是Arduino的函式tone。

      換換腳位吧。

      Delete
    8. 好,謝謝你!

      Delete
  16. 請問你有遇過digitalRead在自訂函數中抓按鈕值時,出現的是-1,但是把他移到loop的函數時,就又正常抓到1的值?

    ReplyDelete
    Replies
    1. digitalRead只會回傳0或1。

      哪裡出錯了吧?

      Delete
  17. 你好,我只有複製程式碼貼上編譯,顯示variable or field 'playMelody' declared void,不知道為甚麼....

    ReplyDelete
    Replies
    1. 原因請看http://yehnan.blogspot.tw/2012/02/arduinoenum.html

      可先使用我的原始碼
      https://github.com/yehnan/arduino_practices/tree/master/SimonSays

      Delete
  18. 為甚麼下面出現:
    sketch_dec26a.ino:7:17: error: variable or field 'playMelody' declared void
    sketch_dec26a.ino:7:17: error: 'Melody_Enum' was not declared in this scope
    sketch_dec26a.ino:10:5: error: 'NOTE_C4' was not declared in this scope
    sketch_dec26a.ino:10:14: error: 'NOTE_D4' was not declared in this scope
    sketch_dec26a.ino:10:23: error: 'NOTE_E4' was not declared in this scope
    sketch_dec26a.ino:10:31: error: 'NOTE_F4' was not declared in this scope
    sketch_dec26a.ino:24:1: error: 'Melody' does not name a type
    sketch_dec26a.ino: In function 'void playMelody(Melody_Enum)':
    sketch_dec26a.ino:51:14: error: 'melodys' was not declared in this scope
    sketch_dec26a.ino: In function 'void playQuestionsTone()':
    sketch_dec26a.ino:82:24: error: 'q_num' was not declared in this scope
    sketch_dec26a.ino:83:27: error: 'questions' was not declared in this scope
    sketch_dec26a.ino: In function 'boolean check()':
    sketch_dec26a.ino:91:24: error: 'q_num' was not declared in this scope
    sketch_dec26a.ino:92:12: error: 'questions' was not declared in this scope
    sketch_dec26a.ino:92:28: error: 'answers' was not declared in this scope
    sketch_dec26a.ino: In function 'void loop()':
    sketch_dec26a.ino:101:12: error: 'state' was not declared in this scope
    sketch_dec26a.ino:103:19: error: 'reset' was not declared in this scope
    sketch_dec26a.ino:110:13: error: 'questions' was not declared in this scope
    sketch_dec26a.ino:110:54: error: 'q_num' was not declared in this scope
    sketch_dec26a.ino:111:13: error: 'answers' was not declared in this scope
    sketch_dec26a.ino:115:13: error: 'answer_num' was not declared in this scope
    sketch_dec26a.ino:117:13: error: 'lastClickTime' was not declared in this scope
    sketch_dec26a.ino:125:27: error: 'lastClickTime' was not declared in this scope
    sketch_dec26a.ino:135:21: error: 'lastClickTime' was not declared in this scope
    sketch_dec26a.ino:136:21: error: 'answers' was not declared in this scope
    sketch_dec26a.ino:136:29: error: 'answer_num' was not declared in this scope
    sketch_dec26a.ino:146:16: error: 'answer_num' was not declared in this scope
    sketch_dec26a.ino:146:30: error: 'q_num' was not declared in this scope
    sketch_dec26a.ino:154:13: error: 'q_num' was not declared in this scope
    Error compiling.

    ReplyDelete
    Replies
    1. 原因請看http://yehnan.blogspot.tw/2012/02/arduinoenum.html

      解決問題之後的程式碼,可到底下網址下載:
      https://github.com/yehnan/arduino_practices/tree/master/SimonSays

      Delete
  19. 您好,我嘗試為這個小遊戲加入計分統計,想用序列埠顯示,加入程式碼使得原本遊戲無法正常作用,蜂鳴器也無聲音,不太清楚是哪個環節出錯
    我宣告了兩個變數score和totscore,在出題目時每一個音就算1分,答對幾個音即得幾分score,答錯那段撥放結束音樂程式碼中加入將得分score放入總得分totscore,這些都還能正常遊戲
    但加入序列埠後就無法正常遊戲了

    ReplyDelete
  20. 您好,我嘗試為這個小遊戲加入計分統計,想用序列埠顯示,加入程式碼使得原本遊戲無法正常作用,蜂鳴器也無聲音,不太清楚是哪個環節出錯
    我宣告了兩個變數score和totscore,在出題目時每一個音就算1分,答對幾個音即得幾分score,答錯那段撥放結束音樂程式碼中加入將得分score放入總得分totscore,這些都還能正常遊戲
    但加入序列埠後就無法正常遊戲了

    ReplyDelete
    Replies
    1. 請提供程式碼。

      Delete
    2. This comment has been removed by the author.

      Delete
  21. This comment has been removed by the author.

    ReplyDelete
  22. 我只抓我有添加程式的部分程式碼
    註解部分為自己添加的程式

    int score =0; //自行宣告計分變數
    int totscore; //自行宣告總分變數

    void setup(){
    Serial.begin(9600); //自行加入,想透過Serial顯示分數
    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));//亂數製作問題
    SPI.begin();
    max7219(SCANLIMIT,7);
    max7219(DECODEMODE,0);
    max7219(INTENSITY,8);
    max7219(DISPLAYTEST,0);
    max7219(SHUTDOWN,1);
    for(byte i=0;i<8; i++)
    {
    max7219(i+1,0);
    }

    }



    case STATE_QUESTION:{

    for(byte j=0; j<4; j++){
    for(byte i=0; i<8; i++)
    {
    max7219(i+1,sprite[j][i]);
    }
    delay(200);
    }

    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);
    score++; //自行加入
    }
    answer_num = 0;
    playQuestionsTone();
    lastClickTime = millis();
    state = STATE_ANSWER;
    break;
    }





    case STATE_CORRECT:{
    q_num++;
    playMelody(MELODY_CORRECT);
    totscore = score; //自行加入,答對得分放入總得分;
    for(byte i=0; i<8; i++)
    {
    max7219(i+1,win[i]);
    }
    delay(2000);
    state = STATE_START;
    break;
    }



    case STATE_WRONG:{
    Serial.print(totscore); //答錯顯示最後總得分
    playMelody(MELODY_WRONG);
    for(byte i=0; i<8; i++)
    {
    max7219(i+1,lose[i]);
    }

    delay(2000);
    state = STATE_START;
    break;
    }

    ReplyDelete
    Replies
    1. > 在出題目時每一個音就算1分,答對幾個音即得幾分score,
      這兩句話矛盾。你要的是後者吧?

      你在case STATE_QUESTION:裡加入score++;,該處是顯示問題的地方,
      還沒有回答問題,怎麼會去增加score?

      你可以在check()做檢查,計算玩家答對了幾個音。

      > 這些都還能正常遊戲 但加入序列埠後就無法正常遊戲了
      從你給的程式碼,我看不出為何如此。


      你改成使用LED矩陣吧,使用了max7219,我假定在未加入Serail之前,max7219的部份已完成。

      Delete
  23. 版大您好
    我才剛剛玩arduino
    我有個設想要利用arduino 做一個按照順序開燈可以打開電磁鎖的機關
    目前我
    開關已經做好了
    做4-6個開關a b c d e f
    這四個開關一開始都是關掉的
    當按照順序從a b c d e f 依序打開燈 電磁鎖也可以打開
    連線的部分我基本都會
    但是
    想請問這種按照順序的程式要如何寫出來了

    謝謝您的答覆

    ReplyDelete
  24. > 4-6個開關a b c d e f
    四個還是六個?

    宣告全域陣列,儲存使用者開燈的順序,
    當燈全暗時,進入預備輸入開燈順序的狀態,
    當輸入(開燈)六次,而且順序正確,就打開電磁鎖。
    當輸入六次,而順序錯誤,就不作事,並且清除陣列。
    若輸入到一半,卻超過一定的時間,逾時,不作事,並且清除陣列。

    大概是這樣吧,運用finite state machine(有限狀態機)的技巧。

    ReplyDelete
    Replies
    1. 您好 我用aedunio做的 因為我不會這個 參考別人寫的 不知道對不對 麻煩您幫我看一下
      void setup() {
      Serial.begin(9600);
      pinMode(2, OUTPUT);
      pinMode(3, OUTPUT);
      pinMode(4, OUTPUT);
      pinMode(5, OUTPUT);
      pinMode(A0, INPUT);
      pinMode(A1, INPUT);
      pinMode(A2, INPUT);
      pinMode(A3, INPUT);

      pinMode(13, OUTPUT);

      digitalWrite(2, HIGH);
      digitalWrite(3, HIGH);
      digitalWrite(4, HIGH);
      digitalWrite(5, HIGH);

      analogWrite(A0, 0);
      analogWrite(A1, 0);
      analogWrite(A2, 0);
      analogWrite(A3, 0);
      digitalWrite(13, LOW);

      }

      int val_1 = 0;
      int val_2 = 0;
      int val_3 = 0;
      int val_4 = 0;
      int previous = 0;






      void loop() {
      val_1 = digitalRead(1);
      val_2 = digitalRead(2);
      val_3 = digitalRead(3);
      val_4 = digitalRead(4);

      if(a && previous = 0){
      previous = 1;
      }else if(b && previous = 1){
      previous = 2;
      }else if(c && previous = 2){
      previous = 3;
      }else if(d && previous = 3){
      previous = 4;

      digitalWrite(13,HIGH);


      }
      您好 這樣我參考一些寫的 不知道能否成功呢!

      Delete
    2. 你把腳位A0 A1 A2 A3作為按鈕的輸入吧,那麼為什麼還要呼叫analogWrite(A0, 0);呢?

      比較previous的數值時,應該用 == ,而不是 = 。

      loop()裡的變數名a b c d,根本沒有宣告啊,連編譯都不會過。


      大概如下吧
      http://pastebin.com/ctrSXJGd
      不過功能有限,
      一旦按對順序,就會一直點亮腳位13的燈。

      Delete
  25. 葉大大您好:目前正在用arduino 寫出 控制5個led燈
    並且用四個光敏電阻去控制四個led燈,當我四個光敏電阻從第一顆依序到第四顆的值,都有超過我所設定的值
    每一次開啟一顆led燈(持續亮led燈直到4顆光敏電阻判斷完成),當四顆光敏電阻都判斷完成之後,正確的話把第五顆led燈點開。
    不正確的話 就重開在來過。

    有參考葉大的狀態程式,但仍然有問題,不管我順序怎麼調 每一次都是進入不正確的程序裡…
    在想是不是因為我讀類比訊號的關係… 一直抓不到正確的順序,正確的順序 已在狀態機的 question 寫死了

    以下是我的程式碼:

    如果一直同一顆照光敏的話 他判斷四次之後 就會檢查對不對了 要改善

    另目前光敏電阻目前哪一點亮 都一律給答案1 所以不管哪顆先點亮 都是給答案1

    ReplyDelete
    Replies
    1. 以下是我的程式碼:
      #define SERIAL_BAUDRATE 9600
      const int leds[5]={2,3,4,5,6};
      float relay_new_value[4];

      int questions[4];
      int answers[4];
      int relayvalue1=0; //讀第1顆光敏電阻的類比值
      int relayvalue2=0; //讀第2顆光敏電阻的類比值
      int relayvalue3=0; //讀第3顆光敏電阻的類比值
      int relayvalue4=0; //讀第4顆光敏電阻的類比值

      typedef enum{

      STATE_START,
      STATE_QUESTION,
      STATE_ANSWER,
      STATE_CORRECT,
      STATE_WRONG,
      STATE_COMPLETE,
      }State;

      State state;


      void setup()
      {
      for(int i=0; i<4;i++)
      {
      pinMode(leds[i],OUTPUT); //亮led 是我的光敏開四個電器控制relay的訊號
      digitalWrite(leds[i],LOW);
      // pinMode(switches[i],INPUT_PULLUP); //判斷switch 按鈕 input_pullup
      }

      Serial.begin(SERIAL_BAUDRATE);

      state = STATE_START;
      q_num=3;
      answer_num=0;

      }


      boolean checkAnswers() //檢查答案對不對 (光敏順序)

      {

      for(int i = 0; i < 4; i++){
      if(questions[i] != answers[i]){
      return false;
      }
      }
      return true;
      }

      Delete

    2. void loop()
      {

      //讀第1顆光敏電阻的ad值-----------------------------------
      relayvalue1=analogRead(A1); //第一顆光敏電阻
      relay_new_value[0]=0.3*relayvalue1 + 0.7*analogRead(A1);
      Serial.print("first photo------>>>>");
      Serial.println(floor(relay_new_value[0]));
      delay(500);
      //讀第1顆光敏電阻的ad值------------------------------------



      //讀第2顆光敏電阻的ad值-----------------------------------
      relayvalue2=analogRead(A2); //第一顆光敏電阻
      relay_new_value[1]=0.3*relayvalue2 + 0.7*analogRead(A2);
      Serial.print("second photo------>>>>");
      Serial.println(floor(relay_new_value[1]));
      delay(500);
      //讀第2顆光敏電阻的ad值------------------------------------

      //讀第3顆光敏電阻的ad值-----------------------------------
      relayvalue3=analogRead(A3); //第一顆光敏電阻
      relay_new_value[2]=0.3*relayvalue3 + 0.7*analogRead(A3);
      Serial.print("third photo------>>>>");
      Serial.println(floor(relay_new_value[2]));
      delay(500);
      //讀第3顆光敏電阻的ad值------------------------------------



      //讀第4顆光敏電阻的ad值-----------------------------------
      relayvalue4=analogRead(A4); //第一顆光敏電阻
      relay_new_value[3]=0.3*relayvalue4+ 0.7*analogRead(A4);
      Serial.print("four photo------>>>>");
      Serial.println(floor(relay_new_value[3]));
      delay(500);
      //讀第4顆光敏電阻的ad值------------------------------------



      switch(state)
      {

      case STATE_START:
      {
      digitalWrite(2,LOW);
      digitalWrite(3,LOW);
      digitalWrite(4,LOW);
      digitalWrite(5,LOW);
      digitalWrite(12,LOW);


      state = STATE_QUESTION;
      }
      break;



      case STATE_QUESTION:
      {

      //自己訂的順序
      questions[0]=1;
      questions[1]=2;
      questions[2]=3;
      questions[3]=4;

      answer_num=0;

      state = STATE_ANSWER;
      break;
      }


      case STATE_ANSWER:
      {


      // 讀取每個開關的狀態
      for(int i=0;i<4;i++)
      {

      int ss= relay_new_value[i];


      if(ss >=950)
      {
      digitalWrite(leds[i],HIGH);
      answers[answer_num] = i; // 把使用者的輸入答案存起來

      answer_num++;

      delay(200);

      break;
      }

      }



      if(answer_num == 4) // 判斷是不是已經回答完了
      {
      Serial.print("check your answer!!!!!>>>>");

      state = checkAnswers() ? STATE_CORRECT:STATE_WRONG;


      }


      break;
      }







      case STATE_CORRECT: // 回答正確,恭喜
      {


      digitalWrite(12,HIGH);

      digitalWrite(2,HIGH);
      digitalWrite(3,HIGH);
      digitalWrite(4,HIGH);
      digitalWrite(5,HIGH);

      break;
      }


      case STATE_WRONG: // 回答錯誤,可惜
      {

      Serial.print("OH NO you wrong!!!!!>>>>");
      state = STATE_START;

      break;
      }





      }//switch end



      } //loop end

      Delete
    3. 試試看吧,後果自負。
      http://pastebin.com/fTK5wb4r

      拿掉不必要的delay,
      修改狀態STATE_QUESTION裡的設定。

      Delete
    4. This comment has been removed by the author.

      Delete
    5. This comment has been removed by the author.

      Delete
    6. This comment has been removed by the author.

      Delete
  26. 紀錄上次達到判斷值的光敏電阻的編號,
    下次有達到判斷值的話,檢查是不是同一個。

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. 不管,反正就是紀錄上次達到判斷值的那個東西。
      下次再達到判斷值的時候,檢查跟上次是不是同一個。

      Delete
    3. This comment has been removed by the author.

      Delete
    4. This comment has been removed by the author.

      Delete
    5. This comment has been removed by the author.

      Delete
    6. This comment has been removed by the author.

      Delete
    7. This comment has been removed by the author.

      Delete
    8. This comment has been removed by the author.

      Delete
    9. This comment has been removed by the author.

      Delete
    10. This comment has been removed by the author.

      Delete
    11. This comment has been removed by the author.

      Delete
  27. This comment has been removed by the author.

    ReplyDelete
  28. This comment has been removed by the author.

    ReplyDelete
  29. This comment has been removed by the author.

    ReplyDelete
  30. This comment has been removed by the author.

    ReplyDelete
  31. This comment has been removed by the author.

    ReplyDelete
  32. http://pastebin.com/BZ3vAj4G
    大概是這樣吧

    抱歉,不想看別人的code。

    ReplyDelete
    Replies
    1. 謝謝葉大
      另想請問一下 ,如果我想用一個arduino接五個relay去控制五個110v的電器
      arduino外接變壓器要接幾v幾安培的才不會燒掉主板呢?

      Delete
    2. 看情況。
      若是Uno
      https://www.arduino.cc/en/Main/ArduinoBoardUno
      筒狀電源輸入端子可以是7-12V,
      Uno穩壓器會轉為5V,
      所以relay要選以5V控制的,
      至於安培數,就看你接的電子零件的功率。

      Delete
    3. 葉大你好:我是uno的 我變壓器是買12v 3a的
      剛剛試了接五個 5v控制的relay各接五個110v的電器
      結果arduino就燒掉了…
      不曉得葉大知道是什麼原因嗎?

      Delete
    4. 呃,燒掉了?沒事吧。

      您只描述結果,我怎麼知道原因呢,呵呵。
      原因當然是接錯線路囉。

      Delete
    5. 抱歉抱歉 因為我沒講清楚
      我接的五個relay的接法是
      繼電器模組的vcc 和gnd 是接到arduino的 5v輸出的pin腳以及 gnd pin腳
      繼電器的模組訊號是接arduino的digital的pin腳
      五個relay分別控制五個電器這樣

      只是我一個繼電器模組 分開測試 都是能夠驅動了

      是不是五個relay的vcc和gnd 不能直接接上arduino的5v pin腳
      要額外給呢?

      Delete
    6. 我後來看了一下 我用的繼電組模組 寫說
      VCC=電源+5V輸入,靜態電流4mA,驅動電流65mA
      GND=電源-5V輸入
      IN=訊號或GND短路
      然後我接了五個… 加起來 是不是已超過 arduino本身能供應的總電流
      所以才壞了呢?

      Delete
    7. 嗯,有點可能,
      如果五個全部都驅動的話,會是65x5 = 325,
      但一般來說,arduino 板子的電流驅動能力,應該超過500才對。

      > 是不是已超過
      要看是哪塊板子。

      否則,另行供應5v電源給繼電器囉。

      Delete
    8. This comment has been removed by the author.

      Delete
    9. 我自己是用uno的板子,自己查了一下arduino上的晶片是atmega328的
      有提到如果所有腳位及輸出的電流量 超過200ma的話 就會燒壞…

      那這樣子的話,只能額外供應5v電源給繼電器使用了
      通常常選擇安培數只要不要小於 總total的電流應該就可以了吧?
      比如買5v 2a的變壓器?

      Delete
    10. 照理說,繼電器模組,除了繼電器本身,還會加上其他的零件,如電晶體放大電流等等。

      > 超過200ma的話 就會燒壞
      這指的是atmega328本身,
      arduino uno板的穩壓器應該高於200ma才對。

      > 額外供應5v電源
      這當然是比較好的作法。

      > 比如買5v 2a的變壓器?
      yes.

      Delete
    11. 葉大您好
      那我繼電器模組五顆 接上額外的變壓器供的5v電
      那變壓器的gnd 要和arduino腳位的gnd 共地嗎?
      還是不需要呢?

      Delete
    12. 你是用繼電器模組嗎?
      模組的腳位都弄好了,通常,一邊接到arduino、另一邊接到電器,如此即可。

      Delete
    13. This comment has been removed by the author.

      Delete
    14. 是的葉大 我是用繼電器模組
      但因繼電器模組有三個腳位 一個是5V 一個是GND 一個是訊號腳位
      那我ARDUINO的GND 應該不需要在接到繼電器模組的GND了吧
      繼電器模組我直接用額外的變壓器供5V給繼電組的5V腳位 以及他的GND腳位
      不需在和ARDUINO的GND 共地了吧~~~

      Delete
    15. 如果需要的話,繼電器模組會幫你共地。

      Delete