-
Notifications
You must be signed in to change notification settings - Fork 0
Defining the Robot
The Robot object is the main element in the VexOS project. The Robot is a singleton, meaning there is exactly one instance of type Robot. The actual Robot variable is hidden, as it is not generally useful outside of VexOS internal functions.
By convention, the Robot is created in a source file named Robot.c. The declarations of the main robot functions and global variables, as well as all Subsystems, Commands and Buttons are in the corresponding Robot.h file.
Below is an example of a simple Robot.c file that demonstrates all of the typical elements:
//
// Robot.c
//
#include "Hardware.h"
#include "Robot.h"
// robot declaration, with Subsystems to install //
DefineRobot(&Drive, &Lift, &Intake);
// shared global variables, should be declared in Robot.h //
Joystick* joystick;
// private variables, only needed in this file //
static PowerExpander* expander;
static LCD* lcd;
// robot constructor, create hardware here //
static void constructor() {
joystick = Joystick_new(1);
expander = PowerExpander_new("Expander", PowerExpanderType_Rev_A1, AnalogPort_1);
lcd = LCD_new("Main LCD", UARTPort_1);
}
// robot initializer, runs after all hardware is ready //
static void initialize() {
// general setup //
VexOS_setProgramName("My test program of DOOM!");
VexOS_setupStandardUI();
// setup control scheme //
Button* b = Joystick_getButton(joystick, JoystickHand_Left, JoystickButtonType_Up);
Button_whileHeld(b, Command_new(&FireTheLaser, LaserDamage_Massive));
// create autonomous "program" Commands //
Autonomous_addProgram(Command_new(&ScoreABillonPoints));
Autonomous_addProgram(Command_new(&SelfDestruct));
Autonomous_restoreLastProgram();
}A typical robot will include both Hardware.h and Robot.h. Robot.h is always needed. But since hardware objects not associated with any Subsystem (such as the Power Expander, the LCD, any autonomous buttons, etc.) are usually declared in Robot.h, you will almost always need Hardware.h too. The order of inclusion is not critical.
The DefineRobot(..subsystems..) statement is a pre-processor macro that creates the Robot singleton instance. Behind the scenes, this macro is making a variable of type struct Robot and populating it with pointers to the Subsystems and the methods defined within the Robot.h file. Using a macro ensures that this process is done correctly, and keeps the dirty bits from the eyes of small children.
The argument to this macro is a comma-separated list of Subsystem pointers. Because Subsystems are visible as variables, not pointers, you must use the C address-of operator (&) to create a pointer to the Subsystem. If that sounds like greek, remember is as a simple directive: put & before Subsystems when you pass them to DefineRobot(...). That's all you need to know about pointers for now.
If you forget the & and type something like this:
// robot declaration, with Subsystems to install //
DefineRobot(Drive, &Lift, &Intake, &Pivot);You will receive an error message similar to the follow:

That is the one major disadvantage of using the pre-processor to hide the dirty bits: error messages are difficult to interpret, because you can't see the true code causing the error. The line number is correct, however, so that should get you on the right track.
There is one other caveat, but it shouldn't come up too often. The pre-processor does not like receiving an empty argument, so if robot is required with no Subsystems, it must be declared as follows:
DefineRobot(NULL);Global variables are values, typically object references (i.e. pointers) that are shared throughout the robot and its components. In this case, the sole global variable is a Joystick object pointer. This is a typical situation, since a Joystick may be read by multiple Commands and it is wasteful to declare the same Joystick more than once.
A declaration for global variables must also be present in Robot.h:
extern Joystick* joystick;This tells everything that includes Robot.h, which is just about everything in the robot, that the joystick global variable is available for use. The actual definition of the variable is what is done in Robot.c.
Note the * after the object type. This means that the variable is not actually a Joystick, but a pointer to a Joystick. In Java, all references to objects are pointers. In C, you have a choice of whether you want a pointer or the actual object data itself. When working with VexOS, similarly to Java, we will always want pointers. If you should forget the *, and type:
Joystick joystick;You will receive an error like this:

The reason for this is that VexOS is hiding the details of the data type Joystick from you, and therefore the compiler can't figure out how much memory it should take up. When we have a pointer, it means the memory is allocated somewhere far away, so Robot.c doesn't need to know anything about it. The non-greek rule for VexOS objects is that they must always be declared as pointers. As with the & operator, that's about all you need to know if you don't wish to get any deeper into learning the C language.
For the curious: the author debated whether the existence of a pointer should be hidden by a typedef, but ultimately decided to stick with C that would be readable to a wide audience, rather than doing something confusing for the sake of beginners. The hope is that when there is no interest in getting deeper into C, the & and * will be absorbed simply as convention.
Private variables are essentially the same as global variables. The key differences are the presence of the static qualifier in front of the variable definition, and the fact that there is no corresponding declaration of the variable in Robot.h. These private variables are only visible to code inside of Robot.c. Good programming practice is that private variables should be used for anything that does not need to be global.
Note also that because PowerExpander and LCD are VexOS objects, they must be declared as pointers.
The robot constructor is a function that is called when the Robot is being created (by VexOS, during "boot up" of the Cortex controller). The function is called only once, immediately after the Cortex powers up or is reset. The constructor function is declared static because it is never called directly by anything outside of the Robot.c file. In this case, it means the same thing as the static used on private variables.
The majority of the work typically done in a constructor is to populate the global and private variables. You can see this happening in the example. The functions being called (Joystick_new(...), PowerExpander_new(...), LCD_new(...)) are all part of VexOS and are declared in VexOS.h or Hardware.h. You can probably pick out the pattern: to create a new VexOS object, and return to you a pointer to the instance that was created, a function of signature _OBJECT__new(...) is called with whatever parameters are needed to construct the object.
The purpose of the robot constructor is to create hardware objects and other non-Command related elements. You are not allowed to create Commands in the robot constructor (or any Subsystem constructor, for that matter). If you try to create a Command in a constructor, the code will compile, but will fail with an error when you try to run it. The reason for this restriction is that the newly-created hardware is not guaranteed to be functional until after the Robot constructor finishes. Commands typically depend on having functional hardware. To avoid these problems, Commands must be created later in the start-up sequence.
For more information see: VexOS Execution Cycle
The robot initializer is run after all hardware is set-up and is guaranteed to be functioning (though it may not be fully capable, i.e. when disabled motors don't actually run, but you can set them, or during autonomous, joysticks always read as 0, but reading is allowed).
Typical operations performed in the initializer are:
- Set-up VexOS core configuration, like the program name and/or the user interface
- Set-up the control scheme by binding Buttons to Commands
- Create and configure autonomous programs
- Register event handlers to do fancy stuff
Typically, once the initialize is finished, nothing in Robot.c will be called again. The robot functions completely on the objects and configuration that were set-up in the initializer (and constructor). This is a rather neat fact and a pleasing consequence of the object-based design philosophy. Of course, there are exceptions to this if events are used. See VexOS Events.
Of note, the function Command_new(class*, ...) takes a pointer to the Command class to instantiate. Command classes are visible to the user program as global variables, so to create a pointer, the & operator must be used, similar to when referencing Subsystems. This is explained more in Defining Commands.