I embarked on trying the Ziegler - Nichols method of PID tuning of an arm this weekend while teaching PID (without calculus). It works pretty nicely I think but I wanted some other opinions from the Vex community.
On a side note: I loved the resources from free range in NZ and jpearman for sample code and epxlainations as well as the “PID without a PHD” paper to help teach the concept. (too lazy to find those links right now) The graphs in the PID without a PHD are critical in teaching how the arm will settle itself out and overshoot then undershoot and settle down.
I am wondering though are what techniques does everyone use to get to the right values of time period when at Kp critical? Does anyone do this in software to get the period of oscillation time or like we did simply use a stopwatch with the arm hitting your hand at the peak of oscillation 10 times good enough?
How can we get the values logged on the PC to make graphs ourselves of the different PID values? (like in PID without a PHD) I am not sure the stream of debug lines can match the 25ms scanning periods. Anyone got writing to a local Cortex file process working yet for telemetry?
We also found the arm will perform differently in different positions giving different critical Kp values - that can effect the PID variables overall. The down position had a different Kp ciritical where it oscillated forever versus in a upper arm position where it eventually settles out. Any thoughts? Do people use different factors for up or down movement or ranges? Or is it more it’s good enough, a touch of overshoot won’t kill ya. If you were doing drive PID, go for the undershoot tuning factors.
Also, do people use PID for the entire range of motion or just for managing a critical stop distance? I see the integral gets really big the further away you are from the target point. I guess this helps counteract the momentum built up in the arm but if the arm is at full speed for some of that distance isn’t the integral part getting too much emphasis? Intergral grows at a much smaller rate the closer you are to the target but at that point it’s carrying some serious baggage until 0 error is reached.
Not sure I have any definitive answers for you, I’ve never been that good at tuning PID and for most of the student robot implementations “good enough” is often where we end up.
We increase Kp until gain is too high and then back it off from there. We often use derivative but tend to avoid integral.
I started working on this but put it on the back burner. It was going to be real time telemetry to a graphical display on the PC. I may get back to it in the summer.
I never use different factors that are dependent on arm position, also the weight of game objects affects control. I do sometimes use an offset to compensate for gravity.
Same PID for the entire range. Integral is limited to give at most 25% of maximum drive. Just for reference, here is the PID update loop from a library that I will release at some point. One thing to notice is that I put the final drive command through a lookup table to compensate for the non-linearity of the motor controllers.
/* Update the process command */
PidControllerUpdate( pidController *p )
if( p == NULL )
if( p->enabled )
// check for sensor port
// otherwise externally calculated error
if( p->sensor_port >= 0 )
int inc = p->drive_cmd / 8;
p->sensor_value += inc;
// Get raw position value, may be pot or encoder
p->sensor_value = SensorValue p->sensor_port ];
// A reversed sensor ?
if( p->sensor_reverse )
if( p->sensor_type == sensorPotentiometer )
// reverse pot
p->sensor_value = 4095 - p->sensor_value;
// reverse encoder
p->sensor_value = -p->sensor_value;
p->error = p->target_value - p->sensor_value;
// force error to 0 if below threshold
if( abs(p->error) < p->error_threshold )
p->error = 0;
// integral accumulation
if( p->Ki != 0 )
p->integral += p->error;
// limit to avoid windup
if( abs( p->integral ) > p->integral_limit )
p->integral = sgn(p->integral) * p->integral_limit;
p->integral = 0;
p->derivative = p->error - p->last_error;
p->last_error = p->error;
// calculate drive - no delta T in this version
p->drive = (p->Kp * p->error) + (p->Ki * p->integral) + (p->Kd * p->derivative) + p->Kbias;
// drive should be in the range +/- 1.0
if( abs( p->drive ) > 1.0 )
p->drive = sgn(p->drive);
// final motor output
p->drive_raw = p->drive * 127.0;
// Disabled - all 0
p->error = 0;
p->last_error = 0;
p->integral = 0;
p->derivative = 0;
p->drive = 0.0;
p->drive_raw = 0;
// linearize - be careful this is a macro
p->drive_cmd = _LinearizeDrive( p->drive_raw );
// return the thing we are really interested in
return( p->drive_cmd );
One thing that we’ve done to help tune PID is to print the position of the sensor each time step as it runs, copy the data to an Excel spreadsheet and graph it. Useful information like the frequency of oscillation can be extracted from that.
I’m sorry I can’t give more information, but I’m in the middle of FRC and lots of homework.
You might be surprised what a very sparse print debug stream can do as far as keeping up with a PID loop. We have employed it from time to time to plot data and visualize what the various components are contributing.
Since the Cortex doesn’t support local file logging, but has a serial port you could log to the serial port with a data logging device, or as I posted here via XBee to a PC.