An autonomous puck-shooting robot powered by ultrasonic navigation,
proportional-derivative (PD) wall-following, and encoder-controlled
precision firing.
The robot operates as a finite state machine. Transitions are driven by ultrasonic sensor readings and timer events.
| State | Behavior | Transition |
|---|---|---|
| Rotate CCW 1 | Spin counter-clockwise searching for back wall | ≤ 15 cm |
| Rotate CCW 2 | Continue spinning to detect side alignment | L + R ≈ 90 cm |
| Line Following | PD-controlled wall following using side sensors | ≥ 150 cm |
| Align & Shoot | Fine-tune alignment, then fire 3 pucks | 3 shots |
| Retreating | Drive in reverse toward loading zone | ≤ 15 cm |
| Loading | Wait 10 seconds for puck reload | timer |
3 TCRT5000 line sensors + 4 TSDP34356 IR sensors (2 front, 2 back) with MCP6294 op-amp conditioning for orientation.
Simplified to 3 HC-SR04 ultrasonic sensors (left, right, rear). Eliminated all analog circuitry for cleaner, more reliable sensing.
| Function | Arduino Pin(s) |
|---|---|
| Left Motor (PWM / IN1 / IN2) | 6 / 7 / 8 |
| Right Motor (PWM / IN3 / IN4) | 9 / 13 / 12 |
| Shooter Motor (PWM / IN1 / IN2) | 16 / 15 / 14 |
| Shooter Encoder (A) | 18 |
| Back Ultrasonic (Trig / Echo) | 2 / 3 |
| Left Ultrasonic (Trig / Echo) | 10 / 11 |
| Right Ultrasonic (Trig / Echo) | 4 / 5 |
Each HC-SR04 measures time-of-flight and converts to centimeters:
distance = (duration × 0.0343) / 2
Speed of sound ≈ 343 m/s = 0.0343 cm/μs. Divide by 2 for round-trip.
PD controller on the difference between left and right distances:
error = leftDist − rightDist
leftSpeed = BASE_L − Kp × err − Kd ×
deriv
rightSpeed = BASE_R + Kp × err + Kd × deriv
Tuned: Kp = 0.7, Kd = 0. Speeds clamped to
[0, 160].
Rotary encoder for precise 90° quarter-turn shots:
PPR = 690 → COUNTS_90 = 173
Slowdown window of 100 counts linearly ramps PWM from 205 → 15.
Timer1 CTC mode, prescaler 64:
OCR1A = 16MHz / 64 / 100Hz − 1 = 2499
Gives a 10 ms control period for the PD loop.
Wall alignment detected when L + R sum hits target:
101.3 cm ± 1.5 cm (rotation)
101.3 cm ± 0.5 cm (fine shoot alignment)
Full Arduino sketch — finite state machine with interrupt-driven encoder counting and a Timer1-based 100 Hz control loop.
Loading...
Raw ultrasonic readings were noisy and occasionally returned bogus values (>300 cm). A simple validity check dramatically improved reliability. A running average or median filter would have helped further.
Early on, our robot was very inconsistent — ultrasonic readings were unreliable when the robot was moving or facing a wall at a non-perpendicular angle. We fixed this by implementing a "strobe" pattern: the robot drives for 500 ms, then stops for 500 ms to take sensor readings while stationary. This gave the ultrasonics clean, stable measurements and made navigation dramatically more repeatable.
Our initial design used 3 line-following sensors, 4 IR sensors with op-amp conditioning, and a servo — way too complex. By stripping it down to just 3 ultrasonic sensors for all navigation, we eliminated most of our analog circuitry and failure points. Fewer sensors meant simpler code, easier debugging, and a more reliable robot.
Structuring code as an FSM made debugging straightforward — we could isolate and test each state independently. Serial logging of state transitions was invaluable.
Team Pringle made it to the semis of the ME 210: The Joy of Curling tourney!