help with coding for linear actuators with rotary encoder feedback

Hi Everyone,
I'm a newbie to C/C++ and arduino. I am trying to use an arduino Mega2056 to control 4 linear actuators. We are attaching rotary encoders on a rack and pinion that is attached to each actuator. As each actuator extends, the rotary encoder will turn this will allow me to determine exact length of each linear actuator. The linear actuators are arranged in pairs parallel to one another and are intended to move in tandom with its parallel member. For our device to work properly, we need to be constantly adjusting the actuator length as a function of the IMU pitch value. For this reason I am afraid to use a while loop to let the program hang until the actuators reach the set value, as a new length may need to be achieved before the actuator reaches the length associated with the pitch at the time of entry for the while loop. For this reason I am also afraid to use delays.

Getting to the crux of the problem:
First, is there a way that I can set a length using as I'm doing in the code below, but also adjust any misalignment that develops between each pair of arms. I've tried to do this below with EqArms, but I realized that it will run almost instantly after the the linear actuators have been sent a new position. What is the best way that I can allow for the adjustment after the actuators reach their assigned lengths, and before they begin heading to their next set point, while minimizing the delay for the next iteration?

Second, when you read from an input pin, such as for the rotary encoders, if it is still turning, will the program hang on the first instance of digitalRead(Rots[0]) until the pin stops receiving a signal, and then start trying to listen to pin the second encoder with digitalRead(Rots[1])? If I need to digitalRead four different pins to keep track of the length of each arm, is this the proper way to go about doing so? Will a single board be able to handle this?

I know this is more than one question, but hopefully I have been clear enough. I would hook this up and try it out, but our parts are on the way and our deadline is too close to not start the coding now. Please help if you can.

#include "UM7.h"
#include <Servo.h>
//#include <SoftwareSerial.h>
//Connect the RX pin on the UM7 to TX1 (pin 18) on the DUE
//Connect the TX pin on the UM7 to RX1 (pin 19) on the DUE

UM7 imu;
Servo LA1; //create servo
Servo LA2;
Servo LA3;
Servo LA4;

//#include <SoftwareSerial.h>
//SoftwareSerial serial1(0,1); // RX, TX
#define K 1

int motorPin = 9;
int velocity = 0;
int n = 0;
int time_init = 0;
int zeropoint = 0;
int average_zero = 0;

//declaring digital input pins for rotary encoders arm 1
int rot1=22;//rotary encoder actuator 1 on arm1
int rot2=23;//rotary encoder actuator 2 on arm 1

//declaring digital input pins for rotary encoders arm 2
int rot3=24;//rotary encoder actuator 3 arm 2
int rot4=25;//rotary encoder actuator 4 arm 2

int Rots[4]={rot1,rot2,rot3,rot4};


//declaring driver pwm pins
 int drv1=14;//driver for actuator 1, arm 1
 int drv2;//driver for actuator 2, arm 1
 int drv3;//driver for actuator 3, arm 2
 int drv4;//driver for actuator 4, arm 2

//declaring initial Lengths of LA's
int Length[4]={0,0,0,0};
 
void setup() //does not return anything
{
  // put your setup code here, to run once:

 //Whichever Port you initialize last will be the one that stays listenting by default;
  Serial.begin(115200);//alLOWs read and write from serialmonitor inputs/USB RO, TO pins
  Serial1.begin(115200);//alLOWs read and write from R1,T1 pins 4
  
  //Serial2.begin(115200);
  //Serial3.begin(115200);
  
  time_init = millis();
  
  //set the rotary encoder pins to receive a digital signal

  pinMode(rot1,INPUT);
  pinMode(rot2,INPUT);
  pinMode(rot3,INPUT);
  pinMode(rot4,INPUT);

//set driver pins;
  pinMode(drv1,OUTPUT);
  pinMode(drv2,OUTPUT);
  pinMode(drv3,OUTPUT);
  pinMode(drv4,OUTPUT);

 LA1.attach(drv1); //attach servo (whatever PWM pin you connect the servo input to)
  LA2.attach(drv2);
  LA3.attach(drv3);
  LA4.attach(drv4);
}

void Eqarms(int arm,int lengthx, int lengthy, int drvx, int drvy){        
    while (lengthx>lengthy){
         digitalWrite(drvy,HIGH);
         //drv1 was the pwm pin so can't just turn it on vs off now can I?  Wouldn't this just be equal
        //to analogWrite(motorpin,255);
        
        while (lengthx<lengthy){
          digitalWrite(drvx,HIGH);//I don't think it will be as simple as this becuase driving High will only be one direction.  I'll have to swap the polarity of the positive and negative pwm
          //terminals in the code? 
		digitalWrite(drvy,LOW);
        if (arm=1){
        
         Length[1]=Length[1]+digitalRead(Rots[1]);// it will be Rots[1]*length per period/count of periods}
        }
		else {Length[3]=Length[3]+digitalRead(Rots[3]);}
		
		}}}
		

        void SetLength(){
        for(int i=0;i<4;i++){
        Length[i]=Length[i]+digitalRead(Rots[i]);}}

void loop() {
  // put your main code here, to run repeatedly:
  if (Serial1.available() > 0) {
    if (imu.encode(Serial1.read()))
    { // Reads byte from buffer.  Valid packet returns true.

      if (millis() - time_init > 3000)
      {
        velocity = K * (abs((imu.roll - average_zero) / 91.02222));
        if (velocity > 254)
          velocity = 254;
        Serial.println(int(velocity));
      Serial.println(int(average_zero/91.02222));
        int val = map(velocity, 0, 254, 0, 180);

       //if works with servo input
        LA1.write(val);//will use servo library and specify an angle to go to...not sure will work with LA
        LA2.write(val);
        LA3.write(val);
        LA4.write(val);

        }
       
        //Want this to run after they've both actuated to their setpoint
       Eqarms(1,Length[0],Length[1],drv1,drv2);
       Eqarms(2,Length[2],Length[3],drv3,drv4);
        delay (5);//don't necessarilly need
      }
      else
      {
        zeropoint = imu.roll + zeropoint;
        //Serial.println(int(zeropoint/92));
        n = n + 1;
        average_zero = zeropoint / n;
        Serial.print("average_zero is" );
        Serial.println(average_zero/92);
      }
    }
  }
//declaring driver pwm pins
 int drv1=14;//driver for actuator 1, arm 1
 int drv2;//driver for actuator 2, arm 1
 int drv3;//driver for actuator 3, arm 2
 int drv4;//driver for actuator 4, arm 2

Shouldn't you define these pin numbers?

When you are defining constants, like pin numbers, you should declare the variable 'const' to let the compiler help you.

You seem to be treating your Servo output pins as if they were PWM motor driver pins. That is NOT how Servo pins work.

void Eqarms(int arm,int lengthx, int lengthy, int drvx, int drvy){        
    while (lengthx>lengthy){
         digitalWrite(drvy,HIGH);
         //drv1 was the pwm pin so can't just turn it on vs off now can I?  Wouldn't this just be equal
        //to analogWrite(motorpin,255);
        
        while (lengthx<lengthy){
          digitalWrite(drvx,HIGH);//I don't think it will be as simple as this becuase driving High will only be one direction.  I'll have to swap the polarity of the positive and negative pwm
          //terminals in the code? 
 digitalWrite(drvy,LOW);
        if (arm=1){
        
         Length[1]=Length[1]+digitalRead(Rots[1]);// it will be Rots[1]*length per period/count of periods}
        }
 else {Length[3]=Length[3]+digitalRead(Rots[3]);}
 
 }}}

Thank you for the feedback. Again, I am new to the language. I was also surprised when a teammate was able to run the following code that we had developed for servos. I'm confused whether to treat the linear actuator as a motor pin with AnalogWrite(255) or DigitalWrite(motorpin,High) which I thought did the same thing if you want to turn a motor at the max rate...but I'm probably wrong on that count too. Should I use the servo library for the linear actuators or just analogwrite(motorpin,pwmrate)? Will either work?

Additionally, if someone could please advise on the questions in the original post?

void loop() {
if (Serial1.available() > 0) {
if (imu.encode(Serial1.read()))
{ // Reads byte from buffer. Valid packet returns true.
if (millis() - time_init > 3000)
{
velocity = K * (abs((imu.roll - average_zero) / 91.02222));
if (velocity > 254)
velocity = 254;
Serial.println(int(velocity));
int val = map(velocity, 0, 254, 0, 180);
myservo.write(val);
delay (5);

There ARE linear actuators that work like a servo that was cut lengthwise and "uncurled", how about a link to yours, also, sounds like you need a master / slave arrangement and add or subtract an offset to the slave to achieve the angle.

I can see that you have 4 encoders going into 4 digital inputs, but can't see any decoding of the pulse stream that comes from the encoders. The digitalRead will only be giving you current state.

Forgive me if I'm missing something in the code, however most encoders have a pulse stream, 2 if they're quadrature encoders, that allow you to count pulses and thereby determine how many rotations have occurred.

You need to be counting the pulses, and convert to linear motion depending on number of pulses per revolution. eg if each encoder generates 100 pulses per rev, that's 3.6deg per pulse. Using the radius, or teeth pitch, of your pinion you can work out linear distance travelled.

If the encoders are quadrature, then you have 2 pulse streams that give you both distance and direction. These have to be decoded using a state machine.

As previous posts have mentioned, whether you use digital Out, or Servo library depends on your setup. If you're using straight DC motors (or servos setup as continuous rotation) then you need to drive the motors, and in software monitor the encoder outputs to get position feedback. If the actuators & rack are already connected in closed loop, then maybe servo outputs are appropriate

@756E6C
We are using four of these linear actuators:

We are driving them with 4 of these drivers:

Controlling those with hopefully 1 Arduino AtMega2056.

Are you telling me I need a Master-Slave Setup because one board would not be capable of reading the 4 encoders at once?

How can I tell if these "work like a servo" or should be treated as a motor?

@andrewjfox
I really appreciate the detail you've provided. If I set four of the digital pins as INPUT pins as I have done in with the ROTs, will a single board be able to constantly listen for these four pulses for which I can count using some sort of code like:

http://www.arduinoos.com/2010/07/events-management-cont/

In this search I've discovered the concept of interrupts and that may answer the question as to how can I ensure that I capture the inputs of these rotary sensors. Conceptually I understand now that an interrupt would throw an exception inside my loop, allow me to count the changes in states for a rotary encoder and update my length variable, then re-enter my loop. However, I have also read that the interrupt method can only act on one input event at a time. This severely complicates things if two signals such as the rotary encoders that will each turn with the tandem actuation of each pair or actuators. As a reminder, my setup is such that two linear actuators are to be parallel to one another, and act in synch in the same direction. If an interrupt can only monitor one input at a time, can I use this method? My objective is to drive two pairs of these parallel actuators based on a measured orientation from an IMU. Ideally I'd only have to measure one actuator from each pair, but I want to be able to compensate if a differential develops between the lengths of the actuators in each parallel pair.

Does anyone have any recommendations as to how I can read in all four of these lengths through rotary encoders, or otherwise, that would inevitably all be changing at the same time? Is the solution 4 arduino unos to measure each encoder or potentiometer if that's easier? Is there a way to do this with one board? Optimization for speed is critical to my application, as the input is basically an inverted pendulum and these actuators maintain stability under dynamic conditions.

Thank you all in advance for the assistance.

What sort of encoders are you using? I assume they're digital since you have them going into digital pins.

If they're rotary pots (analog) you need to read analog and convert to position.

You can read encoders using digital, but only up to certain speed, which is dependant on your system CPU, interrupts etc

If the encoders are a single pulse train, you can use the digital input to count pulses. This doesn't give you direction so be aware that linear actuators may have some slop, so during direction changes they might move in direction opposite to the way the software is expecting, so instead of adding pulses you'll be subtracting and adding error. That's why most positioning applications use quadrature encoders. They have 2 signals per encoder, which give position and direction. Again you can decode using normal digital pins, but is entirely dependant on speed required. I haven't use interruts on Arduino much so can say whether will do all 4 at once.

If you need speed, there are quadrature decoder chips out there that will do the job, or build your own module using a dedicated leonardo board or similar for each decoder, and connect to the master using SPI or I2C. I'd probably follow this path as it will give you good speed/flexibility/low cost combination. You could also consider embedding the feedback loop in the module so it also drives the motor. Your "master" then does all the IMU calculations, and sends a position setpoint to each module, which then takes care of the motor control and encoder feedback to put the actuator into position. This is kinda how servos work. You set the position reference and the internal controller/gearbox/feedback pot takes care of actually moving it to position.

The actuators are driven like normal DC motor, apply digitalOut signals to the driver board to drive forward/reverse as per data sheet. Can also use PWM output if you want to vary the speed. If you're trying to get precise positioning, I would recommend using PWM so you can slow the motor down as you get closer to the set point.

You have to implement feedback into the software, basically

error = setpoint (where you want them to go) - actual (where they actually are)
if error is greater than acceptable tolerance, keep driving motor in direction required to reduce the error
else stop motor

If your pairs of actuators are always in sync (are they mechanically locked?) consider only using a single encoder for each pair.

Sorry can't provide more specific information