Find peak value of piezo sensor hit, multiple sensors

Hi all,

I am working on a project measuring (and later processing) the readings of piezo pressure sensors. To be more exact, only the peak readings. A sensor is hit, produces a series of waves and I want to record the peak of the first wave and ignore the rest. After many YT tutorials, here's what I came up with so far. Please note this is just a first try for 1 sensor:


// these constants won't change:
const int piezo1 = 34;  // the piezo is connected to analog pin 34
const int threshold1 = 200;   // threshold value to decide when the detected sound is loud enough to be a real hit
const unsigned long debounceDelay1 = 30;  // time elapesed after the last peak reading before reading this sensor again, to debounce

// these variables will change:
int sensorReading1 = 0;  // variable to store the value read from the sensor pin
int currentReading1 = 0;  // variable to 
int peakReading1 = 0;  // variable to store the peak reading to later convert to a MIDI note
unsigned long currentTime1 = 0;  // 
unsigned long peakTime1 = 0;


void setup() {
  pinMode (piezo1, INPUT);
  Serial.begin(9600);       // use the serial port
}

void loop() {
  sensorReading1 = analogRead(piezo1);  // read the sensor and store it in the variable sensorReading1
    if (sensorReading1 >= threshold1)  // only act if sensorReading1 is above threshold1 value
    {
      currentTime1 = millis();  // set currentTime1 for the if comparisson to debounce
      if ((sensorReading1 > currentReading1) && (currentTime1 > (peakTime1 + debounceDelay1))) // if the sensor reading1 is greater than the threshold1, greater than the previous reading and debounce time has elapsed
      {sensorReading1 = currentReading1;}  // replace currentReading1 with the new value, it's going up
      if ((sensorReading1 >= threshold1) && (sensorReading1 < (currentReading1 - 10)))  // if the sensor reading1 is greater than the threshold1 and is going down (past the peak), 
      {currentReading1 = peakReading1; // note the previous reading as the peakReading1, as the last currentReading was the first going down (so after the peak)
      peakTime1 = millis();} // mark when the peakReading1 took place
      Serial.println (peakReading1);
    }
    
   
}

Please let me know where and how I could optimise this. Oh, and if I missed that there is a standard Arduino function that already does this, please enlighten me. My limited vocabulary on the subject doesn't always help me to find the functions available. I will use an ESP32 devBoard to run this on, as it is fast and cheap.

Cheers,

Hugo

That tempts new users, but it is a mistake that many beginners make. Over 50% of questions here are about these dirt cheap low build quality chips.

What hardware do you have in place for connecting the sensors to the Arduino?
If you connect a sensor directly to a pin it can easily reach voltages of over 100V.

The approach you are taking is flawed and will not work. But lets sort out the hardware first.

The hardware is sorted. The sensor is run through a some resistors and has a schottky diode over the port in case of an overvoltage. I know / guess you mean well, please stick to the subject / question at hand.

Thanks,

Hugo

That is what you think, but I have used these sensors in many projects, and I have access to an oscilloscope and know how to use it.

But If you don't want me to tell you what you need to do then I will not.

That makes the two of us, then, on the oscilloscope bit. Although in all honesty I don't own one, I use the one at my job. So far, I have set a little circuit which gives me the readings the ESP32 can handle, with real-world inputs. That is enough for me, now. No doubt there is room for improvement, there always is, but good enough is what I settle for now, and I now focus on the question I posed.

If you cannot suppress the urge to demonstrate your knowlegde on the hardware, please feel free to do so. I am not to old to learn, yet. But maybe we could handle that in a different bespoke topic.

Hugo

In the meantime I found a mistake in my first code, line 27, I wrote the currentReading1 = sensorReading1 the wrong way around.

And, I asked ChatGT for an alternative to my code, this is what it came up with:

// Constants
const int piezo1 = 34;               // Piezo is connected to analog pin 34
const int threshold1 = 200;          // Minimum value to register as a hit
const unsigned long debounceDelay1 = 30; // Delay after a hit to debounce

// Variables
int sensorReading1 = 0;              // Current sensor reading
int peakReading1 = 0;                // Detected peak value
unsigned long lastPeakTime1 = 0;     // Time of last peak detection
bool isDetectingPeak1 = false;        // State flag for peak detection

void setup() {
  pinMode(piezo1, INPUT);
  Serial.begin(9600); // Initialize serial communication
}

void loop() {
  sensorReading1 = analogRead(piezo1); // Read the sensor value

  unsigned long currentTime1 = millis(); // Current time in milliseconds

  if (sensorReading1 >= threshold1) { // If the sensor value exceeds the threshold
    if (!isDetectingPeak1) {
      isDetectingPeak1 = true; // Start detecting a peak
      peakReading1 = sensorReading1; // Initialize the peak
    }

    // Update the peak value if the reading is higher
    if (sensorReading1 > peakReading1) {
      peakReading1 = sensorReading1;
    }
  } else if (isDetectingPeak1) {
    // If we were detecting a peak but the value dropped below the threshold
    isDetectingPeak1 = false; // Stop detecting
    lastPeakTime1 = currentTime; // Record the time of the peak
    Serial.println(peakReading1); // Output the peak value
    peakReading1 = 0; // Reset the peak for the next hit
  }

  // Optional debounce: Ignore readings for a short time after detecting a peak
  if (currentTime - lastPeakTime1 < debounceDelay1) {
    return; // Skip further processing
  }
}

I'm not sure how that works, yet, I'm slowly working through it.

Hugo

Does the code work?
Optimise in what way, speed, memory RAM?

That might be a start.

Could you share a schematic?

What happens after the first signal peak value is detected? Do you have a time out setting? (not for debouncing).

what you describe could be implemented using a state machine.

Here is a small introduction to the topic: Yet another Finite State Machine introduction

curious to know more about how this is wired up exactly

So you are sure that you don't miss the peak value between two ADC samples?

1 Like

Hmmmm, it seems the wiring of the piezo is very special... on my test-setup:
Ring of the sensor is connected to ground.
Centre of sensor is connected to signal (pin 34)
Parrallel to the sensor I have a potentiometer. On the specific sensor I test with (see here for more detail:

), that potentiometer is set to about 8k Ohm. This gives me readings up to 3700 / 3750 when I hit the drum hard. Hitting the drum softly gives a value of 250-ish. If I connect the rim sensor, I have to turn the pot down a bit more. Is more sensitive, obviously. Eventually this 250 - 3750 range will be translates to MIDI velocity 0 - 127 (probably not linear and maybe 5 - 120 or something like that, but those will be settings just like the others).
Parrallel to it all I have two 3V Schottky diodes, one reversed (don't ask me number, I just bought a bunch a while ago to protect ESP32 inputs). 3V gives a reading of 3839 on the port.

I have tried a set-up with the centre of the sensor hooked up to 3V3, the ring to the pin 34 and a potentiometer between pin and ground. Effect is about the same, I see no benefits in this set-up.

There are no doubt better solutions. What I need is a repeatable signal within the measurable range. I have that. There is no need to improve on that. Actually, this is the job I earn a living with. I come up with solutions (in many fields) that are good enough to serve the purpose. If my customer asks for 97% accuracy, I fail at my job if the solution I present is 98% accurate. I will have wasted resources. So, this way of sensing gives me the input I need, repeatable and usable. Hence, I use it.

Anyway, the code ChatGPT came up with seems to work. I don't fully comprehend it, yet, but it works. I will now extend it to multiple sensors, all with there own settings. I will look into J-M-L's suggestion and I am still open for suggestions to simplify the code. The more simple, the more quickly it is running on the ESP32.

Next in the proces will be to have two signals and two output values, one from the drum head and one from the rim sensor. Whichever has the highest value will get to play the MIDI note. This because when I hit the head, the rim sensor is triggered a bit, too, and vice versa. At the moment, the rim sensor overpowers the head sensor 90% of the time when the pot settings are the same, but with the pot settings set correctly and the peak readings sorted, the choice is easy.

Hugo

this should be at the start of the loop (just after setting currentTime1) to be effective

That's the bit I don't comprehend, yet. I feel I don't want the 'return' as it send it back to the top of the program? With multiple sensors (copied set-up, with '2' instead of '1') I don't want that. I am reading about it to learn.

Hugo

It doesn't. It returns from the loop function, which is what happens in either case.

Having that if statement at the bottom of loop() performs no useful function, and it is likely that the compiler just removed it during the optimization process.

If you are not yet up to speed with Arduino, there is a hidden main function that calls loop() repeatedly and indefinitely.

what that line says is

"if you detected the peak time not too long ago, don't bother trying to read a peak and try again". This will repeat until debounceDelay1ms have elapsed.

so having it at the end of the loop, after you actually tried to find a peak is useless.

if you have multiple readings to do then of course you don't want to do that if you found one peak as more peaks might be due on other sensors.

(Also not sure what the bouncing means there, if the signal gets higher than the peak during that bounce period, then it meant you did not really read the peak)

You should explore techniques described in

Eh, on those links: isn't that exactly what I did in my own code and in the ChatGPT code? I use that do do exactly that.

I found (reading the sensor with a scope) there are many waves created when I hit the drum. Most of the time, the first one is the highest. Sometimes, the second one is. I've never measured one where the third or more where higher than the first. Fortunately, the value of the first one is pretty linear with the 'impact' of the hit. So, no need to check the others.

I will change the debouncing bit in the ChatGPT code back to what I did originally in my code, earlier in the code and not using the 'return' command. Thank you, that was a valuable contribution I can work with. And it helps me understand / learn.

Hugo

Not exactly the same as you have one sensor only and you used millis to handle the bouncing period.

If you want multiple sensors you won't want to ignore all the readings after detecting a peak. you'll want to ignore it only for that given pin.

Yes, this is why I gave the currentTime and lastPeakTime (and all other variables) the digit '1' behind them. So for item one, currentTime1, lastPeakTime1 and debounceDelay1. For the next sensor, all those will be named the same but with '2' instead of '1'. So when reading piezo2 and processing everything marked '2', it ignores everything marked '1'. For this '2', it can have a different threshold, debounce time, etc. Each sensor is completely read and processed individually.

At least, that is what I think.

Hugo

You would be better off using arrays / classes rather than numbering your variables and duplicating code

I'll go google that, then.

Cheers,

Hugo