Arcade Drive

It is time to create the ArcadeDrive command which will allow you to drive the robot using a joystick or XBox controller. As you might guess, the ArcadeDrive command for a swerve robot is a fair bit more complicated that that of the Minibot, but, fear not, as most of the heavy lifting is provided by wpilib.

We are going to start by using the following drive function which is provided by wpilib:

This takes as input the desired speed in the x and y directions (in meters/sec) and the desired angular rate of rotation (in radians/sec) and sets the module states to get the robot to drive and rotate as required. This function also takes a boolean fieldRelative which specifies whether the speeds are relative to the current orientation of the robot or absolute with respect to the field.

Now to make this work we are going to need to implement a number of things. The first thing we see is that it uses a variable m_kinematics which is an instance of SwerveDriveKinematics and is declared like this:

The constructor for this class takes the physical positions (in meters) of the swerve wheels with respect to the center of the robot. Pay close attention to the order of these parameters. There will be a number of places where you will see a list such as this and the order must always be the same.

Now in order to move relative to the field, we will need to know the current orientation of the robot. To do this we will need to add a Gyro:

Note that, for the moment, we will be computing the robot’s orientation solely from the gyro. Later, when we add a camera, we will want to incorporate the camera information to correct for gyro drift and this call will need to be changed. Also be sure to reset the gyro at startup by adding this to the DriveSubsystem constructor:

Note that once you create an instance of the gyro, there will be a gyro initialization process that happens on startup. The output to the console will look like:

You will need to wait until the initialization is complete before you can connect to the robot. Occasionally this initialization will fail and you will see a state of -1:

The system will try and retry the initialization but this always fails so if this happens you need to stop your program and restart it.

Now the function toSwerveModuleStates performs the complicated calculations required to set the position and velocity of all of the four wheels in order to move in the desired way. The output of this function is an array of SwerveModuleState with one value for each of the wheels. Note that the order of these states will be the same as the order you used when you created the SwerveDriveKinematics instance.

We then need to create a function that will apply these states to all four wheels:

The call to desaturateWheelSpeeds guarantees that we don’t try and run any of the wheels faster than they can go. We then call setDesiredState for each of the modules. Once again note the order of these. Naturally we need to implement setDesiredState in our SwerveModule class:

Note the call to optimize. This function optimizes the angle and speed by reversing the direction and adding 180 degrees if the amount the wheel must be rotated exceeds 90 degrees. This guarantees that the maximum amount the wheel will have to rotate is 90 degrees.

ArcadeDrive

We are now ready to create the ArcadeDrive command. It is a little complicated so I am going to give you the entire command and then I will discuss the details:

For the constructor we pass in the DriveSubsystem, of course, but we also pass in double suppliers which provide the joystick x, y, and rotation values. These will be direct values from the joystick so they will all range from -1 to +1.

The execute function is where everything happens. We first obtain the current values for the joystick x, y, and rotation settings. We then call the function applyDeadband which will ignore the input if it is within the specified dead zone. I am using 0.02 for the x and y and 0.20 for the rotation. I am using the larger value for the rotation because I am using the twist function (i.e. Z) of the Joystick (as opposed to an XBox controller) and it is very difficult not to slightly twist the joystick when moving the robot and I don’t want it to turn unless I twist it significantly.

We then take this result and pass it into SlewRateLimiter to limit the rate at which the values can change.

This result still represents a value from -1 to +1 and we need to convert this to meters/sec for the x and y and radians/sec for the rotation. We do this by multiplying them by the appropriate conversion factor k_maxDriveSpeedMetersPerSecond or k_maxAngularSpeed. Note that we have chosen 2 radians/sec for the maximum angular speed. I believe that the robot can actually turn faster than that (I have not actually measured it) but turning faster than that makes the robot more difficult to control. You can, of course, change this number to suit your needs.

We are then ready to call the drive function we created in the DriveSubsystem. When testing this I found when the input was all zero, the motors were a bit twitchy so when the inputs are all zero, I stop all the motors instead.

Maintain orientation

You probably will find that as you drive the robot around it’s orientation is not maintained (even when you are not rotating it with the joystick). This is because the precision of the motors and sensors are not perfect. This is particularly apparent when using these cheap motors and 3d printed gears, but you will find the same behavior with the larger FRC robots.

You can fix this issues by using the gyro to maintain the current orientation. The basic idea is that when you are not commanding the robot to rotate, you record the current heading. Then as it moves you measure the difference between the current gyro heading and the desired heading. Then you automatically add a rotate vector that is proportional to the difference between the desired heading and the current heading. This will cause the robot to automatically correct itself. You should create a constant that multiplies the heading difference (e.g. k_p) and adjust it as you would tune the P of a PID loop so that it turns to the correct heading as fast as possible without the robot oscellating.

The implementation of this feature is left as an exercise for the reader.

Next: Shooter Subsystem