Log in

View Full Version : Sample PID code???


EHaskins
24-07-2006, 15:43
I have read the PID white paper on CD, and looked through the programming threads, but I can't seem to write a good PID function.

I was hoping someone would have a PID.c/.h file they could show me.I would appreciate any help.

Thanks,
Eric Haskins

Nikhil Bajaj
24-07-2006, 16:45
I have read the PID white paper on CD, and looked through the programming threads, but I can't seem to write a good PID function.

I was hoping someone would have a PID.c/.h file they could show me.I would appreciate any help.

Thanks,
Eric Haskins

What isn't good about the PID you write? If you PM me with your code or something I'd be happy to look it over for you. :)

JBotAlan
24-07-2006, 17:08
I hate to sorta threadjack, but I'm also having problems with my PID code--I just can't get the thing to stop oscillating. I'll re-read over that whitepaper now that I have oodles of time, but I adjusted the terms according to the directions and got nowhere during the season. It works with minor disturbances, but seems to drift when it is sitting still, then detects the drift and reverses, into bigger and bigger oscillations.

I'll play with it probably next week, but right now I'm going to work on the camera code.

JBot

EHaskins
24-07-2006, 18:20
I can write a simple PID, but I am having trouble when I try to write one to handle more then one output. I would like to be able to have the two/four drive outputs have one group of gains, and also have separate gains for a turret, thrower wheels... :ahh: .


Is this possible, and if anyone has done this can they show me how they did this.

EDIT: My goal is to have 4-6 output channels. 1-2 would have the same gains.

Matt Krass
24-07-2006, 19:40
Are you referring to my whitepaper? If so if you'd like to get in touch with me privately I'll try to help you out directly, or you can post your code here. I'm not quite clear on exactly what you're trying to accomplish, can you elaborate a bit more on it?

Nikhil Bajaj
24-07-2006, 20:45
I can write a simple PID, but I am having trouble when I try to write one to handle more then one output. I would like to be able to have the two/four drive outputs have one group of gains, and also have separate gains for a turret, thrower wheels... :ahh: .


Is this possible, and if anyone has done this can they show me how they did this.

EDIT: My goal is to have 4-6 output channels. 1-2 would have the same gains.

You could implement the PID as a function that gets passed the loop parameters and the error information required when you call it...that way you can have one PID in code but manage many loops with it.

Or am I not understanding the question?

EHaskins
24-07-2006, 21:39
So this is what I have...

#include "pid.h"
#include "ifi_aliases.h"
#include "ifi_default.h"
#include "ifi_utilities.h"
#include "user_routines.h"
#include "printf_lib.h"

int PID_Main (int current, int target, int static_i, int i_max, int i_min,
int static_p, int p_gain, int i_gain, int d_gain){

int pout, perr, iout, ierr, derr, dout;

// calculate errors \\
perr = target - current;
ierr = static_i + perr;
derr = static_p - perr;

// limit ierr \\
if (ierr => i_max)
{
ierr = i_max;
}
else if(ierr =< i_min)
{
ierr = i_min;
}


// calulate outputs from each part \\
pout = p_gain * perr;
iout = i_gain * ierr;
dout = d_gain * derr;

// calculate output \\
return pout + iout + dout;

}

1. I don't know how to retrieve the static variables after the function has processed them.
2. I would also like to create a structure to hold all the info passed to this function so if anyone has any advice on doing that I would appreciate it.

This function may not work properly I had written a single channel PID, but it was early this year and I can't find it. If you see any problems please tell me.

Matt, I was referring to your white paper.

Thanks,
EHaskins

Noah Kleinberg
24-07-2006, 22:02
1. I don't know how to retrieve the static variables after the function has processed them.
2. I would also like to create a structure to hold all the info passed to this function so if anyone has any advice on doing that I would appreciate it.


For your first question:

There are a few ways to do this (if you're referring to the local variables in the function).

One would be to make those variables global. This is the simplest way, but has the disadvantage of needing to copy the data from one channel before calculating data for a second channel.

A better way to do this would be, as you mentioned in your second question, to use a structure, and pass a pointer to it to the PID function.

Here's some code I wrote for this as an example (haven't tested it):

typedef struct
{
int cOutput = 0;
int cValue = 0; //current value
int tValue = 0; //target value

int cError = 0; //current error
int lError = 0; //last error
int sError = 0; //sum of the errors

double pGain = 0;
double iGain = 0;
double dGain = 0;

unsigned char flags = P|I|D;
} PID_Type;


The flags variable is used to determine which type of loop to use, and is not necessary(P I and D are globabl unsigned chars defined as 1, 2, and 4, respectively, the logical or '|' combines the bits); likewise, other variables like i_max that you used in your code could be added.

A pointer to a struct of this type is then passed to a PID function, like yours, and you would then do the same thing that you did but with member variables of the struct instead of local variables in the function and parameters.

The prototype for this function would look like:

void PID_Loop(PID_Type* object).

You could also pass the structure without using pointers (void PID_Loop(PID_Type object) ), but this would be less efficient because a second copy of the object would be made in memory, and also not as flexible because then the original structure cannot be modified by the function.

If you've never used structures before, they're a collection of variables; basically an array with named elements which can be off different sizes. They are defined with the keyword 'struct' before a list of variables in curly braces. The 'typedef' makes the struct a variable type, like any other type such as an int or a char, and then you can use 'PID_Type', for instance, in place of any other variable type.

Hope this was helpful, tell me if anything needs clarification.

EHaskins
25-07-2006, 12:42
Thanks I'll try that, but I have one more question. How do you send a pointer to a function? Do you place a & before the variable name like the code below?
Getdata(&rxdata);
Putdata(&txdata);

Matt Krass
25-07-2006, 13:21
Thanks I'll try that, but I have one more question. How do you send a pointer to a function? Do you place a & before the variable name like the code below?
Getdata(&rxdata);
Putdata(&txdata);

That is the "addressof" operator, it passes the memory address of the variable in question, stick that in an empty pointer and now you have a pointer to that variable.

I recommend using one struct to hold the PID gains, static error variables, and output, and just pass a pointer to the structure when you call your function. This allows you to have multiple PID "objects" which you can then pull the data from it later on for whatever you need it for.

Also, you might consider storing the gains in two unsigned chars each, one as a numerator and a denominator. In doing so you can store fractional math operations in 16 bits instead of 32 (or is it 64 on the PIC?) bit floating point math, and do it much faster

An example:

unsigned float p_gain = 0.5;
unsigned char p_gain_num = 1;
unsigned char p_gain_den = 2;
signed int p_out = 0;

p_out = magical_p_error_global * p_gain; //Integer math, will round down to 0
//making this useless with any non
//integer gains.

p_out = (float)magical_p_error_global * p_gain; //This is slow and memory
//wasting, not good.

p_out = magical_p_error_global * p_gain_num / p_gain_den; //This is good


In the last example, the error is multiplied up, and then divided down, avoiding floating point math operations but still accomplishing the same as multiplying by 0.5. Also it avoids situations where improper promotion (Such as using integer point over floating point math) can cause rounding down to 0 all the time.

Another example, with an error of 10:

10 * 0.5 = 5
10 * 1 / 2 =
(10) / 2 = 5

See? Same thing!

Using multiplication and division of integer numbers is easier on memory and processing time as the PIC doesn't have any hardware support for floating point math.

This isn't perfect, any decimals on the final result will still be truncated, but in our situations this is hardly noticeable. If it's really that important, I suggest learning about fixed point math, its a compromise between my method and "true" floating point.

I'll look over your function when I have some more time and see if I can recommend any improvements besides using a data structure.

EHaskins
25-07-2006, 18:18
Thanks for the help. If I have any other problems I'll put a post
here.

Qbranch
26-07-2006, 20:44
I hate to sorta threadjack, but I'm also having problems with my PID code--I just can't get the thing to stop oscillating. I'll re-read over that whitepaper now that I have oodles of time, but I adjusted the terms according to the directions and got nowhere during the season. It works with minor disturbances, but seems to drift when it is sitting still, then detects the drift and reverses, into bigger and bigger oscillations.

I'll play with it probably next week, but right now I'm going to work on the camera code.

JBot


make sure you remove your deadband around neutral on the victors, don't just rely on your accumulator to do it since it will be accumulating error that doesn't exist.

X-Istence
04-08-2006, 09:02
I hate to sorta threadjack, but I'm also having problems with my PID code--I just can't get the thing to stop oscillating. I'll re-read over that whitepaper now that I have oodles of time, but I adjusted the terms according to the directions and got nowhere during the season. It works with minor disturbances, but seems to drift when it is sitting still, then detects the drift and reverses, into bigger and bigger oscillations.

I'll play with it probably next week, but right now I'm going to work on the camera code.

JBot

Mine does the same thing, I can't test anymore since the robot is out of my hands, but I definitely need to figure out why.

make sure you remove your deadband around neutral on the victors, don't just rely on your accumulator to do it since it will be accumulating error that doesn't exist.

Where would one stick this in a PID loop?

Alan Anderson
04-08-2006, 17:24
...remove your deadband around neutral on the victors,...Where would one stick this in a PID loop?
It doesn't go "in" the PID loop. It's part of the output of the pwm value. Here's one way to do it:

#define minimum 0
#define neutral 128
#define maximum 254
#define deadband 7
// make sure we won't overflow/underflow the value 0-254 range
if (value <= minimum+deadband)
value = value + deadband;
if (value >= maximum-deadband)
value = value - deadband;

// move the value away from neutral by enough to overcome the deadband
if (value < neutral)
value = value - deadband;
if (value > neutral)
value = value + deadband;

Qbranch
08-08-2006, 14:18
Victor Deadband is 124-130, Inclusive. Other than that, power output is very linear. I've tested the victor all throughout its pwm range, and it has a nice linear output from 3-100% duty cycle. I like to run my servos (pid) on a 25ms interrupt timer, so i very rarely leave a deadband in my pid except for neutral, of course.

I'll be happy to answer any other questions you might have.

-Q