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.
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!
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:
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.
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.
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?
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.
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.
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?