Hydrofoil, Potentiometer to change moving average sample amount

Hi
I managed to scramble code together but am stuck on how to dynamically change the smoothing value, or delay.

I have a as5600 to read the rideheight of the boat via stick in the water. To smooth out the waves, i added a moving average library. The sensor data is to control stepper that will adjust the foils to fly higher or lower.
How can i either change the window value with a pot to increase/decrease responsivity, or add a pot controlled delay to adjust the sampling interval of the rotary angle sensor or preferably both.

I have 1st potentiometer to give me manual adjustment to offsett the angle and it works.
2nd pot I would like to use to change the window value but its clobal and cant change it.?.
I can add delay in setup() to slow down sample taking but cannot change it's value.

Please help a newbie. Need to get this to work reliably first before starting to add limit switches and such.
The steppers will follow the angle, perhaps via geared system that is to be figured out later with its values etc.

#include <AccelStepper.h>
// Define a stepper and the pins it will use
AccelStepper stepper(AccelStepper::DRIVER, 7, 5);

#include <Wire.h>
#include <AS5600.h>
#include <movingAvg.h>
movingAvg window(20);
int potpin1 = 0;  // analog pin used to connect the potentiometer
int val1;    // up and downvariable to read the value from the analog pin
int potpin2 = 1;  // analog pin used to connect the potentiometer
int val12;    // up and downvariable to read the value from the analog pin
#ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE
#define SERIAL SerialUSB
#define SYS_VOL   3.3
#else
#define SERIAL Serial
#define SYS_VOL   5
#endif

AMS_5600 ams5600;

int ang, lang = 0;
int angshift;
int gain;
int offset;
int slowdown;


void setup()
{ stepper.setMaxSpeed(2000.0);
  stepper.setAcceleration(1000.0);
  window.begin();                            // initialize the moving average
  // initialize the serial port:
  SERIAL.begin(9600);
  Wire.begin();
  SERIAL.println(">>>>>>>>>>>>>>>>>>>>>>>>>>> ");
  if (ams5600.detectMagnet() == 0 ) {
    while (1) {
      if (ams5600.detectMagnet() == 1 ) {
        SERIAL.print("Current Magnitude: ");
        SERIAL.println(ams5600.getMagnitude());
        break;
      }
      else {
        SERIAL.println("Can not detect magnet");
      }
      delay(1000);
    }
  }
}
/*******************************************************
  /* Function: convertRawAngleToDegrees
  /* In: angle data from AMS_5600::getRawAngle
  /* Out: human readable degrees as float
  /* Description: takes the raw angle and calculates
  /* float value in degrees.
  /*******************************************************/
float convertRawAngleToDegrees(word newAngle)
{
  /* Raw data reports 0 - 4095 segments, which is 0.087 of a degree */
  float retVal = newAngle * 0.087;

  ang = retVal;
  return ang;
}
void loop()
{

  val1 = analogRead(potpin1);
  offset = map(val1, 0, 1023, -5, 5); // scale it to use it with the servo (value between 0 and 180)
  val12 = analogRead(potpin2);
  gain = map(val12, 0, 1023, 0, 100); // scale it to use it with the servo (value between 0 and 180)
  angshift = ang + offset; //
  delay(10);
  //
  int win = angshift;    // angle with potentiometer offset
  int gainwindow = win + gain;
  int avg = window.reading(win);             // calculate the moving average from offsetted angular reading

  //
  if (avg > 180) {// step one revolution  in one direction:
    SERIAL.println(String(convertRawAngleToDegrees(ams5600.getRawAngle()), DEC));
    SERIAL.println(angshift);
    SERIAL.println(avg);
    SERIAL.println(win);
    SERIAL.println(gain);
    SERIAL.println(offset);
    Serial.println("clockwise");
    stepper.runToNewPosition(avg);
    delay(100);
  }

  else if (avg <= 180) { // step one revolution in the other direction:
    SERIAL.println(String(convertRawAngleToDegrees(ams5600.getRawAngle()), DEC));
    SERIAL.println(avg);
    Serial.println("counterclockwise");
    stepper.runToNewPosition(avg);
    delay(100);

  }
}

looks like your using a window, averaging over some # of samples

i've used leaky integration

avg += (sample - avg) * K;        // K < 1

using this approach, K can change dynamically, affecting the amount of smoothing

increasing the size of the window makes the avg include older samples

leaky integration favors the newest samples, K adjusting that weighting

Thanks. I tried it, and think I got it to work but it's way too quick still I think.
i.e. is not sluggish enough.
Does the K need to be <1?
what it does now it takes directly the reading + offset and the avg += (sample - avg) * K
Or am I missing something?
Is this the correct way to map the pot between 0 and 1? to have a range between 0 and 1.

 val12 = analogRead(potpin2);
  K = map(val12, 0, 1023, 0, 1); // scale it to use it with the servo (value between 0 and 180)
  angshift = ang + offset; //
  delay(10);
  //
  avg+= (angshift - avg) * K;

How and where should I define avg? int or define and in loop or setup?

That is an Exponentially Weighted Moving Average Moving average - Wikipedia in the Holt form

Declare average as a float outside the loop. It will adapt pretty quickly. K should be between 0 and 1, and a common starting point is 0.2 or 0.1.

float avg = 0;

The size of the K value also depending on how fast your system changes and how fast you sample it. If you update every 10us and the system only changes 1 ADC count per second, a K of 0.001 won't perform much different than a K = 0.100. With the 9600 baud and delays(), you're not close to a ms update, but you should think in terms of sampling and updating at a rate of maybe 1/10 of the time constant of the system you are controlling.

If you are worried about the initial value, you can do a Fast Initial Response trick:

     avg += (avg == 0)? sample : K *(sample - avg) ; 

     // or just initialize with a first value in setup:

     avg = sample;

did you notice the comment?

1 Like

Hi, Thank you both for bearing with me. Not very fluent with programming and my brain is not made for this.
But thanks to your help, I got this working now perfectly, with the adjustable filter and a delay. With combinations I have a lot of control now.

here's the code if someone else is tackling. Havent cleaned it out yet so there's remnants of past efforts.
Now onwards to finish the project:)

#include <AccelStepper.h>
// Define a stepper and the pins it will use
AccelStepper stepper(AccelStepper::DRIVER, 7, 5);

#include <Wire.h>
#include <AS5600.h>
//#include <movingAvg.h>
//movingAvg window(20);
int potpin1 = 0;  // analog pin used to connect the potentiometer
int value1;    // up and downvariable to read the value from the analog pin
int potpin2 = 1;  // analog pin used to connect the potentiometer
int val2;    // up and downvariable to read the value from the analog pin
int potpin3 = 2;  // analog pin used to connect the potentiometer
int value3;    // up and downvariable to read the value from the analog pin
#ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE
#define SERIAL SerialUSB
#define SYS_VOL   3.3
#else
#define SERIAL Serial
#define SYS_VOL   5
#endif

AMS_5600 ams5600;

int ang, lang = 0;
int angshift;
int gain;
int offset;
int offsetresult;
int K;
int Kscale;
int Kscaleresult ;
int delayresult;
int avg;


void setup()
{ stepper.setMaxSpeed(2000.0);
  stepper.setAcceleration(1000.0);
  //window.begin();                            // initialize the moving average
  // initialize the serial port:
  SERIAL.begin(9600);
  Wire.begin();
  SERIAL.println(">>>>>>>>>>>>>>>>>>>>>>>>>>> ");
  if (ams5600.detectMagnet() == 0 ) {
    while (1) {
      if (ams5600.detectMagnet() == 1 ) {
        SERIAL.print("Current Magnitude: ");
        SERIAL.println(ams5600.getMagnitude());
        break;
      }
      else {
        SERIAL.println("Can not detect magnet");
      }
      delay(1000);
    }
  }
}
/*******************************************************
  /* Function: convertRawAngleToDegrees
  /* In: angle data from AMS_5600::getRawAngle
  /* Out: human readable degrees as float
  /* Description: takes the raw angle and calculates
  /* float value in degrees.
  /*******************************************************/
float convertRawAngleToDegrees(word newAngle)
{
  /* Raw data reports 0 - 4095 segments, which is 0.087 of a degree */
  float retVal = newAngle * 0.087;

  ang = retVal;
  return ang;
}
void loop()
{

  value1 = analogRead(potpin1);
  int offset = map(value1, 0, 1023, -100, 100); // scale it to use it with the servo (value between 0 and 180)
  float offsetresult = (float)offset/10.00;
  val2 = analogRead(potpin2);
  int Kscale = map(val2, 0, 1023, 10, 1000.00); // scale it to use it with the servo (value between 0 and 180)
  float Kscaleresult = (float) Kscale / 1000.00;
  value3 = analogRead(potpin3);
  int delaytime = map(value3, 0, 1023, 0, 8000); // scale it to use it with the servo (value between 0 and 180)
  float delayresult = (float)delaytime/10.00;
  angshift = ang + offsetresult; //
  delay(delayresult);
  //
  avg += (angshift - avg) * Kscaleresult;
  // int win = angshift;    // angle with potentiometer offset
  // int gainwindow = win + gain;
  //int avg = window.reading(win);             // calculate the moving average from offsetted angular reading

  //
  if (avg > 180) {// step one revolution  in one direction:
    SERIAL.println(String(convertRawAngleToDegrees(ams5600.getRawAngle()), DEC));
    
 SERIAL.println(avg);
    SERIAL.println(Kscaleresult);
    stepper.runToNewPosition(avg);
     SERIAL.println(delayresult);
    delay(100);
  }

  else if (avg <= 180) { // step one revolution in the other direction:
    SERIAL.println(String(convertRawAngleToDegrees(ams5600.getRawAngle()), DEC));
    SERIAL.println(avg);
    SERIAL.println(Kscaleresult);
    SERIAL.println(delayresult);
  

    stepper.runToNewPosition(avg);
    delay(100);

  }
}
1 Like

No. For two reasons: 1. map() only works with integers, and 2. the relationship between K and the rate of the filter is not linear (although it's probably linear enough to approximate it like that).

You can find the exact formula to compute the factor K for a given frequency here: Exponential Moving Average (K == α)

This of course requires a constant sampling rate, which your code should take care of. The input to the formula is the normalized frequency, i.e. the desired frequency in Hz divided by the sampling frequency in Hz. The maximum meaningful cutoff frequency is the Nyquist frequency, i.e. half the sample frequency, or a normalized frequency of 0.5. (You can use larger values for α, but you can no longer define the -3dB point.)

Note that if α is (close to) zero, your filter output will just remain constant, regardless of input.

for a K = 1/N, it takes roughly 3N samples for avg to reach a steady state value (like an RC time constant) (the eq is a 1st order low-pass filter)

needs to be a float

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.