How do I tune PID loops for Open Loop stepper motors?

More
25 Oct 2018 19:36 - 25 Oct 2018 21:23 #119399 by cogzoid
I've been trying to figure out the PID loops in linuxcnc, and I think I understand them and why they're there. However, the loop is entirely inside of the software. And since the loop doesn't close around the real physical world at all, I'm wondering how the values are properly tuned. It seems to me that a working set of PID variables in this situation would work equally for all axes.

The default values I have are:
P = 200.0
I = 0.0
D = 0.0
FF0 = 0.0
FF1 = 1.0
FF2 = 0.0
BIAS = 0.0

Are those (rather trivial) values correct? Or is there some other way to tune the PID loops? I noticed a reference to an "auto-tune" feature in the PID source code. But I'm not sure if that's actually called anywhere. It also references some user input that's required, so I'm not sure how that works for this software-only tuning.

If there's a process for tuning these PID variables, I'd love to know it. Thanks!

Here's the auto-tune code for reference...
/*
 * Perform an auto-tune operation. Sets up a limit cycle using the specified
 * tune effort. Averages the amplitude and period over the specified number of
 * cycles. This characterizes the process and determines the ultimate gain
 * and period, which are then used to calculate PID.
 *
 * CO(t) = P * [ e(t) + 1/Ti * (f e(t)dt) - Td * (d/dt PV(t)) ]
 *
 * Pu = 4/PI * tuneEffort / responseAmplitude
 * Ti = 0.5 * responsePeriod
 * Td = 0.125 * responsePeriod
 *
 * P = 0.6 * Pu
 * I = P * 1/Ti
 * D = P * Td
 */
static void
Pid_AutoTune(Pid *this, long period)
{
    hal_float_t                 error;

    // Calculate the error.
    error = *this->pCommand - *this->pFeedback;
    *this->pError = error;

    // Check if enabled and if still in tune mode.
    if(!*this->pEnable || !*this->pTuneMode){
        this->state = STATE_TUNE_ABORT;
    }

    switch(this->state){
    case STATE_TUNE_IDLE:
        // Wait for tune start command.
        if(*this->pTuneStart)
            this->state = STATE_TUNE_START;
        break;

    case STATE_TUNE_START:
        // Initialize tuning variables and start limit cycle.
        this->state = STATE_TUNE_POS;
        this->cycleCount = 0;
        this->cyclePeriod = 0;
        this->cycleAmplitude = 0;
        this->totalTime = 0;
        this->avgAmplitude = 0;
        *(this->ultimateGain) = 0;
        *(this->ultimatePeriod) = 0;
        *this->pOutput = *(this->bias) + fabs(*(this->tuneEffort));
        break;

    case STATE_TUNE_POS:
    case STATE_TUNE_NEG:
        this->cyclePeriod += period;

        if(error < 0.0){
            // Check amplitude.
            if(-error > this->cycleAmplitude)
                this->cycleAmplitude = -error;

            // Check for end of cycle.
            if(this->state == STATE_TUNE_POS){
                this->state = STATE_TUNE_NEG;
                Pid_CycleEnd(this);
            }

            // Update output so user can ramp effort until movement occurs.
            *this->pOutput = *(this->bias) - fabs(*(this->tuneEffort));
        }else{
            // Check amplitude.
            if(error > this->cycleAmplitude)
                this->cycleAmplitude = error;

            // Check for end of cycle.
            if(this->state == STATE_TUNE_NEG){
                this->state = STATE_TUNE_POS;
                Pid_CycleEnd(this);
            }

            // Update output so user can ramp effort until movement occurs.
            *this->pOutput = *(this->bias) + fabs(*(this->tuneEffort));
        }

        // Check if the last cycle just ended. This is really the number
        // of half cycles.
        if(this->cycleCount < *(this->tuneCycles))
            break;

        // Calculate PID.
        *(this->ultimateGain) = (4.0 * fabs(*(this->tuneEffort)))/(PI * this->avgAmplitude);
        *(this->ultimatePeriod) = 2.0 * this->totalTime / *(this->tuneCycles);
        *(this->ff0Gain) = 0;
        *(this->ff2Gain) = 0;

        if(*(this->tuneType) == TYPE_PID){
            // PID.
            *(this->pGain) = 0.6 * *(this->ultimateGain);
            *(this->iGain) = *(this->pGain) / (*(this->ultimatePeriod) / 2.0);
            *(this->dGain) = *(this->pGain) * (*(this->ultimatePeriod) / 8.0);
            *(this->ff1Gain) = 0;
        }else{
            // PI FF1.
            *(this->pGain) = 0.45 * *(this->ultimateGain);
            *(this->iGain) = *(this->pGain) / (*(this->ultimatePeriod) / 1.2);
            *(this->dGain) = 0;

            // Scaling must be set so PID output is in user units per second.
            *(this->ff1Gain) = 1;
        }

        // Fall through.

    case STATE_TUNE_ABORT:
    default:
        // Force output to zero.
        *this->pOutput = 0;

        // Abort any tuning cycle in progress.
        *this->pTuneStart = 0;
        this->state = (*this->pTuneMode)? STATE_TUNE_IDLE: STATE_PID;
    }
}


static void
Pid_CycleEnd(Pid *this)
{
    this->cycleCount++;
    this->avgAmplitude += this->cycleAmplitude / *(this->tuneCycles);
    this->cycleAmplitude = 0;
    this->totalTime += this->cyclePeriod * 0.000000001;
    this->cyclePeriod = 0;
}
Last edit: 25 Oct 2018 21:23 by cogzoid. Reason: typo

Please Log in or Create an account to join the conversation.

More
25 Oct 2018 20:10 #119407 by cmorley
The following user(s) said Thank You: cogzoid

Please Log in or Create an account to join the conversation.

Time to create page: 0.108 seconds
Powered by Kunena Forum