PID controller and tuning quesions

Excuse me,everyone.

I am going to use pid control this season,but i had found a few problems on the pid code as well as tuning.

First question
The largest problem is that i don’t know how to identify whether the robot had completely finished the movement,in other words,how to write the loop condition or when to break the loop? I considered breaking the loop when the robot had met the target for enough time,but i don’t think that’s the best solution.Would you please tell me how do you identify the statement of your robot?

Second question
Another thing that’s worrying me is about tuning the constants.Well,there are many articals telling you how to tune the constants,but when it happened to me,i still don’t surely know how to do that.I use the debug stream to log encoders to make charts,but i don’t really know how to analysis the chart to change the constants.I don’t really know how does a perfect PID graph looks like.That’s mainly because of the experirnce, i thought.

Third question
And i found that there’s some messy code in the debug stream while i write to the debug stream per 5ms.Is that too frequent?

And here’s my chart for only the p control.

http://i1222.photobucket.com/albums/dd493/iammry/QQ622A56FE20130622144226.png

i found there’s a very strange part at 40 x-axis.The encoder suddenly increased 87.
Here’s my another chart for p=0.5

http://i1222.photobucket.com/albums/dd493/iammry/QQ622A56FE20130622144053.png

it still have the same problem.Anyone know what caused this? is that beacuse of the frequency i log the values?Or is it because of the encoder itself or static?

Hope to hear your advice! Thanks very much!

–String

ROBOTC or EasyC?

Sending debug data every 5mS is a bit optimistic, it depends on exactly how much data you are trying to send each time, but it probably is too fast.

Depending on which motors are involved in the PID, there is no need to run the PID loop at a 5mS rate either, most motors (port 1 and 10 behave better) on the cortex will only update every 18mS (and there is variability on this for a couple of reasons) so a good PID update rate is 10mS or 15mS. See this thread for some details.

https://vexforum.com/t/not-all-motor-ports-are-created-equal/20755/1

If you want to post the code I can check it out for you.

Thanks for replying!

I use robotc

It’s true that 5ms might be too fast.I just wanted to get an accurate chart so i only wait for 5ms. I’ll dely it to 10ms.It might be better.

Thank you,and here’s my code

typedef struct
{
	float Kp;
	float Ki;
	float Kd;
	int Error;
	int Integral;
	int Derivative;
	int LastError;
	int Output;
}	TPID;

TPID PIDLeft;
TPID PIDRight;

int UpdatePIDController(TPID &PIDController, int Error)
{
	PIDController.Error = Error;
	PIDController.Integral = (PIDController.Integral * (3 / 4)) + PIDController.Error;
	PIDController.Derivative = PIDController.Error - PIDController.LastError;
	PIDController.LastError = PIDController.Error;
	PIDController.Output =  ((float) PIDController.Error * PIDController.Kp)
			                		 +   (PIDController.Integral * PIDController.Ki)
		                			 + (PIDController.Derivative * PIDController.Kd);
	return PIDController.Output;
}

void LogEncoder()
{
  writeDebugStreamLine("Encoder2:%3d Encoder9:%3d \n",nMotorEncoder[port2], nMotorEncoder[port9]);
}

void leftside(int speed)
{
	motor[port8]=speed;
	motor[port9]=speed;
}

void rightside(int speed)
{
	motor[port2]=speed;
	motor[port3]=speed;
}

task main()
{
  PIDLeft.Kp=0.5;
  PIDLeft.Ki=0;
  PIDLeft.Kd=0;

  PIDRight.Kp=0.5;
  PIDRight.Ki=0;
  PIDRight.Kd=0;

  while(true)
  {
  	PIDLeft.Output = UpdatePIDController(PIDLeft,300-nMotorEncoder[port9]);
  	PIDRight.Output = UpdatePIDController(PIDLeft,300-nMotorEncoder[port2]);
  	LogEncoder();
  	leftside(PIDLeft.Output);
  	rightside(PIDRight.Output);
  	wait1Msec(5);
  }


}

It’s just for testing so the integral might have some problems.And i don’t know when to stop the loop,or how to identify that the movement had been done,so i put them in a infinite loop.That’s worrying me.Thanks for replying me!

Several thoughts not in any particular order.

Edit: Are you using the latest ROBOTC version (3.60) ? If not, then I strongly suggest you upgrade, most (all?) upgrades have been free for the last couple of years as far as I know.

You are overloading the debugStream, this line


writeDebugStreamLine("Encoder2:%3d Encoder9:%3d \n",nMotorEncoder[port2], nMotorEncoder[port9]);

Will send 30 chars to the debug stream so if called at a 5mS rate that will be 6000 chars every second. The serial connection from the joystick to the PC is at 115200 baud or approximately 11000 chars every second. I will go into detail in another post as to why this theoretical rate will never be achieved, but suffice to say that any more than perhaps 30% of that rate would be pushing ROBOTC a little. You have a couple of options here, you could drop the update rate to every 10mS or you could reduce the amount of data send for each call. The labels “Encoder2:” and “Encoder9:” are superfluous so I would drop them out. I tested this and it ran at a 5mS rate.

void LogEncoder()
{
  static long l = 0;
  writeDebugStreamLine("%5d 2:%3d 9:%3d",l++, nMotorEncoder[port2], nMotorEncoder[port9]);
}

19 chars every 5mS so 3800 chars every second, it’s near the limit for ROBOTC. I added the counter so that even if one or two samples were lost you could tell where each sample was supposed to be and create a more accurate graph.

There’s a small error in the mail loop.

PIDLeft.Output = UpdatePIDController(PIDLeft,300-nMotorEncoder[port9]);
  	PIDRight.Output = UpdatePIDController(PIDLeft,300-nMotorEncoder[port2]);

You send PIDLeft to both update calls, one should be PIDRight

Decide where you want to set the Output value, currently you set it both in the UpdatePIDController as well as assigning it using a return value.

You are using the “old style” ROBOTC method to send the PID data as a pointer to the function, it’s not wrong (perhaps you have an older ROBOTC version) but normal C code convention would be.

int UpdatePIDController(TPID *PIDController, int Error)
{
	PIDController->Error = Error;
	PIDController->Integral = (PIDController->Integral * (3 / 4)) + PIDController->Error;
	PIDController->Derivative = PIDController->Error - PIDController->LastError;
	PIDController->LastError = PIDController->Error;
	PIDController->Output =  ((float) PIDController->Error * PIDController->Kp)
			                		 +   (PIDController->Integral * PIDController->Ki)
		                			 + (PIDController->Derivative * PIDController->Kd);
	return PIDController->Output;
}

and the calls

  	PIDLeft.Output  = UpdatePIDController( &PIDLeft,  300-nMotorEncoder[port9]);
  	PIDRight.Output = UpdatePIDController( &PIDRight, 300-nMotorEncoder[port2]);

Also, this line

	PIDController->Integral = (PIDController->Integral * (3 / 4)) + PIDController->Error;

has a problem with the multiply of (3/4). As they are in parenthesis, they will be calculated first and, being integers, will evaluate to 0. You can check if this happens by looking at the assembly code listing, here is the relevant section.

	PIDController->Integral = (PIDController->Integral * (3 / 4)) + PIDController->Error;
0007: AA29060045000E000000     S06(short) = *PIDController:*S00+14(short *) * 0
0011: 800645000C               S06(short) += PIDController:*S00+12(short *)
0016: 1245000E290600           PIDController:*S00+14(short *) = S06(short)

Notice the multiply by 0 in line 7. So either use 0.75 or remove the parenthesis and do as two operations.

	PIDController->Integral = (PIDController->Integral * 0.75) + PIDController->Error;

or

	PIDController->Integral = (PIDController->Integral * 3 / 4) + PIDController->Error;

To detect the position being achieved, if you are running the PID as a sort of one time function, then detect the error being below some threshold, perhaps 10% of the intended movement. However, often the PID loop is run continuously and the target position adjusted by user code. The error is then constantly calculated and if it is large enough causes the motors to be driven. Anyway, here is some revised code with the bugs out, I did not change any of the overall functionality, I will leave that to you to decide how best to do it.

typedef struct
{
	float Kp;
	float Ki;
	float Kd;
	int Error;
	int Integral;
	int Derivative;
	int LastError;
	int Output;
}	TPID;

TPID PIDLeft;
TPID PIDRight;

int UpdatePIDController(TPID *PIDController, int Error)
{
	PIDController->Error      = Error;
	PIDController->Integral   = (PIDController->Integral * 3 / 4) + PIDController->Error;
	PIDController->Derivative = PIDController->Error - PIDController->LastError;
	PIDController->LastError  = PIDController->Error;
	PIDController->Output     =  ((float) PIDController->Error      * PIDController->Kp)
                                         +   (PIDController->Integral   * PIDController->Ki)
                                         +   (PIDController->Derivative * PIDController->Kd);
	return PIDController->Output;
}

void LogEncoder()
{
  static long l = 0;
  writeDebugStreamLine("%5d 2:%3d 9:%3d",l++, nMotorEncoder[port2], nMotorEncoder[port9]);
}

void leftside(int speed)
{
	motor[port8]=speed;
	motor[port9]=speed;
}

void rightside(int speed)
{
	motor[port2]=speed;
	motor[port3]=speed;
}

task main()
{
  PIDLeft.Kp=0.5;
  PIDLeft.Ki=0;
  PIDLeft.Kd=0;

  PIDRight.Kp=0.5;
  PIDRight.Ki=0;
  PIDRight.Kd=0;

  // clear debug window if you want
  wait1Msec(1000);
  clearDebugStream(); 
  
  while(true)
  {
  	PIDLeft.Output  = UpdatePIDController( &PIDLeft,  300-nMotorEncoder[port9]);
  	PIDRight.Output = UpdatePIDController( &PIDRight, 300-nMotorEncoder[port2]);
  	
  	LogEncoder();
  	
  	leftside(PIDLeft.Output);
  	rightside(PIDRight.Output);
  	wait1Msec(5);
  }
}

Thank you ! It seems like I had made some stupid mistakes.

Well,I am learning C language by myself,so it might be not very clear.I am not sure about the pointer

int UpdatePIDController(TPID *PIDController, int Error)
{
	PIDController->Error      = Error;
	PIDController->Integral   = (PIDController->Integral * 3 / 4) + PIDController->Error;
	PIDController->Derivative = PIDController->Error - PIDController->LastError;
	PIDController->LastError  = PIDController->Error;
	PIDController->Output     =  ((float) PIDController->Error      * PIDController->Kp)
                                         +   (PIDController->Integral   * PIDController->Ki)
                                         +   (PIDController->Derivative * PIDController->Kd);
	return PIDController->Output;
}

In the function it uses a pointer to the struct variables(forgive my poor English).So will the assignment in the function change the variable inside the PIDLeft or PIDRight struct? Or does it only change the local variables and the struct is unchanged?If the struct is already assigned in the function, is it necessary to return the value again?

//PIDLeft.Output  = UpdatePIDController( &PIDLeft,  300-nMotorEncoder[port9]);
//PIDRight.Output = UpdatePIDController( &PIDRight, 300-nMotorEncoder[port2]);

And the best application for PID in our team could be rotate a current degree in the autonomous mode.And I will measure the time the error inside the tolerance to ensure that the robot had finished the rotation.Hope this would be workable.
Still thanks for replying me!It’s very kind of you to type so many words.That really moved me.Thank for helping us!

Small errors, they help you learn.

By passing the pointer the function can change the contents of the structure. Take a look at Xander’s recent magazine article on ROBOTC pointers here. It may be a little complicated but still worth a read.

http://botbench.com/blog/2013/05/08/robot-magazine-article-pointers-and-data-structures-in-robotc/

page1
page2
page3

It’s not necessary but doesn’t cause a problem if you choose to do it.

You could make the function have no return value, ie. a void function, then call as follows.

void
UpdatePIDController(TPID *PIDController, int Error)
{
	PIDController->Error      = Error;
	PIDController->Integral   = (PIDController->Integral * 3 / 4) + PIDController->Error;
	PIDController->Derivative = PIDController->Error - PIDController->LastError;
	PIDController->LastError  = PIDController->Error;
	PIDController->Output     =  ((float) PIDController->Error      * PIDController->Kp)
                                         +   (PIDController->Integral   * PIDController->Ki)
                                         +   (PIDController->Derivative * PIDController->Kd);
}
UpdatePIDController( &PIDLeft,  300-nMotorEncoder[port9]);
UpdatePIDController( &PIDRight, 300-nMotorEncoder[port2]);

Play around with it and look at the variables in the ROBOTC debugger to see what is happening.

I love this method.

Yep,it’s really kind of you to help me a lot.I’m really happy to found that I had a completed pid routine.I am extremely grateful for your advice.Thanks a lot!