Flywheel Code

Hi Everyone. How can I improve my code for my flywheel? I looked up how to code for a flywheel and people used Take Back Half instead of PID but I don’t get the Take Back Half thing. So I used a PID code. Is there a way to optimize my code? And is my code even rational?

Thanks,

#pragma config(Sensor, dgtl2,  FlywheelEncoder, sensorQuadEncoder)
#pragma config(Motor,  port1,           FlywheelMotor, tmotorVex393HighSpeed_HBridge, openLoop)
//*!!Code automatically generated by 'ROBOTC' configuration wizard               !!*//



long CurrentFlywheelSpeed;//In RPM
long CurrentFlywheelEncoder = SensorValue(FlywheelEncoder);
long FlywheelDeltaTime = time1[T1];

long TargetFlywheelSpeed;//In RPM
long FlywheelError;
long FlywheelPreviousError;
long Integral;
long Derivative;
long Kp = 0;//Placeholder
long Ki = 0;//Placeholder
long Kd = 0;//Placeholder
long FinalPIDSpeed;


long MotorTargetFlywheelSpeed;//range of -127 to 127
long FlywheelTolerance = abs(200)//200 RPM Tolerable Range of Flywheel speed difference (in RPM)



void CurrentFlywheel()//Finds out Current Flywheelspeed
{
	while(true)
	{
		clearTimer(T1);//Timer starts
		CurrentFlywheelSpeed	= CurrentFlywheelEncoder/FlywheelDeltaTime;//Calculates Flywheel Speed from Encoder/Time it takes
	}
}
void TargetFlywheel(int HighFlag, int LowFlag)
{
	while(true)
	{
		if((HighFlag) == 1 )
		{
			TargetFlywheelSpeed = 3000;//Placeholder for Speed in RPM High flag
		}
		else if((LowFlag) == 1)
		{
			TargetFlywheelSpeed = 2500;//Placeholder for Speed in RPM Low flag
		}
		else
		{
			TargetFlywheelSpeed = 0;//Flywheel Stop
		}
	}
}
task FlywheelPID()
{
	FlywheelError = TargetFlywheelSpeed - CurrentFlywheelSpeed;//Calculate Error Proportion
	FlywheelPreviousError = FlywheelError;//Previous Error
	Integral = Integral + FlywheelError*FlywheelDeltaTime;//Integral Calculation
	if(FlywheelError == 0)//No Error makes no Integral
	{
		Integral = 0;
	}
	else if(Integral < abs(127))//Integral Outside usable Range then Integral goes to max inside range
	{
		Integral = 127;
	}
	Derivative = (FlywheelError - FlywheelPreviousError)/FlywheelDeltaTime;//Derivative Calculation
	
	FinalPIDSpeed = Kp*FlywheelError + Ki*Integral + Kd*Derivative;//Final Speed Calculation
	
	motor[FlywheelMotor] = FinalPIDSpeed;//Send to Motors
}

task FlywheelMtr()
{
	while(true)
	{
		MotorTargetFlywheelSpeed = 127/4000*TargetFlywheelSpeed;
		//Motor Speed Calculation as 127 is motor maximum capacity and 4000RPM is maximum
		
		if(SensorValue(FlywheelEncoder) < (TargetFlywheelSpeed + FlywheelTolerance)*360)
		{
			motor[FlywheelMotor] = MotorTargetFlywheelSpeed;
		}
		//When Flywheel runs less than Target and tolerance, run mtoor
		
		else if(SensorValue(FlywheelEncoder) > (TargetFlywheel + FlywheelTolerance)*360)
		{
			motor[FlywheelMotor] = 50;//Stays on for warm up
		}
		//If flywheel is faster than wanted, make motor slower
	}
}

There are many issues. I’ll list a few here.

1.) long Kp = 0;//Placeholder
long Ki = 0;//Placeholder
long Kd = 0;//Placeholder
This is not quite useful. Take a look here: Integral = 127;
So, an integer multiplied by 127 will give either… 0, 127, or n*127, with n being an integer. That invalidates the attempt to keep your ‘FinalPIDSpeed’ within typical motor values.

clearTimer(T1);
CurrentFlywheelSpeed	= CurrentFlywheelEncoder/FlywheelDeltaTime;

This won’t work.

1.) The encoder gives back the total ticks it’s traveled. So, your encoder will give back ever increasing numbers, even though it’s velocity might not be changing.
2.) By clearing the timer right before calculating the speed, you’ll have an astronomically low time that you’re dividing by, giving extremely large errors.

void TargetFlywheel(int HighFlag, int LowFlag)
{
	while(true)
	{
		if((HighFlag) == 1 )
		{
			TargetFlywheelSpeed = 3000;//Placeholder for Speed in RPM High flag
		}
		else if((LowFlag) == 1)
		{
			TargetFlywheelSpeed = 2500;//Placeholder for Speed in RPM Low flag
		}
		else
		{
			TargetFlywheelSpeed = 0;//Flywheel Stop
		}
  • This would be much better:
void TargetFlywheel(int Flag)
{
	while(true)
	{
		if((Flag) == 1 )
		{
			TargetFlywheelSpeed = 3000;//Placeholder for Speed in RPM High flag
		}
		else if((Flag) == 2)
		{
			TargetFlywheelSpeed = 2500;//Placeholder for Speed in RPM Low flag
		}
		else
		{
			TargetFlywheelSpeed = 0;//Flywheel Stop
		}
  • There is no need to over-complicate variables.
	Integral = Integral + FlywheelError*FlywheelDeltaTime;//Integral Calculation
	if(FlywheelError == 0)//No Error makes no Integral
	{
		Integral = 0;
	}
  • Integral will already converge towards 0 at an exponential rate, no need to define as 0.
	else if(Integral < abs(127))//Integral Outside usable Range then Integral goes to max inside range
	{
		Integral = 127;
	}
	Derivative = (FlywheelError - FlywheelPreviousError)/FlywheelDeltaTime;//Derivative Calculation
	
	FinalPIDSpeed = Kp*FlywheelError + Ki*Integral + Kd*Derivative;//Final Speed Calculation
  • You should be checking if the FinalPIDSpeed is over 127, not integral. Remember, the K values are used to convert the final control value into motor power!

  • In task FlywheelPID(), your deltaTime is continuously changing! This is not a PID loop, it’s now a PTDTIT loop.

/******************************************************************************
A couple notes:

-dt in continuous mathematics means "an infinitisimally small amount of unit x (in this case, x is time)"
-dt in programmatic mathematics means "the amount of unit x between the last existing value and the current value (again, x is time)"
-think of the d as standing for "delta", or "difference between"

-An integral is equal to all of the instantaneuous values contained on a curve added up. Continuously, this can be thought of as the area under a curve. We call the time between each value dt. in this case, dt is infinitisimally small.
    -discretely, we need to use the block theorem. As processors cannot calculate an infinite amount of values per second, we need to 'cheat', by increasing the size of dt.
        -Here, we cheat by 'assuming' that the value does not change between measurements. ie, we simply multiply each value by the amount of time between it and the last measured value.
        -again, think of the area under the curve. All we are doing is approximating the area under the curve by extending rectangles from the x axis to the first point the rectangle hits on the curve.
        -the width is the delta time.
        
-A differential is equal to the difference between two values, divided by the time it took to get there.
    -ie, we get the rate of change:
        -delta between valueLast and valueCurrent / delta between their corresponding time stamps.
        
-A proportion is.... well, just a change in the order of magnitude of the measured value. we increase the value by multiplying some factor to the value.
*******************************************************************************/

#include <stdio.h>
int enc_val_last = 0.0;
int nSysTime_last = 0.0;
int motorPower = 0.0;

//kp,i,d modify just how much each part of the PID loop affects the correction. As motor power and rpm have a linear relationship to eachother, these kvalues can be used to convert rpm to power as well.
const float kp = 1.0;
const float ki = 1.0;
const float kd = 1.0;

int intSum = 0.0;
int error_old = 0.0;
int long dt;

int main()
{
    motorPower = motorPower + PID(2500); //call PID loop with target 2500 rpm, adding the resultant correction to the current power.
}

//calculates the correction
int PID(int setPoint){
    int error = setPoint - updateRPM();
    int correction = prop(error) + integral(error) + diff(error);
    nSysTime_last = nSysTime; //save current time, so that we can calculate the next dt
    return correction;
}


//Here, we calculate the current flywheel RPM. 
int updateRPM(){
    int enc_val = sensorValue[flywheelEncoder]; //Get current encoder value
    dt = (nSysTime - nSysTime_last);
    int rpm = (enc_val_last - enc_val) / dt; //(difference between the last measured encoder value and new measured value) / (delta current time - old time) nSysTime is a predfined variable equal to milliseconds run.
    enc_val_last = enc_val; //save current enc_val to old enc_val
    return rpm;
}

int prop(int error){
    return kp * error; //refer to header
}

int integral(int error){
    intSum = intSum + (error * dt); //refer to header
    return intSum;
}

int diff(int error){
    int diffError = (error_old - error) / dt; //refer to header
    return diffError;
}

Here’s an example code I wrote a few years ago. It’ll require slight modification for vex, but it covers the principle. If I remember correctly, JPearman or Tabor have some pretty good codes that are also excellent resources. Lot’s to learn there too.

@Alex241N Thanks. I appreciate your help!

Plz don’t delete this post…this is really useful!

@Alex241N And also, can you explain Take back Half? And how it’s different from PID and how to code it? That would be awesome.

Thank you though.

Also, I realized something in your code, when you calculate RPMs, you are using raw encoder values and not rotations. so don’t you have to do this?:


SensorValue(FlywheelEncoder)/360

And also when you use the dT, don’t you have to turn the dT into 1 minute and see how many rotations the Sensor detects per minute to get the RPM?

Yeah! No problem! Actually, you don’t have to convert. The values won’t be in true rpm, but they’ll have a lot less error in them if you don’t simplify them. What we are really going off of here is the the encoder ticks per time unit. You can of course convert the final integral, derivatives and proportional units back into rpm (or convert into rpm there) but you’d have to convert without fixed factors, as dt can vary depending on your other tasks.

You can’t turn the dt into anything except it’s raw value. The dt is the time difference between two measurements. You would have to take your encoder ticks/ 360 (depending on gear ratio) dividing by dt, then multiply by 1000 (1000 for 1000 milliseconds).

@Alex241N Okay. So I will try to convert to RPM in the final P, I and D units. Thank you for explaining the DT!

@RougeScaless correct, you convert right before multiplying the k-factors. I don’t usually do that though. Rather, I convert my setpoint to match the incoming data, as I’ve found that to be more accurate.

All of the explanations to each component are also in my title block if you’d like some more clarification!

Glad to offer my help!

@Alex241N Title Block???

Title block in the code :slight_smile:

Okay Thanks!