Data smoothing question.

I have my eye and eyelid code working properly. Now, I'm looking to smooth things out a bit. The input , and output, values causing jitters in my servos. I was looking at data smoothing, and the examples I've looked at deal with the input side. Can this be used on the outputs? Or both? Since I'm modifying values based on other values, what do you feel would give the most benefit?

Smoothing example.

const int numReadings = 10;
 
int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average
 
int inputPin = A0;
 
void setup() {
  // initialize serial communication with computer:
  Serial.begin(9600);
  // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}
 
void loop() {
  // subtract the last reading:
  total = total - readings[readIndex];
  // read from the sensor:
  readings[readIndex] = analogRead(inputPin);
  // add the reading to the total:
  total = total + readings[readIndex];
  // advance to the next position in the array:
  readIndex = readIndex + 1;
 
  // if we're at the end of the array...
  if (readIndex >= numReadings) {
    // ...wrap around to the beginning:
    readIndex = 0;
  }
 
  // calculate the average:
  average = total / numReadings;
  // send it to the computer as ASCII digits
  Serial.println(average);
  delay(1);        // delay in between reads for stability
}

My code.

#include <Servo.h> 
 // create servo object to control a servo 
Servo servo1;  // upper left eyelid 
Servo servo2;  // lower left eyelid 
Servo servo3;  // upper right eyelid
Servo servo4;  // lower right eyelid
Servo servo5;  // left eye horizontal
Servo servo6;  // right eye horizontal
Servo servo7;  // left eye vertical 
Servo servo8;  // right eye vertical 


int pospot1 = A0;
int lrpos = A1;
int udpos = A2;
int blinksw = 52; 
int lidpos;
int lidposlu;
int lidposll;
int lidposru;
int lidposrl; 
int lefteyeud;
int lefteyelr;
int righteyeud;
int righteyelr;



void setup() 
{ 
  pinMode(A0,INPUT);
  pinMode(A1,INPUT);
  pinMode(A2,INPUT);
  pinMode(52,INPUT);
  servo1.attach(9);  // attaches the servos to the servo object 
  servo2.attach(8);
  servo3.attach(7);
  servo4.attach(6);
  servo5.attach(5);
  servo6.attach(4);
  servo7.attach(3);
  servo8.attach(2);
  Serial.begin(38400);
  
} 
 
void loop() 
{ 
  lidpos = analogRead(pospot1);
  lidpos = map(lidpos, 0, 1023, 1370, 1800);

 //left eye code
   lefteyelr = map(analogRead(lrpos), 0, 1023, 580, 2370);
   lefteyeud = map(analogRead(udpos), 0, 1023, 550, 2400);

 //right eye code
   righteyelr = map(analogRead(lrpos), 0, 1023, 580, 2370);
   righteyeud = map(analogRead(udpos), 0, 1023, 550, 2400);
   
 // eye position write
   servo5.writeMicroseconds(lefteyelr);             
   servo6.writeMicroseconds(lefteyeud);
   servo7.writeMicroseconds(righteyelr);             
   servo8.writeMicroseconds(righteyeud);


  // eye lid position and blink function
    
   lidpos = map(analogRead(pospot1), 0, 1023, 1370, 1800);
    int eyeVerticalPosition = map(analogRead(udpos), 0, 1023, 550, 2400);
    int lidposlu = constrain(lidpos + ((lefteyeud-1500)/5), 1370, 1800);
    int lidposll = constrain(lidpos - ((lefteyeud-1500)/5), 1370, 1800);
    int lidposru = constrain(lidpos + ((righteyeud-1500)/5), 1370, 1800);
    int lidposrl = constrain(lidpos - ((righteyeud-1500)/5), 1370, 1800);
    
    if (digitalRead(blinksw) == HIGH)
    {
   servo1.writeMicroseconds(1800);              // tell servos to blink 
   servo2.writeMicroseconds(1800);
   servo3.writeMicroseconds(1800);
   servo4.writeMicroseconds(1800);
   Serial.println("Blink");
   delay(400); 
 }
   else (digitalRead(blinksw) == LOW);
   { 
     servo1.writeMicroseconds(lidposlu);              // set eyelid position 
     servo2.writeMicroseconds(lidposll);
     servo3.writeMicroseconds(lidposru);
     servo4.writeMicroseconds(lidposrl);
     
     delay(25);
 }

  // debug serial info
 Serial.print(lidpos);
 Serial.print(','); 
 Serial.print(lefteyelr);
 Serial.print(',');
 Serial.print(lefteyeud);
 Serial.print(','); 
 Serial.print(righteyelr);
 Serial.print(',');
 Serial.println(righteyeud);
 delay(20);
}

You can 'smooth' input values using the simple filter below:

                                    // Exponential Moving Average filter...
#define FILTER_TIME_CONST   20.0    // time constant: time in samples to reach ~ 63% of a steady value from zero
                                    // the rise time of a signal to > 90% is approx 2.5 times this value
                                    // time to >99% is approx 30 times this value
                                    // For a value of 6, we want c. 50 samples on startup
                                    // small deviations (e.g. 5%) settle to >99.5% in approx 3 times this value    
#define FILTER_WEIGHT (FILTER_TIME_CONST/(FILTER_TIME_CONST +1.0))

// add new value to filtered value
// - returns new filtered value
//
double filter( double filteredValue, int newValue )
{
  return (FILTER_WEIGHT * filteredValue) + (1.0-FILTER_WEIGHT) * (double)newValue;  
}

for example:

float smoothedValue= 0.0;

smoothedValue= filter( smoothedValue, analogRead(inputPin) );

Adjust the FILTER_TIME_CONST for the amount of smoothing you need. The 'smoothness' of the value is affected by the time constant and by the rate at which you do the analogRead().

Looking at your code, try starting with the value of 20.0 I've shown above - it's a bit of a guess, but I think it should be smoother than your current solution.

Also note that 'smoother' also means 'slower to respond'.

Yours,
TonyWilk

1 Like

If your motion is jittery you might have noisy controls. Try moving the controls back and forth for a while to see if that cleans the contacts.

You should get rid of as many delays as you can to make the motion follow the controls more exactly. Try commenting out the Serial.print() lines to see if that helps.

Servo servo1;  // upper left eyelid
Servo servo2;  // lower left eyelid
Servo servo3;  // upper right eyelid
Servo servo4;  // lower right eyelid
Servo servo5;  // left eye horizontal
Servo servo6;  // right eye horizontal
Servo servo7;  // left eye vertical
Servo servo8;  // right eye vertical

Why not use meaningful names?

Servo upperLeftEyelid; // servo 1
Servo lowerLeftEyelid; // servo 2

You can see that swapping the position of the name and the comment renders the comments rather stupid looking. What does that say about your names?

The main reason for the names and comments being the way they are, is that the hardware layout changes as I work on it. It's simpler to change a single digit in the servo names to change the config. As I get farther along, I'll clean up the code and might use the more detailed names.

It's simpler to change a single digit in the servo names to change the config.

I do not understand that. The servos are not wandering around, looking for work. You have stuff attached to the servo, that is making something move. If that something is the upper left eyelid, then name the servo instance that.

If that servo needs to be controlled by a different pin, the instance name doesn't need to change.

The input , and output, values causing jitters in my servos.

95% of the servo jitter problems reported on this forum are due to inadequate servo power supplies and/or bad wiring.

Plan on servos drawing 1 or more Amperes each, so for 8 servos, you need a separate, 5-6V 8A power supply. Solder all power connections as breadboards cannot handle servo current draw, and connect all the grounds.

I'm using a 200w PC power supply to power these servos right now. According the the label, it's capable of 18 amps on the 5v rail. I'm in the process of soldering headers, and wiring, so that should reduce the chances of that causing the jitter.