形象解释PID算法+PID算法源代码
小明接到这样一个任务:
有一个水缸点漏水(而且漏水的速度还不一定固定不变),要求水面高度维持在某个位置,一旦发现水面高度低于要求位置,就要往水缸里加水。
小明接到任务后就一直守在水缸旁边,时间长就觉得无聊,就跑到房里看小说了,每30分钟来检查一次水面高度。水漏得太快,
每次小明来检查时,水都快漏完了,离要求的高度相差很远,小明改为每3分钟来检查一次,结果每次来水都没怎么漏,不需要加水,来得太频繁做的是无用功。
几次试验后,确定每10分钟来检查一次。这个检查时间就称为采样周期。
开始小明用瓢加水,水龙头离水缸有十几米的距离,经常要跑好几趟才加够水,于是小明又改为用桶加,一加就是一桶,跑的次数少了,加水的速度也快了,
但好几次将缸给加溢出了,不小心弄湿了几次鞋,小明又动脑筋,我不用瓢也不用桶,老子用盆,几次下来,
发现刚刚好,不用跑太多次,也不会让水溢出。这个加水工具的大小就称为比例系数。
小明又发现水虽然不会加过量溢出了,有时会高过要求位置比较多,还是有打湿鞋的危险。他又想了个办法,在水缸上装一个漏斗,
每次加水不直接倒进水缸,而是倒进漏斗让它慢慢加。这样溢出的问题解决了,但加水的速度又慢了,有时还赶不上漏水的速度。
于是他试着变换不同大小口径的漏斗来控制加水的速度,最后终于找到了满意的漏斗。漏斗的时间就称为积分时间 。
小明终于喘了一口,但任务的要求突然严了,水位控制的及时性要求大大提高,一旦水位过低,必须立即将水加到要求位置,而且不能高出太多,否则不给工钱。
小明又为难了!于是他又开努脑筋,终于让它想到一个办法,常放一盆备用水在旁边,一发现水位低了,不经过漏斗就是一盆水下去,这样及时性是保证了,但水位有时会高多了。
他又在要求水面位置上面一点将水凿一孔,再接一根管子到下面的备用桶里这样多出的水会从上面的孔里漏出来。这个水漏出的快慢就称为微分时间。
拿一个水池水位来说,我们 可以制定一个规则,
把水位分为超高、高、较高、中、较低、低、超低几个区段;
再把水位波动的趋势分为甚快、快、较快、慢、停几个区段,并区分趋势的正负;
把输出分为超大幅 度、大幅度、较大幅度、微小几个区段。
当水位处于中值、趋势处于停顿的时候,不调节;
当水位处于中值、趋势缓慢变化的时候,也可以暂不调节;
当水位处于较高、趋势缓慢变化 的时候,输出一个微小调节两就够了;
当水位处于中值、趋势较快变化的时候,输出进行叫 大幅度调节……。
如上所述,我们需要制定一个控制规则表,然后制定参数判断水位区段的界值、波动趋 势的界值、输出幅度的界值。
PID算法的解释-转自老外的博客
博客地址:
http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/
源文件下载地址:
http://www.arduino.cc/playground/Code/PIDLibrary
DOWNLOAD
PID Library
PID Front-End using Processing.org
- Latest version: v0.3
Older Versions can be viewed / downloaded here
THE BASICS
What Is PID?
From Wikipedia: "A PID controller calculates an ‘error‘ value as the difference between a measured [Input] and a desired setpoint. The controller attempts to minimize the error by adjusting [an Output]."
So, you tell the PID what to measure (the "Input",) Where you want that measurement to be (the "Setpoint",) and the variable to adjust that can make that happen (the "Output".) The PID then adjusts the output trying to make the input equal the setpoint.
For reference, in a car, the Input, Setpoint, and Output would be the speed, desired speed, and gas pedal angle respectively.
Tuning Parameters
The black magic of PID comes in when we talk about HOW it adjusts the Output to drive the Input towards Setpoint. There are 3 Tuning Parameters (or "Tunings"): Kp, Ki & Kd. Adjusting these values will change the way the output is adjusted. Fast? Slow? God-awful? All of these can be achieved depending on the values of Kp, Ki, and Kd.
So what are the "right" tuning values to use? There isn‘t one right answer. The values that work for one application may not work for another, just as the driving style that works for a truck may not work for a race car. With each new application you will need to try Several Tuning values until you find a set that gives you what you want.
Note: there is also now a PID Autotune Library that can help you determine tuning parameters.
Should I Use PID?
PID is a pretty impressive control algorithm, but it‘s not magic. You should at least be able to answer "yes" to these questions:
- Is it worth the extra work? If you‘re using a more basic system and it‘s working for you, there‘s no real reason to use PID.
- Is your system repeatable? The PID needs some semblance of repeatability. When the output is changed from A to B, at least roughly the same thing should happen every time. (think of a car where you step on the gas and sometimes you speed up and sometimes you slow down. not repeatable, not a place where you could use PID)
(This list will probably grow as I think of more criteria. These are the big ones though)
A Note About Relays
Even though a PID controller is designed to work with an analog output, it is possible to connect to a discrete output such as a relay. Be sure to look at the RelayOutput example below.
Questions?
Try the newly created Google group.
The Library
Using The PID Library has two benefits in my mind
- There are many ways to write the PID algorithm. A lot of time was spent making the algorithm in this library as solid as any found in industry. If you want to read more about this, check out this detailed explanation.
- When using the library all the PID code is self-contained. This makes your code easier to understand. It also lets you do more complex stuff, like say having 8 PIDs in the same program.
Functions
Examples
***************************************************************
* Arduino PID Library - Version 1.1.1
* by Brett Beauregard <[email protected]> brettbeauregard.com
*
* This Library is licensed under a GPLv3 License
***************************************************************
- For an ultra-detailed explanation of why the code is the way it is, please visit:
http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/
- For function documentation see: http://playground.arduino.cc/Code/PIDLibrary
#ifndef PID_v1_h #define PID_v1_h #define LIBRARY_VERSION 1.1.1 class PID { public: //Constants used in some of the functions below #define AUTOMATIC 1 #define MANUAL 0 #define DIRECT 0 #define REVERSE 1 //commonly used functions ************************************************************************** PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and double, double, double, int); // Setpoint. Initial tuning parameters are also set here void SetMode(int Mode); // * sets PID to either Manual (0) or Auto (non-0) bool Compute(); // * performs the PID calculation. it should be // called every time loop() cycles. ON/OFF and // calculation frequency can be set using SetMode // SetSampleTime respectively void SetOutputLimits(double, double); //clamps the output to a specific range. 0-255 by default, but //it‘s likely the user will want to change this depending on //the application //available but not commonly used functions ******************************************************** void SetTunings(double, double, // * While most users will set the tunings once in the double); // constructor, this function gives the user the option // of changing tunings during runtime for Adaptive control void SetControllerDirection(int); // * Sets the Direction, or "Action" of the controller. DIRECT // means the output will increase when error is positive. REVERSE // means the opposite. it‘s very unlikely that this will be needed // once it is set in the constructor. void SetSampleTime(int); // * sets the frequency, in Milliseconds, with which // the PID calculation is performed. default is 100 //Display functions **************************************************************** double GetKp(); // These functions query the pid for interal values. double GetKi(); // they were created mainly for the pid front-end, double GetKd(); // where it‘s important to know what is actually int GetMode(); // inside the PID. int GetDirection(); // private: void Initialize(); double dispKp; // * we‘ll hold on to the tuning parameters in user-entered double dispKi; // format for display purposes double dispKd; // double kp; // * (P)roportional Tuning Parameter double ki; // * (I)ntegral Tuning Parameter double kd; // * (D)erivative Tuning Parameter int controllerDirection; double *myInput; // * Pointers to the Input, Output, and Setpoint variables double *myOutput; // This creates a hard link between the variables and the double *mySetpoint; // PID, freeing the user from having to constantly tell us // what these values are. with pointers we‘ll just know. unsigned long lastTime; double ITerm, lastInput; unsigned long SampleTime; double outMin, outMax; bool inAuto; }; #endif
/********************************************************************************************** * Arduino PID Library - Version 1.1.1 * by Brett Beauregard <[email protected]> brettbeauregard.com * * This Library is licensed under a GPLv3 License **********************************************************************************************/ #if ARDUINO >= 100 #include "Arduino.h" #else #include "WProgram.h" #endif #include <PID_v1.h> /*Constructor (...)********************************************************* * The parameters specified here are those for for which we can‘t set up * reliable defaults, so we need to have the user set them. ***************************************************************************/ PID::PID(double* Input, double* Output, double* Setpoint, double Kp, double Ki, double Kd, int ControllerDirection) { myOutput = Output; myInput = Input; mySetpoint = Setpoint; inAuto = false; PID::SetOutputLimits(0, 255); //default output limit corresponds to //the arduino pwm limits SampleTime = 100; //default Controller Sample Time is 0.1 seconds PID::SetControllerDirection(ControllerDirection); PID::SetTunings(Kp, Ki, Kd); lastTime = millis()-SampleTime; } /* Compute() ********************************************************************** * This, as they say, is where the magic happens. this function should be called * every time "void loop()" executes. the function will decide for itself whether a new * pid Output needs to be computed. returns true when the output is computed, * false when nothing has been done. **********************************************************************************/ bool PID::Compute() { if(!inAuto) return false; unsigned long now = millis(); unsigned long timeChange = (now - lastTime); if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double input = *myInput; double error = *mySetpoint - input; ITerm+= (ki * error); if(ITerm > outMax) ITerm= outMax; else if(ITerm < outMin) ITerm= outMin; double dInput = (input - lastInput); /*Compute PID Output*/ double output = kp * error + ITerm- kd * dInput; if(output > outMax) output = outMax; else if(output < outMin) output = outMin; *myOutput = output; /*Remember some variables for next time*/ lastInput = input; lastTime = now; return true; } else return false; } /* SetTunings(...)************************************************************* * This function allows the controller‘s dynamic performance to be adjusted. * it‘s called automatically from the constructor, but tunings can also * be adjusted on the fly during normal operation ******************************************************************************/ void PID::SetTunings(double Kp, double Ki, double Kd) { if (Kp<0 || Ki<0 || Kd<0) return; dispKp = Kp; dispKi = Ki; dispKd = Kd; double SampleTimeInSec = ((double)SampleTime)/1000; kp = Kp; ki = Ki * SampleTimeInSec; kd = Kd / SampleTimeInSec; if(controllerDirection ==REVERSE) { kp = (0 - kp); ki = (0 - ki); kd = (0 - kd); } } /* SetSampleTime(...) ********************************************************* * sets the period, in Milliseconds, at which the calculation is performed ******************************************************************************/ void PID::SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime; ki *= ratio; kd /= ratio; SampleTime = (unsigned long)NewSampleTime; } } /* SetOutputLimits(...)**************************************************** * This function will be used far more often than SetInputLimits. while * the input to the controller will generally be in the 0-1023 range (which is * the default already,) the output will be a little different. maybe they‘ll * be doing a time window and will need 0-8000 or something. or maybe they‘ll * want to clamp it from 0-125. who knows. at any rate, that can all be done * here. **************************************************************************/ void PID::SetOutputLimits(double Min, double Max) { if(Min >= Max) return; outMin = Min; outMax = Max; if(inAuto) { if(*myOutput > outMax) *myOutput = outMax; else if(*myOutput < outMin) *myOutput = outMin; if(ITerm > outMax) ITerm= outMax; else if(ITerm < outMin) ITerm= outMin; } } /* SetMode(...)**************************************************************** * Allows the controller Mode to be set to manual (0) or Automatic (non-zero) * when the transition from manual to auto occurs, the controller is * automatically initialized ******************************************************************************/ void PID::SetMode(int Mode) { bool newAuto = (Mode == AUTOMATIC); if(newAuto == !inAuto) { /*we just went from manual to auto*/ PID::Initialize(); } inAuto = newAuto; } /* Initialize()**************************************************************** * does all the things that need to happen to ensure a bumpless transfer * from manual to automatic mode. ******************************************************************************/ void PID::Initialize() { ITerm = *myOutput; lastInput = *myInput; if(ITerm > outMax) ITerm = outMax; else if(ITerm < outMin) ITerm = outMin; } /* SetControllerDirection(...)************************************************* * The PID will either be connected to a DIRECT acting process (+Output leads * to +Input) or a REVERSE acting process(+Output leads to -Input.) we need to * know which one, because otherwise we may increase the output when we should * be decreasing. This is called from the constructor. ******************************************************************************/ void PID::SetControllerDirection(int Direction) { if(inAuto && Direction !=controllerDirection) { kp = (0 - kp); ki = (0 - ki); kd = (0 - kd); } controllerDirection = Direction; } /* Status Funcions************************************************************* * Just because you set the Kp=-1 doesn‘t mean it actually happened. these * functions query the internal state of the PID. they‘re here for display * purposes. this are the functions the PID Front-end uses for example ******************************************************************************/ double PID::GetKp(){ return dispKp; } double PID::GetKi(){ return dispKi;} double PID::GetKd(){ return dispKd;} int PID::GetMode(){ return inAuto ? AUTOMATIC : MANUAL;} int PID::GetDirection(){ return controllerDirection;}
Example
/******************************************************** * PID Basic Example * Reading analog input 0 to control analog PWM output 3 ********************************************************/ #include <PID_v1.h> #define PIN_INPUT 0 #define PIN_OUTPUT 3 //Define Variables we‘ll be connecting to double Setpoint, Input, Output; //Specify the links and initial tuning parameters double Kp=2, Ki=5, Kd=1; PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); void setup() { //initialize the variables we‘re linked to Input = analogRead(PIN_INPUT); Setpoint = 100; //turn the PID on myPID.SetMode(AUTOMATIC); } void loop() { Input = analogRead(PIN_INPUT); myPID.Compute(); analogWrite(PIN_OUTPUT, Output); }
/******************************************************** * PID Adaptive Tuning Example * One of the benefits of the PID library is that you can * change the tuning parameters at any time. this can be * helpful if we want the controller to be agressive at some * times, and conservative at others. in the example below * we set the controller to use Conservative Tuning Parameters * when we‘re near setpoint and more agressive Tuning * Parameters when we‘re farther away. ********************************************************/ #include <PID_v1.h> #define PIN_INPUT 0 #define PIN_OUTPUT 3 //Define Variables we‘ll be connecting to double Setpoint, Input, Output; //Define the aggressive and conservative Tuning Parameters double aggKp=4, aggKi=0.2, aggKd=1; double consKp=1, consKi=0.05, consKd=0.25; //Specify the links and initial tuning parameters PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT); void setup() { //initialize the variables we‘re linked to Input = analogRead(PIN_INPUT); Setpoint = 100; //turn the PID on myPID.SetMode(AUTOMATIC); } void loop() { Input = analogRead(PIN_INPUT); double gap = abs(Setpoint-Input); //distance away from setpoint if (gap < 10) { //we‘re close to setpoint, use conservative tuning parameters myPID.SetTunings(consKp, consKi, consKd); } else { //we‘re far from setpoint, use aggressive tuning parameters myPID.SetTunings(aggKp, aggKi, aggKd); } myPID.Compute(); analogWrite(PIN_OUTPUT, Output); }
/******************************************************** * PID RelayOutput Example * Same as basic example, except that this time, the output * is going to a digital pin which (we presume) is controlling * a relay. the pid is designed to Output an analog value, * but the relay can only be On/Off. * * to connect them together we use "time proportioning * control" it‘s essentially a really slow version of PWM. * first we decide on a window size (5000mS say.) we then * set the pid to adjust its output between 0 and that window * size. lastly, we add some logic that translates the PID * output into "Relay On Time" with the remainder of the * window being "Relay Off Time" ********************************************************/ #include <PID_v1.h> #define PIN_INPUT 0 #define RELAY_PIN 6 //Define Variables we‘ll be connecting to double Setpoint, Input, Output; //Specify the links and initial tuning parameters double Kp=2, Ki=5, Kd=1; PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); int WindowSize = 5000; unsigned long windowStartTime; void setup() { windowStartTime = millis(); //initialize the variables we‘re linked to Setpoint = 100; //tell the PID to range between 0 and the full window size myPID.SetOutputLimits(0, WindowSize); //turn the PID on myPID.SetMode(AUTOMATIC); } void loop() { Input = analogRead(PIN_INPUT); myPID.Compute(); /************************************************ * turn the output pin on/off based on pid output ************************************************/ if (millis() - windowStartTime > WindowSize) { //time to shift the Relay Window windowStartTime += WindowSize; } if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, HIGH); else digitalWrite(RELAY_PIN, LOW); }