Software
We implemented a state
machine to effectively and robustly organize the various tasks that our
robot needed to accomplish. (For
more detail on our state machine, please visit this page.)
The breakdown of the major groups of modules is as follows. Pseudocode and source code for each state machine and module can be found here.
The breakdown of the major groups of modules is as follows. Pseudocode and source code for each state machine and module can be found here.
General
Our highest level of code consists of a Main file that
initializes our state machine and various physical sensors, and then repeatedly
runs our Event Checker to look for new events that affect our state
machine. These events include
triggers from our input sensors, new messages from the Field Master, and
internal events and timeouts that cause state transitions.
Timeouts
To ensure robustness, our code enforces a strict time limit
on nearly every machine state.
This means that our robot will never spend more than a set amount of
time sitting at a dispenser or traveling to a dispenser, but it also applies to
much lower-level states, such as querying a dispenser or requesting a ball,
looking for a line of tape, or waiting for a response from the Field
Master. Our diligence paid off, in
fact, during the semi-final round of competition. When our bump sensor failed to trigger when we arrived at
the first dispenser, our robot shortly gave up (timed out) and proceeded to the
next dispenser anyway, after which it succeeded in requesting and shooting
balls (see video of our semi-final round). In contrast, many other robots stalled
when they failed to receive a response from the Field Master or when they got
stuck en route to a dispenser.
Gameplay and State Machines
Our strategy was to travel to a dispenser, request balls from
that dispenser, and (if we were near our hoop) to shoot balls as we received
them. This made for an elegant
implementation through state machines.
If the game is running (the On state in our Game State state machine),
then our Drivetrain, Gameplay, and Sensors state machines are active. The Gameplay state machine controls
whether we are navigating to a new dispenser (To Dispenser state) or are
executing actions at a dispenser (At Dispenser state). While at a dispenser, we always run the
Request Ball state machine, but we only turn on the Shoot Ball state machine if
we are near our own hoop. This
made for a very simple adjustment to full court strategy – if we are near our
hoop we collect balls and shoot, otherwise we simply collect balls and hold
onto them until we are once again near our own hoop. For more detail on our state machine hierarchy, see here. For a best look at how we coded our state machine, see the
Gameplay section here.
Drivetrain
For most drive commands, our drivetrain module took as parameters a direction and a power. Possible directions ranged from turning left in place to driving straight to turning right in place, and possible powers ranged from full speed forward to stopped to full speed reverse. Our software interpreted the desired direction and power, and calculated the necessary individual motor speeds. As you can see in the mapping below, that's no trivial task!
The code used to implement the above equations is below. (Keep in mind that in our code Left is positive and Right is negative, opposite from above.) Direction, Power and Speeds range from -100 to 100.
signed char absDirection = (signed char)((Direction >= 0) ? Direction : -Direction); signed char LeftMotorSpeed = (signed char)(((signed int)Direction*Power <= 0) ? Power : ((100 - 2*absDirection)*(signed int)Power/100)); signed char RightMotorSpeed = (signed char)(((signed int)Direction*Power >= 0) ? Power : ((100 - 2*absDirection)*(signed int)Power/100));
where ( expression ? if_true : if_false) is the ternary operator. These results were then mapped to appropriate PWM duty cycles and polarities to give the desired motor speeds.