While the goal every year is to create the most competitive robot possible with the tools and abstractions we've built up so far, we want everyone to understand the entire robot's code base each year. Therefore, reusing complicated code from previous year's robots is off-limits unless everyone understands how it works internally (if it breaks, they'll know how to attempt fixing it). Writing documentation or tutorials/labs is encouraged to help other students understand pieces of software quicker in the future.
VSCode should already be installed from following the bootstrap page. For robot software development, follow the instructions here to install WPILib and roboRIO ARM toolchain. Other very important robot resources that you should go through can be found on docs.wpilib.org. These docs will help you learn more about what robots are made from, resources that you can use in your robot code, and example projects that might help you with something you're doing.
Design patterns are used all over software development. Expert programmers know the idiomatic design patterns for their language to solve a problem quickly, efficiently, and with few bugs. We have common design patterns in robot software. A list of design patterns with descriptions, usage advice, and examples is provided below. Also, we have robot software from previous years on GitHub that students can study, understand, and potentially reuse.
TimedRobot
abstracts away the loops required for
SampleRobot
and provides better timing guarantees. TimedRobot
provides Init()
functions, which are run upon the Driver
Station's transition into the corresponding mode, and
Periodic()
functions, which are run on an interrupt at a
regular interval (~20ms) during the corresponding mode. They include:
DisabledInit()
AutonomousInit()
TeleopInit()
TestInit()
DisabledPeriodic()
AutonomousPeriodic()
TeleopPeriodic()
TestPeriodic()
RobotInit()
and RobotPeriodic()
are run in all
modes.
TimedRobot's autonomous mode, for example, is roughly analogous to the following in SampleRobot.
using namespace std::chrono_literals; void Robot::Autonomous() { AutonomousInit(); while (IsAutonomous() && IsEnabled()) { AutonomousPeriodic(); std::this_thread::sleep_for(20ms); } }
There are multiple ways to drive a robot around; some are in open loop (the driver provides direct input to the system) and others are in closed loop (autonomous driving using encoders). This section discusses the former while the latter is covered in detail in the control theory module.
The two most common ways to drive a robot manually are with WPILib's DifferentialDrive or MecanumDrive classes and with another drive class implementing a custom drive scheme. To make a four-wheeled robot drive around as quick as possible, the following is all that is required (although please don't put function implementations in the header file).
#include <CANTalon.h> #include <Drive/DifferentialDrive.h> #include <Joystick.h> #include <TimedRobot.h> class Robot : public frc::TimedRobot { public: void TeleopPeriodic() { m_drive.CurvatureDrive(m_forwardJoy.GetY(), m_turnJoy.GetX()); } private: CANTalon m_flMotor{0}; CANTalon m_rlMotor{1}; frc::SpeedControllerGroup m_leftMotors{m_flMotor, m_rlMotor}; CANTalon m_frMotor{2}; CANTalon m_rrMotor{3}; frc::SpeedControllerGroup m_rightMotors{m_frMotor, m_rrMotor}; frc::DifferentialDrive m_drive{m_leftMotors, m_rightMotors}; frc::Joystick m_forwardJoy{0}; frc::Joystick m_turnJoy{1}; };
If more complex functionality and features is desired or your drive train is not supported by WPILIb, a custom drive scheme can be used instead. To implement one, the following is required.
See DifferentialDrive.cpp for examples.
In TeleopPeriodic()
, performing actions when the user
presses a button is typically required. The simplest way to do this uses a
Joystick and two booleans for tracking the past and current state of one of
its buttons.
#include <Joystick.h> #include <TimedRobot.h> class Robot : public frc::TimedRobot { public: void TeleopPeriodic() { // If button was pressed if (!oldButtonState && newButtonState) { std::cout << "Joystick button 2 pressed" << std::endl; } // If button was released if (oldButtonState && !newButtonState) { std::cout << "Joystick button 2 released" << std::endl; } // If button was held if (oldButtonState && newButtonState) { std::cout << "Joystick button 2 held" << std::endl; } // Update step oldButtonState = newButtonState; newButtonState = joystick.GetRawButton(2); } private: frc::Joystick joystick{0}; bool oldButtonState = false; bool newButtonState = false; };
First, the booleans are initialized to false
to represent a
released button. At the end of every while loop iteration, the current
state is copied to the old state and a new one is obtained from the
joystick. By performing this operation once every loop iteration, we are
guaranteed to see every rising or trailing edge and can act on it.
Unfortunately, as more buttons are checked, this approach becomes messy and unmaintainable. Also, the possibility of mistyping a boolean check increases and encourages copy-pasting code. WPILib provides functions in the Joystick class for this purpose.
#include <Joystick.h> #include <TimedRobot.h> class Robot : public frc::TimedRobot { public: void TeleopPeriodic() { if (joystick.GetRawButtonPressed(2)) { std::cout << "Joystick button 2 pressed" << std::endl; } if (joystick.GetRawButtonReleased(2)) { std::cout << "Joystick button 2 released" << std::endl; } if (joystick.GetRawButton(2)) { std::cout << "Joystick button 2 held" << std::endl; } } private: frc::Joystick joystick{0}; };
Toggling solenoids to extend or retract mechanisms can be done with the following pattern.
frc::Solenoid solenoid(1); solenoid.Set(!solenoid.Get());
The following is a more verbose way of doing the same thing.
frc::Solenoid solenoid(1); if (solenoid.Get()) { solenoid.Set(false); } else { solenoid.Set(true); }
This pattern is typically used to manually move mechanisms driven by a motor up or down.
frc::Joystick joystick(0); CANTalon motor(1); motor.Set(-joystick.GetY());
There is a negative sign because GetY()
returns negative
values when the joystick is pushed forward.
See the state machine and event framework labs for how we implement state machines, since they are commonly used tools.
This is done when a mechanism driven by a motor should be moved from one position to another reliably and quickly. Note that this should only be done when the device could potentially be set to more than two positions within its range. If only two are required, a solenoid should be used instead. See the labs in the state machines module for examples on how to do this.
WPILib's encoder example may be helpful.
Each year, a new robot project will need to be created in Gerrit (our code review platform) and on GitHub for our competition robot code. First, an admin should create a new empty repository in the frc3512 GitHub organization (no README or license) and in Gerrit. Give them a name and description following the form of the previous years' repositories like "Robot-2017" and "The source code for the 2017 FRC robot.". Next, clone the repository from Gerrit and add the following files to it. See below the list for exceptions in downloading the provided files.
These files should be included in all of FRC Team 3512's software projects.
LICENSE.txt
will need the initial copyright year updated to
the current year. README.md
should be edited to be unique to
the project.
These files should only be included in robot code projects. Some replace the generic files listed above.
README.md
will need the year and robot name updated. "?"
can be used until the robot name is chosen. src/Constants.hpp
is a file we typically use every year and is provided as a placeholder so
the src
folder is added to Git history.
After the files are added to the repository, they should be committed in the first commit with the commit message "Initial Commit". This can then be pushed to Gerrit for review or pushed directly to main by admins. Gerrit will automatically mirror commits to main on GitHub.