Posts Tagged ‘PID’

Arduino PID Autotune Library

Saturday, January 28th, 2012

At long last, I’ve released an Autotune Library to compliment the Arduino PID Library. When I released the current version of the PID Library, I did an insanely extensive series of posts to get people comfortable with what was going on inside.

While not nearly as in-depth, that’s the goal of this post. I’ll explain what the Autotune Library is trying to accomplish, and how it goes about its business.
(more…)

flattr this!

Introducing the osPID

Tuesday, January 3rd, 2012

About 5 months ago I sent an out-of-the-blue email to RocketScream electronics asking if they wanted to help me build an open source PID controller. It’s been far more challenging than I expected, but today I get to announce the release of my first open source hardware project: the osPID!

(pause for applause)

(more…)

flattr this!

PID Library moved to GitHub

Wednesday, December 14th, 2011

As per the request by the Arduino Team, I’ve moved the Arduino PID Library to GitHub. I’ve also used this opportunity to:

  • Make the Library Arduino 1.0 compliant
  • Release the Library under a GPLv3 License

The google code site will still be there, but there will no longer be updates.

flattr this!

A Little Teaser

Thursday, September 15th, 2011

I haven’t done many PID posts in the last couple months. Rest assured I haven’t been sleeping on that front. I’ve been working closely with RocketScream on an OSHW project that should be released soon. Here are a couple teaser pictures to (hopefully) get you drooling.

As I post this I’m on my way to Makerfaire NYC. I’ll have this with me if you’d like a closer look.

flattr this!

Sousvide-O-Mator [PID Showcase]

Wednesday, August 17th, 2011

Sous-vide BabyTop

There have been many sous vide projects floating around the tubes recently. The one I’m showcasing here was first brought to my attention by the Adafruit blog. I found out from the developer that he was in the midst of a full write-up, so I held off on posting. Now that the write-up is done, we can see that he’s put together an impressive little project.

Great documentation, excellent implementation of the PID Library, delicious, delicious meat? What’s not to love? I’m also looking forward to subsequent posts, where he promises to show “how to calibrate the beast :)

flattr this!

Reflow Oven Shield [PID Showcase]

Friday, July 22nd, 2011

The reason I created the PID library was to make it easier for people to integrate PID control into their projects. Surprisingly, it’s actually happening! I’m starting to see a lot of really cool implementations out there. As a result, I’ve decided to start a new category of post: the PID Showcase. These are projects that make me feel that all that work was worth it.

So… here’s the first one: the Reflow Oven Controller Shield from RocketScream.com
Reflow Oven Sheild

It integrates all the components necessary to turn a toaster oven into a reflow oven. Thermocouple Chip… LCD… buzzer… awesome. But the part that I love is how they completely owned the PID part of this project. Look at this reflow curve:

reflow

They made use of setpoint ramping to get the heating rates just right. The output is phenomenal. This came out of a toaster oven:

wow

Excellent work!

flattr this!

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.

Google Groups
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!”

flattr this!

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

#include <PID_v1.h>

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint,2,5,1, DIRECT);

void setup()
{
  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:


826mSec / 10000 = 0.08 mSec
So the PID was only responsible for about a third of the total time. Unless I’m missing something (please tell me if I am,) 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, your first inclination might be to make the sample time as small as possible (1 mSec.) After all, that’s WAY bigger than the 0.08 mSec required. 2 things to remember:

  1. There’s other code in your program. All together it may add up to more than 1mSec
  2. Even if your loop time is less than 1 mSec (say it’s 0.7 mSec,) Compute only gets called once per loop, so the pid will actually be evaluated every 1.4 mSec.

Bottom line, if you’re looking to evaluate the pid really quickly, you need to be careful to avoid weird performance.

flattr this!

Improving the Beginner’s PID: Direction

Friday, April 15th, 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. All the examples I’ve shown so far have been direct acting. That is, an increase in the output causes an increase in the input. For reverse acting processes the opposite is true. In a refrigerator for example, an increase in cooling causes the temperature to go down. To make the beginner PID work with a reverse process, the signs of kp, ki, and kd all must be negative.

This isn’t a problem per se, but the user must choose the correct sign, and make sure that all the parameters have the same sign.

The Solution

To make the process a little simpler, I require that kp, ki, and kd all be >=0. If the user is connected to a reverse process, they specify that separately using the SetControllerDirection function. this ensures that the parameters all have the same sign, and hopefully makes things more intuitive.

The Code

/*working variables*/
unsigned long lastTime;
double Input, Output, Setpoint;
double ITerm, lastInput;
double kp, ki, kd;
int SampleTime = 1000; //1 sec
double outMin, outMax;
bool inAuto = false;

#define MANUAL 0
#define AUTOMATIC 1

#define DIRECT 0
#define REVERSE 1
int controllerDirection = DIRECT;

void Compute()
{
   if(!inAuto) return;
   unsigned long now = millis();
   int timeChange = (now - lastTime);
   if(timeChange>=SampleTime)
   {
      /*Compute all the working error variables*/
      double error = Setpoint - Input;
      ITerm+= (ki * error);
      if(ITerm > outMax) ITerm= outMax;
      else if(ITerm < outMin) ITerm= outMin;
      double dInput = (Input - lastInput);

      /*Compute PID Output*/
      Output = kp * error + ITerm- kd * dInput;
      if(Output > outMax) Output = outMax;
      else if(Output < outMin) Output = outMin;

      /*Remember some variables for next time*/
      lastInput = Input;
      lastTime = now;
   }
}

void SetTunings(double Kp, double Ki, double Kd)
{
   if (Kp<0 || Ki<0|| Kd<0) return;

  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);
   }
}

void SetSampleTime(int NewSampleTime)
{
   if (NewSampleTime > 0)
   {
      double ratio  = (double)NewSampleTime
                      / (double)SampleTime;
      ki *= ratio;
      kd /= ratio;
      SampleTime = (unsigned long)NewSampleTime;
   }
}

void SetOutputLimits(double Min, double Max)
{
   if(Min > Max) return;
   outMin = Min;
   outMax = Max;

   if(Output > outMax) Output = outMax;
   else if(Output < outMin) Output = outMin;

   if(ITerm > outMax) ITerm= outMax;
   else if(ITerm < outMin) ITerm= outMin;
}

void SetMode(int Mode)
{
	bool newAuto = (Mode == AUTOMATIC);
	if(newAuto == !inAuto)
	{  /*we just went from manual to auto*/
		Initialize();
	}
	inAuto = newAuto;
}

void Initialize()
{
   lastInput = Input;
   ITerm = Output;
   if(ITerm > outMax) ITerm= outMax;
   else if(ITerm < outMin) ITerm= outMin;
}

void SetControllerDirection(int Direction)
{
   controllerDirection = Direction;
}

PID COMPLETE

And that about wraps it up. We’ve turned “The Beginner’s PID” into the most robust controller I know how to make at this time. For those readers that were looking for a detailed explanation of the PID Library, I hope you got what you came for. For those of you writing your own PID, I hope you were able to glean a few ideas that save you some cycles down the road.

Two Final Notes:

  1. If something in this series looks wrong please let me know. I may have missed something, or might just need to be clearer in my explanation. Either way I’d like to know.
  2. This is just a basic PID. There are many other issues that I intentionally left out in the name of simplicity. Off the top of my head: feed forward, reset tiebacks, integer math, different pid forms, using velocity instead of position. If there’s interest in having me explore these topics please let me know.

Creative Commons License

flattr this!

Improving the Beginner’s PID: Initialization

Friday, April 15th, 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. We turned it off, but now let’s look at what happens when we turn it back on:

Yikes! The PID jumps back to the last Output value it sent, then starts adjusting from there. This results in an Input bump that we’d rather not have.

The Solution

This one is pretty easy to fix. Since we now know when we’re turning on (going from Manual to Automatic,) we just have to initialize things for a smooth transition. That means massaging the 2 stored working variables (ITerm & lastInput) to keep the output from jumping.

The Code

/*working variables*/
unsigned long lastTime;
double Input, Output, Setpoint;
double ITerm, lastInput;
double kp, ki, kd;
int SampleTime = 1000; //1 sec
double outMin, outMax;
bool inAuto = false;

#define MANUAL 0
#define AUTOMATIC 1

void Compute()
{
   if(!inAuto) return;
   unsigned long now = millis();
   int timeChange = (now - lastTime);
   if(timeChange>=SampleTime)
   {
      /*Compute all the working error variables*/
      double error = Setpoint - Input;
      ITerm+= (ki * error);
      if(ITerm> outMax) ITerm= outMax;
      else if(ITerm< outMin) ITerm= outMin;
      double dInput = (Input - lastInput);

      /*Compute PID Output*/
      Output = kp * error + ITerm- kd * dInput;
      if(Output> outMax) Output = outMax;
      else if(Output < outMin) Output = outMin;

      /*Remember some variables for next time*/
      lastInput = Input;
      lastTime = now;
   }
}

void SetTunings(double Kp, double Ki, double Kd)
{
  double SampleTimeInSec = ((double)SampleTime)/1000;
   kp = Kp;
   ki = Ki * SampleTimeInSec;
   kd = Kd / SampleTimeInSec;
}

void SetSampleTime(int NewSampleTime)
{
   if (NewSampleTime > 0)
   {
      double ratio  = (double)NewSampleTime
                      / (double)SampleTime;
      ki *= ratio;
      kd /= ratio;
      SampleTime = (unsigned long)NewSampleTime;
   }
}

void SetOutputLimits(double Min, double Max)
{
   if(Min > Max) return;
   outMin = Min;
   outMax = Max;
   
   if(Output > outMax) Output = outMax;
   else if(Output < outMin) Output = outMin;

   if(ITerm> outMax) ITerm= outMax;
   else if(ITerm< outMin) ITerm= outMin;
}

void SetMode(int Mode)
{
	bool newAuto = (Mode == AUTOMATIC);
	if(newAuto && !inAuto)
	{  /*we just went from manual to auto*/
		Initialize();
	}
	inAuto = newAuto;
}

void Initialize()
{
   lastInput = Input;
   ITerm = Output;
   if(ITerm> outMax) ITerm= outMax;
   else if(ITerm< outMin) ITerm= outMin;
}

We modified SetMode(…) to detect the transition from manual to automatic, and we added our initialization function. It sets ITerm=Output to take care of the integral term, and lastInput = Input to keep the derivative from spiking. The proportional term doesn’t rely on any information from the past, so it doesn’t need any initialization.

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.
Next >>

Update: Why not ITerm=0?

I have been getting a lot of questions recently asking why I don’t set ITerm=0 upon intialization. As an answer, I’d ask you to consider the following scenario: The pid is in manual, and the user has set the output to 50. After a time, the process steadies out to an input of 75.2. The user makes the Setpoint 75.2 and turns on the pid. What should happen?

I contend that after switching to automatic the output value should stay at 50. since the P and D terms will be zero, the only way this will happen is if ITerm is initialized to the value of Output.

If you are in a situation where you need the output to initialize to zero, there is no need alter the code above. Just set Output=0 in your calling routine before turning the PID from Manual to Automatic.



Creative Commons License

flattr this!