← All projects
Project 01 · Robotics & Mechatronics

hammerHead — Maze Solving Robot

A differential-drive robot that navigates an unknown line maze on its own. A 5-channel IR array reads the track, a PID loop keeps the robot centred on the line, and a decision layer above it resolves junctions by the left-hand rule, reverses out of dead ends, and confirms the finish before stopping. Built on an Arduino Nano with a TB6612FNG driver and N20 gear motors.

Controller
Arduino Nano
Sensing
5-ch IR array
Navigation
Left-hand rule
Drive
2 × N20 gear motor

Introduction

Two problems in one robot — staying on the line, and choosing where to go.

Most line followers do one thing: sit on a closed loop and stay centred. hammerHead has to do that and decide, at every branch, which way to go — with no map of the maze and no memory of having been there before. Those are two genuinely different problems, and keeping them separate is the whole design of the firmware.

The lower layer is continuous control: read the line position, compute the error, and apply a PID correction to the two wheel speeds. It runs every cycle and does one job — hold the line. The upper layer is discrete: each cycle it classifies what the sensor array is looking at, and if that pattern is something other than plain line, it interrupts the PID loop and executes a manoeuvre instead — take the left branch, U-turn out of a dead end, or stop at the finish.

Splitting it this way means the PID gains can be tuned once against straight line-following behaviour and then left alone, while the navigation logic is developed and debugged independently. It also means a failure is easy to localise: wobbling on a straight is a gains problem, taking the wrong branch is a decision-layer problem.

hammerHead maze solving robot on the maze
📷
Hero photo — assembled robot
assets/projects/line-follower/img/hero.jpg
Fig. 1 — hammerHead on the maze.

Design objectives

  • Solve an unknown maze — reach the finish without prior knowledge of the layout.
  • Deterministic decisions — the same junction always produces the same choice, so a failed run is reproducible and therefore debuggable.
  • Reliable tracking — no loss of line on the tightest curve between junctions.
  • No false finishes — a wide junction must never be mistaken for the goal.
  • Recoverable — able to reverse out of any dead end and continue.
  • Serviceable — sensor board and driver mounted so either can be swapped without a full teardown.

Demonstration

hammerHead running the maze.

What to watch for: This video demonstrates how the decision is made when the bot enciunters junction. Junction is defined as the places in the path where bot has >= 2 options for moving. Look for what bot does when it reaches the junction.

Hardware setup

Bill of materials and why each part was chosen.

Subsystem Component Why this part
Controller Arduino Nano (ATmega328P) Small footprint, 8 analog inputs — the full array reads directly with no multiplexer. 16 MHz leaves plenty of headroom for the control loop plus the decision logic.
Line sensor 5-channel IR reflectance array Does double duty: the weighted average gives continuous position for PID, and the raw pattern across all five channels classifies junctions, dead ends and the finish.
Motor driver TB6612FNG dual H-bridge MOSFET output stage — roughly 0.1 V drop versus ~1.5 V on an L298N. Crucially it also handles the rapid full reversals a U-turn demands without overheating.
Actuators 2 x N20 micro metal gear motor High torque density in a tiny package, and the metal gearbox survives the direction reversals that both PID correction and U-turns demand.
Gear ratio 1:100 Sets the speed/torque trade-off. Fill in your actual ratio and no-load RPM.
Wheels Dia. 45 x 15 (Width) Wheel diameter converts motor RPM into ground speed and sets the achievable turn radius.
Power 9 V DC Must hold voltage under the current spike when both motors reverse simultaneously.
Chassis 3D printed Rigid enough that the sensor-to-axle distance does not flex during cornering.
Caster ball caster Third contact point — low friction so it does not fight the differential drive or resist a spin turn.

Why TB6612FNG over the usual L298N: on N20 motors running at 6 V, the L298N's bipolar output stage wastes around 1.5 V as heat — a quarter of the supply gone before the motor sees it. The TB6612FNG uses MOSFETs and drops roughly 0.1 V, so the motors get nearly full battery voltage, the driver stays cool without a heatsink, and low-speed control near the PWM deadband is far more predictable. On a maze solver that matters twice over, because every junction turn is a low-speed manoeuvre.

Wiring

The sensor array is read on the analog inputs, the driver takes two PWM channels plus direction pins, and the motor supply is kept on a separate rail from the logic supply with a common ground. Replace the pin numbers below with your actual assignments.

Signal Nano pin Goes to
Sensor 1–5A0 – A4IR array analog outputs
PWMA / PWMBD5 / D9TB6612FNG speed inputs
AIN1 / AIN2D3 / D4Left motor direction
BIN1 / BIN2D7 / D8Right motor direction
STBYD4Driver standby — held high to enable
VMBattery positive (motor rail)
VCC5VDriver logic supply
Wiring diagram of the hammerHead electronics
🔌
Wiring diagram
assets/projects/line-follower/img/wiring.png
Fig. 2 — Electrical schematic: Nano, sensor array, TB6612FNG and motor rail.

Line sensing

Each IR sensor has an LED that generates infrared light, which strikes the surface below and reflections back to a receiver. The amount of reflected light depends on surface color: black surface absorbs most IR and reflects less, whereas white surface reflects more. The receiving photodiode gets different amounts of light based on the surface, and its resistance varies inversely with light intensity-higher IR light produces lower resistance. The arduino reads this as an anlog value, compares it against a threshold, and formats it as a binary digit: 1 = Black line detected, 0 = White surface. For example, when the robot is centered directly over the black line, the five sensors read 11011.

Question 1 — where is the line?

For the PID loop, the five normalised values are collapsed into a single weighted position. Multiplying each sensor value by its index and dividing by the sum gives a continuous estimate of where the line sits under the array — including positions between two sensors, which is what allows smooth proportional correction instead of bang-bang switching.

Question 2 — what am I standing on?

For the decision layer, the same readings are thresholded to a 5-bit black/white pattern. Four patterns matter, and everything else is treated as ordinary line following:

PatternMeaningAction
00000 All five on black — either a wide junction or the finish pad Ambiguous — must be resolved by driving forward and resampling
11111 All five on white — the line has ended Dead end → U-turn
111xx / xx111 Line continues left/Right Take required turn
11011 Centred on the line Normal PID following

The ambiguity that causes most maze-solver failures: a T-junction and the finish pad look identical the instant the array crosses them — all five sensors read black. They can only be told apart by what happens next. This is why the flowchart's finish branch drives forward a short distance and resamples before committing: if line reappears, it was a junction; if the array is still fully black, it is the goal. Getting this wrong in either direction is fatal — stop early and the maze is unsolved, miss it and the robot drives off the far edge.

The second detail that mattered was sensor ride height. A few millimetres too high and the IR spots overlap, so adjacent channels stop discriminating — the position estimate goes mushy and, worse, junction patterns become unreliable. Too low and the robot scrapes on any surface imperfection. Here, hammerHead is built with sensors at exactly 3 mm from path surface.

Close-up of the 5-channel IR sensor array mounted at the front of the chassis
📷
Sensor array mounting
assets/projects/line-follower/img/sensor-array.jpg
Fig. 3 — The 5-channel array on its front mount, showing the ride height.

Control strategy

Every cycle runs the same sequence: read the sensors, test for the three special cases in priority order, and if none of them fire, fall through to PID line following. The ordering is deliberate — finish is checked first because stopping is irreversible, then junctions, then dead ends.

Junction priority — the left-hand rule

At every junction hammerHead takes the leftmost available option: Left > Straight > Right. This is the left-hand wall-following rule, and it is not an arbitrary preference — for any simply connected maze (one with no detached island walls and no closed loops), consistently hugging one wall is guaranteed to reach the exit. It may traverse the entire maze doing it, but it cannot get permanently stuck.

The cost is that it is a blind algorithm — it stores nothing, learns nothing, and will walk into every dead end the maze offers. That is the trade: no memory, no map, no localisation, and correspondingly almost no RAM, in exchange for a guaranteed but non-optimal path.

  • Left available → turn left, always, even if straight ahead is clear.
  • No left, straight available → continue forward.
  • No left, no straight → turn right.
  • None of the above → dead end, U-turn.

Why the turn must overshoot the junction: the sensor array sits ahead of the drive axle, so the moment it detects a branch the wheels have not reached it yet. The turn routine has to drive forward by the sensor lead distance first, pivot, then hand control back to PID. Get this offset wrong and the robot pivots early, clips the corner, and loses the line — which then looks like a dead end and triggers a spurious U-turn.

PID tuning procedure

The gains are tuned on a plain looped track first, with the maze logic disabled — there is no point tuning a controller while a decision layer keeps interrupting it.

  1. Ki = Kd = 0. Raise Kp until the robot follows the line but oscillates steadily.
  2. Add Kd until the oscillation damps out. Typically an order of magnitude larger than Kp, because the derivative of a per-loop error is a small number.
  3. Add Ki last, only if there is a visible steady offset to one side. Clamp it — on a maze the error is never meant to be steady, and an unbounded integral winds up on every turn.
  4. Raise BASE_SPEED and repeat — gains that work at low speed will not hold at high speed.
  5. Re-enable the maze logic and verify junctions are still detected cleanly at the new speed.
Parameter Final value Effect of increasing
Kp50Sharper correction, more oscillation
Ki0.0Removes steady bias, risks windup
Kd5Damps overshoot, amplifies sensor noise
baseSpeed150Faster solve, less margin for junction detection

Code Structure

The firmware is written in Arduino C++.The flowchart shows the runtime decision order; the call graph shows how the functions depend on each other.

Program flowchart of the hammerHead maze solving firmware
🔀
Program flowchart
img/flowchart.png
Fig. 4 — Decision order: finish → junction → dead end → PID follow → motor control.
Function call graph of the hammerHead firmware
🕸️
Call graph
img/callgraph.png
Fig. 5 — Function call hierarchy and dependencies.

Program structure

  • setup() — configure pins, enable the driver, run the sensor calibration sweep.
  • readSensors() — sample all five channels, normalise, threshold to a B/W pattern.
  • allBlack() / allWhite() — pattern tests for the finish and dead-end branches.
  • finishDetected() — drive forward, resample, disambiguate finish from junction.
  • junctionDetected() — identify which branches exist at the current node.
  • handleJunction() — apply Left > Straight > Right and execute the turn.
  • turnBack() — 180° pivot to reverse out of a dead end.
  • followLine() — error, clamped integral, derivative, weighted sum.
  • motorControl() — map signed speeds onto TB6612FNG direction pins and PWM.

Source code: the complete Arduino sketch, wiring table and tuning notes are in the repository — github.com/DhanPrasad/hammerHead.

Mechanical design & assembly

Chassis layout, geometry, and the CAD model.

The chassis was modelled in CAD before anything was cut, so that the sensor offset, wheelbase and centre of mass could be set deliberately rather than discovered afterwards. On a maze solver the geometry is not just a handling question — it directly determines whether junctions are detected correctly.

  • Array width — must span wider than the line so that a side branch actually lands under an outer sensor. Too narrow and junctions are simply invisible.
  • Sensor lead distance — how far ahead of the drive axle the array sits. This is the distance every junction turn must compensate for, so it needs to be a known, fixed number.
  • Wheelbase and track width — set the minimum turn radius, and whether the robot can pivot in place cleanly for a U-turn.
  • Centre of mass — kept low and biased toward the drive axle so the wheels stay loaded and do not slip during a pivot.
CAD assembly render of hammerHead
🧩
CAD assembly render
assets/projects/line-follower/img/assembly-render.png
Fig. 6 — Full CAD assembly showing chassis, motor mounts, sensor bracket and electronics deck.
Exploded view of the hammerHead assembly
🪛
Exploded view
assets/projects/line-follower/img/exploded.png
Fig. 7 — Exploded view / assembly sequence.

The maze

The layout hammerHead was developed and validated against.

Layout of the line maze
🛣️
Maze / path layout
assets/projects/line-follower/img/path-layout.png
Fig. 8 — Maze layout showing junctions, dead ends and the finish pad.

Files & downloads

Drawings, datasheets and CAD.