PID for a lift?

Can PID be done with potentiometers and on driver control? Our team has been trying to create a correctional code for the arms of our lift:


void Lift(int liftPower)
{
    int powerDifference = SensorValue[rmobilebase] - SensorValue[lmobilebase];
    int powerError = powerDifference*0.3;
    //here you can add constraints for the corrections, e.g. a maximum value.
    motor[lmobilebase] = liftPower + powerError;
    motor[rmobilebase] = liftPower - powerError;
}

	if(vexRT[Btn8D]==1) //mobile base controlled by right back buttons
    {
    	Lift (100);
    }
    else if (vexRT[Btn8U]==1)
    {
    	Lift (-100);
    }
    else
    {
      Lift (0);
    }

However, our lift is still moving at different speeds. Any ideas as to why this doesn’t work would be greatly appreciated!

You already effectively have a P control loop on this system, so adding the other components to your system wouldn’t be too bad, although I’ve found that simple P control is sufficient for this type of task. You might try adjusting your proportional gain (the 0.3 multiplier you have against your error calculation) to see if a higher or lower value helps.

I suggest only adding/subtracting from one of the sides while doing correction, the idea here is that one acts as a master/lead side while the other is a slave/follower. That’s what I used to do on my drivetrain control loops, you might find it to be more stable:


motor[lmobilebase] = liftPower + powerError;
motor[rmobilebase] = liftPower;

instead of


motor[lmobilebase] = liftPower + powerError;
motor[rmobilebase] = liftPower - powerError;

Here’s a solution packaged into a task that I’ve used for position matching on drivetrains:



task
taskDriveHold () {
  driveHoldRunning = true;
  
  while (true) {
    if (leftDriveSetPoint == rightDriveSetPoint) {
      float driveOut = pidCalculate (leftDrivePID, leftDriveSetPoint, SensorValue (leftDriveSensorPort));
      float slaveOut = pidCalculate (driveSlavePID, SensorValue (leftDriveSensorPort), SensorValue (rightDriveSensorPort));

      driveLeftDrive (driveOut);
      driveRightDrive (driveOut + slaveOut);
    } else {
      driveLeftDrive (pidCalculate (leftDrivePID, leftDriveSetPoint, SensorValue (leftDriveSensorPort)));
      driveRightDrive (pidCalculate (rightDrivePID, rightDriveSetPoint, SensorValue (rightDriveSensorPort)));
    }
  }
}


That code snippet uses a function called pidCalculate, here’s the implementation for that, along with a struct I use to package all the stored information that a PID controller needs:


typedef struct{
  float m_fKP;
  float m_fKI;
  float m_fKD;
  float m_innerBand;
  float m_outerBand;
  float m_fSigma;
  float m_fLastValue;
  unsigned long m_uliLastTime;
  float m_fLastSetPoint;
} PID;


float
pidCalculate (PID pid, float fSetPoint, float fProcessVariable) {
  float fDeltaTime = (float)(nPgmTime - pid.m_uliLastTime) / 1000.0;
  pid.m_uliLastTime = nPgmTime;

  float fDeltaPV = 0;
  
  if(fDeltaTime > 0)
    fDeltaPV = (fProcessVariable - pid.m_fLastValue) / fDeltaTime;
  pid.m_fLastValue = fProcessVariable;

  float fError = fSetPoint - fProcessVariable;
	
  if(fabs(fError) > pid.m_innerBand && fabs(fError) < pid.m_outerBand)
    pid.m_fSigma += fError * fDeltaTime;

  if (fabs (fError) > pid.m_outerBand)
    pid.m_fSigma = 0;

  float fOutput = fError * pid.m_fKP
                             + pid.m_fSigma * pid.m_fKI
                              - fDeltaPV * pid.m_fKD;
	
  return fOutput;
}

General advice: Use datalog (if programming in RobotC, general logging facility otherwise).
You’d see what the observed angular error was and what kind of correction did your algorithm produced.

Considering the fact that the potentiometer has about 15 “ticks” per an angular degree, your gain of 0.3 would produce a power difference of 10 (+5 on one side vs. -5 on the other side) for every degree of mismatch. That sounds fine to me, but it heavily depends on:

  1. the type of the lift, especially if it is non-linear. 4-bar or elevator could tolerate bigger mismatch, while RD4B or scissor lift could be very sensitive on the edges of the usable span.
  2. Sensor gearing - maybe that 1-degree error as seen by the potentiometers is much bigger on the arm due to the sensor location/axle.
  3. Sensor matching/calibration - does your lift, when perfectly balanced, show the same value on both sides? Does it still match at different heights? There are 2-3 variables at play here: Sensor offset (how much is a “zero” on each side?), sensor gain (how much does the sensors increment for the same amount of rotation?) and sensor linearity (is the increment per degree the same everywhere on the working span?)

Other thing you need to consider is the sensor noise and position repeatability - with the 4096 “ticks”, the Cortex is trying to discern 1mV on a weak signal typically routed along motor power wires and through few connectors - don’t expect resolution to be better than 1-2 degrees anyway.