PID Theory Question

Is loop delay something that typically gets adjusted? Honestly, I have it included in the code because I saw someone else using it and thought being able to adjust it someday would be a nice feature.

If I am understanding your suggestion, I should change the integral calculation to be:

integral = accumulated_error * ki * FW_LOOP_SPEED;

or would it make more sense to have the multiplier be a fraction of the quickest speed we would ever use (20mSec), i.e.:

integral = accumulated_error * ki * (FW_LOOP_SPEED / 20);

I have attached a readout of my velocity readings as requested.

We have a total of four motors that are mechanically linked together driving a double flywheel. What I mean by mechanically linked is that the left and right flywheels are tied directly together via gearing so they will always be turning at the same speed relative to each other (that is ignoring any slop in the drive train).

Looks like Kp is too high. But it doesn’t look terrible.
Drop Kp by 10% and add in your integrator.

I can see the periodic oscillation, but most of that noise is because of the encoder resolution, can’t do much about it unless you want to start averaging. That brings it’s own set of problems and the Ki is an average anyway.

I wouldn’t do that yet. Just stick with your 20ms sampling speed and don’t change it.

You don’t really have to calculate RPM either.

That data is not from PID control. @tabor473 wanted to see what my velocity readings look like in the absence of any controllers… this is what my velocity readings look like by just sending a fixed motor power value of 80. Sorry for any confusion!

No wonder it looks so good and stable :slight_smile:

Friction looks good too.
So a motor drive of 80 gives you the target stepping you were looking for at 114?

Can you show samples with Kp = 1, 2 and 4?

80 was a typo… sorry! The power was set at 70 in that graph to give a target velocity of 114.

I will post the results of kp at 1, 2 and 4 later this afternoon when I get back to the robot.

Gotta run for a bit, but here is what you might try given your setup and timing:

  1. Set Kp = 0.5 and Ki = Kd = 0.
  2. Keep increasing Kp by 10% until you get a sustained oscillation that you can hear in the flywheel and see on your excel graph
  3. Set Kp 63% of the Kp value that gave you the oscillation.
  4. Set Ki = Kp / 100
  5. Graph that and see what you get
  6. Can fiddle from here on out but I wouldn’t change Kp by more than +/-10% from the value in step 3
  7. Move Ki around from 0.5x to 1.5x of the value found in Step 4

Use floating point for all of this for now

So it isn’t something that is changed often or tuned in a similar way to kP but it does make sense to not have integral break if it were to be changed. The divide by minimum delay is fine if you make it 20.0 rather than 20. That just ensures something like 50 isn’t read as 50/20=2 but rather 50/20.0=2.5.

Okay so the velocity calculations seem to be perfect. So now we can go back to working with the actual PID.

@tabor473 @TriDragon So I am trying to get some data for you to look at and was hoping to include current_velocity and motor_drive in the same data set, but the output from the debugger is making this tough to do when I use the following code:

datalogAddValueWithTimeStamp(0, motor_velocity);
datalogAddValueWithTimeStamp(1, motor_drive);

It is returning the desired information, but it creates three rows of data for the same time stamp (row 1 contains the current_velocity point with a zero for motor_drive, row 2 contains a zero for current_velocity and the actual data point for motor_drive. I know I can just offset one column by a row to get the two values to line up, but then I have zeros in every other row. Is there an easier way to compile the desired data?

See the datalog example, you need to use code of this form.


        // datalogAddValue when surrounded by datalogDataGroupStart and datalogDataGroupEnd
        // adds several values to one row of the datalog with a common time stamp
        datalogDataGroupStart();
        datalogAddValue( 0, global_1 );
        datalogAddValue( 1, global_2 );
        datalogAddValue( 2, global_3 );
        datalogDataGroupEnd();

All three values have the same timestamp, be sure to match the start and end calls.

You could try writing directly to the debug stream instead and then saving to a text file.
Ex:


writeDebugStreamLine("%d,%3.1f,%d", Nsystime, motor_velocity, motor_power);

This just prints the data directly to the debug stream in debugger windows. You can then save it when stopped to a text file and import into excel. The escape characters with digits (like %3.1f) define the number of digits before and after the decimal place, for 3 before and 1 after with a float. You could just put this in the flywheel control loop, or just any loop with a delay so you don’t get repeated data.

@jpearman @frogs2345 Thank you guys for the quick reply! You are AWESOME!

This also works well and is the way we have always collected data prior to V4.52.

No Problem! If you want to see what some of the data looks like, I uploaded a file which graphed 5 variables spat out by the robot here. You just have to be careful to limit the amount of data going from the robot, or it might overflow and forget characters (like don’t just use %f, and add a limiter).

@tabor473 @TriDragon Ok guys, I had some time to mess around tonight and have attached some data files. I started with kp =1.0 and the flywheel went into crazy oscillation, so I didn’t bother trying to get data for kp = 2 or 4 as requested. Sorry… don’t want to wreck my teams motors!

I found that a value for kp that gave little to no oscillation (audible or visual in excel) was at 0.4. I have attached that data as well. As suggested by @TriDragon, I then took 63% of kp which gave me 0.252 and set that as my new kp value. Dividing that by 100 gave me 0.00252 for ki. The system seemed stable but took almost 9 seconds to achieve the target velocity, so clearly I have a ways to go! The next question is what do I start modifying to improve the time it takes to reach the setpoint?

Thanks again for all of your help so far!

I wish I could share some more insights about PID tuning, but we are still debugging our own code. However, I could share a graph that I made to explain a simplified version of TBH to my team.

Let say you need to run flywheel at the speed (wf). If you already know the power level (uf) that would give you (wf) you can set it right away and have flywheel accelerate as shown with curve (1) until you are satisfied with your speed at a time t10. But this is slow and likely you don’t know required power level (uf) or it depends on some external variables?

Our simplified TBH will initially set power to 100% (u0). (You really shouldn’t do that in order to protect motor gears and prevent PTC from overheating) .

When velocity reaches your target (wf) at a time (t1) you take back half of the power and set it to (u1) and when it crosses target again you set it to u2 = (u0+u1)/2, then when it crosses again you set u3=(u1+u2)/2, and so forth until you are happy with the result at a time t9.

Notice that power curves (u1), (u2), (u3), and (u4) are not horizontal lines due to gain accumulation. This is “I” component that brings velocity curve (6) last few Hz to the target without crossing it.

The first thing you might ask is why would it overshoot if you reduce power as soon as velocity reaches the target? To answer this question lets look at the graph with more details:

First of all, you need to know that depending on which motor port you are using there might be delay in changing power of the motor. Let say we don’t know that ports 1 and 10 are fast and are using port2 that gives us 30ms delay on average.

So when you apply full power (u0) the motor only starts turning at 30ms with velocity following exponential curve (2).

At a time (t1) flywheel reaches desired speed, but our main loop runs on 20ms or even larger interval (to reduce measurement noise) and we only become aware of it at a time (t2). Algorithm immediately reacts by changing motor power to (u1), but by the time (t3) when the command reaches the motor controller we already overshoot.

Similar delays happen with the next segments of the curve and you end up doing several bounces before you accept the final speed at a time t9. The delays could easily add up to 0.1 sec or more on each bounce.

Clearly the cause of overshoot is accumulation of the various delays. So is there anything that could be done to reduce it?

First of all, you should run your flywheel motors on ports 1 and 10 to reduce command delay. Then you could reduce your main loop delay to 1ms. Finally, you can switch to IME where getMotorVelocity() could give you almost instantaneous velocity value. However, be aware that IME may introduce additional sources of measurement noise.

This all will reduce overshoot but not eliminate it. The real solution may be not to wait for velocity to cross target threshold, but start reducing the power preemptively, thus requiring the “D” term of PID.

It is surprising you are oscillating with that low of a value. It looks like you are running mostly I at this point.

Since you know that 70 gives you 114. Set a low limit on the output power of 50 instead of 0. So the motor ranges from 50 to 127.

Then go work the gains again.

Do you think the oscillations are coming from slop in the drive train? I can move the flywheel a little over 1/3 of a revolution before the motor shaft rotates… I would think that this would cause a lot of bouncing around in the encoder as the motor tries to control the speed.

doesn’t help, but no, since you are using IME’s, correct?

Take a look at this thread and get this all straightened out first, it’s very important:

https://vexforum.com/t/how-to-calculate-motor-flywheel-velocity-in-robotc/32336/1

Yes… 1 IME

That is an amazing post @jpearman ! I integrated James’ code into mine and it appears as if it is working perfectly… with one minor problem that I have been trying to figure out for the past two hours and I highly doubt that it has anything to do with James’ code! For some reason my error calculations stopped automatically updating. The error calc runs once and stops. If I press any button on the controller that is designated to set the target velocities, the error calculation runs with each button press. I am afraid I am going to wear out the buttons if I leave the code this way because I need to hit them as fast as I can to keep updating the error calculation! Is there any chance someone would be willing to take a look at the new code and see where I messed up?

Thanks!

You have the error calculation inside the function that sets the target velocity.

/*Set the controller position*/
void FwVelocitySet( int velocity )
{
	// set target_velocity velocity (motor rpm)
	target_velocity = velocity;

	// error calculations
	current_error = target_velocity - motor_velocity;
	last_error    = current_error;
}

Move the error calculation into the PID control loop.