Arduino PID in Manual Mode?

I’d like to use the Arduino PID library for some controls, but I need to synchronize it manually.

What I have is a continually monitored analog input that I’m manually averaging, and upon a digital input change, it locks that average into the INPUT for the PID, and I’d like it at that point, and at that point only to calculate the PID OUTPUT based on the error.

Basically, since I need to synchronize the updates to OUTPUT, I’m worried that an automatic running PID will end up going haywire for the derivative and integral gain when it thinks that winding up the OUTPUT is doing nothing to the INPUT, resulting in it constantly railing.

What I expect is that I’ll have a PID controller that has a “SetSampleTime” that is ignored and instead the sequential datapoints stored in the controller are manually added. Is this what will actually happen?

So something like:

#include <PID_v1.h>

double input, output, setpoint, inputavg;
PID myPID(&input, &output, &setpoint, 1.0, 0.05, 0.25, DIRECT);
float timeconstant 0.01;

void setup() {

  // Give it a setpoint target for the input of 100.
  setpoint = 100;

  // Set it to "Manual" (whatever that means).
  myPID.SetMode(MANUAL);
}

void loop() {

  // Do a running average of analog input A0.
  inputavg = inputavg*(1-timeconstant) + analogRead(A0)*timeconstant;

  // If a digital pin goes high, then and only then update the PID output.
  // Let's pretend that this magical input only goes high for one loop,
  // in practice I'll be doing it with an interrupt setting a flag on positive edge.

  if (digitalRead(0) == HIGH) {

    // The double buffered average is put into the input.
    input = inputavg;

    // The PID calculates the error and determines a new output.
    myPID.Compute();

    // I manually send out the new output. Pretend that this function works.
    analogWrite((unsigned int)output);
  }
}

PID is supposed to monitor some input and compare that to a setpoint, and automatically make some adjustments. It does not sound like that is what you want. So, forget PID.

1 Like

Sure I do, I just want the SampleTime to be ignored to let me manually add new data points to the PID history. I'd still like the PID to store history to do the integral and derivative terms. Yeah, I can implement it manually, but it's definitely still a PID.

Maybe the point of confusion is that there are two time scales. The first is the rate that I'm sampling data, and the second is the rate that the PID runs at.

I'm sampling data continually and storing it in an average, but I don't want the PID to be aware of the derivative or integral of the raw data, only the average which is sampled only on an external interrupt.

Methinks you need to get a better understanding of how a PID actually works. What you describe will not work well at all...

Regards,
Ray L.

1 Like

I'm really not sure how to respond to something that vague.

It's not particularly complicated. The system will just be either unstable or unnecessarily slow if I can't control the timing manually.

  • Imagine a television signal, lots of analog data grouped together into one frame.
  • The average value of that data is the average brightness of a frame. This is the input control variable.
  • The goal is to make the average brightness equal to something mid-range, this is the setpoint.
  • Each frame, the gain on the input is varied a bit. If the average brightness was too high, it turns down the gain. If the average brightness was too low, it turns up the gain. The gain is the output control variable.

Consider this, if that was too complicated to follow.

Say I have a frame rate of 1kHz.

I set the SampleTime to exactly 1ms. Okay, it might work, but let's say that the phase is 10%, so the SampleTime triggers after 100us. Now it's basing the new output control signal partially on the old data and partially on the new data. My phase lag between setting the output control and the input changing is almost nothing, so this is silly to do.

Say I set the SampleTime to 100us, so I get 10 triggers within one coherent frame. Let's say that the brightness is too low. The PID tells you to raise the output level. It checks back in 100us, and sees that the brightness hasn't changed (because I can only change the output on frame sync pulses), so tells you to raise the output level again. And again. And again. Until it's railed at the maximum gain. Next time around, the brightness is too high, so it tells you to lower the control gain. And again. And again. And now it's railed low. Unstable. Phase margin. Won't work at all.

Say I set the SampleTime to 10ms, so it only checks the brightness every ten frames. Now it's stable, but it's not as fast as it could easily be by making it synchronous. Yeah, I can do that if I have to, but I don't, so why should I? I don't actually care how much time passes between frames, I just want it to add a data point to the history each frame and update the output control signal each frame. I don't care about seconds, I only care about frames.

I have the information needed to run the PID perfectly in phase with the control variable being updated and avoid the phase issue to keep it perfectly stable.

There is obviously still a PID.

All I want to know is if PID.Compute() works in MANUAL mode by adding another data point and updating the output right then or not.

Compute takes the input value YOU provide, and calculates an output value, that YOU must then do something with. If you sample your input, delay a while, call Compute, delay a while, then write the output to wherever it goes, you'll end up with really lousy control over whatever it is you're trying to control. Sampling the input, computing, and writing the output should be, to the extent possible, treated as an atomic operation, with no unnecessary delays, or you'll never be able to tune the PID for a good response. You can't simply call Compute at random intervals, as the coefficients you provide to the PID all get scaled by the PID interval. And you will introduce noise into the control loop, and the I and D terms are already very prone to instability due to noise in the input data. The best way to use a PID is to setup a timer interrupt that performs the sample/compute/write at consistent intervals. If you don't do that, you'll likely never be able to use the D term, and the I term will also not perform well.

I have no clue why you bring up Nyquist. That has nothing to do with a PID...

Regards,
Ray L.

1 Like

I feel like we’re talking past each other.

I know that you can’t call Compute at random intervals. This is obvious.

How about if I phrase it like this.

I have a regular clock interval coming in. I want to trigger the PID using that external clock instead of the internal timer interrupt.

The reason shouldn’t matter, but it is a good one. I can only update the output on the external clock because otherwise the data will be corrupted.

As you put it, “Sampling the input, computing, and writing the output should be, to the extent possible, treated as an atomic operation, with no unnecessary delays, or you’ll never be able to tune the PID for a good response.”

If I want it to be as atomic an operation as possible I must synchronize it or else there will be a phase lag between the output and input inside the controller.

Does that clarify?

If the library can’t handle that, I’ll just implement it myself, this wasn’t intended to spark some aggressive debate about the capabilities of PID. It was a simple question about whether I could manually trigger PID sampling, which in my case I’m talking about “manually triggering” based on a regular external timer pulse.

In MANUAL mode, you can call compute() whenever you like, and the time-keeping is then entirely up to you, The PID will neither know nor care whether you are calling at regular intervals. That's what MANUAL mode is for.

Regards,
Ray L.

I am looking to manually control my heating element in a kettle for brewing beer after using the PID to control the mash temperature. I have been thinking I would need to write a separate function for that, but it seems like I might not need to do that.

So in manual control, I control the duty cycle (somehow that is not defined in the PID library). If I want 75% power, I have the element on for 3.25 seconds (assuming I am using a 5 second window). So I can enter a value between 0 and 100% using my rotary encoder and using some code mojo which I have not thought out yet, I can control the pulse width of my heating element. In other words, does manual control more or less substitute something else that I manually enter for the output of the PID?

If so, I will be saving some memory space by not having a separate function for PWM.

PID as a concept is blind to the sample period, but more concretely, things like Nyquist etc. will come into it (as it will in any sampling context) - it's up to the user/observer to decide if it's working for the project or not.

Personally, I'd forget that library and for the academic exercise copy and paste a PID loop from somewhere (google 'PID C example' or similar) and add your own controllable period (use millis())

It'll be easier to write/hack a more general example.

Actually, have you actually looked into the PID library code ?? maybe it's not that hard anyway ?

Oops,

I didn't see post #8...

That's pretty much what I've said, but more specific and helpful :slight_smile:

1:1:
PID as a concept is blind to the sample period, but more concretely, things like Nyquist etc. will come into it (as it will in any sampling context) - it's up to the user/observer to decide if it's working for the project or not.

Personally, I'd forget that library and for the academic exercise copy and paste a PID loop from somewhere (google 'PID C example' or similar) and add your own controllable period (use millis())

It'll be easier to write/hack a more general example.

Actually, have you actually looked into the PID library code ?? maybe it's not that hard anyway ?

Have you looked at the PID library code? It is well written, well documented, and very general-purpose. It works very well, so there is nothing to be gained by "rolling your own". It also includes, in the more recent versions, many almost essential enhancements, like anti-wind-up and derivative "kick", that a basic DIY PID would not have.

Regards,
Ray L.

RayLivingston:
In MANUAL mode, you can call compute() whenever you like, and the time-keeping is then entirely up to you, The PID will neither know nor care whether you are calling at regular intervals. That's what MANUAL mode is for.

Regards,
Ray L.

That's not true (at least with the current version of the library), if in MANUAL mode, then compute() will just return false immediately

See the code for yourself:

bool PID::Compute()
{
   if(!inAuto) return false;
   .
   .
   .

in MANUAL mode the function will not compute the PID, in fact the PID is basically OFF.