Encoder as PIDSource

WPILib question.

Is there a reason that the Encoder class doesn’t implement the PIDSource interface? I see that it includes the header file but it doesn’t actually inherit from PIDSource. Is this just a “TODO” for the lib or is there something fundamental that prevents using an encoder as PIDSource?

I’m asking because I was thinking we might want to use a PIDController to control the position of a “elevator” on our robot. Example, I might have a victor (the PIDOutput) that controls a motor for a chain drive that moves the elevator up and down and an encoder that tells me when i have reached my desired position.

Maybe the reason it wasn’t implemented is because one person what might want to use position as the PIDSource and another would want to use the rate as the PIDSource.

ya that’s a good point. Any suggestions on how to control the position of my elevator? I think I want some kind of feedback control. I’d rather not just run the motor at a specific speed for a specific time.

We just ran into this problem a couple hours ago. Does anyone know how to use the encoder as a PID source?

For position control, the GetRaw() and/or GetDistance() methods will probably provide the data you need. As far as how to do it, a simple subclass of both Encoder and PIDSource that implements the PidGet method should do the trick.

Thanks for the help everyone. I put together the subclass that Alexander described and will post it here in case it helps anyone else. It does compile but hasn’t been tested in any way.

PIDEncoder.h

#include "Encoder.h"
#include "PIDSource.h"

class PIDEncoder : public Encoder, PIDSource
{
public:
	//constructor
	PIDEncoder(UINT32 aSlot, UINT32 aChannel,
			UINT32 bSlot, UINT32 bChannel,
			bool reverseDirection, EncodingType encodingType);
	
	//destructor
	~PIDEncoder();
	
	//virtual from PIDSource
	double PIDGet();
	
private:
	
};

PIDEncoder.cpp

#include "PIDEncoder.h"

//constructor
PIDEncoder::PIDEncoder(UINT32 aSlot, UINT32 aChannel,
		UINT32 bSlot, UINT32 bChannel,
		bool reverseDirection, EncodingType encodingType) : Encoder(aSlot,aChannel,bSlot,bChannel,reverseDirection,encodingType)
{

}
	
//destructor
PIDEncoder::~PIDEncoder()
{
	
}
	
//virtual from PIDSource
double PIDEncoder::PIDGet()
{
	return this->GetDistance();
}

You just described what I want to do this year with our robot. We are also going to use an elevator and we want to use the Distance function of the Encoder for a PIDSource.

I have tried for 2 years to use the PIDSource class to drive our robot a certain distance with the encoders and failed each time. I always run out of time, and I, still after 2 years of beating through the C++ code, do not understand classes completely. I muddle through.

This year me and the students HAVE to figure this out in order for the robot they designed to work correctly, so needless to say, I am very interested in this thread.

How do you use your above PIDEncoder in the robot main code to get the elevator to stop at the correct distance? I see the class and that looks nice but I don’t know how to use it. If you want to PM me to keep “spoilers” away that’s fine, though honestly I guarantee you there are many students and mentors out there running into the same problems.

cplusplus.com has a good guide if you want to solidify your knowledge of C++.

To use the class that jwakeman posted, you need to make use of PIDController. Basically, initialize a PIDController with P, I, and D values, a PIDSource, and a PIDOutput in your constructor.

So, say you use a victor to drive your elevator and an encoder to measure distance, you might have something like this:


Victor *ap_elevatorDrive;
PIDEncoder *ap_elevatorEncoder;
PIDController *ap_elevatorControl;

Constructor()
{
    ap_elevatorDrive = new Victor(args);
    ap_elevatorEncoder = new PIDEncoder(args);
    ap_elevatorControl = new PIDController(1.0, 0.0, 0.0, ap_elevatorEncoder, ap_elevatorDrive);
}

Then, in your mainloop, you’d call the PIDController’s SetSetpoint() method with whatever distance you want the elevator to travel to; the units are wholly dependent on the values returned by PIDEncoder’s PidGet() method.

The WPILib User’s Guide has a complete example of position control with a PIDController and a potentiometer.

Hope that helped,
-Alexander

This did help, I have read the example you mention in the User’s Guide and finally got it to mostly work with a potentiometer last year, but didn’t know how to get it to work with an encoder. jwakeman’s sub-class was apparantly the magic I needed, and I wasn’t sure how to use it. Between your post and his I should be able to beat it into submission now.

Our goal this week is to get some sort of prototype setup so we can work on encoders since they have become mandatory for us this year with the design we want. Thanks everyone.

(P.S. I will read that tutorial you hyperlinked since I still have a hard time figuring out when to use *, &, ::, ., -> and all the other heiroglyphics in C++. If there was one simple use throughout for calling classes and such I could get it. The Getting Started with C++ guide from the FIRST website made an attempt to explain these differences in pointers and references and I am getting closer, but it could do with some full sample code examples instead of just snippets)

The example Alexander gave is probably a little simpler but here is a class we are working on to represent the elevator controller. Basically this class inherits from PIDController (it “is” a PIDController) and contains the victor and encoder objects that are required for PIDOputput and PIDSource. It is still under construction but it compiles and illustrates the concept I think. Also, our team is hosting an open source project for our development efforts on google code (http://code.google.com/p/first-team63/). Can’t guarantee when/what will get posted here but I will try to keep it updated as we make progress. Here the elevator class as it stands now:

ElevatorController.h:

#include "PIDController.h"
#include "Victor.h"
#include "PIDEncoder.h"

class ElevatorController : public PIDController
{
public:
	ElevatorController(float p, float i, float d,float period = 0.05);
	
	~ElevatorController();
	
	void SetHeight(double dHeight);
	
private:
	Victor *motorController;
	
	PIDEncoder *encoder;
	
};

ElevatorController.cpp:

#include "ElevatorController.h"

ElevatorController::ElevatorController(float p, float i, float d, float period)
: PIDController(p,i,d,this->encoder, this->motorController, period)
{
	this->encoder = new PIDEncoder(1, 1, 1, 1, false, CounterBase::k1X); //pass proper parameters here later
	this->motorController = new Victor(1,1); //and here
	this->PIDController::SetInputRange(0.0,0.0);//and here
}
	
ElevatorController::~ElevatorController()
{
	
}
	
void ElevatorController::SetHeight(double dHeight)
{
	this->PIDController::Reset();
	this->PIDController::SetSetpoint(dHeight);
	this->PIDController::Enable();
}

This is (was) a TODO… it will be implemented in the next update.

There is a new SetPIDSourceParameter() method that lets you pick which one is your process variable.

-Joe

I just tried to implement what you guys have been giving me into my code and when I try to compile I get the following error:

C:/WindRiver/Team2992_2011_Code_1/IterativeDemo.cpp:150: error: PIDSource' is an inaccessible base ofPIDEncoder’

and it highlights in red the line from my code below “elevatorControl = new PIDController(0.1, 0.01, 0.001, elevatorEncoder, elevatorMotor);”

I did tweak jwakeman’s PIDEncoder.h and ".cpp to below because I prefer to do without the slot designation for the cRio.

PIDEncoder.h


#include "Encoder.h"
#include "PIDSource.h"

class PIDEncoder : public Encoder, PIDSource
{
public:
	//constructor
	PIDEncoder(UINT32 aChannel,UINT32 bChannel,
			bool reverseDirection, EncodingType encodingType);
	
	PIDEncoder(UINT32 aSlot, UINT32 aChannel,
			UINT32 bSlot, UINT32 bChannel,
			bool reverseDirection, EncodingType encodingType);
	
	//destructor
	~PIDEncoder();
	
	//virtual from PIDSource
	double PIDGet();
	
private:
	
};

PIDEncoder.cpp


#include "PIDEncoder.h"

//constructor

PIDEncoder::PIDEncoder(UINT32 aChannel, UINT32 bChannel,
		bool reverseDirection, EncodingType encodingType) : 
		Encoder(aChannel,bChannel,reverseDirection,encodingType)
{

}

PIDEncoder::PIDEncoder(UINT32 aSlot, UINT32 aChannel,
		UINT32 bSlot, UINT32 bChannel,
		bool reverseDirection, EncodingType encodingType) : Encoder(aSlot,aChannel,bSlot,bChannel,reverseDirection,encodingType)
{

}
	
//destructor
PIDEncoder::~PIDEncoder()
{
	
}
	
//virtual from PIDSource
double PIDEncoder::PIDGet()
{
	return this->GetDistance();
}

And here is what I put in my IterativeRobot code:

Under Iterative Robot Class:

PIDEncoder *elevatorEncoder;
PIDController *elevatorControl;
Victor *elevatorMotor;

Inside Constructor:

elevatorEncoder = new PIDEncoder(11,12,true,Encoder::k4X);
elevatorEncoder->SetDistancePerPulse(0.04318);	//inches	//UPDATE!!
elevatorEncoder->Start();

elevatorMotor = new Victor(5);

elevatorControl = new PIDController(0.1, 0.01, 0.001, elevatorEncoder, elevatorMotor);

Did I do a boneheaded thing when tweaking jwakeman’s code, or did I not understand how to call the stuff correctly in my IterativeRobot code? Can you guys help me debug this issue?

Instead of

class PIDEncoder : public Encoder, PIDSource

explicitly declare both superclasses as public, like this:

class PIDEncoder : public Encoder, public PIDSource

Dang it! We had used the information that you guys had given us and finally had our robot working and PIDing with the encoders and then the WPI Library got updated with a mandatory update and broke it all again! I get a wall of red from redundant PIDSources or some such thing.

Grr, can someone please help me get set back up, now using the default, updated PIDController class that has the encoders as a source now. Specifically can someone help me figure out how to tell the encoder to use distance as its source?

So instead of constructing my encoders with the nice subclasses from earlier in the thread, I do:


Encoder *elevatorEncoder;
PIDController *elevatorControl;

//constructor
elevatorEncoder = new Encoder(11,12,true,Encoder::k4X);
elevatorEncoder->SetDistancePerPulse(0.009);	//inches	//UPDATE!!
elevatorEncoder->Start();

elevatorControl = new PIDController(0.1, 0.01, 0.001, elevatorEncoder, elevatorMotor);

//periodic
elevatorControl->Enable();
elevatorControl->SetPID(((leftStick->GetThrottle()+1)/2)*0.1,
	((rightStick->GetThrottle()+1)/2)*0.01,0.0);	//tuning
elevatorControl->SetSetpoint(ElevatorPosition);

I assume somewhere in the constructor I use
“void Encoder::SetPIDSourceParameter ( PIDSourceParameter pidSource )” from the help file, but I can’t find the list of variables which denotes distance. Can someone help me figure out how to get setup correctly?

Just add the call to SetPIDSourceParameter in your constructor. The PIDSourceParameter enum is defined in the Encoder header file and in the doxygen help.

-Joe

So I added this in my constructor:

elevatorEncoder->SetPIDSourceParameter(Encoder::kDistance);

Is that correct? We want to test our robot today and see how it performs and I can’t afford to introduce code problems outside of my experience level and lose a day of tweaking on our new hardware. (Today is hopefully first test day of our finalish robot)

I am not going to update the robot’s image until I know this will work. We have a working set of code with the previous posts and I’ll be using an un-updated computer to tweak our code while testing our robot today.

I am somewhat worried because when I was calling Encoder->, the SetPIDSourceParameter did not show up under the little menu of methods I could select. Also none of the colors changed when I entered the above line. Do I have to do a fresh build or something for the built in Windriver “IntelliSense” to catch up?

Looks OK to me. I’m not sure exactly what it takes to update the intellisense. Perhaps you have to refresh the project or reopen workbench or something.

-Joe

All right, just to post our results, we got the new update on our robot, and the new Encoder class works a champ now. The code I posted earlier does work as posted if anyone else is looking for examples.

The “Intellisense” will update if you close and re-open Windriver. Good day today.

It is nice that the Encoder class can be used as a PIDSource now but it ends up that it still doesn’t help me. I am using one encoder for both position AND speed feedback. Not a problem tho i just create two classes, RatePIDSource and PositionPIDSource. I pass a reference to a single Encoder into the constructor of these two objects and in their PIDGet() return distance or rate accordingly.