Hi, folks.
For years we used hardware PID that worked fine with CRT hardware. But now we’re trying to use the software PID for certain functions that may not be directly based on the CTR hardware.
The question I have is - how do I specify the max and min output values? Code analysis appears to indicate that for a simple P-robot (with I and D turned to 0, for instance) the outout is simply calculated as the PID ERROR multiplied by a P value.
Now, we need to control the motors, meaning, we need to provide an output value between -1 and +1, and we also used hardware PID to account for maximum velocity etc.
The old PIDSubsystem had setOutputRange, which I am not sure can be used with the PIDCommand now in wpilib2. Also is there a way to specify the maximum velocity to be accounted in PID calculations? In other words, I’d like to see how can I accomplish a simple PID profile (trapezoid is preferred, but even the max acceleration one would do).
Regarding clamping the output, the motor set() calls clamp to -1 to 1 range, but if you want to clamp to a smaller range you can use MathUtil.clamp()
.
Thank you for the link, Peter!
A followup question if you do not mind - if the trapezoid profile is used, what is the maximum output value range? It appears I can specify max velocity and max acceleration.
Re: clamp - thank you for the suggestion. We can certainly do so. However, its use will still depend on the min/max values. Does it mean that at the initialize of the PID we need to determine the difference between the current and target values and use that for our value clamp?
You also mentioned that the motor set does the clamp automatically. But where does it get the min and max for it?
Example - we have a sensor that goes between -420 and +420. We can zero it if needed. We want to run PID for the movement between, say, points 0 and +192 (the values will change). In this case depending on the PID constants (say P=1, the others are 0) I will get the output value of 192 initially, which will decrease.
I’d like to feed that to the arcadeDrive. So, feeding 192 to the arcadeDrive is not really useful. I mean, it will be clamped at 1. And that means I will get a value of 1 until almost very end. It will, of course, correct at the end of the trajectory. But for the most of the drive it will indeed be a value much higher than 1.
The question I have is about making the whole process smoother. Am I correct that with low value of the absolute error and the low value of the usable output we need to start with really low P values, in such case around 0.01, especially if the expected drive-to-target time is less than a second?
In this case what do teams usually do when they need to cover a wide range of distances (e.g. if expressed in encoder ticks - sometimes cover 10,000 ticks, and other times - 200,000 ticks) - use the same PID controller values, or apply two different PID values in such situations to make the drive “smoother”?
TrapezoidProfile gets you from position A to position B while enforcing acceleration and velocity limits. The velocity setpoint will always be within that limit. This is no direct limit on voltage; you’d need an exponential profile for that (which WPILib does not have yet).
Use a motion profile to limit your movement, not a voltage clamp.
-1 is defined to be the minimum voltage output of the motor controller, and 1 is defined to be the maximum voltage output of the motor controller. The actual voltage that is depends on the battery charge state (typically, 1 corresponds to about 12 V under no load).
If the controller input is degrees and the output is voltage, the K_p gain will have units of V/degree.
If the controller output is voltage, just divide by 12 (or the actual battery voltage at the time).
The most robust way is with a motion profile and one set of PD gains. The goal is to keep the voltages involved away from saturation so you don’t have a loss of control authority, and the movement becomes more repeatable. For example, if you’re applying 12 V and a distance error develops, you have no voltage headroom for corrections.
Tyler - thank you much!
We will test a trapezoid profile then and see if we can get more meaningful numbers. And yes, my goal is indeed try and get better handle on a range of numbers, so the numbers will be more meaningful, rather than basically keep the controllers oversaturated for almost all of the time with no room for corrections.
Am I correct that the max velocity and max acceleration numbers are the ones that are going to limit the final output values produced by a PID controller that also uses the profile?
A motion profile limits the output voltages in the sense that finite, achievable voltages can be used to follow the motion profile perfectly over time (via feedforward). Feedback is then applied to compensate for disturbances.
No; they are only used for profile generation. Feedback is entirely separate.
So, how do I limit output values? The suggestion above from Tyler was that application of the motion profile will limit the output to the reasonable/usable numbers. In other words, if my Kp is right (and reasonably small), and I set a profile with the max velocity and max acceleration, the output produced will be in a reasonable -1…+1 range for both the short and long “distances”, and it will be close to +1 or -1 for the “cruise driving at maximum speed” rather than starting at ridiculously high numbers and dropping down as the goal is approached. Did I misinterpret that?
Clamp the control signal before sending it to the motors. Your reasoning is more or less right that it isn’t super important to do this, since the control signal should stay in a reasonable range throughout the motion if you have planned your motion well.
I think we are struggling to understand something similar…
We use a ProfiledPIDController with trapezodial constraints where we specific the maximum velocity and acceleration.
As part of the calculate loop we pass in the position error.
We use pid.getSetpoint().velocity
to get the velocity to send to the drive subsystem.
Is pid
using the actual position error we send in and comparing it to the motion profiled expected position to produce its output? And therefore we can use it as feedback to adjust the velocity we send to the drive subsystem?
Said differently if Kp is 1 and the other gains are 0… What is output value from calculate?
pid.calculate(DistanceToTargetInMeters, 0);
I wrote a small test simulation before I confused the students to try to understand what is going on. The output value appears to be 1/100th of pid.getSetpoint().velocity
which could be constant error in my simulation (bug) but I was hoping it would be zero because in my simulation I’m attempting to move the position exactly as the profile would expect.
Test Code
public class TrapezoidTest extends CommandBase {
public ProfiledPIDController m_xPID = null;
private int m_count = 0;
private double m_position = 5.0;
private double m_sum_transX = 0;
/*
*
* Want to create a PID controller that specifies the velocity of the robot to reduce the position error to zero.
* * MUST be able to specify a maximum velocity
* * MUST be able to specify a maximum acceleration
/** Creates a new TrapezoidTest. */
public TrapezoidTest() {
// Use addRequirements() here to declare subsystem dependencies.
}
// Called when the command is initially scheduled.
@Override
public void initialize() {
m_count = 0;
m_sum_transX = 0;
m_position = 2.0; // we are 2 meters away
double VELOCITY_MAX = 1.0;
double ACCELERATION_MAX = 1.0;
double TRANSLATE_P = 1.0;
double TRANSLATE_D = 0.0;
TrapezoidProfile.Constraints translateConstraints = new TrapezoidProfile.Constraints(
VELOCITY_MAX, ACCELERATION_MAX);
m_xPID = new ProfiledPIDController(TRANSLATE_P, 0, TRANSLATE_D, translateConstraints);
m_xPID.reset(getErrorX(), 0);
m_xPID.setTolerance(0.05);
}
// returns the current position error
private double getErrorX() {
return m_position;
}
// Called every time the scheduler runs while the command is scheduled.
@Override
public void execute() {
// error in x position
double error = this.getErrorX();
// calculate velocity to request in x axis
double calcX = m_xPID.calculate(error, 0);
double nextVX = m_xPID.getSetpoint().velocity;
System.out.println(m_count++ + "\terror: " + error + "\tcalcX: " + calcX + "\tnextVX: " + nextVX);
// simulate the position changes based on the velocity
m_position += m_xPID.getSetpoint().velocity * 0.02;
}
// Called once the command ends or is interrupted.
@Override
public void end(boolean interrupted) {}
// Returns true when the command should end.
@Override
public boolean isFinished() {
// end if less than 5 cm of error
if (Math.abs(this.getErrorX()) < 0.05) {
return true;
}
return false;
}
}
In my case the easiest way to explain the behavior was to look at the the WPI code.
As our team discovered, auto-capping works just fine on the small travel distances, such as the ones involved in rotation, even if the profile technically is not trapezoid. Since the turn takes little time, as long as you adjust your Pd well, the turn works fine without oscillating, with very little error. For now we’re sticking with the hardware PID for linear travel, which worked fine for us in the past.
The output will be equal to the current position error; this comes directly from the definition of the PID controller.
The only thing the PID controller does is provide feedback based on the error. It does not depend on the setpoint in any way, except transitively through the error calculation. One could write a PID controller API which takes a single “error” parameter and never sees the setpoint at all!
In the case of the profiled PID the error is the difference between the observed position and the planned/profiled position?
Yes. The current loop setpoint is the output of the profile calculation.
Awesome. Thank you!
This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.