Reading peaks

I’m reading a signal from a piezo and I’d like to find the peaks and get the average between them. So far I’ve written this code:

void loop() {
  sendFrequency(20e3);  // freq
  //sensorValue[0]= 0;
  //sensorValue[1] = 0;
  int x = 0;
  int sensorPeakSum = 0;
  for (int i = 1; i<= n; ++i) {
  //sendFrequency(20e3); //which place is right?
  sensorValue[i] = analogRead(A0);
  
  if (sensorValue[i-2] < sensorValue[i-1] & sensorValue[i-1]>sensorValue[i]) {
    sensorPeakSum+= sensorValue[i-1]; 
    x=x+1;
  }
  else { 
  }
  }

  float dataVoltage = (sensorPeakSum/x) * (5.0/1023.0);
  Serial.println(dataVoltage);

Apparently there is something wrong, when I read it in the serial monitor, it starts at random values, such as 0.58 and start decreasing.

Is the logic right?

I’ve tested the arduino input pin and it is working just fine.

Thanks.

Your logic in your if-statement is not completely proper syntax, but should get the desired turnout in the end. But the direct sensing of the piezzo with analogread might not be working out. The signal might not be strong enough to be detected by the ADC, and may need amplification. Have you measured the signal with other means? (oscilloscope/digital multimeter) How many milivolts should the peaks be? Do you even know what the signal looks like? Is this an ac voltage oscillation around 0 volts, or is there a static voltage-component in the signal. How about taking 100 or something samples and store them into a small array. (depending on the arduino model, you may not have very much RAM memory) Then send it in bulk over to the serial monitor in leisure time.

Another cause could be the duration of the loops. Serial transmisison, and also floating point calculation for a microcontroller takes ages compared to a analog measurement. So each turn of the for-loop, the piezo might be damped out already before the next turn. Again, knowing what signal samples you can expect to get would help to rule out this case. You probably do not need to send serial data after each measurement in the for loop, so you probabely want to leave that serial println out of it. Until the for loop times out, and then send the result… if any.

To bug-check your code I would also send out the measured samples so you can check the logic reasoning and value calculation yourself. Never expect your code to be correct the first time. Provide yourself with enough data to be able to check what the microcontroller/arduino decides and does internally at each step. But I guess this is hard to do in a time critical sampling loop. Leave as much processing aside. If possible do this on the pc, instead of by the arduino. An analog sample takes only 2 bytes. Even just 10 bits at most.

In regards to your use of operators in the if-statement:

First, you use a bitwise-and operator to relate the detection of slopes of the 3 most recent samples. A logical-and operator (&&) would be more appropriate. Still, it should work out identically as logical operations are in essence a descicion between zero and not-zero.

Second, you did not surround each logical statement in round brackets. So you may get processing order issues this way. The & operator executes after relational operators < >. So you still get off the hook here. But in the future encompass each logical statement part in between round brackets to avoid mis-interpretation of what you want:

...
if ( (sensorValue[i-2] < sensorValue[i-1] ) &&  (sensorValue[i-1]>sensorValue[i]) ) {
    sensorPeakSum+= sensorValue[i-1]; 
    x=x+1;
  }
  else { 
  }
...

I forgot to mention:

I presume the variable n is set to a specific value somewhere else in your code!?!. Or else the for loop could run as long as whatever random bits happend to be in that memory location.

[EDIT]Totally forgot: how is this thing wired up in the first place.

Valen:
[EDIT]Totally forgot: how is this thing wired up in the first place.

Yes, I'd like to know that too. Is the piezo followed by an envelope detector or ?? Also what Arduino is doing the sampling ? I infer from the comments that it's supposed to be 20 kHz. Is that fast enough ? Is that even possible with the flavor of Arduino being used ? A standard *analogRead()* on an Uno (16 MHz) takes about 120 usec. That's just over 8 kHz.

Thanks for the answers.

I’m using a Arduino UNO, and with some adjusts I could get pretty good peak results.

I’m sweeping the frequency between 20KHz and 40KHz.

The wiring is simple, I’m using pins 5,6,7 and 9 for the DDS Function Generator; 10, 11, 12 and 13 for the SD reader and A0 to read the voltage from the piezo.

Valen, thanks for the clear answer, I helped me a lot. I think if I post the whole code it would be better to explain things.

I will post the code to someone who may need it.

/*
 * A simple single freq AD9850 Arduino test script
 * Original AD9851 DDS sketch by Andrew Smallbone at www.rocketnumbernine.com
 * Modified for testing the inexpensive AD9850 ebay DDS modules
 * Pictures and pinouts at nr8o.dhlpilotcentral.com
 * 9850 datasheet at http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf
 * Use freely
 */
 
 #define W_CLK 5       // Pin 8 - connect to AD9850 module word load clock pin (CLK)
 #define FQ_UD 9       // Pin 9 - connect to freq update pin (FQ)
 #define DATA 6       // Pin 10 - connect to serial data load pin (DATA)
 #define RESET 7      // Pin 11 - connect to reset pin (RST).
 
 #define pulseHigh(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); }
 
 #include <SD.h>
 
 int n=300;
 int sensorValue[300];
 const int chipSelect = 10;
 char filename[50];
 
 
 // transfers a byte, a bit at a time, LSB first to the 9850 via serial DATA line
void tfr_byte(byte data)
{
  for (int i=0; i<8; i++, data>>=1) {
    digitalWrite(DATA, data & 0x01);
    pulseHigh(W_CLK);   //after each bit sent, CLK is pulsed high
  }
}
 
 // frequency calc from datasheet page 8 = <sys clock> * <frequency tuning word>/2^32
void sendFrequency(double frequency) {
  int32_t freq = frequency * 4294967295/125000000;  // note 125 MHz clock on 9850
  for (int b=0; b<4; b++, freq>>=8) {
    tfr_byte(freq & 0xFF);
  }
  tfr_byte(0x000);   // Final control byte, all 0 for 9850 chip
  pulseHigh(FQ_UD);  // Done!  Should see output
}
 
void setup() {
 // configure arduino data pins for output
  pinMode(FQ_UD, OUTPUT);
  pinMode(W_CLK, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(RESET, OUTPUT);
 
  pulseHigh(RESET);
  pulseHigh(W_CLK);
  pulseHigh(FQ_UD);  // this pulse enables serial mode - Datasheet page 12 figure 10
  Serial.begin(9600);
  Serial.print("Initializing SD Card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");
  int m = 0;
  snprintf(filename, sizeof(filename), "data%03d.txt", m); // includes a three-digit sequence number in the file name
  while(SD.exists(filename)) {
    m++;
    snprintf(filename, sizeof(filename), "data%03d.txt", m);
  }
  File dataFile = SD.open(filename,FILE_READ);
  Serial.println(m);
  Serial.println(filename);
  dataFile.close();
  //now filename[] contains the name of a file that doesn't exist
  }
 
void loop() {
  for (unsigned long fq = 20000; fq<= 40000; fq+=5) {
  sendFrequency(fq);  // freq
  //while(1);
  int x=0;
  int sensorPeakSum = 0;
  for (int i = 2; i<= n; ++i) {
  sensorValue[i] = analogRead(A0);
  
  if (sensorValue[i-2] < sensorValue[i-1] & sensorValue[i-1]>sensorValue[i]) {
    sensorPeakSum+= sensorValue[i-1]; 
    x=x+1;
  }
  else { 
  }
  }
  float voltage = (sensorPeakSum/x)* (15.0/1023.0);
  File dataFile = SD.open("data001.txt", FILE_WRITE);
// if the file is available, write to it:
  if (dataFile) {
    dataFile.print(voltage);
    dataFile.print(",");
    dataFile.print(fq); 
    dataFile.println();
    dataFile.close();
    // print to the serial port too:
    Serial.print(voltage);
    Serial.print(",");
    Serial.print(fq); 
    Serial.println();
  }  
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening file");
  } 
 delay(1);
}
while (1) {}
}

I’m having a problem with the file name, if instead of using “File dataFile = SD.open(“data001.txt”, FILE_WRITE);” I use “File dataFile = SD.open(filename, FILE_WRITE);” it doesn’t work.

I was working with a similar code, without the if statement to find the peaks and the “filename” was working just fine.

Does anybody know why?

  for (int i = 2; i<= n; ++i) {
  sensorValue[i] = analogRead(A0);
 
  if (sensorValue[i-2] < sensorValue[i-1] & sensorValue[i-1]>sensorValue[i]) {
    sensorPeakSum+= sensorValue[i-1];

You really should correct the if statement as suggested by Valen (use && and parentheses).

However, a more serious problem is that the if statement references array elements that do not seem to be initialized. If you start with i=2 in the for loop, what is stored in sensorValue[i-2] or [i-1]? Depending on the particular C/C++ language compiler and linker, there will either be a random number in that location; or there will always be zero.

Your setup raises more questions than it answers. You say you have the piezo connected to A0. Ok, but do realize that a piezo generated AC voltages. The voltage changes from positive to negative and back. And those peaks become less intense as time passes. So the decay is a natural response. But you are missing halve of the swing.

But you also seem to use a AD9850 DDS signalgenerator. It is not at all clear how this influences the piezo. Is it directly connected to the Piezo pins? If so, how? Is there some other piezo transmitter you have not told about? Is the piezo directly excited by the signal generator, or is there some accoustic or mechanical linkage? Please show a schematic. The piezo cannot start vibrating out of itself.

I don’t think the analog measurement with analog read is even quick enough to measure 20kHz and above frequencies. At best you can measure 10K samples/sec according to the Arduino wiki: http://arduino.cc/en/Reference/analogRead So this is slower than the lowest intended frequency. And the way you identify the peaks you needthe sample rate to be well above the Nyquist rate. [(Wiki) So this could explain your random values. The slope you are trying to detect is done over entire periods. Try to make a test sketch to measure the time it takes to do a 10000 analog samples and calculate the average time. Just making analog measurements. No other code or if or loop-branches.

If you want to do this then you need to rectify this AC signal to dc. You should be able to do this in a halve-wave rectifier. (ground->piezo->diode->capacitor to ground, paralleled by a resistor. You would then measure the voltage of the capacitor. The piezo would charge the capacitor each halve wave through the diode if the peaks are high enough. When the peak has passed and not enough anymore to charge the capacitor due to the reverse biased diode, the resistor slowly discharges the capacitor. This way you are closer to the actual peaks (due to the diode drop the output is a bit lower) than the random sampling method you use now.

See part 7.3 in this link for an example.

jremington: Good find! I didn’t realize that those first two array elements never got values placed in them.](Nyquist rate - Wikipedia)

I’m pretty sure in my compiler [i-1] and [i-2] is 0, which works just fine for what I want.

My project works like that: the DDS Function Generator sends 20KHz-40KHz at 1.04V to an op amp circuit, which increases the voltage to ˜4V. I use this as an input to the first piezo (at one end of a plate), and I read this information from another piezo (at the other end of the plate) with the A0 pin.

Thanks for the information about 10KHz, I’ll make some changes and see what happens.

Regardless, my main problem now is the name of the files, since I’m building the arduino on a breadboard and I won’t have the computer interface to change the file’s name all the time.

felipearcaro:
…it doesn’t work.

Very enlightening. Any compiler errors? Or does it not seem to work at runtime. (no new file is created) Any error messages or data sent to the serial monitor?

Serial monitor was showing all the data, the problem was with the SD part. I changed some details I got the code working again

/*
 * A simple single freq AD9850 Arduino test script
 * Original AD9851 DDS sketch by Andrew Smallbone at www.rocketnumbernine.com
 * Modified for testing the inexpensive AD9850 ebay DDS modules
 * Pictures and pinouts at nr8o.dhlpilotcentral.com
 * 9850 datasheet at http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf
 * Use freely
 */
 
 #define W_CLK 5       // Pin 8 - connect to AD9850 module word load clock pin (CLK)
 #define FQ_UD 9       // Pin 9 - connect to freq update pin (FQ)
 #define DATA 6       // Pin 10 - connect to serial data load pin (DATA)
 #define RESET 7      // Pin 11 - connect to reset pin (RST).
 char filename[20];
 
 
 #define pulseHigh(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); }
 
 int n=300;
 int sensorValue[300];
 const int chipSelect = 10;
 #include<SD.h>
 
 // transfers a byte, a bit at a time, LSB first to the 9850 via serial DATA line
void tfr_byte(byte data)
{
  for (int i=0; i<8; i++, data>>=1) {
    digitalWrite(DATA, data & 0x01);
    pulseHigh(W_CLK);   //after each bit sent, CLK is pulsed high
  }
}
 
 // frequency calc from datasheet page 8 = <sys clock> * <frequency tuning word>/2^32
void sendFrequency(double frequency) {
  int32_t freq = frequency * 4294967295/125000000;  // note 125 MHz clock on 9850
  for (int b=0; b<4; b++, freq>>=8) {
    tfr_byte(freq & 0xFF);
  }
  tfr_byte(0x000);   // Final control byte, all 0 for 9850 chip
  pulseHigh(FQ_UD);  // Done!  Should see output
}
 
void setup() {
 // configure arduino data pins for output
  pinMode(FQ_UD, OUTPUT);
  pinMode(W_CLK, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(RESET, OUTPUT);
 
  pulseHigh(RESET);
  pulseHigh(W_CLK);
  pulseHigh(FQ_UD);  // this pulse enables serial mode - Datasheet page 12 figure 10
  Serial.begin(9600);
    Serial.print("Initializing SD Card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  int l=0;
  snprintf(filename, sizeof(filename), "data%03d.txt", l); // includes a three-digit sequence number in the file name
  while(SD.exists(filename)) {
    l++;
    snprintf(filename, sizeof(filename), "data%03d.txt", l);
  }
  File dataFile = SD.open(filename,FILE_READ);
  
  dataFile.close();
  Serial.println(l);
  Serial.println(filename);
  
}
 
void loop() {
  for (unsigned long fq = 20000; fq<= 40000; fq+=10) {
  sendFrequency(fq);  // freq
  //while(1);
  int x=0;
  int sensorPeakSum = 0;
  for (int i = 2; i<= n; ++i) {
  sensorValue[i] = analogRead(A0);
  
  if (sensorValue[i-2] < sensorValue[i-1] & sensorValue[i-1]>sensorValue[i]) {
    sensorPeakSum+= sensorValue[i-1]; 
    x=x+1;
  }
  else { 
  }
  }
  float voltage = (sensorPeakSum/x)* (10.0/1023.0);
  File dataFile = SD.open(filename, FILE_WRITE);
// if the file is available, write to it:
  if (dataFile) {
    dataFile.print(voltage);
    dataFile.print(",");
    dataFile.print(fq); 
    dataFile.println();
    dataFile.close();
    // print to the serial port too:
    Serial.print(voltage);
    Serial.print(",");
    Serial.print(fq); 
    Serial.println();
  }  
  // if the file isn't open, pop up an error:
  else {
    //Serial.println("error opening file");
  }
 delay(1);
}

while (1) {}
}