proportional control

Hi everyone

I couldn’t decide whether this should go in the hardware, sensors or programming section since it involves all(more of a software solution I think I need though), so I hope this is the right place.

So I’m doing a little test with trying to set up proportional control with a potentiometer and a 3000 step stepper motor(it’s highly geared, which is why it has 3000 steps), so if the pot moves 50 degrees, to stepper will as well.
All the hardware is working and I’ve done some basic smoothing for the pot data, however my stepper motor is still jittering around quite a bit. So when the pot is still and the filtering kicks in everything is fine, but fine movements of a difference is very small it causes the jittering.

So my question is, does anyone have any information on programming techniques that may help combat this jittering. I could add a threshold so that if the movement different between the stepper and the pot is less than something like 1 degree (remember there are 3000 steps so resolution is ~0.12 degrees), then it won’t move, but in my eyes that just decreases the resolution and perhaps even increases jittering, so I was hoping anyone had some ideas for a more advanced approach than that.

It’s fine when there is a lot of reasonable difference in movement, but it’s when things are at a stand still and just slightly touched/moved that the jittering arises. I was going to also experiment using a UM7 sensor for proportional input, however there will be significantly more variation from the output of that sensor, which is why I was starting with just a pot.

For reference here is my code for you to view, if interested. I was originally stepping the stepper manually but then decided to use accelstepper library due to it’s wide variety of functions that may be useful in the future and makes the code more compact.

#include <AccelStepper.h>

#define AIN A0
#define filterSamples 13
int smoothArray[filterSamples];
int rawData, smoothData;

AccelStepper stepper(1, 12, 13); //first motor, clock pin 12, direction pin 13


void setup() {  
  Serial.begin(9600);
  stepper.setMaxSpeed(1000);
}


void loop() {
  rawData = analogRead(AIN);

  smoothData = digitalSmooth(rawData, smoothArray);

  rawData = map(rawData, 0, 1023, 0, 3000);
  smoothData = map(smoothData, 0, 1023, 0, 3000);

  Serial.print(rawData);
  Serial.print("    ");
  Serial.println(smoothData);
  
  stepper.moveTo(smoothData);
  stepper.setSpeed(1000);
  stepper.runSpeedToPosition();
}

int digitalSmooth(int rawIn, int *sensSmoothArray) {     // "int *sensSmoothArray" passes an array to the function - the asterisk indicates the array name is a pointer

  int j, k, temp, top, bottom;
  long total;
  static int i;
 // static int raw[filterSamples];
  static int sorted[filterSamples];
  boolean done;

  i = (i + 1) % filterSamples;    // increment counter and roll over if necc. -  % (modulo operator) rolls over variable
  sensSmoothArray[i] = rawIn;                 // input new data into the oldest slot

  for (j=0; j<filterSamples; j++){     // transfer data array into anther array for sorting and averaging
    sorted[j] = sensSmoothArray[j];
  }
  done = 0;                // flag to know when we're done sorting              
  while(done != 1){        // sort lowest to highest
    done = 1;
    for (j = 0; j < (filterSamples - 1); j++){
      if (sorted[j] > sorted[j + 1]){     // swap
        temp = sorted[j + 1];
        sorted [j+1] =  sorted[j] ;
        sorted [j] = temp;
        done = 0;
      }
    }
  }

  // throw out top and bottom 15% of samples - limit to throw out at least one from top and bottom
  bottom = max(((filterSamples * 15)  / 100), 1); 
  top = min((((filterSamples * 85) / 100) + 1  ), (filterSamples - 1));
  k = 0;
  total = 0;

  for ( j = bottom; j< top; j++){
    total += sorted[j];  // total remaining indices
    k++; 

  }
  return total / k;    // divide by number of samples
}

Thanks, any help or advice appreciated :smiley:

Why don't you try using a rotary encoder as the input - each click is definite and the software counts them. There can be no jitter.

Do you know how much jitter you have at the analog input?

Do you realize there are better ways to low-pass filter than averaging N readings and truncating
to an integer afterwards? That doesn't remove single-bit jitter, just makes it less likely.

A simple digital low pass filter using float would return a much more stable result without needed an
array. The float result then would create quantization errors on the call to map.

float average = 0 ;  // single float is enough state for a one-pole filter

void loop ()
{
  int latest = analogRead (AIN) ;
  average += 0.01 * (latest - average) ; // one pole digital filter, about 20Hz cutoff
  stepper.moveTo (map (average, 0, 1023, 0, 3000)) ;
  stepper.run () ;  // just use run, don't wait, need to keep reading pot
}

There is not much jitter, but that ironically is part of the problem. when there's only 2-3 values of difference in analog input, this is where the jitter is worst as the motor is not moving a significant amount and so vibrates all over the place. I'll also try your averaging example using floats, I never thought to do that, thankyou MarkT.

As for marco-c's advice, I'm not quite sure what that means, as using an encoder defeats the problem as I'm trying to make it work with multiple types of input, like UM7 mpu's, pots, flex sensors etc... But yes, if I wanted to replace a pot for a rotary encoder, that could be valid.

Thanks everyone, I'll give an update later

UPDATE:

So I tested and modified the code example given by MarkT so that it uses a UM7 IMU.
Also as a note, when I first started using MarkT’s example using just stepper.run(); from the accelstepper library, it resulted in my stepper hardly moving (I then realized that stepper.run implements acceleration). So I went ahead and added acceleration values in void setup which resulted in smoother operation, but I’m still working on experimenting with values to get it as smooth as possible. (The stepper ramping up also resulted in more heat from the motor/more power draw I think as things started to get warmer quicker. I don’t know whether this is usual but it’s only from observation/assumption for now)

But this is what works:

#include <AccelStepper.h>
#include <UM7.h>

UM7 imu;


#define AIN 0
int latest;
float average = 0;

AccelStepper stepper(1, 12, 13);

void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
  stepper.setMaxSpeed(1000);
}

void loop() {

  if (Serial1.available() > 0) {
    if (imu.encode(Serial1.read())) {
      latest = imu.pitch/91.02222;
      Serial.println(latest);
    }
  }
  
  //latest = analogRead(AIN);
  average += 0.01 * (latest - average);
  stepper.moveTo(map(average, 0, 1023, 0, 3000));
  stepper.setSpeed(1000);
  stepper.runSpeedToPosition();
}

The low pass filter is definitely better than the smoothing array had before. There’s also quite a few options for me to go with now. Which are basically increase the step resolution from 1/1 to anywhere up to 1/16 via my tb6600 driver or implement acceleration and adjust filter, acceleration and speed values .

So I’ve almost got it to an ideal state. If anyone has any other ideas to try then that would be fantastic.

I think I might also move on to try experimenting with a high torque geared DC motor and attach a precision potentiometer so the motor has feedback. Not sure how that will turn out using float values on both sensor and DC motor side.

I’ll give any updates when I produce results or come across a problem.

Thanks

Do as servos do: Dead-band

If you have your potentiometer connected between GND and Vcc and your motor is disconnected you should get a very stable analogue reading if you monitor it using the serial monitor. At most you may see changes in the least significant bit, in other words the analogue reading may change by one. I would expect that reading to be virtually independent of the Vcc supply voltage.

I believe the motor is causing Vcc to change due to current surges when the motor starts and/or 'kickback' spikes from the motor winding inductances. Many threads in this forum advise using separate power supplies for an Arduino and a motor. That could be a solution to the issue but, assuming it's only a small stepper motor, it may be possible to resolve the issue by improving the decoupling of the power supply to the motor and to the Arduino.

Yes, you can use a dead-band, but that dead-band should need to be an analogue reading change of only plus or minus one.

Stepper heating is usually maximum when stationary, that's probably why its getting hotter!