Memory game: sometimes it does detect sound and sometimes it doesn't

Idea of the project:
I am creating a memory game where the user should repeat a sequence. This sequence consist of different colours of lights and also of one sound. For example, the user sees: red led lamp is active, than yellow lamp and than it hears beep. The task of the user is to press the button of red led lamp, than of yellow led lamp and to clap his or her hands.

Problem:
The sequence only with different colours of lights works perfectly fine. I have some troubles with sound detection. Sometimes it works but there are almost always a lot of misses.

Could you please help me to understand what goes wrong? Because I am quite desperate

My code:

#define PLAYER_WAIT_TIME 2000 // The time allowed between button presses - 2s 

/* if with servo motors:
#include <Servo.h>
Servo servo;
int angle = 0; */

/* if with led matrix: */
#include "LedControl.h"
/*
 pin 5 is connected to the DataIn 
 pin 4 is connected to the CLK 
 pin 3 is connected to LOAD nc 
 MAX72XX is the number of matrix, now it's 1, the last argument.
 */
LedControl lc=LedControl(5,4,3,1);
/* we always wait a bit between updates of the display */
unsigned long delaytime=1500;

byte sequence[100];           // Storage for the light sequence
byte curLen = 0;              // Current length of the sequence
byte inputCount = 0;          // The number of times that the player has pressed a (correct) button in a given turn 
byte lastInput = 0;           // Last input from the player
byte expRd = 0;               // The LED that's suppose to be lit by the player
bool btnDwn = false;          // Used to check if a button is pressed
bool wait = false;            // Is the program waiting for the user to press a button
bool resetFlag = false;       // Used to indicate to the program that once the player lost

byte soundPin = 7;            // Speaker output
int sensorPin=2;              // Sound sensor input

byte noPins = 3;              // Number of buttons/LEDs (While working on this, I was using only 2 LEDs)
                              // You could make the game harder by adding an additional LED/button/resistors combination.
byte pins[] = {13, 10, sensorPin}; // keep sensorPin the last one
// Button input pins and LED ouput pins - change these vaules if you want to connect your buttons to other pins
                              // The number of elements must match noPins below
                              
long inputTime = 0;           // Timer variable for the delay between user inputs

void setup() {
  /* if with servo:
  servo.attach(8);
  servo.write(angle); */

  /* if with led: */

  /*
   The MAX72XX is in power-saving mode on startup,
   we have to do a wakeup call
   */
  lc.shutdown(0,false);
  /* Set the brightness to a medium values */
  lc.setIntensity(0,7);
  /* and clear the display */
  lc.clearDisplay(0);

  pinMode(soundPin, OUTPUT);    //set the sound pin on the output mode forever
  pinMode(sensorPin, INPUT);    //set the sensor pin on the input
  
  delay(3000);                // This is to give me time to breathe after connection the arduino - can be removed if you want
  Serial.begin(9600);         // Start Serial monitor. This can be removed too as long as you remove all references to Serial below
  Reset();
}

///
/// Sets all the pins as either INPUT or OUTPUT based on the value of 'dir'
///

/* if with led: the next three functions are for expressing faces */

void writeHappy() {
  byte h[8]={B00100001,B01000101,B10000101,B10000000,B10000000,B10000101,B01000101,B00100001};
  lc.setRow(0,0,h[0]);
  lc.setRow(0,1,h[1]);
  lc.setRow(0,2,h[2]);
  lc.setRow(0,3,h[3]);
  
  lc.setRow(0,4,h[4]);
  lc.setRow(0,5,h[5]);
  lc.setRow(0,6,h[6]);
  lc.setRow(0,7,h[7]);
  delay(delaytime);
  }

void writeNeutral() {
  byte e[8]={B00000000,B01000001,B01000101,B01000000,B01000000,B01000101,B01000001,B00000000};
  lc.setRow(0,0,e[0]);
  lc.setRow(0,1,e[1]);
  lc.setRow(0,2,e[2]);
  lc.setRow(0,3,e[3]);
  lc.setRow(0,4,e[4]);
  lc.setRow(0,5,e[5]);
  lc.setRow(0,6,e[6]);
  lc.setRow(0,7,e[7]);
  //delay(delaytime);
  }

void writeSad() {
  byte s[8]={B10000010,B01000001,B00101101,B00100000,B00100000,B00101101,B01000001,B10000010};
  lc.setRow(0,0,s[0]);
  lc.setRow(0,1,s[1]);
  lc.setRow(0,2,s[2]);
  lc.setRow(0,3,s[3]);
  lc.setRow(0,4,s[4]);
  lc.setRow(0,5,s[5]);
  lc.setRow(0,6,s[6]);
  lc.setRow(0,7,s[7]);
  delay(delaytime);
  }

void setPinDirection(byte dir){
  for(byte i = 0; i < (noPins - 1); i++){   //minus one ensures you don't change the mode of the sound pins
    pinMode(pins[i], dir); 
  }
}

//send the same value to all the LED pins
void writeAllPins(byte val){
  for(byte i = 0; i < noPins; i++){
    digitalWrite(pins[i], val); 
  }
}

//Makes a (very annoying :) beep sound
void beep(byte freq){
  analogWrite(soundPin, 2);
  delay(freq);
  analogWrite(soundPin, 0);
  delay(freq);
}

///
/// Flashes all the LEDs together
/// freq is the blink speed - small number -> fast | big number -> slow
///
void flash(short freq){
  setPinDirection(OUTPUT); /// We're activating the LEDS now
  for(int i = 0; i < 3; i++){
    writeAllPins(HIGH);
    beep(50);
    delay(freq);
    writeAllPins(LOW);
    delay(freq);
  }
}

///
///This function resets all the game variables to their default values
///
void Reset(){
  writeNeutral();
  flash(500);
  curLen = 0;
  inputCount = 0;
  lastInput = 0;
  expRd = 0;
  btnDwn = false;
  wait = false;
  resetFlag = false;
}

///
/// User lost
///
void Lose(){

  /* if with servo:
  for(angle = 0; angle < 100; angle++)  
  {                                  
    servo.write(angle);               
    delay(10);                   
  }
  delay(500);
  */

  //to include lights and sound:
  flash(50);  
  beep(50);

  /* if led matrix */
  writeSad();
  writeNeutral();
}

/// User won:
void Win(){
  /*if with servo:
  // now scan back from 180 to 0 degrees
  for(angle = 100; angle > 0; angle--)    
  {                                
    servo.write(angle);           
    delay(15);       
  }
  delay(500); */

  /*if with led matrix */
  writeHappy();
  writeNeutral();

  }

///
/// The arduino shows the user what must be memorized
/// Also called after losing to show you what you last sequence was
///
void playSequence(){
  //Loop through the stored sequence and light the appropriate LEDs in turn or beep
  for(int i = 0; i < curLen; i++){
      Serial.print("index: ");
      Serial.println(i);
      Serial.print("sequence[i]: ");
      Serial.println(sequence[i]);
      if (sequence[i]==sensorPin) {
        beep(500);
      } else {
        digitalWrite(sequence[i], HIGH);
        delay(500);
        digitalWrite(sequence[i], LOW);
        delay(250);
      }
    } 
}

///
/// The events that occur upon a loss
///
void DoLoseProcess(){
  Lose();             // Flash all the LEDS quickly (see Lose function)
  delay(1000);
  /*playSequence();     // Shows the user the last sequence - So you can count remember your best score - Mine's 22 by the way :)
  delay(1000);*/
  Reset();            // Reset everything for a new game
}

///
/// Where the magic happens
///
void loop() {  
  if(!wait){      
                            //****************//
                            // Arduino's turn //
                            //****************//
    setPinDirection(OUTPUT);                      // We're using the LEDs
    Serial.println("arduino's turn");
    
    randomSeed(analogRead(A0));                   // so that the sequence differs in every game
    sequence[curLen] = pins[random(0,noPins)];    // Put a new random value in the next position in the sequence -  https://www.arduino.cc/en/Reference/random
    curLen++;                                     // Set the new Current length of the sequence
    
    playSequence();                               // Show the sequence to the player
    //beep(50);                                     // Make a beep for the player to be aware
    
    wait = true;                                  // Set Wait to true as it's now going to be the turn of the player
    inputTime = millis();                         // Store the time from the beginning of the program to measure the player's response time
  }else{ 
                            //***************//
                            // Player's turn //
                            //***************//
    setPinDirection(INPUT);                       // We're using the buttons
    Serial.println("player's turn");
    
    if(millis() - inputTime > PLAYER_WAIT_TIME){  // If the player takes more than the allowed time,
      DoLoseProcess();                            // All is lost :(
      return;
    }      
        
    if(!btnDwn){                                  // 
      expRd = sequence[inputCount];               // Find the value we expect from the player
      Serial.print("sequence[inputCount]: ");     // Serial Monitor Output - Should be removed if you removed the Serial.begin above
      Serial.println(expRd);                      // Serial Monitor Output - Should be removed if you removed the Serial.begin above
      
      for(int i = 0; i < noPins; i++){           // Loop through the all the pins
        if(pins[i]==expRd)                        
          continue;                               // Ignore the correct pin
        if(digitalRead(pins[i]) == HIGH){         // Is the button pressed or sound above the threshold
          lastInput = pins[i];
          resetFlag = true;                       // Set the resetFlag - this means you lost
          btnDwn = true;                          // This will prevent the program from doing the same thing over and over again
          Serial.print("Read: ");                 // Serial Monitor Output - Should be removed if you removed the Serial.begin above
          Serial.println(lastInput);              // Serial Monitor Output - Should be removed if you removed the Serial.begin above
        }
      }      
    }

    if(digitalRead(expRd) == 1 && !btnDwn)        // The player pressed the right button
    {
      inputTime = millis();                       // 
      lastInput = expRd;
      inputCount++;                               // The user pressed a (correct) button again
      btnDwn = true;                              // This will prevent the program from doing the same thing over and over again
      Serial.print("Read: ");                     // Serial Monitor Output - Should be removed if you removed the Serial.begin above
      Serial.println(lastInput);                  // Serial Monitor Output - Should be removed if you removed the Serial.begin above
    }else{
      if(btnDwn && digitalRead(lastInput) == LOW){  // Check if the player released the button
        btnDwn = false;
        delay(20);
        if(resetFlag){                              // If this was set to true up above, you lost
          DoLoseProcess();                          // So we do the losing sequence of events
        }
        else{
          if(inputCount == curLen){                 // Has the player finished repeating the sequence
            if (curLen != 0) {Win();};
            wait = false;                           // If so, this will make the next turn the program's turn
            inputCount = 0;                         // Reset the number of times that the player has pressed a button
            delay(delaytime);
          }
        }
      }
    }    
  }
}

Why "-1"?

for loops are great, aren't they?

Me too. I can't see any code for it. Where is it?

The last pin in pins[] is sensorPin. Because for other pins I use the same pin for input and output. But I use two different pins for sound (one for sensor and one for emitter). So I just assign one of them for forever for sensor and another one for emitter, and they shouldn't be reassigned. But sensorPin is still in pins[] so that it could be picked by the random choice to be put into the random sequence (and when it has to be "played", I use if statement to say if sensorPin then beep() ).

Ok, it makes a little more sense now.

It sounds like your sound sensor is the problem. Have you tested it in isolation? Perhaps using one of the basic test sketches on the examples menu?

What is this sensor? Please post a link to the specs and post a schematic showing how it is wired to the Arduino.

Yes, in isolation it works. I've a simple program that prints to the serial monitor, 0 when sound below the threshold and 1 when sound above the threshold, it also makes a beep when 1, so that works fine. I adjust the sensor on that program so that it has just the right threshold, and then I upload my "game" program, and there it doesn't work.

The sensor is wired like this: GND to the ground on the board, 5v to the + on the board where 5V is sent to, OUT to the digital pin 2, which is supposed to also work with interruption pins on my Arduino UNO. (Speaker output is wired to the pin 9).

Because all this wiring works perfectly within the simple testing program I suppose that the problem lies not in the wiring. I suppose that because every time the player has 2 seconds to input the right action, it loops around 13-15 times through the loop() (based on the output of the serial monitor). The code that is supposed to check if there is input from the right pin is in one place of the loop(), but in other places the computer is busy with other variables. If the clap happens in one of those moments then it's not registered. Therefore I've tried the interruption function to monitor the input constantly, but the functioning didn't improve. Well, it did, now it registers the clap sometimes, but then it doesn't register the next action, so the player looses anyways, even when she wasn't supposed to.

You should not need to use interrupts in this sketch. If using interrupts seems to offer a solution, that's an indication that something's wrong with the structure of your code: to many delays, blocking code, that kind of thing. There are quite a few uses of delay() in your code, but not every use is necessarily a problem. The longer the delay(), the more likely it is a problem. The more frequently the delay() is used, the more likely it is a problem. Reading through your code, I can't see what the problem is exactly, but maybe tomorrow when I am less full of pizza and wine I will be able to spot a problem. Hopefully you will get to the problem before I do!

1 Like

I haven't studied your code, but...

A hand-clap is "difficult" because it only lasts for a fraction of a second and unless it's an interrupt you can't read it continuously. It only gets read for an instant each time through the loop and that has to happen at the same time as the clap.

So, you can't have any delays in the loop that's "listening" for the clap and you need to minimize the other instructions in that loop, or your hardware has to stretch the clap-time so the processor can read it whenever it gets around to it.

How are you attaching both buttons and LEDs to the same pins? Please post a schematic.

Tip: you can easily enforce that the correct number of pins is in the array by writing

byte pins[noPins] = {13, 10, sensorPin};

Now, if the number of pins in the array is not 3 you will get an error when you compile the sketch.

Thank you for the tip about the noPins, I didn't know that. I'm more accustomed to Python, it's all a bit different there.

That's a very confusing diagram! I asked you for a schematic with a good reason, they are less confusing than diagrams like that one, if done correctly.

Fritzing has a schematic editor. Just switch to schematic view, organise your components to minimise crossing wires, and re-draw the wires (Fritzing will draw dashed lines, based on the breadboard diagram, to help you get them all right).

Fritzing breadboard diagrams can be a useful tool, although it would be better if Fritzing forced users to draw a schematic first, then helped the user to lay out a breadboard circuit to match their schematic.

The strange and annoying thing about Fritzing's breadboard view is that it lets users do exactly what you have done, which can't actually be done in reality: it allows two wires to be inserted into the same breadboard hole. That would be very difficult and would require so much force that it would damage a real Breadboard!

Your re-use of Arduino pins for both LEDs and buttons is quite clever, but there is a serious risk, I think. If the user were to press a button while the pin is in OUTPUT mode and set to LOW, there would be a short circuit which would damage the pin. Am I right about that?

Thank you! That's almost what I thought. But do you have experience with the interrupt function?
I can't understand how it works. It looks like it supposed to store the value if there was a clap somewhere during the loop, even if the computer was looping around other stuff. Or is it not the case? Because I don't know how to implement it without delays. It needs to give a player time to clap, to press the button and stuff, otherwise there is 0 time to do that right?

Also displaying the happy and sad face of the robot - it needs to be displayed for some time to give the user the feedback, otherwise there is not feedback and it's not obvious what is happening.

Or do you mean there could be places in the code where there could be delays, but not in the part that is supposed to listen to the inputs? And also how can you "stretch" the clap?

I don't know if schematic is done correctly, but I tried.

You've done the first thing I mentioned, you've switched to schematic view. That's one click. Then you gave up.

You haven't really started. Let us know when you've finished it!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.