Chief Delphi

Chief Delphi (http://www.chiefdelphi.com/forums/index.php)
-   C/C++ (http://www.chiefdelphi.com/forums/forumdisplay.php?f=183)
-   -   WPILib PID controller object (http://www.chiefdelphi.com/forums/showthread.php?t=72918)

Phoenix Spud 25-01-2009 14:45

WPILib PID controller object
 
Has anyone used the PIDController object? How well did it work? We are looking at implementing the PIDController object and were wondering if anyone had any words of wisdom before we start.

Thanks!

wt200999 25-01-2009 14:56

Re: WPILib PID controller object
 
http://www.chiefdelphi.com/forums/sh...ad.php?t=71781

no example has been posted as far as I know though

Jared Russell 25-01-2009 20:24

Re: WPILib PID controller object
 
I'm not a huge fan of the design paradigm WPI used for their PIDController class (requiring you to implement the PIDSource and PIDOutput interfaces) so I rolled my own. I made it so you call a "calculate()" method synchronously inside your Teleop_Periodic() function, passing in sensor readings and getting output values as well. It just makes more sense to me.

BradAMiller 25-01-2009 20:38

Re: WPILib PID controller object
 
Here's an excerpt from the Users Guide that will publish in an update in the next day or so (the formatting will be better in the document)...

PID controllers are a powerful and widely used implementation of closed loop control. The PIDController class allows for a PID control loop to be created easily and runs the control loop in a separate thread at consistent intervals. The PIDController automatically checks a PIDSource for feedback and writes to a PIDOutput every loop. Sensors suitable for use with PIDController in WPILib are already subclasses of PIDSource. Additional sensors and custom feedback methods are supported through creating new subclasses of PIDSource. Jaguars and Victors are already configured as subclasses of PIDOutput, and custom outputs may also be created by sub-classing PIDOutput.
The following example shows how to create a PIDController to set the position of a turret to a position related to the x-axis on a joystick using a single motor on a Jaguar and a potentiometer for angle feedback. As the joystick X value changes, the motor should drive to a position related to that new value. The PIDController class will ensure that the motion is smooth and stops at the right point.
A potentiometer that turns with the turret will provide feedback of the turret angle. The potentiometer is connected to an analog input and will return values ranging from 0-5V from full clockwise to full counterclockwise motion of the turret. The joystick X-axis returns values from -1.0 to 1.0 for full left to full right. We need to scale the joystick values to match the 0-5V values from the potentiometer. This can be done with the following expression:
Code:

(turretStick.GetX() + 1.0) * 2.5
The scaled value can then be used to change the setpoint of the control loop as the joystick is moved.
The 0.1, 0.001, and 0.0 values are the Proportional, Integral, and Differential coefficients respectively. The AnalogChannel object is already a subclass of PIDSource and returns the voltage as the control value and the Jaguar object is a subclass of PIDOutput.
Code:

        Joystick turretStick(1);
        Jaguar turretMotor(1);
        AnalogChannel turretPot(1);
        PIDController turretControl(0.1, 0.001, 0.0, &turretPot, &turretMotor);
       
        turretControl.Enable();  // start calculating PIDOutput values

        while(IsOperator())
  {
                turretControl.SetSetpoint((turretStick.GetX() + 1.0) * 2.5);
                Wait(.02);                // wait for new joystick values
        }

The PIDController object will automatically (in the background):
• Read the PIDSource object, in this case the turretPot analog input
• Compute the new result value
• Set the PIDOutput object, in this case the turretMotor
This will be repeated periodically in the background by the PIDController. The default repeat rate is 50ms although this can be changed by adding a parameter with the time to the end of the PIDController argument list. See the reference document for details.

Kruuzr 26-01-2009 09:05

Re: WPILib PID controller object
 
Hi Brad...

This approach works great if your source is already pre-defined to be a PID source, but what about the situation ( such as an autotracking turret) where the source value will be an X offset from the middle of the image? Do we have to derive a new PIDSource object and implement any/all needed functions? The effort is compounded by the fact that the image offset is a relative number and would be hard to relate to an absolute turret positioning value.

Steve C

Daniel Jones 27-01-2009 12:19

Re: WPILib PID controller object
 
The update posted last night has many of the sensors in WPILib defined as PIDSources, and Victors and Jaguars as PIDOutputs. Make sure to re-image the cRIO with the latest image before using the update.

heydowns 27-01-2009 13:46

Re: WPILib PID controller object
 
We've been using the PIDController for a few weeks now, with pretty good results. It is very nice in the aspect that all the control happens in the background using a separate task.

The only issue we have noticed is some instability when using the "Debug"/Load to RAM functionality in Workbench. Specifically, when repeatedly unloading and loading subsequently newer versions of our program, sometimes PID tasks will not function properly at all. Other times, the system will refuse to take and run the new image, citing all sorts of resource allocation issues on the console.

This instability may NOT be caused by PIDController usage, however -- we haven't done sufficient testing yet to say one way or another.

We haven't applied the newest update yet -- will likely do that tonight.

Phoenix Spud 02-02-2009 18:46

Re: WPILib PID controller object
 
Quote:

Originally Posted by heydowns (Post 808951)
We haven't applied the newest update yet -- will likely do that tonight.

How did it work once you had both downloaded the code and got the code to rebuild?

heydowns 02-02-2009 23:08

Re: WPILib PID controller object
 
Worked no differently in Update 3 than it did in Update 2.

PIDController, and the Notifier class that it is built on, has some issues if you start deleting them (or letting C++ clean them up if one falls out of scope). Those issues are being worked on, though, and exist in both Update 2 and 3.

sircedric4 03-02-2009 07:36

Re: WPILib PID controller object
 
I have been trying to get the PIDController to work with my robot the last few days without any luck. What we want to do is exactly like the example above. We have a turret and a potentiometer that we want to use and everytime I include the PIDController calls in my program, the cRIO just locks up and goes to its disabled mode. (All my Jaguars and Victors flashing)

I have an error message that was outputted when it crashes, does anyone know what is going on, or what this means?

---------------------------------------------------------------------
eclipse.buildId=20080514-1211
java.version=1.5.0_11
java.vendor=Sun Microsystems Inc.
BootLoader constants: OS=win32, ARCH=x86, WS=win32, NL=en_US
Command-line arguments: -os win32 -ws win32 -arch x86

Info
Mon Feb 02 17:19:12 CST 2009
Target Exception: Vector 0x600 : Alignment Exception status=0x134CE7C on VxWorks6x_10.29.92.2 in Kernel Task FRC_RobotTask:0xc7b6c8
at pc=0x134CE98 in SetSetpoint()
---------------------------------------------------------------------

For further information I am using the Iterative Robot example and here is the pertinant code: I already have a bunch of code written and like the Iterative over the Simple Robot so any changes from the example code you see, I had to do to get the program to compile.

************************************************** ****
In my .h file:
Jaguar *TurretPIDMotor;
AnalogChannel *TurretPIDPot;
PIDController *turretControl;

In my .cpp file:

In TeleopInit()
Jaguar TurretPIDMotor(3);
AnalogChannel TurretPIDPot(1);
PIDController turretControl(0.1,0.001,0.0, &TurretPIDPot, &TurretPIDMotor);
turretControl.Enable();

In TeleopPeriodic()
TurretGoto is being set from 0-5 using an equation and it is float defined
turretControl->SetSetPoint(TurretGoto);

************************************************** **

I tried putting the stuff in TeleopInit up in the top of the code before any functions are called and where most of my other variables are set or initalized but it wouldn't compile. The code compiles with everything above. I also tried using "turretControl.SetSetPoint" as was shown in the example, but I had to use "->" to get it to compile. (This is an issue I am always having, I don't know what all the ".", "->", "&", "*" and stuff means or why some times one works and one doesn't)

I am almost to the point where I am thinking of just writing my own subroutine so that I can at least know what the code I am using is doing. The PIDController looks like an awesome shortcut though so I would rather try to get it to work first. The above code compiles fine but when I run the robot it doesn't work.

I am using a 10-turn potentiometer as the sensor and a window motor as the motor driving the sensor.

Can anyone give me some pointers or have you already solved this problem and can tell me how you fixed it?

Mike Mahar 03-02-2009 07:48

Re: WPILib PID controller object
 
Quote:

Originally Posted by sircedric4 (Post 813268)
************************************************** ****
In my .h file:
Jaguar *TurretPIDMotor;
AnalogChannel *TurretPIDPot;
PIDController *turretControl;

In my .cpp file:

In TeleopInit()
Jaguar TurretPIDMotor(3);
AnalogChannel TurretPIDPot(1);
PIDController turretControl(0.1,0.001,0.0, &TurretPIDPot, &TurretPIDMotor);
turretControl.Enable();

In TeleopPeriodic()
TurretGoto is being set from 0-5 using an equation and it is float defined
turretControl->SetSetPoint(TurretGoto);

************************************************** **

Change the lines in TeleopInit to:
Code:

turretPIDMotor = new Jaguar(3);
turretPIDPot = new AnalogChannel(1);
turretControl = new PIDController(0,1, 0.001, 0.0, turretPIDPot, turretPIDMotor);
turretControl->Enable();

Note that the ampersands (&) are no long on the 4th and 5th arguments to PIDController.

You were declaring a pointer to all of these classes and then allocating local versions in TeleopInit. The local version were being deleted when TeleopInit returned and the pointers you had in TeleopPeriodic were NULL and causing your program to crash.

Jared Russell 03-02-2009 08:03

Re: WPILib PID controller object
 
Quote:

Originally Posted by sircedric4 (Post 813268)
I have been trying to get the PIDController to work with my robot the last few days without any luck. What we want to do is exactly like the example above. We have a turret and a potentiometer that we want to use and everytime I include the PIDController calls in my program, the cRIO just locks up and goes to its disabled mode. (All my Jaguars and Victors flashing)

I have an error message that was outputted when it crashes, does anyone know what is going on, or what this means?

---------------------------------------------------------------------
eclipse.buildId=20080514-1211
java.version=1.5.0_11
java.vendor=Sun Microsystems Inc.
BootLoader constants: OS=win32, ARCH=x86, WS=win32, NL=en_US
Command-line arguments: -os win32 -ws win32 -arch x86

Info
Mon Feb 02 17:19:12 CST 2009
Target Exception: Vector 0x600 : Alignment Exception status=0x134CE7C on VxWorks6x_10.29.92.2 in Kernel Task FRC_RobotTask:0xc7b6c8
at pc=0x134CE98 in SetSetpoint()
---------------------------------------------------------------------

For further information I am using the Iterative Robot example and here is the pertinant code: I already have a bunch of code written and like the Iterative over the Simple Robot so any changes from the example code you see, I had to do to get the program to compile.

************************************************** ****
In my .h file:
Jaguar *TurretPIDMotor;
AnalogChannel *TurretPIDPot;
PIDController *turretControl;

In my .cpp file:

In TeleopInit()
Jaguar TurretPIDMotor(3);
AnalogChannel TurretPIDPot(1);
PIDController turretControl(0.1,0.001,0.0, &TurretPIDPot, &TurretPIDMotor);
turretControl.Enable();

In TeleopPeriodic()
TurretGoto is being set from 0-5 using an equation and it is float defined
turretControl->SetSetPoint(TurretGoto);

************************************************** **

I tried putting the stuff in TeleopInit up in the top of the code before any functions are called and where most of my other variables are set or initalized but it wouldn't compile. The code compiles with everything above. I also tried using "turretControl.SetSetPoint" as was shown in the example, but I had to use "->" to get it to compile. (This is an issue I am always having, I don't know what all the ".", "->", "&", "*" and stuff means or why some times one works and one doesn't)

I am almost to the point where I am thinking of just writing my own subroutine so that I can at least know what the code I am using is doing. The PIDController looks like an awesome shortcut though so I would rather try to get it to work first. The above code compiles fine but when I run the robot it doesn't work.

I am using a 10-turn potentiometer as the sensor and a window motor as the motor driving the sensor.

Can anyone give me some pointers or have you already solved this problem and can tell me how you fixed it?

This looks to be a scope issue. You have declared your variables as types "Jaguar *", "AnalogChannel *", and "PIDController *". These are POINTERS to objects of the given class types. Then, in TeleopInit(), you declare NEW variables of type "Jaguar", "AnalogChannel", and "PIDController". These are not pointers, but rather the objects themselves. Since the types are different, you are actually declaring completely different variables in TeleopInit(). And, since they are declared in a function, after the function is done the variables are out of scope and can no longer be used.

In order to have everything work properly, you need to reconcile the differences. Try putting this in TeleopInit():

Code:

In TeleopInit()
TurretPIDMotor = new Jaguar(3);
TurretPIDPot = new AnalogChannel(1);
turretControl = new PIDController(0.1,0.001,0.0, TurretPIDPot, TurretPIDMotor);
turretControl->Enable();

When you have variables of type "class_name *", they are pointers and you must create something for them to point to. Hence, the "new" operator. Now, actual objects (without the *) can have their members referenced with the dot (.) operator. But class pointers have their members referenced with the arrow (->) operator. You have pointers, so we use the arrow.

Also, since everything is now a pointer, you don't need the "&" in front of the two variables you pass to the PIDController constructor. The "&" is like an "anti-pointer". If something is a pointer, putting "&" in front of it will get you the actual object.

Example:

Code:

MyClass* myPointer = new MyClass();
myPointer->SomeFunction(); // Legal
myPointer.SomeFunction(); // Illegal!

MyClass myObject();
myObject->SomeFunction(); // Illegal!
myObject.SomeFunction(); // Legal

(&myPointer).SomeFunction(); // Legal, since (&myPointer) is now an object

Now in TeleopPeriodic, you can use the pointers from the header file to do the following:

Code:

In TeleopPeriodic()
TurretGoto is being set from 0-5 using an equation and it is float defined
turretControl->SetSetpoint(TurretGoto);

Let me know if you have any more problems. Yes, the differences between values, pointers, and references can be very tricky to understand. Try this page for a brief explanation - but know that understanding these sometimes subtle differences takes time!

sircedric4 03-02-2009 08:08

Re: WPILib PID controller object
 
Quote:

You were declaring a pointer to all of these classes and then allocating local versions in TeleopInit. The local version were being deleted when TeleopInit returned and the pointers you had in TeleopPeriodic were NULL and causing your program to crash.
Thanks! I have run into similar problems before when I tried to translate what code supposedly works in the SimpleRobot class examples to work in the IterativeRobot class with header files. Can you point me to some website or something that explains what all these pointers and local versions and such are? I only learned enough C to make the old control system work and that was extremely straightforward but this object oriented stuff for the new system is just hard for an old school VB programmer. I spend all my time fighting nomenclature because I don't understand it instead of programming good stuff.

Anyway, the program compiled with the changes above but I'll have to wait till tonight to see if I'll get the good old crash of death. Thanks again, I owe you a drink of your choice somehow. :-)

Edited:
Wow, as I was typing one thank you, a class on what I was doing wrong showed up! Thanks to Abwehr as well in some instruction. I hope my ignorance helps some other teams out there that are just coding through the monkey-see monkey do steal from examples type of coding I am doing.

Jared Russell 03-02-2009 08:11

Re: WPILib PID controller object
 
Quote:

Originally Posted by sircedric4 (Post 813281)
Thanks! I have run into similar problems before when I tried to translate what code supposedly works in the SimpleRobot class examples to work in the IterativeRobot class with header files. Can you point me to some website or something that explains what all these pointers and local versions and such are? I only learned enough C to make the old control system work and that was extremely straightforward but this object oriented stuff for the new system is just hard for an old school VB programmer. I spend all my time fighting nomenclature because I don't understand it instead of programming good stuff.

Anyway, the program compiled with the changes above but I'll have to wait till tonight to see if I'll get the good old crash of death. Thanks again, I owe you a drink of your choice somehow. :-)

This page is a good start: http://www.cplusplus.com/doc/tutorial/pointers.html

sircedric4 04-02-2009 07:18

Re: WPILib PID controller object
 
Success! Thanks everyone for the tips on what it takes to get the PIDController not to crash when I put it on the cRIO.

I did run into some problems when trying to get the PIDController to drive towards my setpoint. The example from WPI above seemed to indicate I need to set my input to 0-5 for a potentiometer. After banging my head for a while and then figuring out how to output PIDController->GetError() to my dashboard I figured out that it is really looking for 0-1024 at least in my application. If you are having trouble getting your PIDController to make sense to you, you might check to see what numbers it is based on for your application.

Does anyone know what it would take to drive the PIDController based on the averagevoltage of the analog input? Would I set it up like:

turretControl = new PIDController(P,I,D, turretPot->GetAverageVoltage, turretMotor)

Or do I have to do something with the other PIDController sub-classes/functions?

Jared Russell 04-02-2009 08:10

Re: WPILib PID controller object
 
Quote:

Originally Posted by sircedric4 (Post 813849)
Success! Thanks everyone for the tips on what it takes to get the PIDController not to crash when I put it on the cRIO.

I did run into some problems when trying to get the PIDController to drive towards my setpoint. The example from WPI above seemed to indicate I need to set my input to 0-5 for a potentiometer. After banging my head for a while and then figuring out how to output PIDController->GetError() to my dashboard I figured out that it is really looking for 0-1024 at least in my application. If you are having trouble getting your PIDController to make sense to you, you might check to see what numbers it is based on for your application.

Does anyone know what it would take to drive the PIDController based on the averagevoltage of the analog input? Would I set it up like:

turretControl = new PIDController(P,I,D, turretPot->GetAverageVoltage, turretMotor)

Or do I have to do something with the other PIDController sub-classes/functions?

I would think that GetAverageVoltage() would suffice.

Mike Soukup 04-02-2009 11:13

Re: WPILib PID controller object
 
Quote:

Originally Posted by sircedric4 (Post 813849)
Does anyone know what it would take to drive the PIDController based on the averagevoltage of the analog input? Would I set it up like:

turretControl = new PIDController(P,I,D, turretPot->GetAverageVoltage, turretMotor)

Or do I have to do something with the other PIDController sub-classes/functions?

The PIDController constructor takes floats for P, I, & D, a PIDSource pointer for the 4th argument, and a PIDOutput for the 5th argument. So assuming P, I, D are just placeholders for your constants, the first three look good. I'm also assuming that in your example, turretPot is an AnalogChannel pointer. Look at AnalogChannel.h and you'll see that it derives from PIDSource. You should just pass in your turretPot object, the way you wrote it should cause a compiler error. Again assuming turretMotor is a Jaguar or Victor pointer, the 5th argument looks good.

If you still don't understand exactly how the PIDController class works, here's a rundown of the methods that get called when it runs:
  • The PIDController hooks itself to an interrupt via the Notifier class and the Calculate() method gets called periodically
  • Calculate() calls m_pidInput->PIDGet(), where m_pidInput is the source that was passed into the constructor, so look at PIDGet() in the AnalogChannel class. That method is a single line that returns GetAverageValue().
  • After a bunch of fancy PID math, Calculate() calls m_pidOutput->PIDWrite(), where m_pidOutput is the output that was passed into the constructor, so look at PIDWrite() in the Jaguar or Victor classes. Those methods are just wrappers for Set().
Hopefully that clears up any misunderstandings about the PIDController.

sircedric4 04-02-2009 11:31

Re: WPILib PID controller object
 
Quote:

Originally Posted by Mike Soukup (Post 813950)
The PIDController constructor takes floats for P, I, & D, a PIDSource pointer for the 4th argument, and a PIDOutput for the 5th argument. So assuming P, I, D are just placeholders for your constants, the first three look good. I'm also assuming that in your example, turretPot is an AnalogChannel pointer. Look at AnalogChannel.h and you'll see that it derives from PIDSource. You should just pass in your turretPot object, the way you wrote it should cause a compiler error. Again assuming turretMotor is a Jaguar or Victor pointer, the 5th argument looks good.

If you still don't understand exactly how the PIDController class works, here's a rundown of the methods that get called when it runs:
  • The PIDController hooks itself to an interrupt via the Notifier class and the Calculate() method gets called periodically
  • Calculate() calls m_pidInput->PIDGet(), where m_pidInput is the source that was passed into the constructor, so look at PIDGet() in the AnalogChannel class. That method is a single line that returns GetAverageValue().
  • After a bunch of fancy PID math, Calculate() calls m_pidOutput->PIDWrite(), where m_pidOutput is the output that was passed into the constructor, so look at PIDWrite() in the Jaguar or Victor classes. Those methods are just wrappers for Set().
Hopefully that clears up any misunderstandings about the PIDController.

It does clear things up. The GetAverageValue() is what was blowing my mind till I could output what the code was doing and see it was looking for a 0-1024 value. The nice example that was posted previously in the thread indicated that the value to set the SetSetPoint needed to be between 0-5 because the potentiometer reads 0-5 volts. Your explanation shows that is not the case.

Once I just sent the information in terms of the 10 bit "raw" data the potentiometer outputs my robot started to converge. I am still trying to figure out what values of P,I, & D to send so that the turret doesn't jitter, but I understand that is mostly empirical depending on your system.

Edited to Ask:
How did you look at the source code of the WPILib to find what the PIDGet() call was doing? I can only find the .h files for the different classes. Needless to say it would be easier to figure out my problems if I could see what each of the library classes was actually supposed to be doing.

Jared Russell 04-02-2009 11:59

Re: WPILib PID controller object
 
You can get the full source on this page.

Scroll down to the link for WPILibRC0.1718SourceCode.zip

Mike Soukup 04-02-2009 14:24

Re: WPILib PID controller object
 
Quote:

Originally Posted by sircedric4 (Post 813961)
Once I just sent the information in terms of the 10 bit "raw" data the potentiometer outputs my robot started to converge. I am still trying to figure out what values of P,I, & D to send so that the turret doesn't jitter, but I understand that is mostly empirical depending on your system.

Your PID numbers should be tiny since the error is from 0 to 1024 and the output is from -1.0 to 1.0. Think on the scale of hundredths, thousandths, or smaller.

sircedric4 04-02-2009 14:29

Re: WPILib PID controller object
 
Quote:

Originally Posted by Mike Soukup (Post 814053)
Your PID numbers should be tiny since the error is from 0 to 1024 and the output is from -1.0 to 1.0. Think on the scale of hundredths, thousandths, or smaller.

Thanks! Once you pointed it out I had one of those little "eureka" moments. I have read a couple of papers from ChiefDelphi on PID and how it works, but it hasn't percolated enough for me to "grok" it until your one line sentence. :-) Now its starting to make some sense.

I love this forum, you guys are great with your fast responses to those of us muddling through this stuff for the first time.

sircedric4 08-02-2009 12:29

Re: WPILib PID controller object
 
Quote:

Originally Posted by Kruuzr (Post 808184)
Hi Brad...

This approach works great if your source is already pre-defined to be a PID source, but what about the situation ( such as an autotracking turret) where the source value will be an X offset from the middle of the image? Do we have to derive a new PIDSource object and implement any/all needed functions? The effort is compounded by the fact that the image offset is a relative number and would be hard to relate to an absolute turret positioning value.

Steve C

I guess Steve was ahead of the game by a week or so. Did you get your question answered because I have the same one now? How do I make the X position from center of the camera a new PIDSource so that I can then PID my turret to the center of the image?

I am using the variable name horizontalDestination = par1.centered_mass_x_normalized which gives me a nice -1 to 1 for the position of the target on the camera from centered. How do I make horizontalDestination a PIDSource so that the PIDController will drive the turret motor until horizontalDestination = 0?

Kruuzr 10-02-2009 13:03

Re: WPILib PID controller object
 
We've just coded this last night and hope to try it tonight. You need to derive your own class from the PIDSource class. In PIDSource.h, notice that there is one method defined:

virtual double PIDGet() = 0;

This is what's known as a pure virtual method; There is no default implementation This means you can't create a PIDSource object, only one derived from it with this method overridden with your own. The code looks something like this:

// .h file
#include <WPILib.h>
class MyPIDSource : public PIDSource
{
public:

double PIDGet();
}

// .cpp file
#include "MyPIDSource.h"

double MyPIDSource::PIDGet()
{
double pidSourceValue;

// calculate 'double' value that you want compared to the set point
// feel free to format it the same as you expect with your set point
pidSourceValue = ...

// return value
return pidSourceValue;
}

Before you create your controller object, you must create an instance of your class. Pass this pointer as a parameter to PIDController for the PIDSource arg.

Now, every 50 ms (assuming you've started it going), the PIDController will read the PID source, compare the value to the set point, do it's PID calculations, and write the new value to the PID output device.

With our setup, we are turning a turret that has a camera mounted on it to track the vision target. My PID Source uses the X component of the Particle report to get the error value. So our set point is permanently set to 0 (centered in the camera). But we have to take into account the position of the turret with respect to our two end stops. As we get closer to one of our stops (as read from our 10 turn pot attached to the turret gears) we have to supply a smaller number until it gets to 0 at the end, assuming we're driving it in that direction. We can also disable our 'auto tracking' by just returning a 0, and allow manual control by just passing back the z component of a joystick to represent the value. It's actually quite powerful.

I'll let you know how it works.

Hope this helped...

Steve

sircedric4 10-02-2009 14:25

Re: WPILib PID controller object
 
Quote:

Originally Posted by Kruuzr (Post 817943)
We've just coded this last night and hope to try it tonight. You need to derive your own class from the PIDSource class. In PIDSource.h, notice that there is one method defined:

virtual double PIDGet() = 0;

This is what's known as a pure virtual method; There is no default implementation This means you can't create a PIDSource object, only one derived from it with this method overridden with your own. The code looks something like this:

// .h file
#include <WPILib.h>
class MyPIDSource : public PIDSource
{
public:

double PIDGet();
}

// .cpp file
#include "MyPIDSource.h"

double MyPIDSource::PIDGet()
{
double pidSourceValue;

// calculate 'double' value that you want compared to the set point
// feel free to format it the same as you expect with your set point
pidSourceValue = ...

// return value
return pidSourceValue;
}

Before you create your controller object, you must create an instance of your class. Pass this pointer as a parameter to PIDController for the PIDSource arg.

Now, every 50 ms (assuming you've started it going), the PIDController will read the PID source, compare the value to the set point, do it's PID calculations, and write the new value to the PID output device.

With our setup, we are turning a turret that has a camera mounted on it to track the vision target. My PID Source uses the X component of the Particle report to get the error value. So our set point is permanently set to 0 (centered in the camera). But we have to take into account the position of the turret with respect to our two end stops. As we get closer to one of our stops (as read from our 10 turn pot attached to the turret gears) we have to supply a smaller number until it gets to 0 at the end, assuming we're driving it in that direction. We can also disable our 'auto tracking' by just returning a 0, and allow manual control by just passing back the z component of a joystick to represent the value. It's actually quite powerful.

I'll let you know how it works.

Hope this helped...

Steve

I have coded something up as well, but won't get a chance to test it till tonight either. I think this will work, but if any code guru's see something wrong let me know. I am also using a 10 turn pot on a turret gear and want to do exactly what you are talking about.

----------------------------------------
I have a CamSource.h which is:
----------------------------------------
#include "WPILib.h"

class CamSource : public PIDSource
{
public:
CamSource();

double PIDGet();
void SetSource(double);
private:
double source;

};

------------------------------------------------
My CamSource.cpp file is:
----------------------------------------
#include "CamSource.h"

CamSource :: CamSource()
{}

void CamSource :: SetSource (double sourcedum)
{
source = sourcedum;
}

double CamSource :: PIDGet()
{
return source;
}

----------------------------------------
Then in my main code I do the following to use it. Basically I set the CamSource to what the X difference on the camera is, and then feed it to my PIDController.

RobotCode.h:
----------------------------------------
PIDController *turretCamControl;
CamSource *CameraBrains;

RobotCode.cpp
----------------------------------------
CameraBrains = new CamSource();
turretCamControl = new PIDController(CamP, CamI, CamD, CameraBrains, TurretPIDMotor);

Then in my teleoperated periodic:
turretCamControl->Enable();
CameraBrains->SetSource(horizontalDestination);
turretCamControl->SetSetpoint(0);

-----------------------------------------
I think it will work, at least it compiles fine. horizontalDestination is the normalized X parameter from the camera. Thanks for your help, what you have outlined is pretty much the route I went as well. I don't know why, WPI couldn't just write a controller that allows us to just input a dang variable. It would be much easier.

sircedric4 12-02-2009 09:50

Re: WPILib PID controller object
 
All that code I posted above seemed to work fine. I haven't got the camera on the actual robot yet, that'll be tonight or tomorrow but the motor I had it on separately seemed to track correctly with the camera as the PIDSource.

Thanks again for the help.

Kruuzr 12-02-2009 12:39

Re: WPILib PID controller object
 
Glad to hear you have some initial success. What I noticed (and you may have accounted for this in your real code) is that it does not take into account any stops you may have. Can the turret turn 360 deg? We have two stops and a pot tied to the gears to give us an absolute position. We have to use that info to slow down/stop near the ends.

Steve C.

sircedric4 12-02-2009 21:57

Re: WPILib PID controller object
 
Quote:

Originally Posted by Kruuzr (Post 819315)
Glad to hear you have some initial success. What I noticed (and you may have accounted for this in your real code) is that it does not take into account any stops you may have. Can the turret turn 360 deg? We have two stops and a pot tied to the gears to give us an absolute position. We have to use that info to slow down/stop near the ends.

Steve C.

We have limit switches to stop our turret when it gets to either end along with hard stops. One thing I have noticed is that the way the code works on the machine is different this year it seems. I was playing around with another motor on limit switches today and it seems that the second you Set the Jaguar class the command is sent. I was handling limit switches with blanket if-then statements at the end of the teleop periodic that set the motors to zero if it would drive past the limit switches. What happened in real life was that my motors would "jitter" when the limit switch was depressed instead of just stopping.

I troubleshot this for a few hours before removing the various Set Jaguar statements throughout my code. Now I have a variable that I modify throughout the code, and only sent the actual Set statement once. I was used to nothing actually being commanded till the end of each loop and so I was used to being able to modify stuff after I had set it once earlier, but it looks like I can't get away with this now. I have no idea how this is gonna work with myPIDControllers yet but it doesn't bode well. Right now I am trying to figure out how I am gonna make sure the turret doesn't "jitter" when it gets to the limit switches. I am still using blanket if-thens at the end of my teleop loop for the turret until I can figure out some way to control my PID loop.

Kruuzr 13-02-2009 11:10

Re: WPILib PID controller object
 
We have our PIDController now up and running with either the z axis of a joystick or the x offset of a camera (same ranges) to give it its input (PIDSource). Last night, we had the turret tracking the ball pretty well. Time to tweak the PID constants:eek:

If you don't have a turret position sensor device of some sort, you can still incorporate the limit switches into the PID controller setup. In your PIDSource object, check the limit switch for the direction you are turning toward. If closed, return a 0.0 to stop the motor. If you're still getting some 'jitter', you can set a flag for the direction you were travelling to keep the output 0 until you start trying to track toward the other direction. The problem with just limit switches is that you can't start slowing down before the end stops.

One more thing that just struck me... If you're trying to use Set statements for the same Jaguar that the PIDController is using, you will be fighting it. The PID loop runs every 50ms. You can set it to 0, but the PIDController will shortly overwrite it.

Steve C.

Mr. Lim 16-02-2009 08:17

Re: WPILib PID controller object
 
Quote:

Originally Posted by sircedric4 (Post 817976)
Code:

void CamSource :: SetSource (double sourcedum)
{
        source = sourcedum;
}

double CamSource :: PIDGet()
{
        return source;
}


We've done pretty much exactly the same thing as you did here. I think we're running into a small concurrency problem though, and would like to hear from you as well.

As once in a blue moon your camera "spaz" while it is tracking?

Very rarely, but often enough to make it a pain, our camera tracker will move out of position quickly then recover for no apparent reason.

I think it's because of a classic concurrency issue where source is being read and written at the same time.

The PID controller runs in a separate task, and so potentially both SetSource and PIDGet could be called at the same time.

Would any more experienced programmers out there recommend locks? And if so, how would you implement them in the quoted code?

sircedric4 16-02-2009 08:26

Re: WPILib PID controller object
 
Quote:

Originally Posted by Kruuzr (Post 819909)
We have our PIDController now up and running with either the z axis of a joystick or the x offset of a camera (same ranges) to give it its input (PIDSource). Last night, we had the turret tracking the ball pretty well. Time to tweak the PID constants:eek:

If you don't have a turret position sensor device of some sort, you can still incorporate the limit switches into the PID controller setup. In your PIDSource object, check the limit switch for the direction you are turning toward. If closed, return a 0.0 to stop the motor. If you're still getting some 'jitter', you can set a flag for the direction you were travelling to keep the output 0 until you start trying to track toward the other direction. The problem with just limit switches is that you can't start slowing down before the end stops.

One more thing that just struck me... If you're trying to use Set statements for the same Jaguar that the PIDController is using, you will be fighting it. The PID loop runs every 50ms. You can set it to 0, but the PIDController will shortly overwrite it.

Steve C.

We finally got our robot integrated Saturday and took it out for test runs and practice and it turned out my PID loops just did not work. I tried to fix it for several hours and never could get the turret to move towards the center of the camera view. I finally called it quits Saturday night, and Sunday morning just set the motor speed to be equal to the camera X-axis difference.

I put a speed cap on the turret and it now works good enough for our purposes. I had the same problem with the PID controller stuff on my turret when it was trying to goto a position stated by another potentiometer. I don't know if I just don't understand the language good enough to debug it, but this year's experience has tailored my expectations from now on. I'll probably still try to get PID stuff working on future robots (as I have for the last 3 years), but I'll always have a backup plan to fall back on like I did this year. Our turret is now driven by joystick and the camera routine works adequately enough to zero in on a target once the user gets it close.

Kruuzr 16-02-2009 08:59

Re: WPILib PID controller object
 
Quote:

Originally Posted by SlimBoJones (Post 821927)
We've done pretty much exactly the same thing as you did here. I think we're running into a small concurrency problem though, and would like to hear from you as well.

As once in a blue moon your camera "spaz" while it is tracking?

Very rarely, but often enough to make it a pain, our camera tracker will move out of position quickly then recover for no apparent reason.

I think it's because of a classic concurrency issue where source is being read and written at the same time.

The PID controller runs in a separate task, and so potentially both SetSource and PIDGet could be called at the same time.

Would any more experienced programmers out there recommend locks? And if so, how would you implement them in the quoted code?

If your code in SetSource consists of just an assignment statement, it's probably not a concurrency issue. Although it's poor style to not protect it with a semaphore, a simple assignment of an int or float is usually what's called an 'atomic' operation, which means that it would not be interrupted in the middle by a task switch. (It happens in one machine language operation) I would look elsewhere for the cause.
If you have more than one line in SetSource (or if you want to get used to how to do multithreaded programming) you need to protect the function with a semaphore. The 'C programming guide for FRC.pdf' page 78 discusses how to do this. It's not hard, just a matter of adding a line at the beginning and end of your function.

Steve C


All times are GMT -5. The time now is 12:18.

Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi