Xylophone Tune Detector

Change the baud rate to 250000 so as not to slow things down too much. Then instead of opening the serial monitor to see the numbers, open up the serial plotter in the tools menu and it will draw the trace which will be much easier to see what you are getting.

OK, so I did some testing. I changed the test code, and just hooked up one sensor. I tapped that sensor repeatedly and not all strikes were registering. The values for about 12 strikes of the same intensity were:

251,1023,23,7,162,753,30,474,118,1.

So it looks like a sensor or mounting problem. I have the enclosed sensors coming in today. Any other thoughts? I also attached a picture of my hookup so I provide you all with all the info that I have.

// these constants won't change:
const int ledPin = 13;      // led connected to digital pin 13
const int knockSensor = A0; // the piezo is connected to analog pin 0
const int threshold = 25;  // threshold value to decide when the detected sound is a knock or not


// these variables will change:
int sensorReading = 0;      // variable to store the value read from the sensor pin
int ledState = LOW;         // variable used to store the last LED status, to toggle the light

void setup() {
  pinMode(ledPin, OUTPUT); // declare the ledPin as as OUTPUT
  Serial.begin(9600);       // use the serial port
}

void loop() {
  // read the sensor and store it in the variable sensorReading:
  sensorReading = analogRead(knockSensor);

  // if the sensor reading is greater than the threshold:
  if (sensorReading >= threshold) {
    // toggle the status of the ledPin:
    ledState = !ledState;
    // update the LED pin itself:
    digitalWrite(ledPin, ledState);
    // send the string "Knock!" back to the computer, followed by newline
    Serial.println(sensorReading);
  }
  delay(50);  // delay to avoid overloading the serial port buffer
}
/code]

I also tried hooking up one of the new enclosed piezos and had the same results. Erratic readings, false hits and false misses. Could it be the Uno itself?

Two things:- 1) You are still using a slow speed for the serial data, use 250000 baud. This cuts down the time you wait in the loop.

2) Using a delay every time through the loop is a problem. It is giving you false readings. What happens is that your tap on the sensor can occur at any time so that translates to anywhere in your code. If your tap starts while the code is in the delay, it has to wait until it comes out of the delay before it is measured and by that time it could be well past the peak reading.

3) ( no on expects the Spanish Inquisition ) the smaller readings might also happen because the sample coincides with the onset of the pulse and so has not yet had time to build up.

I will make the changes. I tried 250000 baud, but was getting no response whatsoever.

Now when I hook it up and run it through another knock sketch that I morphed to my liking it works perfect. I cannot figure out what variable in this sketch is creating an environment for the piezo that stabilizes it.

// Pin definitions
const int knockSensor = 0;         // Piezo sensor on pin 0.
const int programSwitch = 2;       // If this is high we program a new code.
const int relay = 3;           // Gear motor used to turn the lock.
const int redLED = 4;              // Status LED
const int greenLED = 5;            // Status LED
 
// Tuning constants.  Could be made vars and hoooked to potentiometers for soft configuration, etc.
const int threshold = 150;           // Minimum signal from the piezo to register as a knock
const int rejectValue = 35;        // If an individual knock is off by this percentage of a knock we don't unlock..
const int averageRejectValue = 25; // If the average timing of the knocks is off by this percent we don't unlock.
const int knockFadeTime = 150;     // milliseconds we allow a knock to fade before we listen for another one. (Debounce timer.)
const int relayon = 650;      // milliseconds that we run the motor to get it to go a half turn.

const int maximumKnocks = 20;       // Maximum number of knocks to listen for.
const int knockComplete = 2000;     // Longest time to wait for a knock before we assume that it's finished.


// Variables.
int secretCode[maximumKnocks] = {28, 31, 89, 93, 87, 100, 28, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // Initial setup: "Shave and a Hair Cut, two bits."
int knockReadings[maximumKnocks];   // When someone knocks this array fills with delays between knocks.
int knockSensorValue = 0;           // Last reading of the knock sensor.
int programButtonPressed = false;   // Flag so we remember the programming button setting at the end of the cycle.

void setup() {
  pinMode(relay, OUTPUT);
  pinMode(redLED, OUTPUT);
  pinMode(greenLED, OUTPUT);
  
  
  Serial.begin(9600);               			// Uncomment the Serial.bla lines for debugging.
  Serial.println("Program start.");  			// but feel free to comment them out after it's working right.
  
  digitalWrite(greenLED, HIGH);      // Green LED on, everything is go.
}

void loop() {
  // Listen for any knock at all.
  knockSensorValue = analogRead(knockSensor);
  
  if (knockSensorValue >=threshold){
    listenToSecretKnock();
  }
} 

// Records the timing of knocks.
void listenToSecretKnock(){
  Serial.println("knock starting");   

  int i = 0;
  // First lets reset the listening array.
  for (i=0;i<maximumKnocks;i++){
    knockReadings[i]=0;
  }
  
  int currentKnockNumber=0;         			// Incrementer for the array.
  int startTime=millis();           			// Reference for when this knock started.
  int now=millis();
  
  digitalWrite(greenLED, LOW);      			// we blink the LED for a bit as a visual indicator of the knock.
  delay(knockFadeTime);                       	        // wait for this peak to fade before we listen to the next one.
  digitalWrite(greenLED, HIGH);  
  
  do {
    //listen for the next knock or wait for it to timeout. 
    knockSensorValue = analogRead(knockSensor);
    if (knockSensorValue >=threshold){                   //got another knock...
      //record the delay time.
      Serial.println("knock.");
      now=millis();
      knockReadings[currentKnockNumber] = now-startTime;
      currentKnockNumber ++;                             //increment the counter
      startTime=now;          
      // and reset our timer for the next knock
      digitalWrite(greenLED, LOW);  
      delay(knockFadeTime);                              // again, a little delay to let the knock decay.
      digitalWrite(greenLED, HIGH);
      if (programButtonPressed==true){
        digitalWrite(redLED, HIGH);                         
      }
    }

    now=millis();
    
    //did we timeout or run out of knocks?
  } while ((now-startTime < knockComplete) && (currentKnockNumber < maximumKnocks));
  
  //we've got our knock recorded, lets see if it's valid
  {             // only if we're not in progrmaing mode.
    if (validateKnock() == true){
      triggerDoorUnlock();
       
    } else {
      Serial.println("Secret knock failed.");
      digitalWrite(greenLED, LOW);  		// We didn't unlock, so blink the red LED as visual feedback.
      for (i=0;i<4;i++){					
        digitalWrite(redLED, HIGH);
        delay(100);
        digitalWrite(redLED, LOW);
        delay(100);
      }
      digitalWrite(greenLED, HIGH);
    }
  
  }
}


// Runs the motor (or whatever) to unlock the door.
void triggerDoorUnlock(){
  Serial.println("Door unlocked!");
  int i=0;
  
  
  digitalWrite(relay, HIGH);                 // Closes the relay
  digitalWrite(greenLED, HIGH);            // And the green LED too.
  
  delay (2000);                    // Keeps relay closed for 2s
  
  digitalWrite(relay, LOW);            // Opens the relay
  
  // Blink the green LED a few times for more visual feedback.
  for (i=0; i < 5; i++){   
      digitalWrite(greenLED, LOW);
      delay(100);
      digitalWrite(greenLED, HIGH);
      delay(100);
  }
   
}

// Sees if our knock matches the secret.
// returns true if it's a good knock, false if it's not.
// todo: break it into smaller functions for readability.
boolean validateKnock(){
  int i=0;
 
  // simplest check first: Did we get the right number of knocks?
  int currentKnockCount = 0;
  int secretKnockCount = 0;
  int maxKnockInterval = 0;          			// We use this later to normalize the times.
  
  for (i=0;i<maximumKnocks;i++){
    if (knockReadings[i] > 0){
      currentKnockCount++;
    }
    if (secretCode[i] > 0){  					//todo: precalculate this.
      secretKnockCount++;
    }
    
    if (knockReadings[i] > maxKnockInterval){ 	// collect normalization data while we're looping.
      maxKnockInterval = knockReadings[i];
    }
  }
   
  /*  Now we compare the relative intervals of our knocks, not the absolute time between them.
      (ie: if you do the same pattern slow or fast it should still open the door.)
      This makes it less picky, which while making it less secure can also make it
      less of a pain to use if you're tempo is a little slow or fast. 
  */
  int totaltimeDifferences=0;
  int timeDiff=0;
  for (i=0;i<maximumKnocks;i++){ // Normalize the times
    knockReadings[i]= map(knockReadings[i],0, maxKnockInterval, 0, 100);      
    timeDiff = abs(knockReadings[i]-secretCode[i]);
    if (timeDiff > rejectValue){ // Individual value too far out of whack
      return false;
    }
    totaltimeDifferences += timeDiff;
  }
  // It can also fail if the whole thing is too inaccurate.
  if (totaltimeDifferences/secretKnockCount>averageRejectValue){
    return false; 
  }
  
  return true;
  
}/code]

bsrhardy: I will make the changes. I tried 250000 baud, but was getting no response whatsoever.

Did you change the speed on the serial monitor as well as in the code?

bsrhardy: Now when I hook it up and run it through another knock sketch that I morphed to my liking it works perfect. I cannot figure out what variable in this sketch is creating an environment for the piezo that stabilizes it.

That was because you never completed an in depth analysis of the data. You just peeked at a few numbers that arrived very slowly. So you missed a lot.

I assume that the spikes you are trying to detect can be pretty short.

So instead of a delay, which may lead to completely missing the spike, you should wait for the signal and meanwhile read as frequently as you can:

while(analogRead(knockSensor)<50){} //untested

lg, couka

couka:
I assume that the spikes you are trying to detect can be pretty short.

So instead of a delay, which may lead to completely missing the spike, you should wait for the signal and meanwhile read as frequently as you can:

while(analogRead(knockSensor)<50){} //untested

lg, couka

+1, if you want to wait for the ringing to subside, you should only do it after registering a spike.

Grumpy_Mike: Did you change the speed on the serial monitor as well as in the code?

Of course not, being dumb is so much easier than actually thinking. I will make the change. Thanks.

I am getting to the far depths of my abilities on this. I did not think it would be this difficult and I think I am making it more difficult than needed. The nutshell is:

I have five piezo sensors that need to be struck a total of seven times. I want the system to listen all 5 and when #5 is struck to listen for all 5 again. If #2 is struck then listen again to all 5, else return If after #2 is struck #3 is struck listen to all 5 again, ELSE return If #4 is struck then listen to all 5 again, ELSE return If #3 is struck then listen to all 5 again, ELSE return If #2 is struck then listen to all 5 again, ELSE return If #1 is struck then DO (action), ELSE return.

I need metrics to account for threshold and decay.

I unfortunately cannot see the forest for the trees and am getting uber frustrated as I am sure you all are answering these questions.

Hi,

I need metrics to account for threshold and decay.

So you need to:

  • detect a key strike?
  • measure its peak?
  • measure its decay time?

Do you need to measure its frequency?

I think you may be using the wrong sensor. Piezo type sensors, usually have a narrow frequency bandwidth of operation, and not a output linear response to sensed audio level.

What is your application? What is it you are going to do with the data you want to acquire?

Thanks.. Tom. :)

TomG,

Way more info than I need. I am using a xylophone to drive an output. There is a tune that I want to play and when that tune is played it drives the output. Very simple. I am not collecting any data just listening for a strike or "Knock". When the UNO detects the correct order of "knocks" it drives the output. By metrics I actually meant variables to control threshold of knocks, to avoid false or double strikes, and a delay, to allow the strike to decay again avoiding false or double strikes.

I feel I am overthinking it all.

bsrhardy: I feel I am overthinking it all.

I think you are trying to get it done in one step. Before trying to detect any patterns, find out how to reliably detect a hit. Then you can think about debouncing, then about patterns.

lg, couka

couka: I think you are trying to get it done in one step. Before trying to detect any patterns, find out how to reliably detect a hit. Then you can think about debouncing, then about patterns.

lg, couka

Yes, you have deftly sidestepped the troubleshooting instructions that were given to you (as pointed out in reply #27). Of course changing the baud rate won't help for a sketch that is broken. That was so you could read the raw value stream, which you obviously never did.

aarg: Yes, you have deftly sidestepped the troubleshooting instructions that were given to you (as pointed out in reply #27). Of course changing the baud rate won't help for a sketch that is broken. That was so you could read the raw value stream, which you obviously never did.

I did read the value stream and provided a small sampling from what I found. I am not trying to sidestep anything. I merely tried a sketch that i knew worked in the same manner (which it did) and was trying to discern how that set up differed from the state_machine sketch that I created to run the project.

Even though I received the data from the test of the sensors all it really showed me was that there was a spike which I expected and a descending value as it decayed. So, I know they were producing the needed spike to continue on with my project.

couka: I think you are trying to get it done in one step. Before trying to detect any patterns, find out how to reliably detect a hit. Then you can think about debouncing, then about patterns. lg, couka

I tried another knock sketch to see if it would record the reliable hits (which it did), that let me know that the sensors would work for my need. There was no bleed from one to another etc.. there were no false hits and I could hit in rapid succession with success.

So, knowing that the sensors now work, also that the mounting will work, and the placement will work. I turn to the sketch. I had a sketch, that I felt would reliably work. i am still working with that sketch, and will continue, so it comes back to my first post and the troubleshooting assistance I originally posted for.

bsrhardy: I did read the value stream and provided a small sampling from what I found.

Where? I looked through the entire thread and I don't see it. I only see a list of about 10 numbers from a program that doesn't do what we asked you to do.

The idea was to "scope" the entire pulse to get a sense of the amplitude and timing. That will make the rest of the design much easier and more reliable.

This is a sketch that will plot the output of one sensor connected to analogue input 0. Run this and open up the serial plotter window.

// Pietzo Plot - Mike Cook
// using int buffers
// Open up the plot window
const int bufferSize = 500;
int inBuffer[bufferSize];
void setup() {
Serial.begin(250000);
displayWave(); // clear display
//Serial.print("hi");
}

void loop() {
  getWave(0); // create input buffer wave 
  displayWave(); // to the plot window
  while(analogRead(0) > 10) { } // wait until signal drops below threshold
}

void getWave(int wave){
  // clear buffer
    for(int i=0; i<bufferSize; i++){
    inBuffer[i] = 0;
     }
   while( analogRead(0) < 10 ) { } // wait for trigger
   for(int i=0; i<bufferSize; i++){
    inBuffer[i] = analogRead(0);
    //delay(2); // control sample rate
   }

}

void displayWave(){
  for(int i=0; i<bufferSize; i++){
    Serial.println(inBuffer[i]);
  }
}

I tried this just tapping my finger on the sensor. The results are different each time, but you will see how reliable your sensor is in place in your instrument. This is what I got:-

P_plot.png

P_plot2.png

Thanks Mike,

Here is what I got on a couple of strikes. I am getting a pretty consistent pulse then a slight fade.