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)

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.


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