Help with PID in IQ

Hi there. This is essentially a follow-up to the thread I posted here a while back. With the Vexcode IQ v2.0 release, I was finally able to start coding in C++ so I could have a PID program for my project. However, since I don’t use C++ often, I looked for some guides and happened to stumble upon @Connor’s guide for V5 PID. Naturally, I thought that it would work because both V5 and IQ share the same programming language now, but it didn’t take long for me to understand that something was off.

I spent yesterday’s evening and today’s morning as well as noon trying to figure out what was the problem, but I’m still equally as stumped now as I was before. Most likely, I think it’s just bad programming on my part (as I am notorious for making syntax errors), but I do remember a post somewhere saying that IQ has very little memory and hence just doesn’t work, which could be another reason for why the PID loop simply isn’t working. Alternatively, I could try and revert back to ROBOTC, but I’m not entirely sure if the IQ robot accepts ROBOTC code as of late.

Some more information before I share the code: my robot is a relatively small one, with two motors, two distance sensors at the front and back, two left and right bumpers on the front, as well as five TouchLEDs thrown in for decoration’s sake. I also have a color sensor that is currently not in use. A gyro would have probably been more than optimal for my situation, but unfortunately, I didn’t have access to them whatsoever.

All of that being said, attached below is my code. It’s a simple test that involves the robot going up to a wall and stopping either before it hits or when the bumper touches the wall. This is also the primary method in which I would have wanted to experiment with the PID loop through changing the kP, kI, and kD (side note: recommendations for numeric values to experiment with here would be appreciated).

// Include the IQ Library
#include "iq_cpp.h"
  
// Allows for easier use of the VEX Library
using namespace vex;

int Brain_precision = 0;
float myVariable;

//Settings
double kP = 2.0;
double kI = 0.04;
double kD = 0.0;
double turnkP = 2.0;
double turnkI = 0.04;
double turnkD = 0.0;

//Autonomous Settings
int desiredValue = 200;
int desiredTurnValue = 0;

int error = FrontDistance.distance(mm) - 200; // SensorValue - DesiredValue : Position
int prevError = 0; // Position 20 milliseconds ago
int derivative; // error - prevError : Speed
int totalError = 0; // totalError = totalError + error

int turnError; // SensorValue - DesiredValue : Position
int turnPrevError = 0; // Position 20 milliseconds ago
int turnDerivative; // error - prevError : Speed
int turnTotalError = 0; // totalError = totalError + error

bool enableDrivePID = true;

//variables modified for use
bool resetDriveSensors = false;

int drivePID(){
  while(enableDrivePID){

    if (resetDriveSensors) {
      resetDriveSensors = false;
      LeftMotor.setPosition(0,degrees);
      RightMotor.setPosition(0,degrees);
    }


    int leftMotorPosition = LeftMotor.position(degrees);
    int rightMotorPosition = RightMotor.position(degrees);

    //////////////////////////////////////////////////////////////////
    // Lateral movement PID
    /////////////////////////////////////////////////////////////////
    //Get average of the two motors
    int averagePosition = (leftMotorPosition + rightMotorPosition)/2;
    
    //Proportional
    error = averagePosition - desiredValue;
    
    //Derivative
    derivative = error - prevError;
    
    //Integral
    //totalError += error;

    double lateralMotorPower = error * kP + derivative * kD;

    /////////////////////////////////////////////////////////////////
    // Turning movement PID
    /////////////////////////////////////////////////////////////////
    //Get average of the two motors

    int turnDifference = leftMotorPosition - rightMotorPosition;

    //Proportional
    turnError = turnDifference - desiredTurnValue;
    
    //Derivative
    turnDerivative = turnError - turnPrevError;

    //Integral
    //turnTotalError += turnError;

    double turnMotorPower = turnError * turnkP + turnDerivative * turnkD;
    ////////////////////////////////////////////////////////////////////////

    LeftMotor.spin(forward, lateralMotorPower + turnMotorPower, voltageUnits::volt);
    RightMotor.spin(forward, lateralMotorPower - turnMotorPower, voltageUnits::volt);
    
    prevError = error;
    turnPrevError = turnError;

    
    vex::task::sleep(20);

  }
  
  return 1;
}
void onevent_TouchLED_released_0() {
  TouchLED.setBrightness(100);
  TouchLED.setColor(white);
  LeftMotor.spin(forward);
  RightMotor.spin(forward);
  LeftMotor.setVelocity(100.0, percent);
  RightMotor.setVelocity(100.0, percent);
  wait(3.0, seconds);
  while (true) {
    if (FrontDistance.distance(inches) < 10.0) {
      TouchLED.setColor(blue_green);
      repeat(50) {
        drivePID();
        if(FrontDistance.distance(inches) < 6.0){
          break;
        }
      }

    }
    if (LBumper.pressing()) {
      TouchLED.setColor(red_violet);
      LeftMotor.setStopping(brake);
      RightMotor.setStopping(brake);
    }
    if (RBumper.pressing()) {
      TouchLED.setColor(yellow_orange);
      LeftMotor.setStopping(brake);
      RightMotor.setStopping(brake);
    }
  wait(20, msec);
  }
}


int main() {
  // register event handlers
TouchLED.released(onevent_TouchLED_released_0);
  
  wait(15, msec);

  resetDriveSensors = true;
}

Thanks in advance for your help! I will be happy to elaborate on any further details that I may have missed.

2 Likes

It’s quite late here, so I will respond with a full answer in a couple hours. I think the reason why its not working is due to the fact that you didnt run the PID algorithm, and also you should make sure to run it as a task. But before you run the PID you should make sure to modify your code such that you change the desired values to move the robot instead of telling the motor to spin specific velocities.

5 Likes

Can you describe what is, or is not, happening ?

3 Likes

I have to be honest I am confused as well.

I don’t want to make hasty generalizations, but I am assuming that the OP likely is unaware of how tasks work. I mean I didn’t learn about tasks and while loops until sophomore year when I took computer science class. Anyways, @Tidebreaker if you want we can discuss in DM’s about what tasks are, how to utilize them, how to stop them, and also the ability to make the code work properly. Bear in mind that PID by itself only really works well with tracking wheels with the drivetrain, but if you aren’t doing tracking wheels then you may need to understand that there may be some drift. I don’t think it’s right to apply additional items if you don’t understand the foundation of the first items presented, so I think it’s a good idea to start small rather than going big with a blindfold.

4 Likes

Yep, you would be right. I don’t know too much about PID other than a basic understanding of the three components, really. I looked over your PID tutorial code and tried using in V5, where it worked perfectly. I tried again in IQ and got this:
Screen Shot 2020-08-03 at 15.45.34
I don’t know why exactly this is happening, nor do I know how I can prevent it. It doesn’t even mention what kind of error we’re dealing with here. It could be a bug, or just the IQ program not being able to deal with this amount of memory. Given that I won’t be using this program for the actual competition, it’s a bit frustrating to try for hours and to have it all waste away still.
Another thing - is it possible to do this with the drivetrain instead of separately with two wheels? For every PID program I saw, people always configured two wheels and used those instead of a single drivetrain.
Connor, thanks for the offer, I’ll gladly accept some help!
Sorry for the long break, by the way. I had some issues to take care of in the meantime.

1 Like

Does VEXcode IQ allow you to control motors by voltage? Historically, I thought it wasn’t possible to bypass the internal PID?

VEXcode is telling you that the code has an error, it should also show where that error is in the code and if you double click on the problem line it will move the editor and cursor to that position. We would need to see the code to know why that is, perhaps paste a complete screen shot (if there’s no other information perhaps attach the project so I can build it).

No, nothing new in VEXcode we could not do in ROBOTC etc.

5 Likes

That code does seem to have some motor spin commands with voltage as a unit that I guess are the V5 ones and wouldn’t work on IQ.

the voltage units will just be ignored, values will be treated as rpm.
so, if values were +/- 12, then the motor will probably not move much.

5 Likes

Yikes, so coding PID is not gonna do well since it would interfere with the internal PID. Right? Would you believe motion profiling is the most impactful solution for something like this?

There have been plenty of successful PID loops implemented on IQ, just code it sending percent or rpm to the motor and see what happens.

7 Likes

[quote=“jpearman, post:11, topic:83404, full:true”]

I found a program written by Connor that works in V5, but unfortunately not in IQ. I suspect that this is due to vex.h not being compatible with IQ. If I will not be able to run PID experiments on IQ robot, I’ll be forced to do so on V5 when school reopens, but since there is no clear understanding when it will be due to the whole pandemic, I’m afraid I might be in a tight spot. The experiments I am talking about would be used for an essay analyzing the PID algorithm, and I can use code written by others, making sure to properly reference the creator.
If someone can help with writing such code to IQ, that would be much appreciated.
Current code:

#include "iq_cpp.h"
#include "vex.h"
#include <cmath> 

using namespace vex;

competition Competition;

void pre_auton(void) {
  vexcodeInit();
}

//Settings
double kP = 0.0;
double kI = 0.0;
double kD = 0.0;
double turnkP = 0.0;
double turnkI = 0.0;
double turnkD = 0.0;
int maxIntegral = 300;
int integralBound = 3; 

//Autonomous Settings
int desiredValue = 200;
int desiredTurnValue = 0;

int error; //SensorValue - DesiredValue : Position
int prevError = 0; //Position 20 miliseconds ago
int derivative; // error - prevError : Speed
int totalError = 0; //totalError = totalError + error

int turnError; //SensorValue - DesiredValue : Position
int turnPrevError = 0; //Position 20 miliseconds ago
int turnDerivative; // error - prevError : Speed
int turnTotalError = 0; //totalError = totalError + error

bool resetDriveSensors = false;

//Variables modified for use
bool enableDrivePID = true;

double signnum_c(double x) {
  if (x > 0.0) return 1.0;
  if (x < 0.0) return -1.0;
  return x;
}

int drivePID(){
  
  while(enableDrivePID){

    if (resetDriveSensors) {
      resetDriveSensors = false;
      LeftMotor.setPosition(0,degrees);
      RightMotor.setPosition(0,degrees);
    }

    //Get the position of both motors
    int leftMotorPosition = LeftMotor.position(degrees);
    int rightMotorPosition = RightMotor.position(degrees);

    ///////////////////////////////////////////
    //Lateral movement PID
    /////////////////////////////////////////////////////////////////////
    //Get average of the two motors
    int averagePosition = (leftMotorPosition + rightMotorPosition)/2;

    //Potential
    error = averagePosition - desiredValue;

    //Derivative
    derivative = error - prevError;

    //Integral
    if(abs(error) < integralBound){
    totalError+=error; 
    }  else {
    totalError = 0; 
    }
    //totalError += error;

    //This would cap the integral
    totalError = abs(totalError) > maxIntegral ? signnum_c(totalError) * maxIntegral : totalError;

    double lateralMotorPower = error * kP + derivative * kD + totalError * kI;
    /////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////
    //Turning movement PID
    /////////////////////////////////////////////////////////////////////
    //Get average of the two motors
    int turnDifference = leftMotorPosition - rightMotorPosition;

    //Potential
    turnError = turnDifference - desiredTurnValue;

    //Derivative
    turnDerivative = turnError - turnPrevError;

    //Integral
    if(abs(error) < integralBound){
    turnTotalError+=turnError; 
    }  else {
    turnTotalError = 0; 
    }
    //turnTotalError += turnError;

    //This would cap the integral
    turnTotalError = abs(turnTotalError) > maxIntegral ? signnum_c(turnTotalError) * maxIntegral : turnTotalError;

    double turnMotorPower = turnError * turnkP + turnDerivative * turnkD + turnTotalError * turnkI;
    /////////////////////////////////////////////////////////////////////

    LeftMotor.spin(forward, lateralMotorPower + turnMotorPower, voltageUnits::volt);
    RightMotor.spin(forward, lateralMotorPower - turnMotorPower, voltageUnits::volt);

    prevError = error;
    turnPrevError = turnError;
    vex::task::sleep(20);

  }
  return 1;
}

void autonomous(void) {

  vex::task pidExperiment(drivePID);

  resetDriveSensors = true;
  desiredValue = 300;
  desiredTurnValue = 600;

  vex::task::sleep(1000);

  resetDriveSensors = true;
  desiredValue = 300;
  desiredTurnValue = 300;


}

int main() {
  // Set up callbacks for autonomous and driver control periods.
  Competition.autonomous(autonomous);

  // Run the pre-autonomous function.
  pre_auton();

  // Prevent main from exiting with an infinite loop.
  while (true) {
    wait(100, msec);
  }
}

Well, you have to write the IQ code in an IQ project, you cannot just use vex.h from a V5 project in an IQ project, the header files are different for the two platforms. However. the majority of the C++ code will be compatible, so you should be able to get PID working.

Things like this

competition Competition;

are not compatible with IQ, there is no competition control for an IQ robot.

So a new IQ project looks like this.

// Include the IQ Library
#include "iq_cpp.h"

// Allows for easier use of the VEX Library
using namespace vex;

int main() {
  // Begin project code
  
}

iq_cpp.h replaces vex.h for an IQ project.

You can pretty much cut and paste the pid code, but IQ has no motor voltage control, that needs to be rpm as we said this morning. The code in auton can be moved into main just for test, again, IQ has no concept of competition control.

code that will compile may look like this.

code
//----------------------------------------------------------------------------
//                                                                            
//    Module:       main.cpp                                                  
//    Author:       {author}                                                  
//    Created:      {date}                                                    
//    Description:  IQ project                                                
//                                                                            
//----------------------------------------------------------------------------

// Include the IQ Library
#include "iq_cpp.h"

// Allows for easier use of the VEX Library
using namespace vex;

//Settings
double kP = 0.0;
double kI = 0.0;
double kD = 0.0;
double turnkP = 0.0;
double turnkI = 0.0;
double turnkD = 0.0;
int maxIntegral = 300;
int integralBound = 3; 

//Autonomous Settings
int desiredValue = 200;
int desiredTurnValue = 0;

int error; //SensorValue - DesiredValue : Position
int prevError = 0; //Position 20 miliseconds ago
int derivative; // error - prevError : Speed
int totalError = 0; //totalError = totalError + error

int turnError; //SensorValue - DesiredValue : Position
int turnPrevError = 0; //Position 20 miliseconds ago
int turnDerivative; // error - prevError : Speed
int turnTotalError = 0; //totalError = totalError + error

bool resetDriveSensors = false;

//Variables modified for use
bool enableDrivePID = true;

double signnum_c(double x) {
  if (x > 0.0) return 1.0;
  if (x < 0.0) return -1.0;
  return x;
}

int drivePID(){
  
  while(enableDrivePID){

    if (resetDriveSensors) {
      resetDriveSensors = false;
      LeftMotor.setPosition(0,degrees);
      RightMotor.setPosition(0,degrees);
    }

    //Get the position of both motors
    int leftMotorPosition = LeftMotor.position(degrees);
    int rightMotorPosition = RightMotor.position(degrees);

    ///////////////////////////////////////////
    //Lateral movement PID
    /////////////////////////////////////////////////////////////////////
    //Get average of the two motors
    int averagePosition = (leftMotorPosition + rightMotorPosition)/2;

    //Potential
    error = averagePosition - desiredValue;

    //Derivative
    derivative = error - prevError;

    //Integral
    if(abs(error) < integralBound){
    totalError+=error; 
    }  else {
    totalError = 0; 
    }
    //totalError += error;

    //This would cap the integral
    totalError = abs(totalError) > maxIntegral ? signnum_c(totalError) * maxIntegral : totalError;

    double lateralMotorPower = error * kP + derivative * kD + totalError * kI;
    /////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////
    //Turning movement PID
    /////////////////////////////////////////////////////////////////////
    //Get average of the two motors
    int turnDifference = leftMotorPosition - rightMotorPosition;

    //Potential
    turnError = turnDifference - desiredTurnValue;

    //Derivative
    turnDerivative = turnError - turnPrevError;

    //Integral
    if(abs(error) < integralBound){
    turnTotalError+=turnError; 
    }  else {
    turnTotalError = 0; 
    }
    //turnTotalError += turnError;

    //This would cap the integral
    turnTotalError = abs(turnTotalError) > maxIntegral ? signnum_c(turnTotalError) * maxIntegral : turnTotalError;

    double turnMotorPower = turnError * turnkP + turnDerivative * turnkD + turnTotalError * turnkI;
    /////////////////////////////////////////////////////////////////////

    LeftMotor.spin(forward, lateralMotorPower + turnMotorPower, rpm);
    RightMotor.spin(forward, lateralMotorPower - turnMotorPower, rpm);

    prevError = error;
    turnPrevError = turnError;
    vex::task::sleep(20);

  }
  return 1;
}

int main() {
  // Begin project code
  vex::task pidExperiment(drivePID);

  resetDriveSensors = true;
  desiredValue = 300;
  desiredTurnValue = 600;

  vex::task::sleep(1000);

  resetDriveSensors = true;
  desiredValue = 300;
  desiredTurnValue = 300;  

  vex::task::sleep(10000);
}

(obviously you still need to configure motors etc. in the graphical config)

all the constants will need adjusting for an IQ robot, and IQ is also very limited in the number of tasks that can be started, it has the same limitations as blocks, main and two further threads if any events are being used.

4 Likes

@jpearman Thanks for the code! My robot is finally moving, albeit with some nuances. The program, unfortunately, does not decelerate the robot and seems to be stuck in an infinite loop ( I toyed around with the desiredValues, kP, kI, kD etc, nothing seems to be working so far).

Could it be possible that the problem arises from the calculation of degrees and not rpm? Also, for the spin functions below,

code
LeftMotor.spin(forward, lateralMotorPower + turnMotorPower, rpm);
RightMotor.spin(forward, lateralMotorPower - turnMotorPower, rpm);

is there any reasons why rpm is written and not turns?

It also seems that the right wheel lags behind slightly the left, and when changed to spinToPosition or spinFor stops working entirely. Is it possibly that lateralMotorPower - turnMotorPower = 0?

2 Likes