Stepper Control based on 2 changable values.

Status
Not open for further replies.

Ceetee

Member
Hello to Everybody and Best Wishes for the New Year.

I would like a bit (or a lot) of help please. I am trying to operate a stepper motor, via an Easydriver board, based on 2 analog inputs. These inputs are the 'Command' or 'Set Point' and the other is a 'Feedback' signal that changes in response to the stepper motor moving a sensor over a variable number of rotations, basically a closed loop system. The stepper actually rotates a threaded rod that moves a nut up and down then in turn rotates the sensor, this part I cannot change as it is already fitted.

The idea is that you adjust the command signal and the stepper will move until both signals are the same values (for this I have used percentage as an output value because the ranges of the 2 inputs are different and can be any values). For this I want the stepper to slow down when it is close to the command value before it stops and then when a new command signal is input it accelerates the stepper up to a given speed until it is close then slows then stops again. Both the command and feedback signals in the real world are actually 4-20mA but I will sort that out in the future once I get the stepper to operate as I plan.

I have written some code for a T3.6 that appears to operate, in as much as the stepper runs at a constant speed and changes direction based on whether the command signal is higher or lower that the feedback signal. My problem, and there may be many I suspect, is I cannot get the stepper to slow properly or speed up in both directions.

My coding, below, is far from perfect and I appear to have gotten lost in the numerous 'if' statements, is there another way?. This code will eventually become part of a larger program should I get this part to work properly.

Code:
#include <ResponsiveAnalogRead.h>

// Stepper motor is connected to Easydriver board
byte directionPin = 34;
byte stepPin = 33;

// Initial Stepper Pulse and Duty cycle
int pulseWidthMicros = 40;  // microseconds Pulse length
int microsbetweenSteps = 80; // microseconds Duty Cycle width

// Pins for ADC
const int Feedback = A9; // 10K Pot connected to 3.3V & AGND
const int Command = A8;  // 10K Pot connected to 3.3V & AGND

// Real values to introduce differeing datums and ranges as would be in real life.
int MINA = 932;
int MAXA = 3203;
int COML = 400;
int COMH = 3845;

// Other variables
double Output, Output1;
float Ad = 0.0; // Command Signal Range MAXA-MINA
float Cd = 0.0; // Feedback Signal Range COMH-COML

// Initialise ADC to pins
ResponsiveAnalogRead value1(Command, true);
ResponsiveAnalogRead value2(Feedback, true);

void setup() {
  analogReadResolution(12); // Set ADC resolutions to 12 bits
  Serial.begin(115200);
  delay(2000);
  //Set-up Pins for Stepper Motor
  pinMode(directionPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  // Set-up ADC pins for input
  pinMode(Command, INPUT);
  pinMode(Feedback, INPUT);
  // Calculate Signal ranges
  Ad = (MAXA - MINA) / 100; // example 3203-932= 2270/100=22.7 bits/%
  Cd = (COMH - COML) / 100; // example 3845-400= 3445/100=34.45 bits/%

}

int value3;
int value4;
float value5 = 0.0;
float value6 = 0.0;
float lastvalue = 0.0;

void loop() {

  value1.update(); // Take ADC Reading Command
  value2.update(); // Take ADC Reading Feedback
  value3 = value1.getValue(); // Retrieve actual value
  value4 = value2.getValue(); // Retrieve actual value
  value5 = value3 / Ad; // This is incorrect, calculates % of ADC ouput but
  value6 = value4 / Cd; // doesn't take datum value into account.

  // Check if Command is bigger than Feedback signal to set stepper direction
  if (value5 > value6) {
    digitalWriteFast(directionPin, HIGH);
  }
  else
  {
    digitalWriteFast(directionPin, LOW);
  }
  // Check if  micro's between pulses is less than 1999 to operate stepper or stop
  if (microsbetweenSteps < 1999) {
    digitalWriteFast(stepPin, HIGH);
    delayMicroseconds(pulseWidthMicros); // this line is probably unnecessary
    digitalWriteFast(stepPin, LOW);
    delayMicroseconds(microsbetweenSteps);
  }
  // Check if command signal is higher than the feedback signal
  if (value5 > value6) {
    Output1 = value5 - value6;
    Output = 100 - Output1; // Output is inverted from Output1

    // This is where I check if the Output is between 98 & 100% so I can slow/quicken the stepper down
    if (Output > 98 && Output < 100) {

      // Then decide which direction the signal is going and act
      if (lastvalue < value6) {
        microsbetweenSteps = microsbetweenSteps - 1; // Speed up
        if (microsbetweenSteps < 100) {
          microsbetweenSteps = 100; // prevent duty cycle being to short
        }
      }
      else
      {
        microsbetweenSteps = microsbetweenSteps + 1; // Slow Down
        if (microsbetweenSteps > 5000) {
          microsbetweenSteps = 5000;
        }
      }
    }
    else
    {
      microsbetweenSteps = 100; // When outside 98% - 100% run Stepper quickish
    }
  }
  // The section below is based on the above section
  // Is Feedback > Command signal
  if (value6 > value5) {
    Output1 = value6 - value5;
    Output = 100 - Output1;

    if (Output > 98 && Output < 100) {
      if (lastvalue < value6) {
        microsbetweenSteps = microsbetweenSteps - 100;
        if (microsbetweenSteps < 100) {
          microsbetweenSteps = 100;
        }
      }
      else
      {
        microsbetweenSteps = microsbetweenSteps + 100;
        if (microsbetweenSteps > 5000) {
          microsbetweenSteps = 5000;
        }
      }
    }
    else
    {
      microsbetweenSteps = 100;
    }
  }

  // Print out values
  Serial.print("Command = ");
  Serial.print(value3);
  Serial.print(" Feedback = ");
  Serial.print(value4);
  Serial.print(" Command2 = ");
  Serial.print(value5);
  Serial.print(" Feedback = ");
  Serial.println(value6);
  Serial.print(" Output = ");
  Serial.print(Output);
  Serial.print(" Output1 = ");
  Serial.print(Output1);
  Serial.print(" Micro's = ");
  Serial.println(microsbetweenSteps);
  Serial.println();
  Serial.print("Last Value = ");
  Serial.println(lastvalue);

  lastvalue = value6; // take previous value for checking signal direction
  delay(2);
}


I have tried AccelStepper and TeensyStep libraries but both seem to want to be given a number of steps for it to move to rather than based on a second input. There may be others out there that will do just what I want but I haven't found them yet, still looking.

Any guidance would be appreciated.

Chris
 
Hello neurofun many thanks for the reply.

I agree that is what I thought initially.

I did try PID route but and it was not 100% successful, from memory it would reduce speed and increase speed but only if the error was one way such as command higher than feedback and not vice versa, but I am willing to try again. Now all I need to do is dig that particular piece of code out and have another look. Thanks for your input.
 
In retrospect a pid loop might be overkill for your application. I had a look at the AccelStepper lib and that's all you need. It also dramatically simplifies your code.
Have a look, you will have to fiddle with the parameters in setup to make it behave as you want.
the code is pretty self explanatory and should work but I have no hardware to test it out. So review it carefully.
If you have any questions feel free to ask.

Code:
#include <ResponsiveAnalogRead.h>
#include <AccelStepper.h>

AccelStepper stepper(AccelStepper::DRIVER, 33, 34);

// Pins for ADC
const int Feedback = A9; // 10K Pot connected to 3.3V & AGND
const int Command = A8;  // 10K Pot connected to 3.3V & AGND

// Real values to introduce differeing datums and ranges as would be in real life.
int MINA = 932; // Command Signal Range MAXA-MINA
int MAXA = 3203;
int COML = 400; // Feedback Signal Range COMH-COML
int COMH = 3845;

// Other variables
double error;
float deadBand;
float scale;

// Initialise ADC to pins
ResponsiveAnalogRead value1(Command, true);
ResponsiveAnalogRead value2(Feedback, true);

void setup() {
  analogReadResolution(12); // Set ADC resolutions to 12 bits
  Serial.begin(115200);
  delay(2000);
  // Set-up ADC pins for input
  pinMode(Command, INPUT);
  pinMode(Feedback, INPUT);

  //adjust following paramerters
  stepper.setMaxSpeed(500);
  stepper.setAcceleration(50);
  deadBand = 50.0; //stop moving when error < deadBand
  //scale equals to the maximum possible error , iow the maximum travel distance in steps
  //scale = length of lead screw / pitch of lead screw * steps per revolution
  scale = 100000;
  
  //uncomment to reverse stepper direction
  // stepper.setPinsInverted(true, false, false);
}

int value3;
int value4;
float value5 = 0.0;
float value6 = 0.0;

void loop() {
  value1.update(); // Take ADC Reading Command
  value2.update(); // Take ADC Reading Feedback
  value3 = value1.getValue(); // Retrieve actual value
  value4 = value2.getValue(); // Retrieve actual value
  value5 = float(value3 - MINA) / float(MAXA - MINA) * scale; // convert command range from MINA->MAXA to 0->scale
  value6 = float(value4 - COML) / float(COMH - COML) * scale; // convert feedback range from COML->COMH to 0->scale

  error = value6 - value5;
  if(abs(error) < deadBand ) error = 0;

  stepper.move(error);
  stepper.run();
}
 
Hello neurofun

That is brilliant.. :) It functions in both directions then decelerates/accelerates about the Deadband just what I was after. I spent most of the weekend trying PID control and kept getting really lost with the stepper either rotating CW or CCW but couldn't get it to stop and then change direction. Your code is a lot simpler than my original and makes sense when I see it written down, so many many thanks for your help.

I can now start playing again.

Thanks again.
 
You're welcome. I hope all was not lost and you learned something about pid control over the weekend :)
Now do yourself a favor and rename your objects, variables to something more explanatory. It will make your code easier to read for everyone, including yourself.
Code:
const int commandPin = A8;
ResponsiveAnalogRead commandAnalogIn(commandPin, true);
commandAnalogIn.update();
commandRaw = commandAnalogIn.getValue();
commandScaled = float(commandRaw - MINA) / float(MAXA - MINA) * scale;
error = feedbackScaled - commandScaled;
 
The more I learn the more I want to learn.

I agree with you about naming things so that it is easier to understand, especially for me, so I must try harder.

Thanks again
 
Status
Not open for further replies.
Back
Top