P. 1
Arduino PID Complete

Arduino PID Complete

|Views: 739|Likes:

More info:

Published by: Norizham Abdul Razak on Nov 07, 2011
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

07/06/2013

pdf

text

original

Arduino « Project Blog

http://brettbeauregard.com/blog/tag/arduino/

Project updates and… um…

« Older Entries

PID Q&A Group
Monday, April 25th, 2011

Over the past week I’ve had several great conversations regarding the new PID Library, and regarding PID in general. Of course those are all in my email, so you can’t see them. This highlights another problem with the previous version of the library. The main place for online Q&A was an unwieldy thread on the Arduino forum. You could (and can) email me of course, but that’s not how some people operate.

DIY PID Control
So I’m trying a little experiment. I’ve created a google group in an attempt to make the Q&A experience better for everyone. If you’ve got a question, shoot! If you’ve successfully implemented a PID, please share! (if you hurry you can be the first person, IN THE WORLD, to post on the group)First-post prize goes to Eric Miller, who gets bonus points for not saying “first!” Tags: Arduino, PID Posted in PID | No Comments »

How Fast is the Arduino PID Library?
Wednesday, April 20th, 2011

A couple people have asked me about speed since v1 was released. Since I had no idea, I decided I should do a little benchmarking. (Tests were done on a Duemilanove with the ATMega168.)

The Test
1 2 3 4 5 6 7 #include <PID_v1.h> double Setpoint, Input, Output; PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT); void setup() {

1 of 27

7/3/2011 11:17 PM

Arduino « Project Blog

http://brettbeauregard.com/blog/tag/arduino/

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

Input = analogRead(0); Setpoint = 100; myPID.SetMode(AUTOMATIC); Serial.begin(9600); } void loop() { unsigned long startTime = millis(); /*Do a PID calculation 10000 times*/ for(int i=0;i<10000;i++) { Input = analogRead(0); myPID.Compute(); analogWrite(3,Output); } /*print the elapsed time*/ Serial.println(millis()‐startTime); }

The code above is a modification of the Basic PID example. Rather than do the pid stuff once though, it calls it 10000 times in a row. Dividing the resulting elapsed time by 10000 gives us precision down into the microsecond range. Not pictured in this code, I also went into PID_v1.cpp and made SampleTime=0. That ensured that every time Compute() was called, the PID equation was evaluated. One last note. Notice that analogRead and analogWrite are in the for loop. I couldn’t think of a situation where you’d have a PID without also having those functions somewhere in the loop, so I included them in the test.

The Results

2088 mSec / 10000 = 0.21 mSec Ok. Pretty fast. Now just for fun, I commented out the read and write code (lines 21 & 23) to see how much of the 0.21 was due to the Compute function:

2 of 27

7/3/2011 11:17 PM

com/blog/tag/arduino/ 826mSec / 10000 = 0. Even if your loop time is less than 1 mSec (say it’s 0. an increase in cooling causes the temperature to go down. I require that kp. PID Posted in PID | 2 Comments » Improving the Beginner’s PID: Direction Friday. ki.7 mSec. 2 things to remember: 1. If the user is connected to a 3 of 27 7/3/2011 11:17 PM . if you’re looking to evaluate the pid really quickly. All the examples I’ve shown so far have been direct acting.08 mSec required. This isn’t a problem per se. In a refrigerator for example.) After all. and make sure that all the parameters have the same sign. and kd all be >=0. To make the beginner PID work with a reverse process. There’s other code in your program. 2011 (This is the last modification in a larger series on writing a solid PID algorithm) The Problem The processes the PID will be connected to fall into two groups: direct acting and reverse acting. you need to be careful to avoid weird performance. and kd all must be negative. that’s WAY bigger than the 0.08 mSec So the PID was only responsible for about a third of the total time. April 15th. your first inclination might be to make the sample time as small as possible (1 mSec. an increase in the output causes an increase in the input. but the user must choose the correct sign. so the pid will actually be evaluated every 1. ki.Arduino « Project Blog http://brettbeauregard.) it looks like the Compute function is only slightly slower than an analog read or an analog write! Some Closing Thoughts Looking at these results. the signs of kp. For reverse acting processes the opposite is true.4 mSec.) Compute only gets called once per loop. Bottom line. All together it may add up to more than 1mSec 2. Tags: Arduino. Unless I’m missing something (please tell me if I am. The Solution To make the process a little simpler. That is.

if(ITerm > outMax) ITerm= outMax.com/blog/tag/arduino/ reverse process. unsigned long now = millis(). kp = Kp. kd = Kd / SampleTimeInSec. lastTime = now. double Input. if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint ‐ Input. bool inAuto = false. Setpoint. double Ki. else if(Output < outMin) Output = outMin. double kp. ki. kd. double Kd) { if (Kp<0 || Ki<0|| Kd<0) return. outMax. 7/3/2011 11:17 PM . this ensures that the parameters all have the same sign. double ITerm. and hopefully makes things more intuitive. if(Output > outMax) Output = outMax. ki = Ki * SampleTimeInSec.Arduino « Project Blog http://brettbeauregard. double dInput = (Input ‐ lastInput). int timeChange = (now ‐ lastTime). double SampleTimeInSec = ((double)SampleTime)/1000. they specify that separately using the SetControllerDirection function. } } void SetTunings(double Kp. /*Compute PID Output*/ Output = kp * error + ITerm‐ kd * dInput. lastInput. //1 sec double outMin. /*Remember some variables for next time*/ lastInput = Input. else if(ITerm < outMin) ITerm= outMin. The Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 4 of 27 /*working variables*/ unsigned long lastTime. int SampleTime = 1000. ITerm+= (ki * error). #define MANUAL 0 #define AUTOMATIC 1 #define DIRECT 0 #define REVERSE 1 int controllerDirection = DIRECT. void Compute() { if(!inAuto) return. Output.

com/blog/tag/arduino/ 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 if(controllerDirection ==REVERSE) { kp = (0 ‐ kp). else if(Output < outMin) Output = outMin. kd = (0 ‐ kd). outMin = Min. if(newAuto == !inAuto) { /*we just went from manual to auto*/ Initialize(). } } void SetOutputLimits(double Min.Arduino « Project Blog http://brettbeauregard. } inAuto = newAuto. double Max) { if(Min > Max) return. else if(ITerm < outMin) ITerm= outMin. ki *= ratio. if(Output > outMax) Output = outMax. if(ITerm > outMax) ITerm= outMax. else if(ITerm < outMin) ITerm= outMin. ITerm = Output. } void Initialize() { lastInput = Input. ki = (0 ‐ ki). if(ITerm > outMax) ITerm= outMax. } } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime. outMax = Max. kd /= ratio. } void SetMode(int Mode) { bool newAuto = (Mode == AUTOMATIC). } void SetControllerDirection(int Direction) { 5 of 27 7/3/2011 11:17 PM . SampleTime = (unsigned long)NewSampleTime.

2. or might just need to be clearer in my explanation. For those readers that were looking for a detailed explanation of the PID Library. We turned it off. If something in this series looks wrong please let me know. Off the top of my head: feed forward. Either way I’d like to know. but now let’s look at what happens when we turn it back on: 6 of 27 7/3/2011 11:17 PM . integer math. For those of you writing your own PID. I may have missed something. Tags: Arduino. I hope you were able to glean a few ideas that save you some cycles down the road. 2011 (This is Modification #6 in a larger series on writing a solid PID algorithm) The Problem In the last section we implemented the ability to turn the PID off and on. PID | 17 Comments » Improving the Beginner’s PID: Initialization Friday. Two Final Notes: 1. PID Posted in Coding. We’ve turned “The Beginner’s PID” into the most robust controller I know how to make at this time. different pid forms. reset tiebacks. April 15th. } And that about wraps it up. There are many other issues that I intentionally left out in the name of simplicity. using velocity instead of position. Beginner's PID. This is just a basic PID. If there’s interest in having me explore these topics please let me know. I hope you got what you came for.Arduino « Project Blog http://brettbeauregard.com/blog/tag/arduino/ 104 105 controllerDirection = Direction.

7/3/2011 11:17 PM . This results in an Input bump that we’d rather not have.com/blog/tag/arduino/ Yikes! The PID jumps back to the last Output value it sent. Setpoint. int timeChange = (now ‐ lastTime). bool inAuto = false.) we just have to initialize things for a smooth transition. The Solution This one is pretty easy to fix. kd. unsigned long now = millis(). double ITerm. Output. if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint ‐ Input. int SampleTime = 1000. //1 sec double outMin. ki. then starts adjusting from there. That means massaging the 2 stored working variables (ITerm & lastInput) to keep the output from jumping.Arduino « Project Blog http://brettbeauregard. The Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 7 of 27 /*working variables*/ unsigned long lastTime. #define MANUAL 0 #define AUTOMATIC 1 void Compute() { if(!inAuto) return. outMax. double kp. double Input. Since we now know when we’re turning on (going from Manual to Automatic. lastInput.

/*Remember some variables for next time*/ lastInput = Input. if(ITerm> outMax) ITerm= outMax. SampleTime = (unsigned long)NewSampleTime. if(newAuto && !inAuto) { /*we just went from manual to auto*/ 8 of 27 7/3/2011 11:17 PM . } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime. double dInput = (Input ‐ lastInput). else if(Output < outMin) Output = outMin. outMax = Max. else if(ITerm< outMin) ITerm= outMin. double Ki. else if(Output < outMin) Output = outMin. if(Output > outMax) Output = outMax. kd = Kd / SampleTimeInSec. ki = Ki * SampleTimeInSec. } } void SetTunings(double Kp. } } void SetOutputLimits(double Min. ki *= ratio. double Max) { if(Min > Max) return.com/blog/tag/arduino/ 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 ITerm+= (ki * error).Arduino « Project Blog http://brettbeauregard. kp = Kp. } void SetMode(int Mode) { bool newAuto = (Mode == AUTOMATIC). /*Compute PID Output*/ Output = kp * error + ITerm‐ kd * dInput. else if(ITerm< outMin) ITerm= outMin. lastTime = now. if(Output> outMax) Output = outMax. if(ITerm> outMax) ITerm= outMax. double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000. kd /= ratio. outMin = Min.

PID Posted in Coding. PID | No Comments » 9 of 27 7/3/2011 11:17 PM . } void Initialize() { lastInput = Input.com/blog/tag/arduino/ 76 77 78 79 80 81 82 83 84 85 86 87 Initialize(). The proportional term doesn’t rely on any information from the past. and we added our initialization function. } We modified SetMode(…) to detect the transition from manual to automatic. Beginner's PID. else if(ITerm< outMin) ITerm= outMin.Arduino « Project Blog http://brettbeauregard. if(ITerm> outMax) ITerm= outMax. It sets ITerm=Output to take care of the integral term. } inAuto = newAuto. Next >> Tags: Arduino. so it doesn’t need any initialization. and lastInput = Input to keep the derivative from spiking. ITerm = Output. The Result We see from the above graph that proper initialization results in a bumpless transfer from manual to automatic: exactly what we were after.

The PID will become very confused: “I keep moving the output. you just overwrite its value. you will likely get a huge and immediate change in the output value.” As a result. no matter what the PID says. when you stop over-writing the output and switch back to the PID. sometimes you don’t care what it has to say. Output=0. 2011 (This is Modification #5 in a larger series on writing a solid PID algorithm) The Problem As nice as it is to have a PID controller. The common terms for these states are “Manual” (I will adjust the value by hand) and “Automatic” (the PID will automatically adjust the output). Let’s see how this is done in code: The Code 10 of 27 7/3/2011 11:17 PM . and nothing’s happening! What gives?! Let me move it some more. Let’s say at some point in your program you want to force the output to a certain value (0 for example) you could certainly do this in the calling routine: void loop() { Compute().Arduino « Project Blog http://brettbeauregard. This is a terrible idea in practice however. April 15th.com/blog/tag/arduino/ Improving the Beginner’s PID: On/Off Friday. } This way. The Solution The solution to this problem is to have a means to turn the PID off and on.

if(ITerm> outMax) ITerm= outMax. } } void SetTunings(double Kp. kd /= ratio. double ITerm. double kp. } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime. 11 of 27 7/3/2011 11:17 PM . else if(ITerm< outMin) ITerm= outMin. int timeChange = (now ‐ lastTime). #define MANUAL 0 #define AUTOMATIC 1 void Compute() { if(!inAuto) return. lastTime = now.com/blog/tag/arduino/ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 /*working variables*/ unsigned long lastTime. double Ki. double dInput = (Input ‐ lastInput). /*Compute PID Output*/ Output = kp * error + ITerm‐ kd * dInput. ki. kd = Kd / SampleTimeInSec. Output. SampleTime = (unsigned long)NewSampleTime. outMax. ki *= ratio. ki = Ki * SampleTimeInSec. if(Output > outMax) Output = outMax. unsigned long now = millis(). int SampleTime = 1000. double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000. bool inAuto = false. double Input. /*Remember some variables for next time*/ lastInput = Input.Arduino « Project Blog http://brettbeauregard. kd. lastInput. ITerm+= (ki * error). Setpoint. else if(Output < outMin) Output = outMin. kp = Kp. if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint ‐ Input. //1 sec double outMin.

} A fairly simple solution. immediately leave the Compute function without adjusting the Output or any internal variables. } void SetMode(int Mode) { inAuto = (Mode == AUTOMATIC). outMax = Max. If you’re not in automatic mode. which is kind of what we need. The Result It’s true that you could achieve a similar effect by just not calling Compute from the calling routine. else if(ITerm< outMin) ITerm= outMin. but this solution keeps the workings of the PID contained. if(Output > outMax) Output = outMax. else if(Output < outMin) Output = outMin. That leads us to the next issue… Next >> 12 of 27 7/3/2011 11:17 PM .Arduino « Project Blog http://brettbeauregard. double Max) { if(Min > Max) return.com/blog/tag/arduino/ 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 } } void SetOutputLimits(double Min. By keeping things internal we can keep track of which mode were in. if(ITerm> outMax) ITerm= outMax. outMin = Min. and more importantly it let’s us know when we change modes.

com/blog/tag/arduino/ Tags: Arduino. The Solution – Step 1 13 of 27 7/3/2011 11:17 PM . PID Posted in Coding. When the setpoint is dropped the output has to wind down before getting below that 255-line. Beginner's PID. By default the PID doesn’t know this.Arduino « Project Blog http://brettbeauregard. the PWM output on an Arduino accepts values from 0-255. For example. Since in reality the value is clamped at 255 it’s just going to keep trying higher and higher numbers without getting anywhere. If it thinks that 300-400-500 will work. it’s going to try those values expecting to get what it needs. PID | No Comments » Improving the Beginner’s PID: Reset Windup Friday. The problem reveals itself in the form of weird lags. It occurs when the PID thinks it can do something that it can’t. 2011 (This is Modification #4 in a larger series on writing a solid PID algorithm) The Problem Reset windup is a trap that probably claims more beginners than any other. Above we can see that the output gets “wound up” WAY above the external limit. April 15th.

the pid stops summing (integrating. To my mind this is unacceptable. Even though the Integral Term has been safely clamped. we get an immediate response when the setpoint drops into a range where we can do something. There’s still a difference between what the pid thinks it’s sending. void Compute() 14 of 27 7/3/2011 11:17 PM . double kp. Since the output doesn’t wind-up. yielding a result higher than the output limit. but the one that I chose was as follows: tell the PID what the output limits are. //1 sec double outMin. kd. we make that a valid assumption. and what’s being sent. the Integral term would go back to growing and growing. outMax. The Solution – Step 2 Notice in the graph above though. double Input. If the user calls a function called “SetOutputLimits” they’ve got to assume that that means “the output will stay within these values. double ITerm. In the code below you’ll see there’s now a SetOuputLimits function.) The Code 1 2 3 4 5 6 7 8 /*working variables*/ unsigned long lastTime. P and D are still adding their two cents. In addition to clamping the I-Term. Once either limit is reached. we’re not all the way there. that while we got rid that windup lag. Output.com/blog/tag/arduino/ There are several ways that windup can be mitigated. int SampleTime = 1000. why clamp the Integral separately? If all we did was clamp the output. we’d see that telltale lag on the step down. (Note: You might ask why we need to clamp both. If we’re going to do the output anyway. Though the output would look nice during the step up.” So for Step 2. Why? the Proportional Term and (to a lesser extent) the Derivative Term. Setpoint. lastInput.) It knows there’s nothing to be done.Arduino « Project Blog http://brettbeauregard. we clamp the Output value so that it stays where we’d expect it. ki.

outMax = Max.com/blog/tag/arduino/ 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 { unsigned long now = millis(). /*Remember some variables for next time*/ lastInput = Input. double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000. if(ITerm> outMax) ITerm= outMax. if(ITerm> outMax) ITerm= outMax. ki = Ki * SampleTimeInSec. else if(ITerm< outMin) ITerm= outMin. } } void SetTunings(double Kp.Arduino « Project Blog http://brettbeauregard. else if(Output < outMin) Output = outMin. SampleTime = (unsigned long)NewSampleTime. 15 of 27 7/3/2011 11:17 PM . else if(ITerm< outMin) ITerm= outMin. kd /= ratio. kp = Kp. int timeChange = (now ‐ lastTime). ki *= ratio. if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint ‐ Input. } } void SetOutputLimits(double Min. if(Output > outMax) Output = outMax. double Ki. else if(Output < outMin) Output = outMin. double dInput = (Input ‐ lastInput). /*Compute PID Output*/ Output = kp * error + ITerm‐ kd * dInput. } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime. ITerm+= (ki * error). kd = Kd / SampleTimeInSec. if(Output > outMax) Output = outMax. lastTime = now. outMin = Min. double Max) { if(Min > Max) return.

PID | No Comments » Improving the Beginner’s PID: Tuning Changes Friday. windup is eliminated. 16 of 27 7/3/2011 11:17 PM . the output stays where we want it to.com/blog/tag/arduino/ 63 } A new function was added to allow the user to specify the output limits [lines 52-63]. if you want it to range from 23 to 167. Next >> Tags: Arduino. And these limits are used to clamp both the I-Term [17-18] and the Output [23-24] The Result As we can see. 2011 (This is Modification #3 in a larger series on writing a solid PID algorithm) The Problem The ability to change tuning parameters while the system is running is a must for any respectable PID algorithm.Arduino « Project Blog http://brettbeauregard. Beginner's PID. you can set those as the Output Limits. this means there’s no need for external clamping of the output. April 15th. in addition. PID Posted in Coding.

Here is the state of the beginner’s PID before and after the parameter change above: So we can immediately blame this bump on the Integral Term (or “I Term”).Arduino « Project Blog http://brettbeauregard. you multiply this new Ki times the entire error sum that you have accumulated. That counts damnit!) The solution requires a little basic algebra (or is it calculus?) 17 of 27 7/3/2011 11:17 PM . Why did this happen? It has to do with the beginner’s interpretation of the Integral: This interpretation works fine until the Ki is changed. and it works. That keeps the I Term from bumping. The method I used in the last library was to rescale errSum. Let’s see why.com/blog/tag/arduino/ The Beginner’s PID acts a little crazy if you try to change the tunings while it’s running. all of a sudden. It’s kind of clunky though. Ki doubled? Cut errSum in Half. but I did think of it on my own. and I’ve come up with something more elegant. That’s not what we wanted! We only wanted to affect things moving forward! The Solution There are a couple ways I know of to deal with this problem. Then. (There’s no way I’m the first to have thought of this. It’s the only thing that changes drastically when the parameters change.

int SampleTime = 1000. int timeChange = (now ‐ lastTime). double Ki. 18 of 27 7/3/2011 11:17 PM . } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime. we bring it inside. kd.com/blog/tag/arduino/ Instead of having the Ki live outside the integral. Output. ki = Ki * SampleTimeInSec. /*Compute PID Output*/ Output = kp * error + ITerm ‐ kd * dInput. double Input. Now. but we’ll see that in practice this makes a big difference. It looks like we haven’t done anything. } } void SetTunings(double Kp. Setpoint. ki. there’s no bump because all the old Ki’s are already “in the bank” so to speak. double ITerm. double kp. kd = Kd / SampleTimeInSec. It may make me a geek but I think that’s pretty sexy. //1 sec void Compute() { unsigned long now = millis(). if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint ‐ Input.Arduino « Project Blog http://brettbeauregard. lastTime = now. /*Remember some variables for next time*/ lastInput = Input. ITerm += (ki * error). We get a smooth transfer with no additional math operations. We then store the sum of THAT. When the Ki changes. kp = Kp. we take the error and multiply it by whatever the Ki is at that time. The Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 /*working variables*/ unsigned long lastTime. double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000. double dInput = (Input ‐ lastInput). lastInput.

PID | No Comments » Improving the Beginner’s PID – Derivative Kick 19 of 27 7/3/2011 11:17 PM . The Result So how does this fix things. it rescaled the entire sum of the error. and the new ki only affects things moving forward. rather than just error [Line 15]. the previous error remains untouched. Before when ki was changed. Beginner's PID. every error value we had seen. because Ki is now buried in ITerm. It sums Ki*error.Arduino « Project Blog http://brettbeauregard. Also. } } So we replaced the errSum variable with a composite ITerm variable [Line 4]. With this code. SampleTime = (unsigned long)NewSampleTime. Next >> Tags: Arduino. it’s removed from the main PID calculation [Line 19]. PID Posted in Coding. which is exactly what we want. kd /= ratio.com/blog/tag/arduino/ 41 42 43 44 45 ki *= ratio.

com/blog/tag/arduino/ Friday. The derivative of this change is infinity (in practice. The goal is to eliminate a phenomenon known as “Derivative Kick”. Instead of adding (Kd * derivative of Error). 2011 (This is Modification #2 in a larger series on writing a solid PID algorithm) The Problem This modification is going to tweak the derivative term a bit. The image above illustrates the problem. April 15th. any change in Setpoint causes an instantaneous change in error. This winds up being a perfect solution.Arduino « Project Blog http://brettbeauregard. Since error=Setpoint-Input. EXCEPT when the Setpoint is changing. we 20 of 27 7/3/2011 11:17 PM .) This number gets fed into the pid equation. Luckily there is an easy way to get rid of this. which results in an undesirable spike in the output. The Solution It turns out that the derivative of the Error is equal to negative derivative of Input. since dt isn’t 0 it just winds up being a really big number.

} void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime. Instead of remembering the lastError. lastTime = now.Arduino « Project Blog http://brettbeauregard. SampleTime = (unsigned long)NewSampleTime. int timeChange = (now ‐ lastTime). double dInput = (Input ‐ lastInput). /*Remember some variables for next time*/ lastInput = Input. int SampleTime = 1000. } } The modifications here are pretty easy. double Input. ki *= ratio. Setpoint. //1 sec void Compute() { unsigned long now = millis(). ki. ki = Ki * SampleTimeInSec. Output. } } void SetTunings(double Kp. kp = Kp. double kp. kd. we now remember the lastInput 21 of 27 7/3/2011 11:17 PM . We’re replacing +dError with -dInput. if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint ‐ Input. double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000.com/blog/tag/arduino/ subtract (Kd * derivative of Input). This is known as using “Derivative on Measurement” The Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 /*working variables*/ unsigned long lastTime. double errSum. errSum += error. kd = Kd / SampleTimeInSec. /*Compute PID Output*/ Output = kp * error + ki * errSum ‐ kd * dInput. double Ki. kd /= ratio. lastInput.

The way I see it though. April 15th. Notice that the input still looks about the same. This causes 2 issues: You don’t get consistent behavior from the PID. This may or may not be a big deal. It all depends on how sensitive your application is to output spikes. since sometimes it’s called frequently and sometimes 22 of 27 7/3/2011 11:17 PM .Arduino « Project Blog http://brettbeauregard. 2011 (This is Modification #1 in a larger series on writing a solid PID algorithm) The Problem The Beginner’s PID is designed to be called irregularly. Beginner's PID.com/blog/tag/arduino/ The Result Here’s what those modifications get us. it doesn’t take any more work to do it without kicking so why not do things right? Next >> Tags: Arduino. PID | 2 Comments » Improving the Beginner’s PID – Sample Time Friday. PID Posted in Coding. So we get the same performance. but we don’t send out a huge Output spike every time the Setpoint changes.

The way I’ve decided to do this is to specify that the compute function get called every cycle. //1 sec void Compute() { unsigned long now = millis(). double dErr = (error ‐ lastErr). lastTime = now. double errSum. } void SetSampleTime(int NewSampleTime) { if (NewSampleTime > 0) { 23 of 27 7/3/2011 11:17 PM . based on a pre-determined Sample Time. } } void SetTunings(double Kp. kp = Kp. The Solution Ensure that the PID is called at a regular interval. int SampleTime = 1000. Output. /*Compute PID Output*/ Output = kp * error + ki * errSum + kd * dErr. Setpoint. the PID decides if it should compute or return immediately.Arduino « Project Blog http://brettbeauregard.com/blog/tag/arduino/ it’s not. kd. lastErr. You need to do extra math computing the derivative and integral. since they’re both dependent on the change in time. double Input. Once we know that the PID is being evaluated at a constant interval. errSum += error. double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000. if(timeChange>=SampleTime) { /*Compute all the working error variables*/ double error = Setpoint ‐ Input. Bonus! The Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 /*working variables*/ unsigned long lastTime. /*Remember some variables for next time*/ lastErr = error. kd = Kd / SampleTimeInSec. double kp. ki = Ki * SampleTimeInSec. double Ki. ki. the derivative and integral calculations can also be simplified. int timeChange = (now ‐ lastTime).

That only happens every 55 days. SetSampleTime sets the interrupt frequency. We can merely adjust the Ki and Kd appropriately (lines 30&31) and result is mathematically equivalent. the Ki and Kd will need to be re-tweaked to reflect this new change. in that case. There are three reasons I didn’t use interrupts 1. if the user decides to change the sample time during operation. Because of the time subtraction [Line 10] there will be no issues when millis() wraps back to 0. because we now KNOW that it’s going to be the same time between samples. rather than 1/mS and mS. 2. Also. I may decide to use interrupts in future versions of the PID library. then Compute gets called when it’s time. the PID algorithm will be evaluated at a regular interval [Line 11] 2.com/blog/tag/arduino/ 39 40 41 42 43 44 45 double ratio = (double)NewSampleTime / (double)SampleTime. You’ll hopefully still get some benefit from the modifications that follow. but more efficient. Also Note that I convert the sample time to Seconds on line 29. we don’t need to constantly multiply by time change.Arduino « Project Blog http://brettbeauregard. There would be no need. not everyone will be able to use interrupts. If you plan on doing this with your PID implentation. As far as this series is concerned. Mathematically it works out the same. one little wrinkle with doing it this way though though. Next >> Tags: Arduino. Strictly speaking this isn’t necessary. The Results the changes above do 3 things for us 1. We don’t need to multiply and divide by the timechange anymore. it didn’t occur to me. Regardless of how frequently Compute() is called. but allows the user to enter Ki and Kd in units of 1/sec and s. for lines 9-12. 23. Jimmie Rodgers suggested it while proof-reading the series for me. If I’m honest. PID | 2 Comments » 24 of 27 7/3/2011 11:17 PM . the algorithm now decides for itself if it’s time to calculate. PID Posted in Coding. SampleTime = (unsigned long)NewSampleTime. ki *= ratio. but it saves a multiplication and a division every time the PID is evaluated Side note about interrupts If this PID is going into a microcontroller. and 24. kd /= ratio. Beginner's PID. 3. that’s what lines 39-42 are all about. but we’re going for bulletproof remember? 3. a very good argument can be made for using an interrupt. Things would get tricky if you wanted it implement many PID controllers at the same time. } } On lines 10&11. Since it’s a constant we’re able to move it from the compute code [lines 15+16] and lump it in with the tuning constants [lines 31+32]. go for it! Keep reading this series though.

The last library.com/blog/tag/arduino/ Improving the Beginner’s PID – Introduction Friday. while solid. ki. double timeChange = (double)(now ‐ lastTime). double kp. double Input. Setpoint. but I think I found a not-too-painful way to explain my code. double dErr = (error ‐ lastErr) / timeChange. /*Compute PID Output*/ Output = kp * error + ki * errSum + kd * dErr. This time around the plan is to explain in great detail why the code is the way it is. Output. I’m hoping this will be of use to two groups of people: People directly interested in what’s going on inside the Arduino PID library will get a detailed explanation. April 15th. kd. 2011 In conjunction with the release of the new Arduino PID Library I’ve decided to release this series of posts. double Kd) 25 of 27 7/3/2011 11:17 PM . The Beginner’s PID Here’s the PID equation as everyone first learns it: This leads pretty much everyone to write the following PID controller: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /*working variables*/ unsigned long lastTime. Anyone writing their own PID algorithm can take a look at how I did things and borrow whatever they like. It’s going to be a tough slog. robust pid algorithm. double errSum.Arduino « Project Blog http://brettbeauregard. } void SetTunings(double Kp. errSum += (error * timeChange).” I’ll then improve it step-by-step until we’re left with an efficient. lastErr. didn’t really come with any code explanation. /*Remember some variables for next time*/ lastErr = error. double Ki. lastTime = now. void Compute() { /*How long since we last calculated*/ unsigned long now = millis(). /*Compute all the working error variables*/ double error = Setpoint ‐ Input. I’m going to start with what I call “The Beginner’s PID.

When the controller first turns on. If the algorithm is aware of this interval. On the Arduino.A good PID algorithm is one where tuning parameters can be changed without jolting the internal workings.com/blog/tag/arduino/ 26 27 28 29 30 { kp = Kp. 4.” That is. I hope this helps you out.) True double precision is WAY overkill for PID. it’s designed to ensure that the user enters tuning parameters with the correct sign. Once we’ve addressed all these issues. and implement a solution with side benefits 5. not coincidentally. On/Off (Auto/Manual) . we want a “bumpless transfer. Next >> UPDATE: In all the code examples I’m using doubles. we don’t want the output to suddenly jerk to some new value 7.This last one isn’t a change in the name of robustness per se. PID Posted in Coding. If we’re going to turn this code into something on par with industrial PID controllers.Arduino « Project Blog http://brettbeauregard. Sample Time . Tags: Arduino.In most applications. we’ll have a solid PID algorithm. If the language you’re using does true double precision.Not the biggest deal. Let’s get started. but easy to get rid of. so we’re going to do just that. Controller Direction . On-The-Fly Tuning Changes . Initialization . a double is the same as a float (single precision.The PID algorithm functions best if it is evaluated at a regular interval. PID | 9 Comments » « Older Entries Search for: PID (13) Coding (8) Front End (2) Projects (27) Craft (2) Electronic (10) 26 of 27 7/3/2011 11:17 PM . 2. } Compute() is called either regularly or irregularly. This series isn’t about “works pretty well” though. and it works pretty well. or trying to understand what’s going on inside the PID library. Beginner's PID. Derivative Kick . have the code that’s being used in the lastest version of the Arduino PID Library. Reset Windup Mitigation -We’ll go into what Reset Windup is. without the controller interfering 6. So whether you’re trying to write your own algorithm. we’ll have to address a few things: 1. We’ll also. there is a desire to sometimes turn off the PID controller and adjust the output by hand. I’d recommend changing all doubles to floats. we can also simplify some of the internal math. 3. kd = Kd. ki = Ki.

27 of 27 7/3/2011 11:17 PM .com/blog/tag/arduino/ Mechanical (17) Uncategorized (2) June 2011 May 2011 April 2011 September 2010 August 2010 July 2010 March 2010 November 2009 October 2009 September 2009 August 2009 July 2009 June 2009 May 2009 Project Blog is proudly powered by WordPress Entries (RSS) and Comments (RSS).Arduino « Project Blog http://brettbeauregard.

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->