Amplifier for piezo sensor

Hello,

I am building an electronic drum set and I used some piezo sensors. It works pretty well and I'm starting to be happy with it (for something made out of Luigi Lavaza espresso cans, it's pretty good!! :D), but I would like to amplify the output of the piezo sensors because it feels like the drums are not very sensitive, especially if I use some foam to cushion the hit of the stick to get rid of the "clunk" resulting from the stick hitting the can. What would be the best way to do that? A simple op-amp or a circuit like the one from this thread? In the case of the transistor-based circuit, I understand that if I use the +5V of the Arduino, the amplified signal from the piezo sensor will max out at +5V and I will not damage the input (I would damage the input if I applied more than 5V to it, right?). What about the op-amp circuit? How do I limit the output? Select one that can be powered from a +5V source?

Thanks in advance...

Have you try to change reference of the ADC to INTERNAL? It will give you 5x gain. You will have to limit input signal to 1V, connecting couple diodes in opposite directions between input and ground
OP-AMP, IMHO, is overkill for such application, and link you posted to thread with amplifier based on BJT transistor is absurd (output impedance of piezo about 1000x higher than input imp. of BJT).
Simple circuit with JFET / MOSFET transistor would be o'key.

No, I didn't even know this was possible. I'm not familiar at all with transistors. Would 2N3906 types be OK? Or would a NPN be more practical? Would a simple common emitter scheme be enough if I bias it with the +5V of the Arduino? :blush: Thanks...

There is a good example to start with:

You can search for some variation of it, that better match to your design goals, let say +5V (easy to power up by arduino ) or higher gain (two transistors configuration).
There is one more things, even it's in topic's subject, are you sure you need amplifier?
What I'm getting at, how are you reading analog inputs, as there is an impulse signal, there is a possibility you measure inputs too late, when signal is already decay. Probably you need an envelope detector or some kind a "delay-line" that would hold a peak till your loop come to execute an analogread commands (depends on the code, 10 - 200 millisec ).

Am I sure I need an amplifier? That's a good question... I thought so. I read the signal with a simple analogRead(pin) where pin is the iterator of a for(int pin=0; pin <3; pin++) loop (I'm only using 3 analog inputs right now). I'm guessing that depending on the sample rate, I might be missing the peak like you said. I'm doing measurements right now. I have 2 type of "drums". One on which I don't have any foam on top and one with foam on top. If I give a good hit, the ones without the foam give me anywhere between 723 (out of 1023) and 23! The one with the foam gives me 570 max. This is my test program:

int sensorReading = 0;

void setup() {
 Serial.begin(9600);
}

void loop() {
  for(int pin=0; pin < 3; pin++)
  {
  sensorReading = analogRead(pin);    
  
  if (sensorReading > 16)
  {
  Serial.println("Input: ");         
  Serial.println(pin);         
  Serial.println("Reading: ");         
  Serial.println(sensorReading);         
  delay(100);  // delay to avoid overloading the serial port buffer
  }
  }
}

The output looks something like this:

Input:
0
Reading:
723
Input:
0
Reading:
18
Input:
1
Reading:
40
Input:
2
Reading:
152
Input:
2
Reading:
34

So you might be on to something here. It looks like at best I get 2 cycles worth of reading. What could I do to hold the peak to make sure I read it better and more accurately? Would a capacitor help?

Thanks for your help.

Or in this case is it because the serial print commands slow down everything?

OK, I tried something else, which was to put recordings in an array, like this:

int sensorReading = 0;
int recording[50] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int i=0;

void setup() {
 Serial.begin(9600);
}

void loop() {
  while(i < 50)
  {
  sensorReading = analogRead(0);    
  
  if (sensorReading > 20)
  {
    recording[i] = sensorReading;
    i = i + 1;
  }
  }
  
  if(i == 50)
  {
    for(int j = 0; j < 50; j++)
    {
      Serial.println(recording[j]);
    }
    i = 0;
    Serial.println("Reset counter");
  }
}

This is the output for one hit:

1023
521
60
1023
1023
995
531
172
240
1023
35
1023
1004
1023
403
1023
816
561
1023
1023
619
216
797
680
1023
936
966
973
790
248
139
87
980
529
193
728
1023
536
227
127
151
983
666
146
443
329
501
692
234
68
Reset counter

So maybe I don't need amplification after all. Maybe a way to average a signal instead?

I like your second test-code, it shows that pulse is falling pretty fast, ~50 % between two readings, which is 100 usec or so only. Obviously, when there are 3 analog inputs and plus big "processing" portion of the code in the loop, probability to read pulse in it's life-time is very slim, except when you hit a sensor really hard and electrical charge stay for milliseconds.
There are two option:

  1. simple hardware detector - fast switching diode in serial plus capacitor / resistor to ground to form time constant around 100 millisecond - the longest timing ;
  2. software approach - start "fastanalogread" sampling in the interruption subroutine, with period 50 - 100 usec, volatile variable sensorReading should be subtracted some value, again to form time constant.
sensorReading = fastanalogRead(0);    
  
  if (sensorReading > 1)
  {
    sensorReading -= time_const;
  }

When reading 1024 and you subtract 1 it'd take 1024 cycles or 100 usec x 1024 = 102 millisec

I'm starting to wonder. Couldn't it simply be that I'm losing some values because the Arduino has some code to execute in between two readings? I wonder if it'd be more sensitive to capture 10 values for each reading and only keep the max?

Here is the code (courtesy of SpikenzieLabs):

unsigned char PadNote[6] = {38,49,43,63,40,65};         // MIDI notes from 0 to 127, see values below
unsigned char DPadNote[6] = {36,22,43,63,40,65};         // MIDI notes from 0 to 127, see values below (for digital inputs)
// For SuperDrum FX:
// 22 = closed hi hat
// 26 = Cymbal
// 36 = bass kick
// 38 = snare 1
// 40 = snare 2 or rim shot
// 41 = bass tom
// 42 = closed hi hat
// 43 = mid tom
// 44 = underfoot hi hat
// 45 = mid high tom
// 46 = open hi hat
// 48 = high tom
// 49 = cymbal 1
// 51 = ride bell
// 52 = cymbal 2
// 53 = ride rim
// 55 = cymbal 1
// 57 = cymbal 2

int PadCutOff[6] = {100,100,100,100,100,100};           // Minimum Analog value to cause a drum hit, careful, will pick up neighboring drums if set too low, 400 is fine

int MaxPlayTime[6] = {90,90,90,90,90,90};               // Cycles before a 2nd hit is allowed
int DMaxPlayTime[6] = {500,500,500,500,500,500};               // Cycles before a 2nd hit is allowed

#define  midichannel	0;                              // MIDI channel from 0 to 15 (+1 in "real world")

boolean VelocityFlag  = true;                           // Velocity ON (true) or OFF (false)


//*******************************************************************************************************************
// Internal Use Variables			
//*******************************************************************************************************************

boolean activePad[6] = {0,0,0,0,0,0};                   // Array of flags of pad currently playing (analog pins)
int PinPlayTime[6] = {0,0,0,0,0,0};                     // Counter since pad started to play (analog pins)
boolean DactivePad[6] = {0,0,0,0,0,0};                   // Array of flags of pad currently playing (digital pins)
int DPinPlayTime[6] = {0,0,0,0,0,0};                     // Counter since pad started to play (digital pins)

unsigned char status;

int pin = 0;     
int hit = 0;
float hit_avg = 0.;
boolean hitdetect = false;

//*******************************************************************************************************************
// Setup			
//*******************************************************************************************************************

void setup() 
{
  Serial.begin(31250);                                  // connect to the serial port 31250
}

//*******************************************************************************************************************
// Main Program			
//*******************************************************************************************************************

void loop() 
{
  for(int pin=0; pin < 3; pin++)   // ANALOG INPUTS LOOP - adjust how many analog inputs are read
  {
    hit = analogRead(pin);                              // read the input pin

    if((hit > PadCutOff[pin]))
    {
      if((activePad[pin] == false))
      {
        if(VelocityFlag == true)
        {
          hit = 127 / ((1023 - PadCutOff[pin]) / (hit - PadCutOff[pin]));    // With full range (Too sensitive ?)
//          hit = (hit / 8) -1 ;                                                 // Upper range
        }
        else
        {
          hit = 127;
        }
        MIDI_TX(144,PadNote[pin],hit); 
        PinPlayTime[pin] = 0;
        activePad[pin] = true;
      }
      else
      {
        PinPlayTime[pin] = PinPlayTime[pin] + 1;
      }
    }
    else if((activePad[pin] == true))
    {
      PinPlayTime[pin] = PinPlayTime[pin] + 1;
      
      if(PinPlayTime[pin] > MaxPlayTime[pin])
      {
        activePad[pin] = false;
        MIDI_TX(128,PadNote[pin],127); 
      }
    }
  } 
// DIGITAL INPUTS LOOP - adjust how many digital inputs are read
    for(int pin=8; pin < 9; pin++)
  {
    hitdetect = digitalRead(pin);                              // read the input pin

    if((hitdetect == true))
    {
      if((DactivePad[pin-8] == false))
      {   
//          Serial.println("Hit detected and pad was ready");
//          Serial.println(hitdetect, BIN);
//          Serial.println(DactivePad[pin-8], BIN);
          
          hit = 127;

        MIDI_TX(144,DPadNote[pin-8],hit); 
//          Serial.println("Play note and change pad to not ready");
        DPinPlayTime[pin-8] = 0;
        DactivePad[pin-8] = true;
      }
      else
       {
//          Serial.println("Hit detected and pad was not ready, increment timer");
       DPinPlayTime[pin-8] = DPinPlayTime[pin-8] + 1;
      }
    }
    else if((DactivePad[pin-8] == true))
   // else if((hitdetect == false))
    {
//          Serial.println("Pad was not ready, increment timer");
       DPinPlayTime[pin-8] = DPinPlayTime[pin-8] + 1;
      
       if(DPinPlayTime[pin-8] > DMaxPlayTime[pin-8])
      {
//          Serial.println("Time exceeded, pad changed back to ready, send note OFF");
//          Serial.println(hitdetect, BIN);
//          Serial.println(DactivePad[pin-8], BIN);
        DactivePad[pin-8] = false;
        MIDI_TX(128,DPadNote[pin-8],127); 
      }
    }
  } 
  
}


//*******************************************************************************************************************
// Transmit MIDI Message			
//*******************************************************************************************************************
void MIDI_TX(unsigned char MESSAGE, unsigned char PITCH, unsigned char VELOCITY) 
{
  status = MESSAGE + midichannel;
  Serial.print(status);
  Serial.print(PITCH);
  Serial.print(VELOCITY);
}

Magician:
Obviously, when there are 3 analog inputs and plus big "processing" portion of the code in the loop, probability to read pulse in it's life-time is very slim, except when you hit a sensor really hard and electrical charge stay for milliseconds.

Heh, you got it right before me! XD So essentially I'm missing some peaks because the Arduino is too busy processing code to bother looking at its input?

That is my impression, based on the output you provided:

1023
536
227
127

So what about this, instead of just doing hit = analogRead(pin); , what if I forced the Arduino to take 10 readings in a row (shouldn't take too long) and use the max value, like this:

    for(int i=0; i < 10; i++)
    {
    record[i] = analogRead(pin);                              // read the input pin
    if(record[i] > record_max)
    {
      record_max = record[i];
    }
    }
    hit = record_max;                              // read the input pin
    record_max = 0;

Well, it doesn't seem to make things worse, at least for those "drums" with no foam on them, right now it all works very well, including velocity sensitivity, but for the one with foam on it (a thin layer), it's not very good and the output is pretty low. Basically my problem is there: I want to dampen the sound of the sticks on the bare steel, because, well, my wife is giving me the evil look when I'm messing with it late and it's noisy :D, so I'd like to add a material on top of them to mute the sound, but of course that absorbs some of the energy the piezos need. I'll keep messing with it, I need to stop making noise right now! Thanks for your help though Magician. Cheers!

Event - driven solution ( option number 3 ) connect together digital and analog input, then attach interrupt to digital and inside interrupt subroutine take readings.
Limits - arduino has only 2 digital pin with interruption service

O'K , but problem still exist w/o foam also, as you can't measure velocity, it's nothing else than "knock" sensor :slight_smile:

Magician:
There are two option:

  1. simple hardware detector - fast switching diode in serial plus capacitor / resistor to ground to form time constant around 100 millisecond - the longest timing ;

I think I like your option #1 best. So you're saying add a diode after the piezo to avoid discharging the capacitor into the piezo, then an RC high pass filter with RC about 0.1 s, with the capacitor between the diode and the arduino analog input, and the resistor to the ground? Am I getting that right? And depending on the output level maybe a transistor to boost the input signal?

I draw a schematic, not sure about value of capacitor.
There is there is trade off, higher value most likely decrease a voltage at the input,
some experiments required with values 100 pF to 1 nF.
Do you know what is duration of time between readings? I put 100 millisec as maximum, probably you would need less, so lower value capacitor (100 pF) should be o'k .

Ah, thanks, I thought the capacitor would be in series. What difference would it make if it was?

You can't put diode and capacitor in series, as there would be no way for cap to be discharged..

OK, I tried a 1 microF capacitor with a 100k resistor, it does even the response a lot but lowers it a lot. In the example I showed above, pretty much all numbers are the same: 50... that might be too high. And now a piezo lead got disconnected, I need to solder it again. :0

Repaired the piezo. I changed the cicuit and used a 4-diode bridge rectifier with an RC filter. I figured that the output from the piezo should be a sine wave, right? So I thought that using a bridge rectifier with an RC filter would help smooth out the signal. So now if I simply read the output with R = 100k and C = 0.022 microF, this is what I get for a few repeated hits and a delay of 10 ms (if the output is = 0 I don't print anything to the serial monitor):

387
236
551
50
17
9
4
769
110
26
10
2
1
1
1023
108
53
22
7

If I replace the capacitor with a 1.0 microF electrolytic I get this:

799
693
602
524
457
399
348
304
265
232
203
177
155
136
119
104
91
81
72
63
55
49
43
38
33
29
26
22
20
17
15
13
11
10
9
8
7
6
5
4
3
4
3
2
1
2
1
1
1
1