Hysteresis and How to Detect Peak Analog Value

Hi, all.

I’m writing a function intended to:

  1. read an analog value (using Analog Pin 0 connected to a potentiometer, for now)
  2. detect the peak value (using a 3 “degree” hysteresis)
  3. return the peak value to the main program

The version I have now works, but has a major flaw… the way the code is written, it seems like the peak is detected only if the delta in measured values exceeds the hysteresis within a half-second (due to the delay(500) I have). So if I’m turning the pot so as to increase the value, then turn it in the opposite direction to decrease the value… and do this rather QUICKLY, it all works nicely. BUT… in the real application, it can take a few seconds for the temperature to change (specially when approaching the peak value). In fact, if my peak value is 250 (for example), and I turn the pot slow enough in the opposite direction, I can go all the down to zero, without the code registering a peak.

One option is to simply increase the delay to, say, 3 seconds… but then I have another problem, which is that the reading of the “current” values would also be delayed to once every 3 seconds.
This could make me miss the “real” peak value…
EX: @ 0 sec – value = 100 ** READ VALUE HERE **
@ 1 sec – value = 115
@ 2 sec – value = 100
@ 3 sec – value = 95 ** READ VALUE HERE **
Here, I believe the peak we be logged as “100”, as opposed to “115”.

Any smarter ideas? Thanks! My code is below…

int findPeak()
{
  // variable to store whether the peak has been detected
  int peakNotFound = 1;
  // variable to store new values from the pot
  int newVal = 0;
  // variable to store current values from the pot
  int val = 0;
  // variable to store the peak value
  int peak = 0;
  // variable for loop counter
  int count = 0;
  // amount of hysteresis
  int hysteresis = 3;
  
  // Clear the LCD and position cursor @ top-left
  // FYI: the LCD was initialized in the main program
  lcd.clear();
  // Print a message to the LCD.
  // top row message
  lcd.print("Monitor Peak....");
    
  while (peakNotFound)
  {
    // read pot 10 times and get the sum
    // "potPin" was defined in main program as shown below:
    // "const int potPin = 0;"
    for (count = 0; count < 10; count++) {
    newVal = newVal + analogRead(potPin);
    }
    // get average of readings and remap from 0-1023 to 0-255
    newVal = (newVal / 10);
    newVal = map(newVal, 0, 1023, 0, 255);
    
    // DEBUG code --------------
    Serial.print("val = ");
    Serial.println(val, DEC);
    Serial.print("newVal = ");
    Serial.println(newVal, DEC);
    // -------------------------

    if ((val-newVal) < hysteresis) // if variance is less than hyst
    {
      // print out the value to LCD
      // LCD was cleared in vibrateMotor()
      // set the cursor to bottm row (column 0, line 1)
      lcd.setCursor(0, 1);
      // bottom row formatting: Cyl 1 Temp = xxx
      // bottom row message
      lcd.print("Cyl 1 Temp = ");
      lcd.print(newVal, DEC);
      // if temp transitions from 100 to 99, add a space at the end
      // of the LCD-displayed value to remove remnant trailing "0" 
      if (newVal < 100)
      {
        lcd.print(" ");
      }
    }
    else  // peak has been detected...  exit "while" loop
    {
      peak = val;
      
      // DEBUG
      Serial.print("peak = ");
      Serial.println(peak, DEC);
      
      // peakNotFound is now false
      peakNotFound = 0;
    }
    // set old value to new value
    val = newVal;
    
    // forced to add a half-second delay or "val" and "newVal" will
    // almost always be the same (thus never exceeding the
    // hysteresis)   
    delay(500);
  }
  // return the value of peak to main program
  return peak;     
}
if ((val-newVal) < hysteresis) // if variance is less than hyst

Shouldn’t there be an absolute value in there, somewhere?

Hi,

i would try it this way:

int findPeak()
{
  int cycles = 0;
  int newVal1 = 0;

  // variable to store whether the peak has been detected
  int peakNotFound = 1;
  // variable to store new values from the pot
  int newVal = 0;
  // variable to store current values from the pot
  int val = 0;
  // variable to store the peak value
  int peak = 0;
  // variable for loop counter
  int count = 0;
  // amount of hysteresis
  int hysteresis = 3;
  
  // Clear the LCD and position cursor @ top-left
  // FYI: the LCD was initialized in the main program
  lcd.clear();
  // Print a message to the LCD.
  // top row message
  lcd.print("Monitor Peak....");
    
  while (peakNotFound)
  {
    // read pot 10 times and get the sum
    // "potPin" was defined in main program as shown below:
    // "const int potPin = 0;"
    for (count = 0; count < 10; count++) {
    newVal = newVal + analogRead(potPin);
    }
    // get average of readings and remap from 0-1023 to 0-255
    newVal = (newVal / 10);
    newVal = map(newVal, 0, 1023, 0, 255);
    newVal1 += newVal;
    cycle++;
    // DEBUG code --------------
    Serial.print("val = ");
    Serial.println(val, DEC);
    Serial.print("newVal = ");
    Serial.println(newVal, DEC);
    // -------------------------

    // print out the value to LCD
    // LCD was cleared in vibrateMotor()
    // set the cursor to bottm row (column 0, line 1)
    lcd.setCursor(0, 1);
    // bottom row formatting: Cyl 1 Temp = xxx
    // bottom row message
    lcd.print("Cyl 1 Temp = ");
    lcd.print(newVal, DEC);
    // if temp transitions from 100 to 99, add a space at the end
    // of the LCD-displayed value to remove remnant trailing "0"
    if (newVal < 100)
    {
        lcd.print(" ");
    }
    
    if (cycle == 10)   // 10 cycles measured
    {
       newVal1 := newVal1/10;
       if ((val-newVal) > Hysteresis) { 
         peak = val;
      
         // DEBUG
         Serial.print("peak = ");
         Serial.println(peak, DEC);
      
        // peakNotFound is now false
        peakNotFound = 0;
      }
      // set old value to new value
      val = newVal;
      cycle = 0;
    }
    
    // forced to add a half-second delay or "val" and "newVal" will
    // almost always be the same (thus never exceeding the
    // hysteresis)  
    delay(50); // 10 cycles og 50ms gives also 500ms
  }
  // return the value of peak to main program
  return peak;    
}

Mike

Maybe something like this which doesn’t use a delay…

peakValue = 0;
currentValue = 0;

while (peakValue - currentValue < hysteresis)
  {
    for (count = 0; count < 10; count++) {
      currentValue = currentValue + analogRead(potPin);
    }
    // get average of readings and remap from 0-1023 to 0-255
    currentValue = (currentValue / 10);

    if (currentValue > peakValue) peakValue = currentValue;

  // [Edit:  I had this below the closing brace for the while loop originally] do lcd and serial updates here... 
    // maybe only every 100 iterations to avoid datablasting
  }


return peakValue;

I'll try out your suggestions tonight, guys. Thanks for the input. I'll let you know how it goes.

Well, I tried mike_pa's code first... Unfortunately, I got similar results to my original code. I did notice, though, that the new variable newVal1 was never really used for anything. I just seems to store the measured pot value... and later on divides it by 10, but nothing really uses it. Could this be why?

I didn't get a chance to try out Mitch_CA's code... but just by looking at it, it seems like the lack of delay in reading the measurements, peakValue and currentValue will always be about the same, maybe off by one, at best.

Assume that in the real application, the temperature (pot) value changes slowly... no faster than one unit per second. If I'm reading a current value and a new value faster than once per second, then the two values will always be close to the same, and never exceed the hysteresis.

Thanks for trying to help out... I appreciate it. Any other ideas?

Take a second look. This line...

if (currentValue > peakValue) peakValue = currentValue;

prevents peakValue from ever decreasing. currentValue will go up and down, but peakValue will record... the peak value.

I'll give it a shot, Mitch... Thanks again. I'll let you know how it goes...

Hi,

you are right, sorry for the mistake:

int findPeak()
{
  int cycles = 0;
  int newVal1 = 0;

  // variable to store whether the peak has been detected
  int peakNotFound = 1;
  // variable to store new values from the pot
  int newVal = 0;
  // variable to store current values from the pot
  int val = 0;
  // variable to store the peak value
  int peak = 0;
  // variable for loop counter
  int count = 0;
  // amount of hysteresis
  int hysteresis = 3;

  // Clear the LCD and position cursor @ top-left
  // FYI: the LCD was initialized in the main program
  lcd.clear();
  // Print a message to the LCD.
  // top row message
  lcd.print("Monitor Peak....");

  while (peakNotFound)
  {
    // read pot 10 times and get the sum
    // "potPin" was defined in main program as shown below:
    // "const int potPin = 0;"
    for (count = 0; count < 10; count++) {
    newVal = newVal + analogRead(potPin);
    }
    // get average of readings and remap from 0-1023 to 0-255
    newVal = (newVal / 10);
    newVal = map(newVal, 0, 1023, 0, 255);
    newVal1 += newVal;
    cycle++;
    // DEBUG code --------------
    Serial.print("val = ");
    Serial.println(val, DEC);
    Serial.print("newVal = ");
    Serial.println(newVal, DEC);
    // -------------------------

    // print out the value to LCD
    // LCD was cleared in vibrateMotor()
    // set the cursor to bottm row (column 0, line 1)
    lcd.setCursor(0, 1);
    // bottom row formatting: Cyl 1 Temp = xxx
    // bottom row message
    lcd.print("Cyl 1 Temp = ");
    lcd.print(newVal, DEC);
    // if temp transitions from 100 to 99, add a space at the end
    // of the LCD-displayed value to remove remnant trailing "0"
    if (newVal < 100)
    {
        lcd.print(" ");
    }

    if (cycle == 10)   // 10 cycles measured
    {
       newVal1 := newVal1/10;
             // !!!!! must be newVal1 in the followinf statement
       if ((val-newVal1) > Hysteresis) {
         peak = val;

         // DEBUG
         Serial.print("peak = ");
         Serial.println(peak, DEC);

        // peakNotFound is now false
        peakNotFound = 0;
      }
      // set old value to new value
            //!!! also in the next statement
      val = newVal1;
      cycle = 0;
    }

    // forced to add a half-second delay or "val" and "newVal" will
    // almost always be the same (thus never exceeding the
    // hysteresis)
    delay(50); // 10 cycles og 50ms gives also 500ms
  }
  // return the value of peak to main program
  return peak;
}

hope this helps

Mike

For those following this thread… I’m glad to report that Mitch’s version of the original code works very well. Thanks, Mike. I’ll post it below…

For completeness, I’ll try Mike’s version (with recent corrections) and report back.

It’s a beautiful thing, the fact that are so many ways to write code in order to achieve what you want.

int findPeak()
{
  // variable to store the peak value
  int peak = 0;
  // variable for loop counter
  int currentValue = 0;
  // amount of hysteresis
  int hysteresis = 3;
  // variable for the counter
  int count;

  // Clear the LCD and position cursor @ top-left
  lcd.clear();
  // Print a message to the LCD.
  // top row message
  lcd.print("Monitor Peak!");
   
  while (peak - currentValue <= hysteresis)
  {
      for (count = 0; count < 10; count++) 
      {
        currentValue += analogRead(potPin);
      }
      
      // get average of readings and remap from 0-1023 to 0-255
      currentValue = (currentValue / 10);
      currentValue = map(currentValue, 0, 1023, 0, 255);
      
      // DEBUG CODE -------------------------------------------
      Serial.print("currentValue = ");
      Serial.println(currentValue, DEC);
      delay(20);
      // DEBUG CODE -------------------------------------------
      

      // OPTIONAL LCD CODE ------------------------------------
      // set the cursor to bottm row (column 0, line 1)
      lcd.setCursor(0, 1);
      // bottom row formatting: Cyl 1 Temp = xxx
      // bottom row message
      lcd.print("Cyl 1 Temp = ");
      lcd.print(currentValue, DEC);
      // if temp transitions from 100 to 99, add a space at the end
      // of the LCD-displayed value to remove remnant trailing "0" 
      if (currentValue < 100)
      {
        lcd.print(" ");
      }
      // OPTIONAL LCD CODE ------------------------------------


      if (currentValue > peak)
      {
        peak = currentValue;
      }

      // DEBUG CODE -------------------------------------------
      Serial.print("peak = ");
      Serial.println(peak, DEC);
      delay(20);
      // DEBUG CODE -------------------------------------------
  }
  // return the value of peak to main program
  return peak;     
}

oops.. I meant to thank Mitch, after confirming that his code works. I'll thank Mike in a little while, after checking his code. ;)

OK... the version that Mike proposed still needs a little tweaking. It may be that we're simply not storing the true peak value... or simply the way that I originally set up the code is not the ideal way to do things. In any case, as I have a working version, and the fact that I need to continue with the rest of the code, I'll call the "case" closed.

Thank a lot to both Mike and Mitch for pitching in. At the least the forum now has a working piece of code to detect a peak value.

Cheers!