1. This forum section is a read-only archive which contains old newsgroup posts. If you wish to post a query, please do so in one of our main forum sections (here). This way you will get a faster, better response from the members on Motherboard Point.

# PID... maybe I need a PhD...?

Discussion in 'Embedded' started by cerr, Jan 6, 2012.

1. ### cerrGuest

Hi Guys,

I used Tim Wescott's PID without a PhD document to come up with my
controller loop.
Now, my application is a light source that is being controlled with
the duty cycle of a pwm - I have a current feedback loop that is
telling me how much current is going through the LEDs. Now to get
these values on the same scale, I just read what my ADC reads at
maximum allowed current and that is 100%. And then my input (IRDrive)
is controlling how intens the LEDs should be (again in %).
My control loop looks like this:
double UpdatePID(SPid * pid, double error, double position)
{
double pTerm,
dTerm, iTerm;
pTerm = pid->pGain * error;
// calculate the proportional term
// calculate the integral state with appropriate limiting
pid->iState += error;
if (pid->iState > pid->iMax) pid->iState = pid->iMax;
else if (pid->iState
<
pid->iMin) pid->iState = pid->iMin;
iTerm = pid->iGain * pid->iState; // calculate the integral term
dTerm = pid->dGain * (position - pid->dState);
pid->dState = position;
return pTerm + iTerm - dTerm;
}
And I played around with various P,I and D gains but seem not to be
able to get it ionto a steady state, it keeps flocking all over.
The struct looks like this:
typedef struct
{
double dState; // Last position input
double iState; // Integrator state
double iMax, iMin;
// Maximum and minimum allowable integrator state
double iGain, // integral gain
pGain, // proportional gain
dGain; // derivative gain
} SPid;
Before I implemented the PID controller, I simply had this:
AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback;
/* if (AVGfdbckprcnt < IRDrive){
if (CurrPWM < 255)
CurrPWM++;
PWM_WriteCompare(CurrPWM);
}
else if (AVGfdbckprcnt > IRDrive){
if (CurrPWM > 0)
CurrPWM--;
PWM_WriteCompare(CurrPWM);
}
else
continue;
PWM_WriteCompare(CurrPWM);
Which worked fine and now i replaced this with:
AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback;
CurrPWM = UpdatePID(&Cntrl, AVGfdbckprcnt-IRDrive, AVGfdbckprcnt);
PWM_WriteCompare(CurrPWM);
which doesn't quite work yet... Any clues, hints or suggestions would
be appreciated!

Thank you!
roN

cerr, Jan 6, 2012

2. ### cerrGuest

Ok,

I got a few more details. In order to tuine the controller, in the doc
it says
"If it isn't set already, set the proportional gain to a starting
value between 1 and 100. Your system will probably either show
terribly slow performance or it will oscillate. If you see
oscillation, drop the proportional gain by factors of eight or 10
until the oscillation stops."

Now at 1 my circuit oscillates thus I go down and down and down and am
now at a pGain or 0.020405 and it's still oscillating - it just seems
like the smaller the number gets, the lower the oscillation frequency
gets. I'm now in the milliHertz area already, it seems like it trys to
reach the target ever 3 (or so) seconds and the rest of the time it's
just at 0. It also seems to change as I modify the target to reach but
there's no talk of that in the doc from T. Wescott. Not sure if my
code is off somewhere...?

Any hints or suggestions anyone?

Thanks,
Ron

On Jan 6, 10:13 am, cerr <> wrote:
> Hi Guys,
>
> I used Tim Wescott's PID without a PhD document to come up with my
> controller loop.
> Now, my application is a light source that is being controlled with
> the duty cycle of a pwm - I have a current feedback loop that is
> telling me how much current is going through the LEDs. Now to get
> these values on the same scale, I just read what my ADC reads at
> maximum allowed current and that is 100%. And then my input (IRDrive)
> is controlling how intens the LEDs should be (again in %).
> My control loop looks like this:
> double UpdatePID(SPid * pid, double error, double position)
> {
> double pTerm,
> dTerm, iTerm;
> pTerm = pid->pGain * error;
> // calculate the proportional term
> // calculate the integral state with appropriate limiting
> pid->iState += error;
> if (pid->iState > pid->iMax) pid->iState = pid->iMax;
> else if (pid->iState
> <
> pid->iMin) pid->iState = pid->iMin;
> iTerm = pid->iGain * pid->iState; // calculate the integral term
> dTerm = pid->dGain * (position - pid->dState);
> pid->dState = position;
> return pTerm + iTerm - dTerm;}
>
> And I played around with various P,I and D gains but seem not to be
> able to get it ionto a steady state, it keeps flocking all over.
> The struct looks like this:
> typedef struct
> {
> double dState; // Last position input
> double iState; // Integrator state
> double iMax, iMin;
> // Maximum and minimum allowable integrator state
> double iGain, // integral gain
> pGain, // proportional gain
> dGain; // derivative gain} SPid;
>
> Before I implemented the PID controller, I simply had this:
> AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback;
> /*              if (AVGfdbckprcnt < IRDrive){
>                   if (CurrPWM < 255)
>                     CurrPWM++;
>                   PWM_WriteCompare(CurrPWM);
>                 }
>                 else if (AVGfdbckprcnt > IRDrive){
>                   if (CurrPWM > 0)
>                     CurrPWM--;
>                   PWM_WriteCompare(CurrPWM);
>                 }
>                 else
>                   continue;
>                 PWM_WriteCompare(CurrPWM);
> Which worked fine and now i replaced this with:
> AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback;
> CurrPWM = UpdatePID(&Cntrl, AVGfdbckprcnt-IRDrive, AVGfdbckprcnt);
> PWM_WriteCompare(CurrPWM);
> which doesn't quite work yet... Any clues, hints or suggestions would
> be appreciated!
>
> Thank you!
> roN

cerr, Jan 6, 2012

3. ### Bruce VarleyGuest

"cerr" <> wrote in message
news:...
> Hi Guys,
>
> I used Tim Wescott's PID without a PhD document to come up with my
> controller loop.
> Now, my application is a light source that is being controlled with
> the duty cycle of a pwm - I have a current feedback loop that is
> telling me how much current is going through the LEDs. Now to get
> these values on the same scale, I just read what my ADC reads at
> maximum allowed current and that is 100%. And then my input (IRDrive)
> is controlling how intens the LEDs should be (again in %).
> My control loop looks like this:
> double UpdatePID(SPid * pid, double error, double position)
> {
> double pTerm,
> dTerm, iTerm;
> pTerm = pid->pGain * error;
> // calculate the proportional term
> // calculate the integral state with appropriate limiting
> pid->iState += error;
> if (pid->iState > pid->iMax) pid->iState = pid->iMax;
> else if (pid->iState
> <
> pid->iMin) pid->iState = pid->iMin;
> iTerm = pid->iGain * pid->iState; // calculate the integral term
> dTerm = pid->dGain * (position - pid->dState);
> pid->dState = position;
> return pTerm + iTerm - dTerm;
> }
> And I played around with various P,I and D gains but seem not to be
> able to get it ionto a steady state, it keeps flocking all over.
> The struct looks like this:
> typedef struct
> {
> double dState; // Last position input
> double iState; // Integrator state
> double iMax, iMin;
> // Maximum and minimum allowable integrator state
> double iGain, // integral gain
> pGain, // proportional gain
> dGain; // derivative gain
> } SPid;
> Before I implemented the PID controller, I simply had this:
> AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback;
> /* if (AVGfdbckprcnt < IRDrive){
> if (CurrPWM < 255)
> CurrPWM++;
> PWM_WriteCompare(CurrPWM);
> }
> else if (AVGfdbckprcnt > IRDrive){
> if (CurrPWM > 0)
> CurrPWM--;
> PWM_WriteCompare(CurrPWM);
> }
> else
> continue;
> PWM_WriteCompare(CurrPWM);
> Which worked fine and now i replaced this with:
> AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback;
> CurrPWM = UpdatePID(&Cntrl, AVGfdbckprcnt-IRDrive, AVGfdbckprcnt);
> PWM_WriteCompare(CurrPWM);
> which doesn't quite work yet... Any clues, hints or suggestions would
> be appreciated!
>
> Thank you!
> roN

From your followup posting, this may not be a simple loop oscillation. A bit
of info on your timing would help, maybe it's inferrable from your code but
I don't have the energy to troll through it. Can you let us know the
frequency of the PID function and of the PWM?

Bruce Varley, Jan 7, 2012
4. ### Tim WescottGuest

On Fri, 06 Jan 2012 10:13:26 -0800, cerr wrote:

> Hi Guys,
>
> I used Tim Wescott's PID without a PhD document to come up with my
> controller loop.
> Now, my application is a light source that is being controlled with the
> duty cycle of a pwm - I have a current feedback loop that is telling me
> how much current is going through the LEDs. Now to get these values on
> the same scale, I just read what my ADC reads at maximum allowed current
> and that is 100%. And then my input (IRDrive) is controlling how intens
> the LEDs should be (again in %). My control loop looks like this:
> double UpdatePID(SPid * pid, double error, double position) {
> double pTerm,
> dTerm, iTerm;
> pTerm = pid->pGain * error;
> // calculate the proportional term
> // calculate the integral state with appropriate limiting pid->iState +=
> error;
> if (pid->iState > pid->iMax) pid->iState = pid->iMax; else if
> (pid->iState
> <
> pid->iMin) pid->iState = pid->iMin;
> iTerm = pid->iGain * pid->iState; // calculate the integral term dTerm =
> pid->dGain * (position - pid->dState); pid->dState = position;
> return pTerm + iTerm - dTerm;
> }
> And I played around with various P,I and D gains but seem not to be able
> to get it ionto a steady state, it keeps flocking all over. The struct
> looks like this:
> typedef struct
> {
> double dState; // Last position input double iState; // Integrator state
> double iMax, iMin;
> // Maximum and minimum allowable integrator state double iGain, //
> integral gain
> pGain, // proportional gain
> dGain; // derivative gain
> } SPid;
> Before I implemented the PID controller, I simply had this:
> AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback; /

* if
> (AVGfdbckprcnt < IRDrive){
> if (CurrPWM < 255)
> CurrPWM++;
> PWM_WriteCompare(CurrPWM);
> }
> else if (AVGfdbckprcnt > IRDrive){
> if (CurrPWM > 0)
> CurrPWM--;
> PWM_WriteCompare(CurrPWM);
> }
> else
> continue;
> PWM_WriteCompare(CurrPWM);
> Which worked fine and now i replaced this with: AVGfdbckprcnt =
> ((float)100/(float)MAX_CURRENT) * AVGfeedback; CurrPWM =
> UpdatePID(&Cntrl, AVGfdbckprcnt-IRDrive, AVGfdbckprcnt);
> PWM_WriteCompare(CurrPWM);
> which doesn't quite work yet... Any clues, hints or suggestions would be
> appreciated!
>
> Thank you!
> roN

I'm not sure what else is going on, but your error term should be
command - feedback; you've got the value backwards.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com

Tim Wescott, Jan 7, 2012
5. ### Tim WescottGuest

On Fri, 06 Jan 2012 14:40:16 -0800, cerr wrote:
(top posting fixed)
> On Jan 6, 10:13Â am, cerr <> wrote:
>> Hi Guys,
>>
>> I used Tim Wescott's PID without a PhD document to come up with my
>> controller loop.
>> Now, my application is a light source that is being controlled with the
>> duty cycle of a pwm - I have a current feedback loop that is telling me
>> how much current is going through the LEDs. Now to get these values on
>> the same scale, I just read what my ADC reads at maximum allowed
>> current and that is 100%. And then my input (IRDrive) is controlling
>> how intens the LEDs should be (again in %). My control loop looks like
>> this:
>> double UpdatePID(SPid * pid, double error, double position) {
>> double pTerm,
>> dTerm, iTerm;
>> pTerm = pid->pGain * error;
>> // calculate the proportional term
>> // calculate the integral state with appropriate limiting pid->iState
>> += error;
>> if (pid->iState > pid->iMax) pid->iState = pid->iMax; else if
>> (pid->iState
>> <
>> pid->iMin) pid->iState = pid->iMin;
>> iTerm = pid->iGain * pid->iState; // calculate the integral term dTerm
>> = pid->dGain * (position - pid->dState); pid->dState = position;
>> return pTerm + iTerm - dTerm;}
>>
>> And I played around with various P,I and D gains but seem not to be
>> able to get it ionto a steady state, it keeps flocking all over. The
>> struct looks like this:
>> typedef struct
>> {
>> double dState; // Last position input double iState; // Integrator
>> state
>> double iMax, iMin;
>> // Maximum and minimum allowable integrator state double iGain, //
>> integral gain
>> pGain, // proportional gain
>> dGain; // derivative gain} SPid;
>>
>> Before I implemented the PID controller, I simply had this:
>> AVGfdbckprcnt = ((float)100/(float)MAX_CURRENT) * AVGfeedback; /* Â  Â  Â
>> Â  Â  Â  Â if (AVGfdbckprcnt < IRDrive){
>> Â  Â  Â  Â  Â  Â  Â  Â  Â  if (CurrPWM < 255)
>> Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CurrPWM++;
>> Â  Â  Â  Â  Â  Â  Â  Â  Â  PWM_WriteCompare(CurrPWM);
>> Â  Â  Â  Â  Â  Â  Â  Â  }
>> Â  Â  Â  Â  Â  Â  Â  Â  else if (AVGfdbckprcnt > IRDrive){
>> Â  Â  Â  Â  Â  Â  Â  Â  Â  if (CurrPWM > 0)
>> Â  Â  Â  Â  Â  Â  Â  Â  Â  Â  CurrPWM--;
>> Â  Â  Â  Â  Â  Â  Â  Â  Â  PWM_WriteCompare(CurrPWM);
>> Â  Â  Â  Â  Â  Â  Â  Â  }
>> Â  Â  Â  Â  Â  Â  Â  Â  else
>> Â  Â  Â  Â  Â  Â  Â  Â  Â  continue;
>> Â  Â  Â  Â  Â  Â  Â  Â  PWM_WriteCompare(CurrPWM);
>> Which worked fine and now i replaced this with: AVGfdbckprcnt =
>> ((float)100/(float)MAX_CURRENT) * AVGfeedback; CurrPWM =
>> UpdatePID(&Cntrl, AVGfdbckprcnt-IRDrive, AVGfdbckprcnt);
>> PWM_WriteCompare(CurrPWM);
>> which doesn't quite work yet... Any clues, hints or suggestions would
>> be appreciated!
>>

> Ok,
>
> I got a few more details. In order to tuine the controller, in the doc
> it says
> "If it isn't set already, set the proportional gain to a starting value
> between 1 and 100. Your system will probably either show terribly slow
> performance or it will oscillate. If you see oscillation, drop the
> proportional gain by factors of eight or 10 until the oscillation
> stops."
>
> Now at 1 my circuit oscillates thus I go down and down and down and am
> now at a pGain or 0.020405 and it's still oscillating - it just seems
> like the smaller the number gets, the lower the oscillation frequency
> gets. I'm now in the milliHertz area already, it seems like it trys to
> reach the target ever 3 (or so) seconds and the rest of the time it's
> just at 0. It also seems to change as I modify the target to reach but
> there's no talk of that in the doc from T. Wescott. Not sure if my code
> is off somewhere...?
>
> Any hints or suggestions anyone?

What is the basic behavior of your LED circuit? It seems that this
should be a fairly instantaneous (if not always nicely linear) response:
i.e., the response to a step change in the drive is a step change in the
feedback, without any "tail" -- is this so?

If so, then I'm surprised. That sort of behavior is best handled with a
pure integrator (proportional and differential action are only of help
when the plant shows lag that extends past the next sampling instant). A
purely proportional gain will either be stable or will go into an
oscillation with a period of half the sampling rate; it certainly won't
do what you're seeing. Given that your original code is a very crude
approximation of a pure integrator, I'm even more puzzled why pure
proportional gain causes problems.

plant behavior) try setting the proportional and derivative gains to
zero, and experiment with integral gain only. You should be able to
deduce proper operation -- if the current is too high then the drive
should go down, and visa-versa. What integral action will give you that
your current algorithm doesn't (aside from not giving control guys who
look at it headaches) is a much faster response when the actual feedback
is a good deal different than the target value.

--
Tim Wescott
Control system and signal processing consulting
www.wescottdesign.com

Tim Wescott, Jan 7, 2012