Paul's Tutorials - logoWPITips and Tricks



This page is intended to provide useful information that is either too long to explain in the main tutorial or would not otherwise be included in any lesson. All of these require a certain level of understanding in order to be used. Like the tips and tricks page from the other tutorial, that will be listed below the header for each section.

Note that the levels listed here are the absolute minimum levels of knowledge you will need to implement them. It may be easier for you to learn them at a later time.

If you have not reached lesson 2-1 yet, you may want to read the note about how functions are presented in this tutorial.

Defined Constants

Required Level: Lesson 1-1

You can read more about constants here.

The Header Definition

Aside from providing functions and other utilities for use in a source file, header files can also define specific values that can be accessed using a custom name. Usually, this name is written in ALL_CAPS_WITH_NO_SPACES, utilizing underscores if necessary. A sample header definition is given below.

#define MY_NUMBER 4

Essentially, we are telling the compiler's preprocessor to create a constant called MY_NUMBER, with a value of 4. The compiler will then replace any instance of MY_NUMBER in associated code with the number 4.

Remember that you need to include the header file in all other files with which you intend to use the constant.

Use

The use of the header definition is quite simple. Simply write it instead of its value. So, if we had the function void MyFunction(int), and we wanted to pass it MY_NUMBER, you would simply write:

MyFunction(MY_NUMBER);

This would be equivalent to calling MyFunction(4).

Why?

The use of a header definition depends on the application of the header file itself. In our case, we are using it to turn seemingly arbitrary numbers into a more easily understood format. If you were writing a library, you might use a header definition to avoid the difference of values that occurs between platforms.

Timers

Required Level: Lesson 1-3

If you remember from Lesson 1-3, there was a Timer object that could be used to time events. This section will teach you how to use it.

Managing the timer

The Timer object comes with the following functions to manage the timer:

void Timer::Start()
void Timer::Stop()
void Timer::Reset()

Obviously, you would use these whenever you wanted to start, stop, or reset the timer, respectively. However, the operation of the timer isn't that foolproof.

For one, you should keep in mind that the Reset() function does not stop the timer if it is running. The timer will continue to run, should you only call the Reset() function.

Secondly, the Timer object does not always function as planned. It has some issues, and as such, it should become habitual that you call the Stop() and Reset() functions (back-to-back, in that order) if you need to reset the timer, anyway. Should that not work, then call Stop(), Start(), and Reset() (the first two can switch, depending if you need the timer to continue running or not). Should that not work, then another course of action might be advisable.

You should note that an initialized timer that has not been started yet will have a value of 0, so, depending on what exactly you are using the Timer for, you may need to call the Start() function in the class constructor after in initializing the timer.

Checking the timer

Of course, a timer would be not useful if it did not have some way to access its time.

double Timer::Get()
bool Timer::HasPeriodPassed(double period)

The Get() function simply accesses the time (in seconds) that the timer has counted.

The HasPeriodPassed() function is like checking to see if the timer has gone off, so to speak. Its only parameter is the length, in seconds, of the timed period. Note that HasPeriodPassed() will continue to return true once the defined period has passed. In order to make it return false again, you need to reset the timer.

Timeouts

Required Level: Lesson 2-1

One of the downsides of the GetRawButton() function is that it returns true if there is a button pressed, regardless of how long that it has been pressed for. Because the roboRIO is running the TeleopPeriodic() routine in a loop, one button press in the driver's perspective may register anywhere from 5 to 20 times in the roboRIO. Due to that, manipulating variables by set amounts (incrementing/decrementing) is trickier than it sounds. This section discusses how to create a button timeout.

It is recommended that you create a wrapper class to handle the timeout, as it makes keeping track of the button press a lot cleaner.

There are two methods to do this.

Timer

Requires knowledge of how to use a timer.

This method is the easier method to implement, since it only requires checking the timer. However, this method is also less robust than the other, due to the generally high maintenance level of the Timer() object.

Required Objects

Creating a Timeout with a Timer

For simplicity's sake, let's assume that you have already declared and initialized the above objects.

The first thing that you should do when creating any timeout is check to see if the timeout parameters have passed. This may seem odd at first, as when the TeleopPeriodic() routine is first entered, the timeout will not have occurred. However, you need to rembember that the same code will run over and over. Think 4th-dimensionally!

Here, we will use the HasPeriodPassed() function. A half a second should do for the timeout period. You should not forget that the timer will have a value of 0 upon entering the TeleopPeriodic() routine unless you start the timer at initialization.

Once you have confirmed that it is OK to check for a button press (either the timer has past the timeout or the timeout has not been started yet), check for one. If there is a button press, then run the action you need to run on the button press, then stop, start, and reset the timer (to ensure proper function).

//Assume that the Timer m_timer has been initialized and started in the class constructor.

if (m_timer->HasPeriodPassed(0.5))
{
	if (m_joystick->GetRawButton(1))
	{ 
		//run the action...
		m_timer->Stop();
		m_timer->Start();
		m_timer->Reset();
	}
}

Because the timer is running on its own, that is all you need to do to implement a button timeout with a Timer().

Button Watching Timeout

This method is more robust than the previous one, in large part because it does not rely on a timer. This method also allows for you to easily keep track of individually pressed buttons. It will also not register a long hold as several presses (a Timer timeout will). However, it is slightly harder to implement.

Required Variable

Creating a Button Watching Timeout

This method is not a true timeout, in the sense that it does not actually keep track of time throughout its function. There are two steps to implementing a button watching timeout.

The first step is to check for a button press. While you are checking for a button press, you also need to check to see if the button is currently being held down, using the bool flag that you declared.

Should that pass, then perform your action. Then, set your button flag to true.

Assuming you have a Joystick named m_joystick and a bool named m_pressed:

if (m_joystick->GetRawButton(1) && !m_pressed)
{
	//perform action
	m_pressed = true;
}

The second step is to check and see if the button has been released. This can be done by inverting the boolean statements in the if statement in the previous step. If that condition passes, then reset the flag.

if (!m_joystick->GetRawButton(1) && m_pressed)
{
	m_pressed = false;
}

In this case, once the driver releases the button, the second step will pass, which will enable the first step to register a button press again.

Wrappers

Recommended Level: Lesson 2-2

Note: This concept is similar to subsystem wrappers utilized with PID controllers, but not identical.

Sometimes, the classes provided by WPILib don't provide enough functionality to get a certain task done efficiently and in an organized manner. In order to fix this problem, you can create a class that wraps around (hence the term) a core WPILib class and add the functionality you want.

The way wrapper classes are written depends on the class that is being wrapped around. However, there are some basic concepts that can be discussed about wrappers.

What a Wrapper Does

More specifically, a wrapper is designed to automate tasks performed with a specific class so that they do not need to be rewritten and rewritten in your code. Usually, a wrapper carries out the functions of the core class via functions of the same name, but add extra functionality in the process.

Wrapper functions should be designed so that they interface with external code as much like the original class as possible. So, for example, say you were wrapping around a class with the following function:

bool SomeClass::GetActive (int id) 
//returns true if whatever SomeClass is associated with is 'active' on the specified id, false otherwise

If you were to write a wrapper function for this function, it should look somewhat like this:

bool SomeClassWrapper::GetActive (int id) //returns based on same logic as SomeClass

with the only change being the logic that the GetActive() function operates by.

However, this is not always the case. Sometimes, a function's parameters can be confusing or unclear, which may necessitate the function's parameters changing slightly. In this case, the function's parameters should still represent the same thing, but the way they are passed can change.

For example, if the int id from above represented buttons on a game controller, an enum can be used as a parameter instead.

enum ids {
	ButtonA = 1,
	ButtonB = 2,
	ButtonX = 3,
	//...
}

bool SomeClassWrapper::GetActive (ids id)

How a Wrapper Works

To create a wrapper, create a blank class. Do NOT inherit the class you're trying to wrap around (unlike subsystem wrappers). Instead, instantiate an object of that class as a private member of the wrapper class.

The next thing you need to concern yourself with is the constructor. First, think about how many of the core class constructor's parameters should be available in the wrapper's constructor. Sometimes, the values in the core constructor remain constant in your implementation, so you can just hard-code those parameters into the core object initialization in your constructor and move on. If not, then add them as a parameter for your wrapper class.

Be sure to think about any initialization that your wrapper class might need in addition to the parameters needed by the core class. If you have any configurable functionality in your wrapper that is vital to its function, you should consider adding it to your constructor's parameter list.

The next thing you should do is look at the core class' header file and read through its list of public functions. You're not going to be able to access its private or protected functions, so don't worry about those.

Consider which of the class' public functions need to carry through in your wrapper. If you don't plan on rewriting a function, but you nonetheless need it, write a small function in your class that has the same parameters and return type as the original function, and pass all of the parameters to the original, and return the result, if necessary.

With functions that you do need to add logic to, create a function of the same name and return type as the original function. In that function, gather the data you need from the original, and perform any extra logic that you need there.