Boston
Offline
Full Member
Karma: 0
Posts: 101
Arduino rocks
|
 |
« Reply #90 on: July 12, 2010, 01:48:34 pm » |
By the way, why on earth is the I parameter equal to the reciprocal of the gain as it appears in the PID algorithm? sorry for the confusion. there's various forms of the pid equation. ( http://en.wikipedia.org/wiki/PID_controller scroll down to "Alternative nomenclature and PID forms") they're mathematically equivalent, but the one listed as the "standard form" is the one i chose. it's a little more work on the backend as you mentioned, but I find it easier to tune. if you know the time constant of the process, you just make that the I term, then adjust P to get more aggressive or conservative)
|
|
|
|
|
Logged
|
|
|
|
|
Cumming, Ga
Offline
Edison Member
Karma: 12
Posts: 1388
Ultimate DIY: Arduino
|
 |
« Reply #91 on: July 14, 2010, 09:31:50 pm » |
Brett, ZeroVector ... You guys rock!
ZV, The Timer Library you used was the answer I'd been searching for. I had no clue about it and it allowed me to have much better control over my Kiln temperature with correct PWM output settings.
I use an SSR to control a 2000 Degree F, 20 Amp Kiln. I have now been able to reach my goal of 1290F and was hovering between +/- 6 degrees with minimal overshoot. I was thrilled!
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 4
Arduino rocks
|
 |
« Reply #92 on: July 18, 2010, 01:26:12 am » |
Thanks!that is i 'm looking for.
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 43
Arduino rocks
|
 |
« Reply #93 on: July 19, 2010, 01:21:11 am » |
I'll remove the base tags and leave the Playgound strings.  : 
|
|
|
|
|
Logged
|
|
|
|
|
Boston
Offline
Full Member
Karma: 0
Posts: 101
Arduino rocks
|
 |
« Reply #94 on: July 27, 2010, 01:00:28 pm » |
|
|
|
|
|
Logged
|
|
|
|
|
Chieri (TO) IT
Offline
Newbie
Karma: 0
Posts: 45
conTESTI.eu electronics
|
 |
« Reply #95 on: August 02, 2010, 01:42:26 am » |
Hi, sorry for this out-of-topic post, but I have no idea of the person I need to contact. Do you have any idea or contact to post news topics on this area? I have new products recently developed, for Arduino board that I think are interesting for the community. What is the way? Thank you in advance for your help. Enrico Miglino conTESTI.eu electronics http://www.contesti.eu/projects
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 16
Arduino rocks
|
 |
« Reply #96 on: August 14, 2010, 03:44:54 pm » |
Brett, If you're still considering an integer version, here's a technique I've found worthwhile. Convert everything to long variable type, then assume 12 decimal places. I use the following:
#define floatToFix(a) ((long)((a)*4096.0)) // mult by 2^12 to use floats in fixed point math #define fixToFloat(a) ((float)((a)/4096.0)) // div by 2^12 to convert back to float #define multFix(a,b) ((long)((((long)(a))*((long)(b)))>>12))
for analog reads,
a1 = analogRead(pin); aIn1 = (long)a1 << 12; // add 12 decimal places
this approach speeds up the math without losing precision or accuracy.
hth, Billy
|
|
|
|
|
Logged
|
|
|
|
|
Boston
Offline
Full Member
Karma: 0
Posts: 101
Arduino rocks
|
 |
« Reply #97 on: August 16, 2010, 08:02:01 am » |
#define multFix(a,b) ((long)((((long)(a))*((long)(b)))>>12)) one of the main issues I've been wrestling with on the integer library is ensuring that overflow doesn't happen. I haven't tried this, but if a and b were big enough wouldn't a*b overflow?
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 16
Arduino rocks
|
 |
« Reply #98 on: August 16, 2010, 08:54:12 am » |
Brett, If this overflows, you could reduce the number of bit-shifts or use 'long long' int64_t data type which should not overflow. I'm fumbling my way through modifying the library, and will email it to you for performance baseline testing for speed and accuracy.
Billy
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 16
Arduino rocks
|
 |
« Reply #99 on: August 18, 2010, 09:28:46 am » |
Brett, The integer version of the library is functional, with one caveat... the output increments from 0 to 127 then increments from -128 to -1. It's like the 8th bit is being used for sign. I've renamed the library to PID_Beta6b in order to maintain the integrity of the original library and to make it easy to switch back and forth. Here's the first half of the code: #include <wiring.h> #include <PID_Beta6b.h>
/* Standard Constructor (...)*********************************************** * constructor used by most users. the parameters specified 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 Kc, double TauI, double TauD) {
PID::ConstructorCommon(Input, Output, Setpoint, Kc, TauI, TauD); UsingFeedForward = false; PID::Reset();
}
/* Overloaded Constructor(...)********************************************** * This one is for more advanced users. it's essentially the same as the * standard constructor, with one addition. you can link to a Feed Forward bias, * which lets you implement... um.. Feed Forward Control. good stuff. ***************************************************************************/ PID::PID(double *Input, double *Output, double *Setpoint, double *FFBias, double Kc, double TauI, double TauD) {
PID::ConstructorCommon(Input, Output, Setpoint, Kc, TauI, TauD); UsingFeedForward = true; //tell the controller that we'll be using an external long myBias = (*FFBias * 4096.0); //bias, and where to find it PID::Reset();
}
/* ConstructorCommon(...)**************************************************** * Most of what is done in the two constructors is the same. that code * was put here for ease of maintenance and (minor) reduction of library size ****************************************************************************/ void PID::ConstructorCommon(double *Input, double *Output, double *Setpoint, double Kc, double TauI, double TauD) { PID::SetInputLimits(0, 1023); //default the limits to the PID::SetOutputLimits(0, 255); //full ranges of the I/O
tSample = 1000; //default Controller Sample Time is 1 second
PID::SetTunings(Kc, TauI, TauD);
nextCompTime = millis(); inAuto = false; myOutput = Output; myInput = Input; mySetpoint = Setpoint; }
/* SetInputLimits(...)***************************************************** * I don't see this function being called all that much (other than from the * constructor.) it needs to be here so we can tell the controller what it's * input limits are, and in most cases the 0-1023 default should be fine. if * there's an application where the signal being fed to the controller is * outside that range, well, then this function's here for you. **************************************************************************/ void PID::SetInputLimits(double INMin, double INMax) { //after verifying that mins are smaller than maxes, set the values if(INMin >= INMax) return;
//rescale the working variables to reflect the changes lastInput = multFix(lastInput,divFix(floatToFix(INMax - INMin),inSpan)); accError = multFix(accError,divFix(floatToFix(INMax - INMin),inSpan));
//make sure the working variables are //within the new limits if (lastInput > 1<<12) lastInput = 1<<12; else if (lastInput < 0L) lastInput = 0L; inMin = floatToFix(INMin); inSpan = floatToFix(INMax - INMin); }
/* 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 OUTMin, double OUTMax) { //after verifying that mins are smaller than maxes, set the values if(OUTMin >= OUTMax) return;
//rescale the working variables to reflect the changes lastOutput = multFix(lastOutput,divFix(floatToFix(OUTMax - OUTMin),outSpan));
//make sure the working variables are //within the new limits if (lastOutput > 1<<12) lastOutput = 1<<12; else if (lastOutput < 0L) lastOutput = 0L;
outMin = floatToFix(OUTMin); outSpan = floatToFix(OUTMax - OUTMin); }
/* 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 Kc, double TauI, double TauD) { //verify that the tunings make sense if (Kc == 0.0 || TauI < 0.0 || TauD < 0.0) return;
//we're going to do some funky things to the input numbers so all //our math works out, but we want to store the numbers intact //so we can return them to the user when asked. P_Param = Kc; I_Param = TauI; D_Param = TauD; //convert Reset Time into Reset Rate, and compensate for Calculation frequency double tSampleInSec = ((double)tSample / 1000.0); double tempTauR; if (TauI == 0) tempTauR = 0; else tempTauR = (1.0 / TauI) * tSampleInSec;
if (inAuto) { //if we're in auto, and we just change the tunings, the integral term //will become very, very, confused (trust me.) to achieve "bumpless // transfer" we need to rescale the accumulated error. if(tempTauR != 0.0) //(avoid divide by 0) accError = multFix(accError,divFix(multFix(kc,taur),floatToFix(Kc * tempTauR))); else accError = 0L; } kc = floatToFix(Kc); taur = floatToFix(tempTauR); taud = floatToFix(TauD / tSampleInSec); }
/* Reset()********************************************************************* * does all the things that need to happen to ensure a bumpless transfer * from manual to automatic mode. this shouldn't have to be called from the * outside. In practice though, it is sometimes helpful to start from scratch, * so it was made publicly available ******************************************************************************/ void PID::Reset() {
if(UsingFeedForward) bias = divFix((*myBias - outMin),outSpan); else bias = divFix((floatToFix(*myOutput) - outMin),outSpan); lastOutput = (long)bias; lastInput = divFix((floatToFix(*myInput) - inMin),inSpan);
// - clear any error in the integral accError = 0L;
}
/* 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) { if (Mode!=0 && !inAuto) { //we were in manual, and we just got set to auto. //reset the controller internals PID::Reset(); } inAuto = (Mode!=0); }
/* SetSampleTime(...)******************************************************* * sets the frequency, in Milliseconds, with which the PID calculation is performed ******************************************************************************/ void PID::SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { //convert the time-based tunings to reflect this change taur = multFix(taur,floatToFix((double)NewSampleTime/(double)tSample)); accError = multFix(accError,floatToFix((double)tSample/(double)NewSampleTime)); taud = multFix(taud,floatToFix((double)NewSampleTime/(double)tSample)); tSample = (unsigned long)NewSampleTime; } }
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 16
Arduino rocks
|
 |
« Reply #100 on: August 18, 2010, 09:32:06 am » |
And here's the last portion (wouldn't all fit in one post): /* 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 * * Some notes for people familiar with the nuts and bolts of PID control: * - I used the Ideal form of the PID equation. mainly because I like IMC * tunings. lock in the I and D, and then just vary P to get more * aggressive or conservative * * - While this controller presented to the outside world as being a Reset Time * controller, when the user enters their tunings the I term is converted to * Reset Rate. I did this merely to avoid the div0 error when the user wants * to turn Integral action off. * * - Derivative on Measurement is being used instead of Derivative on Error. The * performance is identical, with one notable exception. DonE causes a kick in * the controller output whenever there's a setpoint change. DonM does not. * * If none of the above made sense to you, and you would like it to, go to: * http://www.controlguru.com . Dr. Cooper was my controls professor, and is * gifted at concisely and clearly explaining PID control *********************************************************************************/ void PID::Compute() { justCalced=false; if (!inAuto) return; //if we're in manual just leave;
unsigned long now = millis();
//millis() wraps around to 0 at some point. depending on the version of the //Arduino Program you are using, it could be in 9 hours or 50 days. //this is not currently addressed by this algorithm.
//here's an attempt to address the millis() wrap issue:
if (now < tSample) nextCompTime = tSample; //...Perform PID Computations if it's time... if (now>=nextCompTime) { //pull in the input and setpoint, and scale them into percent span long scaledInput = divFix((floatToFix(*myInput) - inMin),inSpan); if (scaledInput>(1<<12)) scaledInput = 1<<12; else if (scaledInput<0L) scaledInput = 0L;
long scaledSP = divFix((floatToFix(*mySetpoint) - inMin),inSpan); if (scaledSP>(1<<12)) scaledSP = 1<<12; else if (scaledSP<0L) scaledSP = 0L; //compute the error long err = scaledSP - scaledInput; // check and see if the output is pegged at a limit and only // integrate if it is not. (this is to prevent reset-windup) if (!(lastOutput >= 1<<12 && err>0L) && !(lastOutput <= 0L && err<0L)) { accError = accError + err; }
// compute the current slope of the input signal long dMeas = (scaledInput - lastInput); // we'll assume that dTime (the denominator) is 1 second. // if it isn't, the taud term will have been adjusted // in "SetTunings" to compensate
//if we're using an external bias (i.e. the user used the //overloaded constructor,) then pull that in now if(UsingFeedForward) { bias = divFix((*myBias - outMin),outSpan); }
// perform the PID calculation. long output = bias + multFix(kc,(err + multFix(taur,accError) - multFix(taud,dMeas)));
//make sure the computed output is within output constraints if (output < 0L) output = 0L; else if (output > 1<<12) output = 1<<12;
lastOutput = output; // remember this output for the windup // check next time lastInput = scaledInput; // remember the Input for the derivative // calculation next time
//scale the output from percent span back out to a real world number *myOutput = fixToFloat(multFix(output,outSpan) + outMin);
nextCompTime += tSample; // determine the next time the computation if(nextCompTime < now) nextCompTime = now + tSample; // should be performed
justCalced=true; //set the flag that will tell the outside world that the output was just computed }
}
/***************************************************************************** * STATUS SECTION * These functions allow the outside world to query the status of the PID *****************************************************************************/
bool PID::JustCalculated() { return justCalced; } int PID::GetMode() { if(inAuto)return 1; else return 0; }
double PID::GetINMin() { return fixToFloat(inMin); } double PID::GetINMax() { return fixToFloat(inMin + inSpan); } double PID::GetOUTMin() { return fixToFloat(outMin); } double PID::GetOUTMax() { return fixToFloat(outMin+outSpan); } int PID::GetSampleTime() { return tSample; } double PID::GetP_Param() { return P_Param; } double PID::GetI_Param() { return I_Param; }
double PID::GetD_Param() { return D_Param; }
ps. I also made a feeble stab at millis() rollover protection, not sure if it'll work...
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 16
Arduino rocks
|
 |
« Reply #101 on: August 18, 2010, 09:36:08 am » |
And lastly, the modified header file: #ifndef PID_Beta6b_h #define PID_Beta6b_h
#define floatToFix(a) ((long)((a)*4096.0)) // mult by 2^12 to use floats in fixed point math #define fixToFloat(a) ((double)((a)/4096.0)) // div by 2^12 to convert back to float #define multFix(a,b) ((long)((((long)(a))*((long)(b)))>>12)) // bit-shift right 12 to maintain 12 assumed decimal places #define divFix(a,b) ((long)(((long)(a)<<12)/((long)(b)))) // make Numerator 8:24 int before dividing
class PID {
public:
#define AUTO 1 #define MANUAL 0 #define LIBRARY_VERSION 0.6b
//commonly used functions ************************************************************************** PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and double, double, double); // Setpoint. Initial tuning parameters are also set here
PID(double*, double*, double*, // * Overloaded Constructor. if the user wants to implement double*, double, double, double); // feed-forward
void SetMode(int Mode); // * sets PID to either Manual (0) or Auto (non-0)
void 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 SetInputLimits(double, double); //Tells the PID what 0-100% are for the Input
void SetOutputLimits(double, double); //Tells the PID what 0-100% are for the Output
//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 SetSampleTime(int); // * sets the frequency, in Milliseconds, with which // the PID calculation is performed. default is 1000
void Reset(); // * reinitializes controller internals. automatically // called on a manual to auto transition
bool JustCalculated(); // * in certain situations, it helps to know when the PID has // computed this bit will be true for one cycle after the // pid calculation has occurred
//Status functions allow you to query current PID constants *************************************** int GetMode(); double GetINMin(); double GetINMax(); double GetOUTMin(); double GetOUTMax(); int GetSampleTime(); double GetP_Param(); double GetI_Param(); double GetD_Param();
private:
void ConstructorCommon(double*, double*, double*, // * code that is shared by the constructors double, double, double);
//scaled, tweaked parameters we'll actually be using long kc; // * (P)roportional Tuning Parameter long taur; // * (I)ntegral Tuning Parameter long taud; // * (D)erivative Tuning Parameter
//nice, pretty parameters we'll give back to the user if they ask what the tunings are double P_Param; double I_Param; double D_Param;
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.
long *myBias; // * Pointer to the External FeedForward bias, only used // if the advanced constructor is used bool UsingFeedForward; // * internal flag that tells us if we're using FeedForward or not
unsigned long nextCompTime; // * Helps us figure out when the PID Calculation needs to // be performed next // to determine when to compute next unsigned long tSample; // * the frequency, in milliseconds, with which we want the // the PID calculation to occur. bool inAuto; // * Flag letting us know if we are in Automatic or not
long lastOutput; // * remembering the last output is used to prevent // reset windup. long lastInput; // * we need to remember the last Input Value so we can compute // the derivative required for the D term long accError; // * the (I)ntegral term is based on the sum of error over // time. this variable keeps track of that long bias; // * the base output from which the PID operates
long inMin, inSpan; // * input and output limits, and spans. used convert long outMin, outSpan; // real world numbers into percent span, with which // the PID algorithm is more comfortable.
bool justCalced; // * flag gets set for one cycle after the pid calculates }; #endif
|
|
|
|
|
Logged
|
|
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 2
Arduino rocks
|
 |
« Reply #103 on: November 01, 2010, 08:12:40 am » |
Hi guys, and Brett. First of all thank you for your huge contributions on PID library, it was just the thing needed. Im kind of new to Arduino and PID's too. I have designed PID compensators in control fundamentals class but newer used it as a code on a real system I have a little problem using PID lib. Its actually very user friendly but im trying to control my quadrotor using pid lib. I use pitch, roll angle values from Ardu Imu V2 as an inputs of PID, trying to get output values as esc pwm's. I dont have problems reading from Imu but i dont get output changes. IMU values change like 300 times but output changes 1 or 2 times, or never change sometimes. I have changed the sampiling period of PID a few times but i couldnt find the solution any way. Any suggestions on that ? Thanks... Update : I think i found my mistake, sorry about that. I wasnt careful about PID input variables. I use integer for my pitch, roll, yaw values which is ok for me. My ( int ) pitch doesnt match PID ( double ) Input so PID.Compute calculate just once or a few times. So, now the question is do we have stable working int version of PID lib ? Or i guess i need a conversion. Thanks...
|
|
|
|
« Last Edit: November 01, 2010, 09:35:18 am by kaya13 »
|
Logged
|
|
|
|
|
Abq NM
Offline
Newbie
Karma: 1
Posts: 17
Arduino rocks
|
 |
« Reply #104 on: November 06, 2010, 09:43:26 am » |
I would also like to say many thanks to Brett and all those who have commented and gave suggestion in this forum. I am new as well just got my first Arduino UNO 3 weeks ago. My wife bought a used manual kiln for doing fused glass and I was able to convert it to a controller base one, using your PID library. I ended up using this formula to get the setpoint to work for both going up in temperature and down, for the annealing phase. Setpoint = (((double)(millis()-RampStartTime))/(m_AmntTime*3600000)*m_TempDif)+m_StartTemp; Thanks again Glenn
|
|
|
|
« Last Edit: November 06, 2010, 09:47:02 am by gadman58 »
|
Logged
|
"Some cause happiness wherever they go; others, whenever they go.." - Oscar Wilde
|
|
|
|
|