Code Links
Leader PIC
Our leader PIC had 5 services: InitService, SPILeaderService, EncoderService, CollectService, and DispenseService. It controlled the game timer stop/start via a capacitive touch sensor, our indicator light, read our motor encoders, and various servo motors. The software also utilized a PIC32 SPI hardware abstraction layer.
State Machines
InitService.c
Description: This service keep track of the game stop/start and moves the bot from being idle with the indicators light off to game mode with the indicator light on. Once it receives a button press event on the capacitive touch sensor from an event checker via event ES_START_BUTTON, it posts an ES_INIT_GAME event to all services and toggles the game progress light on. Once the game timer expires, this service posts an ES_END_GAME event to all services. This stops the bot from moving at the end of a round and turns the game progress light off.
SPILeaderService.c
Description: This service handles communication with our follower PIC. It uses the PIC32_SPI_HAL_Starter.c file to set up the PIC in leader mode. If there is not a command actively in the queue to be sent to the follower, the leader sends a query byte to read in data from the follower PIC. If the command is something that exists in the leader's dictionary (stored in the header file) the leader reacts to/handles the new byte.
EncoderService.c
Description: This service tracks the motor shaft motion via interrupts. The number of ticks/pulses are counted based on an event that indicates whether the service should track the bot's straight trajectory or rotating trajectory. Once the target number of ticks has been reached, the encoder service posts a target reached event to the SPILeaderService to indicate the movement has been completed. We only tracked one phase of each encoder to save pin space.
CollectService.c
Description: The final collection software was implemented as a timer-driven finite state machine that coordinates the robot’s arm, grabber, and bucket arm to repeatedly acquire balls from the dispenser. While the original design included explicit approach and back-out states, the final CollectService focuses only on the ball acquisition sequence after alignment has already been completed by higher-level navigation control. Each state transition is primarily driven by timers and handshake events to ensure reliable mechanical motion.
Once triggered, the routine begins by moving the arm and grabber into a ready pose. The arm then lowers toward the dispenser pickup position. After the arm reaches the pickup pose, the robot requests a small forward nudge from the motion controller to ensure the grabber is properly positioned under the dispenser outlet. The grabber then closes to capture the ball, after which a small backward nudge is requested to pull the mechanism away from the dispenser. The arm is subsequently raised to the deposit position and the grabber opens to release the ball into the robot’s internal bucket. A counter tracks the number of collected balls, and the cycle repeats until six balls have been successfully acquired.
Note: The collection state machine begins once the robot has already reached the dispenser alignment position. At that point, the service executes a repeated arm–grab–lift–deposit cycle until the internal bucket contains six balls. When the target count is reached, the service posts a completion event and notifies the higher-level controller that the collection phase is finished.
DispenseService.c
Description: The DispenseService implements the robot’s ball dispensing routine using a timer-driven finite state machine that coordinates several servos to release collected balls into the scoring bucket. The system uses three primary actuators. A positional servo on OC5 (RB6) controls the push-down arm, which lowers to guide balls toward the bucket outlet. A second positional servo on OC4 (RA4) controls the bucket arm, which moves between an initialized travel pose and a dispense pose. A continuous-rotation servo on OC3 (RB10) rotates the bucket bottom to release the balls. In addition, a flag servo on RB3 indicates the robot’s field side (blue or green) using a Timer4-generated control signal.
When the service receives the ES_DISPENSE_START event, the push arm first lowers while the bucket arm remains in its initialized pose. After a short clearance delay, the bucket arm moves to the dispense position and pauses briefly to allow the mechanism to settle. The bucket bottom then rotates to dump the balls. The system alternates between shorter (~90°) and longer (~180°) rotations using a FirstDispense flag to ensure all balls are cleared across successive cycles.
After dispensing, the bucket motor stops and the system waits briefly for balls to finish dropping. The push arm then raises, the bucket arm returns to its initialized pose, and the service posts an ES_DISPENSE_COMPLETE event before returning to the idle state.
To avoid abrupt motion, positional servos use a slew-based controller that gradually steps commands toward the target position at ~20 ms intervals, producing smoother and safer mechanical movement.
Follower PIC
Our follower PIC had 4 services: NavigateService, SPIFollowerService, BeaconService, and ReflectiveSenseService. It controlled the motion of our bot's motors and the bot's steps through the game mode via navigate service, looked for IR beacons via the beacon service, sensed tape through ReflectiveSenseService, and communicated with the leader PIC through SPIFollowerService.
NavigateService.c
Description: This service controls the robots movements throughout the game active mode and directed the motion of our 'bot's motors. NavigateService posts to SPIFollowerService when the robot has reached key points (eg at collector, at bucket 1 or 2, side determined). It determines side by detected beacon L/R initially, then proceeds to the dispenser to collect 6 balls. After 6 collection attempts have been made, the service instructs the bot to move to the first bucket by reversing to it, dispenses the first three balls, then it uses line following to navigate towards the middle bucket and dispenses the other half of the balls.
SPIFollowerService.c
Description: This service handles communication with our leader PIC. It uses the PIC32_SPI_HAL_Starter.c file to set up the PIC in follower mode. Then, it uses an interrupt to detect if there has been any communication through SPI from the leader PIC, and if there is, it reads the buffer and fills it with it's own current command. This return byte to be sent to the leader is changed based on other services posting to the follower to change the command byte with ES_CMD_REQ. Any commands sent by the leader are first evaluated by the follower's command dictionary to ensure they are valid commands, then they are handled by the function HandleCommandByte which posts events to other services accordingly.
BeaconService.c
Description: The beacon service measures the period of incoming IR beacon pulses using Input Capture 1 and Timer3. The measured period is converted into a signal frequency, which is then classified against the known Seesaw beacon frequencies. To ensure reliable detection, the service requires the same beacon to be observed consistently for multiple consecutive cycles before confirming it. Once a beacon is confirmed, the service posts ES_BEACON_FOUND to NavigateService, including the detected beacon ID and the inferred bucket side.
ReflectiveSenseService.c
Description: The reflective sensing service interprets 5-bit tape sensor patterns from the robot’s reflective sensor array and converts them into navigation events used for line following and intersection detection. When a tape pattern change is detected, the service classifies the sensor bitfield to determine whether the robot is centered on the line, off-center, or encountering a corner or T-intersection. The corresponding event is then posted to NavigateService, which adjusts the robot’s motion accordingly. A short timer-based lockout prevents repeated T-intersection detections from being posted while the robot is still passing over the same intersection.
Note: The service acts as a bridge between the low-level reflective sensors and the higher-level navigation logic, translating raw sensor patterns into meaningful motion guidance events.
Event Checkers/HAL
PIC32 SPI HAL
PIC32_SPI_HAL_Starter.c
Description: On both the follower and leader, this file contains functions that help with pin initialization, SPI Leader setup, SPI Follower setup, and sending/reading data.
Event Checkers
EventCheckers.c
Description:
Leader: This file contains event checkers that read any keystroke inputs to the terminal (used for debugging) and for the start button press (sent via capacitive touch switch.) In response, it posts an event to all services linked to the event that occurred.
Follower: This file contains event checkers that read any keystroke inputs to the terminal (used for debugging), any changes to the readings of the 5 tape sensors, any changes in either limit switch, and for any changes in the IR distance sensor.