Improved PID Library with a new PIDMaster Library (BETA testers needed!!)

Hey all,

After browsing through Brett Beauregard (Original author of PID_v1 library) blog, at brettbeauregard.com, I've seen on the comment some good ideas that never got implemented, so I decided I'll try to implement them.

The main improvement of v2 over v1 is the new library I wrote called PIDMaster which basically gathers together multiple instances of PID in order to run them simultaneously and efficiently using a single Timer interrupt (TimerOne) for making sure they all sample on time. The strategy of using interrupts over the use of just sampling around the sampling time (and not exactly on it) is most significantly showing better result when you have a lot of code in your loop() or when your sampling time is set to a low value.

Another small change from the original library is a different computation for the Anti Integral Wind-Up that was suggested in the blog's comments and received positive feedback from Brett but was never actually implemented.

GitHub source: GitHub - barzilaydn/Arduino-PID-Library

Basic Example:

/********************************************************
 * Multiple PIDs with a PIDMaster
 * Running multiple PID instances in parallel with Timer interrupts for better sampling.
 ********************************************************/

#include <TimerOne.h>
#include <PID_v2.h>
#include <PIDMaster.h>

//PID variables
volatile double Right_S, Right_I, Right_O;
volatile double Left_S, Left_I, Left_O;
volatile double Top_S, Top_I, Top_O;

//PID for each rotation type.
PID RightPID(&Right_I, &Right_O, &Right_S, 1.0, 2.0, 3.0, false, 10);
PID LeftPID (&Left_I , &Left_O , &Left_S , 2.0, 3.0, 4.0, false, 10);
PID TopPID  (&Top_I  , &Top_O  , &Top_S  , 3.0, 4.0, 5.0, false, 10);
PIDMaster masterPID (&RightPID, &LeftPID, &TopPID);

void setup()
{
    // When using PIDMaster - Input, Output and Setpoint must be set and get using these setter / getter methods!
    // But compatibility with PID_v1 code remains when not using the PIDMaster.
    
    //initialize the variables we're linked to.
    RightPID.SetInput(analogRead(0));
    LeftPID.SetInput(analogRead(1));
    TopPID.SetInput(analogRead(2));
    
    RightPID.SetSetpoint(45);
    LeftPID.SetSetpoint(15);
    TopPID.SetSetpoint(0);
    
    RightPID.SetOutputLimits(0,255);
    LeftPID.SetOutputLimits(0,255);
    TopPID.SetOutputLimits(0,255);
    masterPID.Start();  //Starts the Timer which automatically handles all PID instances.
}

void loop()
{
    RightPID.SetInput(analogRead(0));
    LeftPID.SetInput(analogRead(1));
    TopPID.SetInput(analogRead(2));
    
    analogWrite(3, RightPID.GetOutput());
    analogWrite(4, LeftPID.GetOutput());
    analogWrite(5, TopPID.GetOutput());
}

I would love some feedback and will much appreciate people testing this out as I haven't tried it yet (still don't have the required hardware).

Feel free to ask any question and to suggest improvements.

Dan

I don't see any code to protect the input and output variables from shearing.

I'm not aware of this definition, what is the shearing of variables? And how can it be prevented?

Thanks for catching that. I didn't realize that I wasn't turning the interrupts off in the main loop when reading / writing to the volatile variables (input, output, setpoint). Though I want it to be more user-friendly than forcing him to turn the interrupts off and on when needed, something automatic would be nice (or an entirely different approach).

I'll try to think on something.. (and I'm open to suggestions :slight_smile:

The usual approach is to use property getter / setter methods.

Got you, I've updated the code on GitHub and the example code in my OP. It is still a bit risky if the user doesn't use the setter / getter methods I've provided (By changing the values directly) but I decided to keep it that way so that PID_v1 code without the use of PIDMaster would still function properly.