Parts list
| Part | Qty | Approx. | Buy |
|---|---|---|---|
| Arduino Nano (CH340, ATmega328P) | 1 | ₹280 | Buy Arduino Nano (CH340, ATmega328P) |
| HC-SR04 ultrasonic sensor | 3 | ₹270 | Buy HC-SR04 ultrasonic sensor |
| TB6612FNG dual motor driver | 1 | ₹180 | Buy TB6612FNG dual motor driver |
| N20 metal gear motors 6 V 300 RPM + brackets + 34 mm wheels | 2 | ₹600 | Buy N20 metal gear motors 6 V 300 RPM + brackets + 34 mm wheels |
| Ball caster | 1 | ₹40 | Buy Ball caster |
| 2× 18650 Li-ion cells + holder with switch (7.4 V) | 1 | ₹350 | Buy 2× 18650 Li-ion cells + holder with switch (7.4 V) |
| Small chassis plate (acrylic or PCB, about 12 × 10 cm) | 1 | ₹120 | Buy Small chassis plate (acrylic or PCB, about 12 × 10 cm) |
| Mini breadboard + jumper wires | 1 | ₹100 | Buy Mini breadboard + jumper wires |
| Total | ₹1,940 |
Affiliate disclosure: “Buy” links may earn FlashReady a small commission at no extra cost to you. Prices are indicative.
Wiring
| Arduino Nano | Part · pin | Wire | Notes |
|---|---|---|---|
| — | TB6612FNG dual DC motor driver · A01 | — | To Left motor (+) (not drawn) |
| — | TB6612FNG dual DC motor driver · A02 | — | To Left motor (−) (not drawn) |
| — | TB6612FNG dual DC motor driver · B01 | — | To Right motor (+) (not drawn) |
| — | TB6612FNG dual DC motor driver · B02 | — | To Right motor (−) (not drawn) |
| 5V | HC-SR04 ultrasonic sensor · VCC | red | |
| D2 | HC-SR04 ultrasonic sensor · Trig | purple | Left sonar |
| D3 | HC-SR04 ultrasonic sensor · Echo | green | |
| GND | HC-SR04 ultrasonic sensor · GND | black | |
| 5V | HC-SR04 ultrasonic sensor #2 · VCC | red | |
| D7 | HC-SR04 ultrasonic sensor #2 · Trig | purple | Front sonar |
| D8 | HC-SR04 ultrasonic sensor #2 · Echo | green | |
| GND | HC-SR04 ultrasonic sensor #2 · GND | black | |
| 5V | HC-SR04 ultrasonic sensor #3 · VCC | red | |
| A1 | HC-SR04 ultrasonic sensor #3 · Trig | purple | Right sonar (A1/A2 used as digital pins) |
| A2 | HC-SR04 ultrasonic sensor #3 · Echo | green | |
| GND | HC-SR04 ultrasonic sensor #3 · GND | black | |
| D5 | TB6612FNG dual DC motor driver · PWMA | orange | Left motor speed |
| D9 | TB6612FNG dual DC motor driver · AIN1 | yellow | |
| D10 | TB6612FNG dual DC motor driver · AIN2 | green | |
| D6 | TB6612FNG dual DC motor driver · PWMB | orange | Right motor speed |
| D11 | TB6612FNG dual DC motor driver · BIN1 | blue | |
| D12 | TB6612FNG dual DC motor driver · BIN2 | cyan | |
| D4 | TB6612FNG dual DC motor driver · STBY | white | HIGH = driver awake |
| 5V | TB6612FNG dual DC motor driver · VCC | red | Logic at 5 V, like the Nano |
| GND | TB6612FNG dual DC motor driver · GND | black | Common ground: battery −, driver and Nano |
| 2× 18650 Li-ion pack (7.4 V) with switch · + | TB6612FNG dual DC motor driver · VM | red | Motor supply after the switch |
| 2× 18650 Li-ion pack (7.4 V) with switch · - | TB6612FNG dual DC motor driver · GND | black | |
| VIN | 2× 18650 Li-ion pack (7.4 V) with switch · + | red | The same switched + runs the Nano (VIN, 7–12 V in) |
Board quirks: The left-hand rule only works for mazes where every wall connects to the outer wall (“simply connected”). Mazes with islands in the middle — some competition mazes are built that way on purpose — need a mapping algorithm such as flood fill. The 90° turns are timed (<code>TURN_MS</code>), not measured. Tune it on your floor with a charged battery; as the battery drops the turns get shorter. Wheel encoders or an MPU6050 fix this properly. The three sonars fire one after another with a pause between them, so one sensor never hears another’s echo. Li-ion 18650 cells can catch fire if shorted or over-discharged: use a holder with a switch, protected cells, a proper Li-ion charger, and never leave the robot charging unattended.
Code
// Wall-following maze robot — Arduino Nano + 3× HC-SR04 + TB6612FNG + 2 N20 gear motors
// Sonars (Trig/Echo): left D2/D3, front D7/D8, right A1/A2, all at 5V.
// TB6612FNG: PWMA D5, AIN1 D9, AIN2 D10 (left), PWMB D6, BIN1 D11, BIN2 D12 (right), STBY D4,
// VCC 5V, VM from the 2S Li-ion pack. It keeps a wall on its LEFT at a set distance (the
// "left-hand rule"): turn left into every opening, go straight if you can, turn right if blocked.
// That solves any maze whose walls are all connected — most school and fest mazes.
constexpr uint8_t L_TRIG = 2, L_ECHO = 3, F_TRIG = 7, F_ECHO = 8, R_TRIG = A1, R_ECHO = A2;
constexpr uint8_t PWMA = 5, AIN1 = 9, AIN2 = 10, PWMB = 6, BIN1 = 11, BIN2 = 12, STBY = 4;
constexpr int WALL_CM = 8; // distance to keep from the left wall
constexpr int FRONT_STOP_CM = 9; // wall ahead closer than this → turn right
constexpr int OPENING_CM = 25; // left reading beyond this = an opening on the left
constexpr int BASE = 150; // cruising PWM (0–255)
constexpr float KP = 9; // steering gain: PWM per cm of error
constexpr int TURN_MS = 330; // time for a 90° pivot at TURN_PWM — tune on your floor
constexpr int TURN_PWM = 170;
void motor(uint8_t pwm, uint8_t a, uint8_t b, int speed) {
digitalWrite(a, speed > 0 ? HIGH : LOW);
digitalWrite(b, speed < 0 ? HIGH : LOW);
analogWrite(pwm, constrain(abs(speed), 0, 255));
}
void drive(int left, int right) {
motor(PWMA, AIN1, AIN2, left);
motor(PWMB, BIN1, BIN2, right);
}
// Distance in cm; 100 when nothing echoes within ~1 m (an open side).
int sonar(uint8_t trig, uint8_t echo) {
digitalWrite(trig, LOW);
delayMicroseconds(2);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);
unsigned long us = pulseIn(echo, HIGH, 6000UL);
delay(8); // let the echo die out before the next sonar fires (no crosstalk)
return us ? us / 58 : 100;
}
void pivot(int dir) { // dir −1 = left, +1 = right, 90° by time
drive(0, 0);
delay(60);
drive(dir * TURN_PWM, -dir * TURN_PWM);
delay(TURN_MS);
drive(0, 0);
delay(60);
}
void setup() {
Serial.begin(115200);
const uint8_t outs[] = {L_TRIG, F_TRIG, R_TRIG, PWMA, AIN1, AIN2, PWMB, BIN1, BIN2, STBY};
for (uint8_t p : outs) pinMode(p, OUTPUT);
pinMode(L_ECHO, INPUT);
pinMode(F_ECHO, INPUT);
pinMode(R_ECHO, INPUT);
digitalWrite(STBY, HIGH); // driver awake
drive(0, 0);
Serial.println(F("Maze robot: starting in 3 s"));
delay(3000);
}
void loop() {
int left = sonar(L_TRIG, L_ECHO), front = sonar(F_TRIG, F_ECHO), right = sonar(R_TRIG, R_ECHO);
Serial.print(F("L "));
Serial.print(left);
Serial.print(F(" F "));
Serial.print(front);
Serial.print(F(" R "));
Serial.print(right);
if (left > OPENING_CM) {
// Opening on the left: roll forward so the body clears the corner, then turn into it.
Serial.println(F(" -> left"));
drive(BASE, BASE);
delay(200);
pivot(-1);
drive(BASE, BASE);
delay(250);
} else if (front < FRONT_STOP_CM) {
Serial.println(right > OPENING_CM ? F(" -> right") : F(" -> dead end, turn round"));
pivot(1);
if (right <= OPENING_CM) pivot(1); // blocked on three sides: U-turn
} else {
// Corridor: steer to hold WALL_CM from the left wall.
int error = left - WALL_CM; // positive = drifting away from the wall → steer left
int steer = constrain((int)(KP * error), -80, 80);
drive(BASE - steer, BASE + steer);
Serial.println(F(" -> follow"));
}
}// Wall-following maze robot — Arduino Nano + 3× HC-SR04 + TB6612FNG + 2 N20 gear motors
// Sonars (Trig/Echo): left D2/D3, front D7/D8, right A1/A2, all at 5V.
// TB6612FNG: PWMA D5, AIN1 D9, AIN2 D10 (left), PWMB D6, BIN1 D11, BIN2 D12 (right), STBY D4,
// VCC 5V, VM from the 2S Li-ion pack. It keeps a wall on its LEFT at a set distance (the
// "left-hand rule"): turn left into every opening, go straight if you can, turn right if blocked.
// That solves any maze whose walls are all connected — most school and fest mazes.
constexpr uint8_t L_TRIG = 2, L_ECHO = 3, F_TRIG = 7, F_ECHO = 8, R_TRIG = A1, R_ECHO = A2;
constexpr uint8_t PWMA = 5, AIN1 = 9, AIN2 = 10, PWMB = 6, BIN1 = 11, BIN2 = 12, STBY = 4;
constexpr int WALL_CM = 8; // distance to keep from the left wall
constexpr int FRONT_STOP_CM = 9; // wall ahead closer than this → turn right
constexpr int OPENING_CM = 25; // left reading beyond this = an opening on the left
constexpr int BASE = 150; // cruising PWM (0–255)
constexpr float KP = 9; // steering gain: PWM per cm of error
constexpr int TURN_MS = 330; // time for a 90° pivot at TURN_PWM — tune on your floor
constexpr int TURN_PWM = 170;
void motor(uint8_t pwm, uint8_t a, uint8_t b, int speed) {
digitalWrite(a, speed > 0 ? HIGH : LOW);
digitalWrite(b, speed < 0 ? HIGH : LOW);
analogWrite(pwm, constrain(abs(speed), 0, 255));
}
void drive(int left, int right) {
motor(PWMA, AIN1, AIN2, left);
motor(PWMB, BIN1, BIN2, right);
}
// Distance in cm; 100 when nothing echoes within ~1 m (an open side).
int sonar(uint8_t trig, uint8_t echo) {
digitalWrite(trig, LOW);
delayMicroseconds(2);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);
unsigned long us = pulseIn(echo, HIGH, 6000UL);
delay(8); // let the echo die out before the next sonar fires (no crosstalk)
return us ? us / 58 : 100;
}
void pivot(int dir) { // dir −1 = left, +1 = right, 90° by time
drive(0, 0);
delay(60);
drive(dir * TURN_PWM, -dir * TURN_PWM);
delay(TURN_MS);
drive(0, 0);
delay(60);
}
void setup() {
Serial.begin(115200);
const uint8_t outs[] = {L_TRIG, F_TRIG, R_TRIG, PWMA, AIN1, AIN2, PWMB, BIN1, BIN2, STBY};
for (uint8_t p : outs) pinMode(p, OUTPUT);
pinMode(L_ECHO, INPUT);
pinMode(F_ECHO, INPUT);
pinMode(R_ECHO, INPUT);
digitalWrite(STBY, HIGH); // driver awake
drive(0, 0);
Serial.println(F("Maze robot: starting in 3 s"));
delay(3000);
}
void loop() {
int left = sonar(L_TRIG, L_ECHO), front = sonar(F_TRIG, F_ECHO), right = sonar(R_TRIG, R_ECHO);
Serial.print(F("L "));
Serial.print(left);
Serial.print(F(" F "));
Serial.print(front);
Serial.print(F(" R "));
Serial.print(right);
if (left > OPENING_CM) {
// Opening on the left: roll forward so the body clears the corner, then turn into it.
Serial.println(F(" -> left"));
drive(BASE, BASE);
delay(200);
pivot(-1);
drive(BASE, BASE);
delay(250);
} else if (front < FRONT_STOP_CM) {
Serial.println(right > OPENING_CM ? F(" -> right") : F(" -> dead end, turn round"));
pivot(1);
if (right <= OPENING_CM) pivot(1); // blocked on three sides: U-turn
} else {
// Corridor: steer to hold WALL_CM from the left wall.
int error = left - WALL_CM; // positive = drifting away from the wall → steer left
int steer = constrain((int)(KP * error), -80, 80);
drive(BASE - steer, BASE + steer);
Serial.println(F(" -> follow"));
}
}Compiled for nano on avr 1.8.8 · 3 Nov 2026
How it works
Put your left hand on the wall at a maze’s entrance and keep it there; you will walk out of any maze whose walls are all joined together. This robot does exactly that with sound instead of a hand. It’s a classic event at school science fairs and college robotics fests, and a good step up from line following: the robot has to steer by distance, not by a black line.
The left-hand rule in code
Every loop the Nano reads three HC-SR04 sonars — left, front and right — and decides:
- Opening on the left (left distance over 25 cm): roll forward 200 ms so the body clears the corner, pivot left 90°, and drive into the new corridor.
- Wall ahead (front under 9 cm): pivot right. If the right side is closed too, it’s a dead end — pivot right twice (a U-turn).
- Otherwise, follow the corridor: a proportional controller compares the left distance with the target 8 cm and steers by the difference —
steer = KP × (left − 8), limited to ±80 — slowing one wheel and speeding up the other.
Each decision is printed to Serial with the three distances, which makes tuning on the floor much easier with a long USB cable.
Why these parts
- HC-SR04 sonars are cheap and measure 2–100 cm well; a maze wall straight beside the robot is an ideal target. The sketch waits at most 6 ms per echo (about 1 m) and treats no echo as “open”.
- The TB6612FNG loses much less voltage than an L298N and is small enough for a maze robot. Its logic runs at 5 V with the Nano; PWM speed on D5/D6.
- N20 metal gear motors turn slowly and evenly, which matters for timed turns.
The Nano takes the battery straight into VIN (7–12 V) and its 5 V regulator feeds the three sonars and the driver’s logic — about 60 mA in total.
Building a test maze
Corridors of about 20–25 cm with walls at least 10 cm tall work well — cardboard boxes cut into strips and taped to the floor are enough. Keep the walls smooth and vertical; corrugated edges and gaps confuse the sonars.
Troubleshooting
It zig-zags along the corridor
KP is too high for your robot. Halve it, then raise it slowly until it tracks the wall smoothly.
It turns left in the middle of a corridor
The left sonar sometimes misses the wall (no echo reads as 100 cm = an opening). Mount it level and pointing square at the wall; ultrasound reflects poorly off a wall at an angle.
Turns are too short or too long
Adjust TURN_MS in steps of 20 ms until a pivot is a clean 90° on your surface.
Upgrades
- Add wheel encoders and turn by counting ticks instead of time.
- Record the turns and remove dead ends (the classic L-S-R-B shortening) for a faster second run.
- Replace the HC-SR04s with VL53L0X laser sensors for small micromouse-style mazes.



