Robot software

Prerequisites: Intro to real-time software
Corequisites: Intro to C++, Control theory

Disclaimer

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.

Installation

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

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

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:

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);
    }
}

Drivetrain with joysticks

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.

Button edge detection

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};
};

Toggle solenoid state

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);
}

Spin motor with joystick

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.

State machines

See the state machine and event framework labs for how we implement state machines, since they are commonly used tools.

Actuate motor to several points quickly

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.

Project setup

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.

Generic download

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.

Robot download

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.

Initial commit

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.