Linear Actuator Control With Accuracy Consideration

Hi,

I am trying to control a linear actuator with has is own potentiometer. I have another potentiometer that I am moving forwards and backwards and the actuator will move forwards and backwards to mimic the position of the input pot.

The sketch below works fine. However, my sketch is getting more complicated by adding temperature and pressure sensors, along with a fan and data logger. I have put all of this (except the logger) into another sketch and this is causing the actuator to move past the ‘goal’ position and it will move backwards and forwards between the ‘goal’ for a long time (because of the time taken to travel through the rest of the loop). Because of this, I was planning on adding an accuracy factor where the arm will stop when it is within a certain percentage of the input pot.

Would anyone be able to suggest a suitable way do this and if they can think of a better command over the if statements to control the actuator could you please tell me.

Thanks for any advice in advance.

#include<LiquidCrystal.h>;
  LiquidCrystal lcd (12, 11, 5, 4, 3, 2);

int Pot_In = A1;
int Pot_Motor = A0;

const int RedLED = 7;
const int GreenLED = 8;

const int Motor_PWM = 6;
const int Arm_In = 22;
const int Arm_Out = 23;
const int Clutch = 24;
const int Accuracy = 5;

long Previous_Millis = 0;
long Interval = 300;


const int kP = 5;
const int kI = 3;
const int kD  = 3;
int prevError = 20;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  pinMode(Pot_In, INPUT);
  pinMode(Pot_Motor, INPUT);
  pinMode(Motor_PWM, OUTPUT);
  pinMode(RedLED, OUTPUT);
  pinMode(GreenLED, OUTPUT);
  pinMode(Arm_In, OUTPUT);
  pinMode(Arm_Out, OUTPUT);
  pinMode(Clutch, OUTPUT);

 digitalWrite(Motor_PWM, LOW);
  digitalWrite(RedLED, LOW);
  digitalWrite(GreenLED, LOW);
  digitalWrite(Arm_In, LOW);
  digitalWrite(Arm_Out, LOW);
  digitalWrite(Clutch, HIGH);

  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
  lcd.print("Valve=");
  lcd.setCursor(0, 1);
  lcd.print("Error=");
  lcd.setCursor(0, 4);
  lcd.print("Heat Sink=");


}

void loop() {

  int sensorValueIn = analogRead(Pot_In);
  map(sensorValueIn, 0, 1023, 0, 100);
  int PercentageIn = map(sensorValueIn, 0, 1023, 0, 100);
  round (PercentageIn);
  int PercentageInRound = PercentageIn;

  int sensorValueAct = analogRead(Pot_Motor);
  map(sensorValueAct, 0, 1023, 0, 100);
  int PercentageAct = map(sensorValueAct, 0, 1023, 0, 100);
  round (PercentageAct);
  int PercentageActRound = PercentageAct;

      int input = PercentageIn;
    int feedback = PercentageAct;
    int outputLimit = 225;

    int dT = millis - Previous_Millis;
    int errorP = input - feedback;
    int errorI = errorI + errorP;
    int errorD = (errorP - prevError) / dT;
    int prevError = errorP;

    int Motor_Power = kP * errorP + kI * errorI + kD * errorD;

    if ((abs(Motor_Power)) > outputLimit) {

      Motor_Power = 255;
    } 

  if (PercentageActRound == PercentageInRound)
  {
    digitalWrite(Arm_In, LOW);
    digitalWrite(Arm_Out, LOW);
    digitalWrite(Clutch, HIGH);
  }

  if (PercentageActRound > PercentageInRound)
  {
    analogWrite(Motor_PWM, 255);
    digitalWrite(Arm_In, HIGH);
    digitalWrite(Arm_Out, LOW);
    digitalWrite(Clutch, HIGH);
  }

  if (PercentageActRound < PercentageInRound)
  {
    analogWrite(Motor_PWM, 255);
    digitalWrite(Arm_In, LOW);
    digitalWrite(Arm_Out, HIGH);
    digitalWrite(Clutch, HIGH);
  }

  void LCD_And_Display ();
  unsigned long Current_Millis = millis();

  if (Current_Millis - Previous_Millis > Interval) {
    // save the last time you blinked the LED
    Previous_Millis = Current_Millis;


    lcd.setCursor(6, 0);
    lcd.print(PercentageInRound);
    lcd.print("% ");

    lcd.setCursor(7, 1);
    lcd.print(PercentageActRound);
    lcd.print("% ");

    lcd.setCursor(13, 0);
    lcd.print(abs(PercentageActRound - PercentageInRound));
    lcd.print("% ");


    if (((abs(PercentageActRound - PercentageInRound) > Accuracy))) //Rule to say if values are out of Precision to flash red LED or if in tolerance to turn on GreenLED
    {
      digitalWrite(GreenLED, LOW);
      digitalWrite(RedLED, HIGH);

    }
    else {
      digitalWrite(RedLED, LOW);
      digitalWrite(GreenLED, HIGH);
    }


  }

}

I cannot help because I do not understand

with has

and

it will move backwards and forwards between the 'goal'

I also do not understand what lines like this are supposed to accomplish:

map(sensorValueIn, 0, 1023, 0, 100);

I also do not understand where lines like this come from or what they are intended to accomplish:

  round (PercentageAct);

It could work but I do not know where to go to check on it.

I’ve never used it, but you might want to give the Arduino PID library a look:
http://playground.arduino.cc/Code/PIDLibrary

I you structure the loop() properly, it should be able to handle all those tasks “simultaneously”.

Sorry,

with has

Was supposed to say "which has". The linear actuator has an inbuilt potentiometer.

 it will move backwards and forwards between the 'goal'

If I set my input potentiometer at 50% and my linear actuator is at 0% it will move to around 50%, then move past it to 55%, then move backwards to 45% and keep doing this forwards and backwards motion.

I also do not understand what lines like this are supposed to accomplish:

Code: [Select]

map(sensorValueIn, 0, 1023, 0, 100);

Linear actuator will move a valve and the map function will alter the value of the potentiometers to have a range of 0-100% to show how far open the valve is.

I also do not understand where lines like this come from or what they are intended to accomplish:

Code: [Select]

round (PercentageAct);

This just round the value and leaves no decimal places so that it can be easily read on the LCD display. With the input and output potentiometers rounded it also allows them to easily 'match' each other’s values without trying to move a millimetres constantly.

I've never used it, but you might want to give the Arduino PID library a look:
http://playground.arduino.cc/Code/PIDLibrary

I you structure the loop() properly, it should be able to handle all those tasks "simultaneously".

At the moment the actuator behaves as expected but as I've added other parts it had brought about the problem with the actuator shooting past the 'goal value' and then moving in front and behind of it. I am using a Dallas ds18b20 temperature sensor and that takes a long time to calculate (around 0.5 secs I believe) and I think this is one of the main causes for my shooting problem.

I've used the serial plotter to check the PID and it looks to be working well.

My biggest question really is, can anyone think of a more suitable way program the actuator to move forwards and backwards in a better way. My understanding is that at the moment the actuator will realise it needs to move backwards, so it will. The Arduino will then go through the rest of the loop updating the display and getting the temperatures and pressures etc. The loop will then start again and the Arduino will realise is has gone past the goal and it will then move the actuator arm forwards, do the rest of the loop and realise its need to go backs, and so on, and so on.

lparry92:
At the moment the actuator behaves as expected but as I've added other parts it had brought about the problem with the actuator shooting past the 'goal value' and then moving in front and behind of it. I am using a Dallas ds18b20 temperature sensor and that takes a long time to calculate (around 0.5 secs I believe) and I think this is one of the main causes for my shooting problem.

If that’s the case, I’d suggest you first try to fix what broke your solution rather than change what was already working.

Start by restructuring your loop() so that things don't block for so long. For example, you don't have to wait for the DS18B20 to finish the temperature measurement. There are ways to kick off the conversion and then get back to your loop() very quickly. You can then do useful stuff while the conversion is taking place and then get the results later. Also, ask yourself if you're requesting more bits of resolution from the DS18B20 than you really need. See the examples in the DallasTemperature library. I imagine you can use similar techniques with your other sensors.

Hi,

 map(sensorValueAct, 0, 1023, 0, 100);

That is not how you write a map function.

https://www.arduino.cc/en/Reference/Map

Tom.... :slight_smile:

  int sensorValueIn = analogRead(Pot_In);
  map(sensorValueIn, 0, 1023, 0, 100);
  int PercentageIn = map(sensorValueIn, 0, 1023, 0, 100);

You expend more resources than is necessary dividing the input range by 10, and THROW AWAY THE EFFORT. Then, you do the same thing again, but save the result. Why?

 int sensorValueAct = analogRead(Pot_Motor);
  map(sensorValueAct, 0, 1023, 0, 100);
  int PercentageAct = map(sensorValueAct, 0, 1023, 0, 100);

Then, you do it again. Why?

 round (PercentageAct);

What possible effect would you expect to see rounding an int? Even if it made sense to do that, why bother when you clearly don’t care what the result is?

 int PercentageInRound = PercentageIn;

  int sensorValueAct = analogRead(Pot_Motor);
  map(sensorValueAct, 0, 1023, 0, 100);
  int PercentageAct = map(sensorValueAct, 0, 1023, 0, 100);
  round (PercentageAct);
  int PercentageActRound = PercentageAct;

      int input = PercentageIn;
    int feedback = PercentageAct;

At this point, input, PercentateIn, and PercentageInRound all hold exactly the same value. Why don’t you make about 6 more copies of it?

feedback, PercentageAct, and PercentageActRound all hold exactly the same value. Why don’t you make about 6 more copies of it?

   int errorD = (errorP - prevError) / dT;
    int prevError = errorP;

So, clearly there are TWO variables named prevError. That’s useless.

 if (PercentageActRound == PercentageInRound)
  {
    digitalWrite(Arm_In, LOW);
    digitalWrite(Arm_Out, LOW);
    digitalWrite(Clutch, HIGH);
  }

  if (PercentageActRound > PercentageInRound)
  {
    analogWrite(Motor_PWM, 255);
    digitalWrite(Arm_In, HIGH);
    digitalWrite(Arm_Out, LOW);
    digitalWrite(Clutch, HIGH);
  }

  if (PercentageActRound < PercentageInRound)

else if is a wonderful statement. You really should learn about it.

   if (((abs(PercentageActRound - PercentageInRound) > Accuracy))) //Rule to say if values are out of Precision to flash red LED or if in tolerance to turn on GreenLED
    {
      digitalWrite(GreenLED, LOW);
      digitalWrite(RedLED, HIGH);

    }
    else {
      digitalWrite(RedLED, LOW);
      digitalWrite(GreenLED, HIGH);
    }

Personally, I’d want to see these lights change state as the actuator moves, NOT on a periodic basis.

And, I’d use the correct type to hold the value returned by millis(). YMMV.

vaj4088:
I cannot help because I do not understand

So why try?

So why try?

Not understanding the goal does not mean that vaj4088 can not see that there are problems with the code.

Optimizing crappy code, which seems to be OPs goal, is pointless. The blatant problems need to be fixed first.

If I set my input potentiometer at 50% and my linear actuator is at 0% it will move to around 50%, then move past it to 55%, then move backwards to 45% and keep doing this forwards and backwards motion.

If you are using the PID technique, then you fix these kinds of behaviours - oscillating, overshoot - by tweaking the PID parameters.

One thing I'd suggest would be to rate-limit your PID samples using millis() or micros(), so as to be feeding samples at a known rate into the algorithm.

If you don't do this, dT will frequently wind up being zero, and you will get all kinds of crazy things happening.

There's no reason to not use floats for your calculations.

Actually ... you are setting previousMillis only when the LCD is updated, but jamming samples into your algoritm as fast as it can take them.

Oh - and you define errorI local to the function instead of static - it will forget it's previous value every time loop() runs. In fact, it will contain garbage off the stack because you use it's uninitialized value for the addition.

Dude - it's a miracle your code works as well as it does.

Dude - it's a miracle your code works as well as it does.

Geez, is it necessary to pull your punches like that? Tell it like it is. 8)

Hi,
Have you tried running the actuator motor at a lower speed?
You are analogWrite at 255, try 180 or lower.
Or when you are within say 10% of setpoint, slow down.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Thanks.. Tom.. :slight_smile: