Quote:
Originally Posted by Doron_Sivan
Kevin, we need to get to the wanted rpm progressively. We could use only PID to get fast but progressively to the wanted rpm and then adjust it with F P and I. I didn't understant though what did you change in the I in your special PIDController.
|
Second thing first, I'm not changing how I is used, just how/when the error is integrated. It happens in the Calculate function.
Code:
void PIDController::Calculate()
{
//SNIP
if (enabled)
{
float input = pidInput->PIDGet();
float result;
PIDOutput *pidOutput;
{
//SNIP
if(m_I != 0)
{
double potentialIGain = (m_totalError + m_error) * m_I;
if (potentialIGain < m_maximumOutput)
{
if (potentialIGain > m_minimumOutput)
m_totalError += m_error;
else
m_totalError = m_minimumOutput / m_I;
}
else
{
m_totalError = m_maximumOutput / m_I;
}
}
m_result = m_P * m_error + m_I * m_totalError + m_D * (m_error - m_prevError) + m_setpoint * m_F;
m_prevError = m_error;
//SNIP
}
Versus:
Code:
void AdvPIDController::Calculate()
{
// SNIP
if (enabled)
{
float input = pidInput->PIDGet();
float result;
float m_interror;
PIDOutput *pidOutput;
{
//SNIP
if(m_I != 0)
{
m_result = m_P * m_error + m_I * m_totalError + m_D * (m_error - m_prevError) + m_setpoint * m_F;
m_interror = m_error;
if ((m_result <= m_minimumOutput || m_result >= m_maximumOutput) && ((m_interror * m_error) > 0))
{
m_interror = 0;
}
double potentialIGain = (m_totalError + m_interror) * m_I;
if (potentialIGain < m_maximumOutput)
{
if (potentialIGain > m_minimumOutput)
m_totalError += m_interror;
else
m_totalError = m_minimumOutput / m_I;
}
else
{
m_totalError = m_maximumOutput / m_I;
}
}
m_result = m_P * m_error + m_I * m_totalError + m_D * (m_error - m_prevError) + m_setpoint * m_F;
m_prevError = m_error;
//SNIP
}
It's a subtle difference, but important. The stock WPI code keeps integrating until the integral action is large enough that it saturates the output all by itself. In a slow reacting system, that can leave you with a lot of integral action to dissipate once you get to your setpoint. That leads to a ton of overshoot. My version doesn't add to integral action as long as the output is saturated from all the control action. So you're not building up a huge amount of integral action while the system is saturated and getting up to your setpoint as fast a it physically can.
Alright, to your first point. You want to get to your setpoint progressively? Like you want to slowly ramp up to your target RPM for some reason? Most people tune their closed loop speed controllers for three things: minimum time to reach (or re-reach) a setpoint, minimum error at steady state, and good robustness to a changing battery voltage. The idea being that you want to be able to just tell the system a setpoint and know it's going to get there and stay there. If you're using a PID controller, you should set up your gains to achieve this and leave them alone after that, for the most part. Changing gains on the fly can cause instabilities if you're not very careful about things.
Slowly changing your setpoint is certainly achievable, but you should do that separately from tuning your PID. For that, you should have a separate class that limits how quickly the PID setpoint can change. So you'd have a currentSetpoint variable, and compare that to the commandedSetpoint, and if the difference is greater than rampRate, you add or subtract rampRate from the currentSetpoint. Otherwise you set them equal. That means your setpoint will only change by a small amount per cycle, so it will slowly ramp up or down.