Your First Mobile Robot: A Simple Collision-Avoiding Rover

A collision-avoiding rover is the ideal first mobile robot project because it combines every foundational robotics skill in a single, immediately satisfying build: wiring a DC motor driver to control two drive motors, connecting an ultrasonic distance sensor to detect obstacles, and writing a behavior loop that drives forward until an obstacle is detected then steers around it — producing a robot that navigates autonomously through any open space without any remote control or pre-programmed path.

Introduction

This is the article where everything you’ve learned about electronics, components, and microcontrollers comes together into something that actually moves, senses, and makes decisions. A collision-avoiding rover is not the most sophisticated robot in the world, but it is genuinely autonomous — it drives itself, senses its environment, and reacts to what it finds without any human input after the power switch is flipped. Watching it navigate around your kitchen table for the first time is one of those experiences in robotics that makes the hours of learning feel completely worthwhile.

More importantly, the skills you build here form the foundation for every robot that follows. The motor wiring you’ll do here is the same wiring you’ll use for a line follower, a robot arm, and a wheeled navigation platform. The sensor-to-behavior loop you’ll write is the same pattern used in sophisticated autonomous vehicles — just with more sensors and more complex responses. Getting it right on a simple project is how you get it right on complex ones.

This article is a complete, step-by-step build guide. By the end, you will have a working robot. Along the way, you will understand not just what to do but why each piece is the way it is — so you can adapt it, modify it, and use it as the starting point for your own designs.

What You Will Build

A two-wheeled differential drive rover that:

  • Drives forward continuously in open space
  • Detects obstacles using an HC-SR04 ultrasonic distance sensor
  • Stops when an obstacle is within 20cm
  • Backs up briefly, turns to one side, then resumes forward motion
  • Repeats this cycle autonomously for as long as the battery lasts
Top view of completed rover:

          ┌──────────────────────┐
          │    [HC-SR04 sensor]  │  ← faces forward
          │     (  )    (  )    │     eyes of the robot
          │                     │
          │   [Arduino Uno]     │
          │                     │
          │   [L298N driver]    │
          │                     │
  [Motor]─┤                     ├─[Motor]
  [Wheel] │   [Battery pack]   │ [Wheel]
          └──────────────────────┘
                  (caster)
                    ↓
              front of travel

Components List

Gather these components before starting. Links to specific products aren’t provided since availability varies by region, but these are standard components available from any electronics supplier (Amazon, eBay, AliExpress, Adafruit, SparkFun, local electronics shops):

Required:

  • Arduino Uno (or Arduino Nano with appropriate wiring adjustments)
  • L298N motor driver module (dual H-bridge, with heatsink)
  • HC-SR04 ultrasonic distance sensor
  • 2× DC gear motors with wheels (yellow “TT” motors are ideal — ~200 RPM at 6V, widely available in packs)
  • Robot chassis kit (acrylic or metal 2-wheel chassis, includes mounting hardware for motors and caster)
  • 4× AA battery holder (6V) OR 6× AA holder (9V) OR 2S LiPo battery with JST connector
  • Jumper wires (at least 20 male-to-male and 10 male-to-female)
  • Small breadboard (optional — for prototyping before final wiring)
  • 9V battery snap connector OR DC barrel jack (to power Arduino from same battery pack)
  • USB cable (for uploading code)

Optional but recommended:

  • Small power switch (to cut battery power without unplugging)
  • Cable ties (for securing wires inside chassis)
  • Hot glue gun (for securing sensor and components)
  • Multimeter (for verifying wiring before power-up)

Total cost estimate: $15–35 USD depending on sourcing and whether you buy a chassis kit or build your own.

Why These Components?

TT gear motors: Inexpensive, widely available, and perfectly matched to small chassis. The 1:48 gear ratio reduces motor speed from ~10,000 RPM to ~200 RPM while multiplying torque — enough to drive a small robot across most surfaces reliably.

L298N motor driver: Handles up to 2A per channel and 46V maximum — far more than these small motors need, giving comfortable headroom. The onboard 5V regulator can power the Arduino when the supply voltage is 7V or higher (saves one power supply).

HC-SR04: The most common beginner ultrasonic sensor for good reason — reliable, easy to use, and accurate to ±3mm over the 2cm–400cm range. At ~$1–3 each, you can afford to have spares.

Step 1: Assemble the Chassis

Most purchased chassis kits include an instruction sheet, but the general assembly process is:

1a. Attach motors to the chassis. The TT motors fit into slots on the chassis sides and are secured with small bolts or zip ties depending on the kit. Mount one motor on each side, with both motor shafts pointing outward (wheel will attach to the shaft).

1b. Attach wheels to motor shafts. Press-fit wheels onto the motor shafts. They should click or friction-fit onto the D-shaped shaft cross-section.

1c. Install the caster wheel. The caster (a small freely-rotating ball or wheel) mounts at the front or rear of the chassis and provides the third contact point, keeping the robot level. It should be positioned at the same height as the drive wheels — adjust mounting position if needed.

1d. Identify the motor wire colors. Each TT motor has two wires (usually red and black, but not always). Note which wires belong to which motor. The direction a motor spins depends on which wire is positive and which is negative — you will determine and potentially swap this in Step 4.

Step 2: Understand the L298N Motor Driver

The L298N module is the electrical interface between the Arduino’s low-current GPIO pins and the motors’ higher current requirements. Understanding its terminals prevents wiring mistakes.

L298N module terminal layout:

┌─────────────────────────────────────┐
│   12V  GND  5V                      │  ← Power input / 5V output terminals
│                                     │
│   OUT1 OUT2     OUT3 OUT4           │  ← Motor output terminals
│                                     │
│   IN1  IN2  EN_A    IN3  IN4  EN_B  │  ← Arduino control inputs
└─────────────────────────────────────┘

Terminal functions:
  12V:  Motor supply voltage (6–12V from battery; label says 12V but accepts lower)
  GND:  Common ground (connect to battery − AND Arduino GND)
  5V:   Output: 5V regulated output when motor supply ≥ 7V
        (Can power Arduino; short-circuits jumper when motor supply < 7V)

  OUT1/OUT2: Motor A output terminals (connect to left motor)
  OUT3/OUT4: Motor B output terminals (connect to right motor)

  IN1, IN2: Direction control for Motor A
    IN1=HIGH, IN2=LOW:  Motor A forward
    IN1=LOW, IN2=HIGH:  Motor A reverse
    IN1=HIGH, IN2=HIGH: Motor A brake (stop)
    IN1=LOW, IN2=LOW:   Motor A coast (stop, no braking)

  IN3, IN4: Direction control for Motor B (same logic as IN1/IN2)

  EN_A: Enable/speed for Motor A
    Jumper installed: Motor A always at full speed
    Jumper removed, PWM signal connected: speed control via PWM
  EN_B: Enable/speed for Motor B (same as EN_A)

The EN_A / EN_B jumpers: For the collision-avoiding rover, we want speed control (to back up more slowly, or future enhancement). Remove the yellow jumpers from EN_A and EN_B and connect Arduino PWM pins to these terminals. If you want simplest possible wiring first (full speed only), leave jumpers installed and skip EN_A/EN_B connections.

Step 3: Wiring — Complete Connection Diagram

Wire each connection one at a time, checking it off as you go. Do not connect the battery until Step 5’s pre-power verification.

Power Connections

Battery pack (+) ──────────────────────────── L298N terminal "12V"
Battery pack (+) ──── [Power switch] ──────── (route through switch first)
Battery pack (−) ──────────────────────────── L298N terminal "GND"
L298N terminal "GND" ──────────────────────── Arduino GND pin
L298N terminal "5V" ───────────────────────── Arduino 5V pin (powers Arduino)

Note: L298N 5V output is only present when motor supply ≥ 7V.
  6× AA (9V nominal): 5V output available → can power Arduino this way
  4× AA (6V nominal): 5V output NOT available → power Arduino via USB or 9V snap

Alternative Arduino power: 9V battery with snap connector to Arduino Vin/GND
  (Arduino's onboard regulator handles 9V → 5V)

Motor Connections

Left motor (+) wire  ──────── L298N OUT1
Left motor (−) wire  ──────── L298N OUT2

Right motor (+) wire ──────── L298N OUT3
Right motor (−) wire ──────── L298N OUT4

Note: "+" and "−" are provisional — actual direction determined in Step 4.
      If motor spins backward when expected to go forward, swap OUT1↔OUT2
      (or OUT3↔OUT4 for right motor).

Arduino to L298N Control Connections

Arduino Pin 5  ──────────────── L298N IN1   (Left motor direction 1)
Arduino Pin 6  ──────────────── L298N IN2   (Left motor direction 2)
Arduino Pin 7  ──────────────── L298N IN3   (Right motor direction 1)
Arduino Pin 8  ──────────────── L298N IN4   (Right motor direction 2)
Arduino Pin 9  ──────────────── L298N EN_A  (Left motor speed, PWM)
Arduino Pin 10 ──────────────── L298N EN_B  (Right motor speed, PWM)

HC-SR04 Ultrasonic Sensor Connections

HC-SR04 VCC  ──────────────── Arduino 5V  (or L298N 5V output)
HC-SR04 GND  ──────────────── Arduino GND
HC-SR04 TRIG ──────────────── Arduino Pin 11
HC-SR04 ECHO ──────────────── Arduino Pin 12

Complete Wiring Summary Table

Connection From To
Motor power + Battery (+) L298N 12V
Motor power − Battery (−) L298N GND
Common ground L298N GND Arduino GND
Arduino power L298N 5V Arduino 5V
Left motor A Left motor wire 1 L298N OUT1
Left motor B Left motor wire 2 L298N OUT2
Right motor A Right motor wire 1 L298N OUT3
Right motor B Right motor wire 2 L298N OUT4
Left dir 1 Arduino Pin 5 L298N IN1
Left dir 2 Arduino Pin 6 L298N IN2
Right dir 1 Arduino Pin 7 L298N IN3
Right dir 2 Arduino Pin 8 L298N IN4
Left speed Arduino Pin 9 L298N EN_A
Right speed Arduino Pin 10 L298N EN_B
Sensor power Arduino 5V HC-SR04 VCC
Sensor ground Arduino GND HC-SR04 GND
Trigger Arduino Pin 11 HC-SR04 TRIG
Echo Arduino Pin 12 HC-SR04 ECHO

Step 4: Mounting the Sensor

The HC-SR04 sensor must face forward — its two cylindrical transducers (one transmits, one receives the ultrasound pulse) need a clear forward view with no chassis obstructing the beam. Mount options:

On top of the chassis at the front: Secure the sensor flat on the chassis surface with double-sided tape or hot glue, transducers facing the direction of travel. This is the simplest mounting.

Upright at the front edge: Mount the sensor vertically at the front edge of the chassis. Most TT motor chassis have holes or notches suitable for zip-tie mounting. This gives the best forward view.

On a servo for scanning (optional enhancement): A hobby servo can pan the sensor left and right, allowing the robot to scan for the best escape route when an obstacle is detected. This is a worthwhile upgrade after the basic robot is working.

Height matters: mount the sensor at a height that detects obstacles the same height as the chassis body or taller. If the sensor is mounted too high, it will miss low obstacles that the chassis would hit. A height of 5–10cm from the ground typically works well for detecting chairs, table legs, walls, and similar obstacles.

Step 5: Pre-Power Verification

Before connecting the battery, verify the most critical connections with a multimeter:

Verification checklist:

□ Continuity between L298N GND and Arduino GND → should beep
□ Continuity between L298N 12V and battery (+) → should beep
□ Continuity between L298N GND and battery (−) → should beep
□ NO continuity between L298N 12V and GND → open (not shorted)
□ Continuity from HC-SR04 VCC to Arduino 5V → should beep
□ Continuity from HC-SR04 GND to Arduino GND → should beep

Check for motor shorts (with battery disconnected):
□ Continuity from OUT1 to OUT2 → should NOT beep (not shorted)
□ Continuity from OUT3 to OUT4 → should NOT beep (not shorted)

If any short exists between power and ground, find and fix it before connecting the battery. A short will immediately discharge the battery through a very low resistance path, potentially causing the battery to heat, vent, or (with LiPo) catch fire.

Step 6: The Complete Arduino Sketch

Upload this sketch to the Arduino using the Arduino IDE before connecting the battery for the first time. This way, when the battery is connected, the robot immediately begins executing tested code rather than sitting in bootloader mode.

/*
 * Collision-Avoiding Rover — Complete Sketch
 * Hardware:
 *   - L298N motor driver
 *   - Left motor: IN1=5, IN2=6, EN_A=9
 *   - Right motor: IN3=7, IN4=8, EN_B=10
 *   - HC-SR04: TRIG=11, ECHO=12
 *
 * Behavior:
 *   Drive forward → obstacle detected within STOP_DISTANCE → stop →
 *   back up briefly → turn → resume forward
 */

// ── Pin definitions ──────────────────────────────────────────────
const int IN1  = 5;   // Left motor direction 1
const int IN2  = 6;   // Left motor direction 2
const int ENA  = 9;   // Left motor speed (PWM)
const int IN3  = 7;   // Right motor direction 1
const int IN4  = 8;   // Right motor direction 2
const int ENB  = 10;  // Right motor speed (PWM)
const int TRIG = 11;  // Ultrasonic trigger
const int ECHO = 12;  // Ultrasonic echo

// ── Behavior parameters ──────────────────────────────────────────
const int  DRIVE_SPEED    = 180;   // Forward speed (0–255); start conservative
const int  TURN_SPEED     = 160;   // Speed during turns
const int  BACKUP_SPEED   = 150;   // Speed during backup
const float STOP_DISTANCE  = 20.0; // Stop if obstacle within this many cm
const int  BACKUP_TIME    = 600;   // ms to reverse before turning
const int  TURN_TIME      = 700;   // ms to turn (adjust for 90° turn on your robot)

// ── Motor control functions ──────────────────────────────────────

void setMotors(int leftSpeed, bool leftForward,
               int rightSpeed, bool rightForward) {
  // Left motor
  analogWrite(ENA, abs(leftSpeed));
  digitalWrite(IN1, leftForward ? HIGH : LOW);
  digitalWrite(IN2, leftForward ? LOW  : HIGH);

  // Right motor
  analogWrite(ENB, abs(rightSpeed));
  digitalWrite(IN3, rightForward ? HIGH : LOW);
  digitalWrite(IN4, rightForward ? LOW  : HIGH);
}

void driveForward(int speed) {
  setMotors(speed, true, speed, true);
}

void driveBackward(int speed) {
  setMotors(speed, false, speed, false);
}

void turnRight(int speed) {
  // Left motor forward, right motor backward = pivot right
  setMotors(speed, true, speed, false);
}

void turnLeft(int speed) {
  // Left motor backward, right motor forward = pivot left
  setMotors(speed, false, speed, true);
}

void stopMotors() {
  analogWrite(ENA, 0);
  analogWrite(ENB, 0);
  // Direction pins don't matter at speed 0, but clean state is good practice
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
}

// ── Distance measurement ─────────────────────────────────────────

float measureDistance() {
  // Send 10µs trigger pulse
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);

  // Measure echo pulse duration (timeout after 25ms = ~4.3m max range)
  long duration = pulseIn(ECHO, HIGH, 25000);

  if (duration == 0) return 999.0;  // Timeout → no obstacle detected (report far)

  // Sound travels at ~343 m/s = 0.0343 cm/µs
  // Distance = (duration / 2) × speed_of_sound (divided by 2: out + back)
  return (duration / 2.0) * 0.0343;
}

float getSmoothedDistance() {
  // Average 3 readings with brief pauses between them
  // HC-SR04 needs at least 60ms between measurements to prevent echo interference
  float sum = 0;
  for (int i = 0; i < 3; i++) {
    sum += measureDistance();
    delay(30);
  }
  return sum / 3.0;
}

// ── Setup ────────────────────────────────────────────────────────

void setup() {
  // Configure motor control pins as outputs
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(ENA, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  pinMode(ENB, OUTPUT);

  // Configure sensor pins
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);

  // Start with motors stopped
  stopMotors();

  // Serial for debugging (open Serial Monitor at 9600 baud to see distances)
  Serial.begin(9600);
  Serial.println(F("Collision-Avoiding Rover — Ready"));

  // Brief startup pause before beginning autonomous operation
  delay(2000);
}

// ── Main behavior loop ───────────────────────────────────────────

void loop() {
  float distance = getSmoothedDistance();

  Serial.print(F("Distance: "));
  Serial.print(distance, 1);
  Serial.println(F(" cm"));

  if (distance > STOP_DISTANCE) {
    // Path is clear — drive forward
    driveForward(DRIVE_SPEED);

  } else {
    // Obstacle detected — execute avoidance maneuver
    Serial.println(F("Obstacle! Avoiding..."));

    // 1. Stop
    stopMotors();
    delay(200);

    // 2. Back up
    driveBackward(BACKUP_SPEED);
    delay(BACKUP_TIME);

    // 3. Stop briefly
    stopMotors();
    delay(100);

    // 4. Turn (alternate left/right using a static variable for variety)
    static bool turnDirection = true;  // true = right, false = left
    if (turnDirection) {
      turnRight(TURN_SPEED);
    } else {
      turnLeft(TURN_SPEED);
    }
    turnDirection = !turnDirection;  // Alternate next time
    delay(TURN_TIME);

    // 5. Stop briefly before resuming
    stopMotors();
    delay(100);
  }
}

Understanding the Code Structure

setMotors() is the core motor function. It takes speed and direction for both motors independently, giving complete control over straight-line driving and turning. All other motion functions call this one.

measureDistance() implements the HC-SR04 protocol: send a 10µs trigger pulse, then measure how long the echo pin stays HIGH. The duration in microseconds divided by 58 gives distance in centimeters (equivalent to the × 0.0343 / 2 formula used in the code).

getSmoothedDistance() averages three readings with 30ms gaps between them. The HC-SR04 datasheet recommends at least 60ms between measurements to prevent the outgoing pulse from interfering with the echo detection — three readings with 30ms gaps gives a 90ms measurement cycle that exceeds this minimum.

The avoidance sequence (stop → back → turn → resume) is the simplest effective behavior. The alternating turn direction prevents the robot from getting stuck in a loop if it repeatedly encounters the same obstacle.

Step 7: First Power-Up and Testing

Phase 1: Stationary motor test

Upload the sketch. Open the Serial Monitor at 9600 baud. Lift the robot off the surface so the wheels spin freely. Connect the battery. The robot should:

  1. Print “Collision-Avoiding Rover — Ready” in Serial Monitor
  2. Wait 2 seconds (the startup delay)
  3. Begin driving motors forward (wheels spinning)

If motors don’t spin: Check EN_A and EN_B connections. Verify the jumpers are removed if PWM speed control wires are connected. Verify battery voltage with a multimeter.

If one motor spins, the other doesn’t: Check the non-spinning motor’s IN3/IN4/ENB connections. Measure voltage at OUT3/OUT4 — should see PWM if IN3=HIGH and IN4=LOW.

Phase 2: Motor direction test

Hold the robot with the front facing away from you and observe wheel rotation:

  • Both wheels should spin so the robot would drive forward (away from you)
  • If one or both wheels spin the wrong direction, swap that motor’s OUT1↔OUT2 (or OUT3↔OUT4) connections

Phase 3: Sensor test

Wave your hand in front of the HC-SR04. The Serial Monitor should show distances decreasing as your hand approaches. When your hand is within 20cm, the motors should stop and the avoidance sequence should trigger.

If sensor reads 999 constantly: Check TRIG and ECHO connections. Verify HC-SR04 is powered (5V/GND). The 999.0 return value indicates timeout (no echo received).

If sensor reads erratically: This is normal — some readings are noisy. The three-reading average reduces this. If still very erratic, check that nothing is directly in front of the sensor at startup.

Phase 4: Floor test

Place the robot on a smooth floor with clear space in all directions. It should drive forward, then steer around anything it encounters. Test in a room with obstacles at varying heights and angles.

Tuning for Your Specific Robot

Every robot is slightly different — motor speeds, wheel diameter, chassis weight, and surface friction all affect behavior. Expect to tune these parameters:

Speed Tuning

If the robot drives in a curve rather than straight (motors at equal speed), one motor is faster than the other. Reduce the PWM value for the faster side:

// Example: left motor slightly faster than right
const int LEFT_SPEED  = 160;  // Reduced to compensate
const int RIGHT_SPEED = 180;  // Full speed

// In driveForward():
setMotors(LEFT_SPEED, true, RIGHT_SPEED, true);

The ideal starting speed (DRIVE_SPEED = 180) provides good torque without stressing components. Too slow (< 100) and the robot may stall on carpet or at slight inclines. Too fast (> 220) and turns become imprecise.

Turn Time Tuning

The TURN_TIME constant determines how far the robot turns when avoiding. The goal is roughly a 90° turn so the robot heads in a perpendicular direction:

Tuning process:
1. Mark the robot's starting orientation with tape on the floor
2. Trigger an avoidance maneuver manually (block the sensor)
3. Observe how far the robot turns
4. Adjust TURN_TIME:
   - Robot turns less than 90°: increase TURN_TIME (try 900)
   - Robot turns more than 90°: decrease TURN_TIME (try 500)
5. Repeat until turn is approximately 90°

Stop Distance Tuning

STOP_DISTANCE = 20cm works for most surfaces and speeds. On carpet (slower), you may need 15cm. At higher speeds or with heavier robots, 25–30cm gives more stopping distance.

Enhancements to Try Next

Once the basic rover works reliably, these enhancements develop important new skills:

Enhancement 1: Add a Second Sensor

A second HC-SR04 mounted on the side (left or right) can detect walls before the robot drives into them from the side. Or mount two sensors angled left and right at 45° to give better coverage:

// Three-sensor version
const int TRIG_LEFT = 11, ECHO_LEFT = 12;   // 45° left
const int TRIG_FWD  = A0, ECHO_FWD  = A1;   // Straight ahead
const int TRIG_RIGHT = A2, ECHO_RIGHT = A3; // 45° right

// Choose turn direction based on which side has more space
float distLeft  = measureDistanceOnPins(TRIG_LEFT, ECHO_LEFT);
float distRight = measureDistanceOnPins(TRIG_RIGHT, ECHO_RIGHT);
if (distRight > distLeft) {
  turnRight(TURN_SPEED);
} else {
  turnLeft(TURN_SPEED);
}

Enhancement 2: Servo-Mounted Scanning Sensor

Mount the HC-SR04 on a servo that sweeps left and right. When the forward path is blocked, scan both sides and choose the direction with more clear space:

#include <Servo.h>
Servo scanServo;

int scanForBestDirection() {
  scanServo.write(180);  // Look left
  delay(300);
  float leftDist = measureDistance();

  scanServo.write(0);    // Look right
  delay(300);
  float rightDist = measureDistance();

  scanServo.write(90);   // Center
  delay(200);

  return (rightDist > leftDist) ? 1 : -1;  // 1=turn right, -1=turn left
}

Enhancement 3: Add LEDs for Status Indication

Status LEDs make the robot’s internal state visible — useful for debugging and more satisfying to watch:

const int LED_FWD   = 13;  // Green: driving forward
const int LED_AVOID = A4;  // Red: avoidance maneuver active

// In driveForward():
digitalWrite(LED_FWD, HIGH);
digitalWrite(LED_AVOID, LOW);

// At start of avoidance:
digitalWrite(LED_FWD, LOW);
digitalWrite(LED_AVOID, HIGH);

Enhancement 4: Serial Command Override

Add the ability to control the robot manually via Serial commands while it runs its autonomous code — useful for testing:

// In loop(), before the distance check:
if (Serial.available()) {
  char cmd = Serial.read();
  switch (cmd) {
    case 'f': driveForward(DRIVE_SPEED);  delay(500); break;
    case 'b': driveBackward(BACKUP_SPEED); delay(500); break;
    case 'l': turnLeft(TURN_SPEED);       delay(300); break;
    case 'r': turnRight(TURN_SPEED);      delay(300); break;
    case 's': stopMotors();               break;
  }
}

Type ‘f’, ‘b’, ‘l’, ‘r’, or ‘s’ in the Serial Monitor to override autonomous behavior momentarily. This is the foundation of a teleoperation mode.

Troubleshooting Reference

Problem Most Likely Cause Check / Fix
Robot doesn’t move at all Battery not connected or power switch off Verify battery voltage; check switch
Robot moves but immediately stops Sensor reading obstacles at startup Hold sensor away from obstacles; check mounting direction
One wheel doesn’t turn Missing IN3/IN4 or ENB connection Verify wiring for non-spinning motor
Both wheels turn same direction; robot spins in circle One motor wired backward Swap OUT1↔OUT2 or OUT3↔OUT4 for inverted motor
Robot drives in a curve Motor speed imbalance Reduce PWM value for faster motor
Sensor always reads 999 Bad TRIG/ECHO connection or no power to sensor Check HC-SR04 VCC, GND, TRIG pin 11, ECHO pin 12
Avoidance never triggers STOP_DISTANCE too small or sensor not working Increase STOP_DISTANCE; verify sensor reads correctly in Serial Monitor
Robot gets stuck turning in place TURN_TIME too long; turning past obstacle Reduce TURN_TIME; check floor for obstacles on all sides
Arduino resets during operation Insufficient power; battery voltage drop under load Check battery freshness; measure voltage under load
Robot works on smooth floor, stops on carpet Motor stall from friction; speed too low Increase DRIVE_SPEED; check motors aren’t mechanically binding

What You’ve Built and Learned

Completing this rover means you have:

  • Successfully wired a motor driver IC to control two DC motors with direction and speed control
  • Correctly connected and operated an HC-SR04 ultrasonic distance sensor
  • Written a multi-function Arduino sketch with a sensor-behavior loop
  • Debugged wiring and code through systematic testing
  • Tuned behavioral parameters (speed, timing, thresholds) for your specific robot

More fundamentally, you’ve built your first complete autonomous system — one where sensing, decision-making, and action happen together in a continuous loop without human intervention. This is the fundamental architecture of every autonomous robot, from this simple rover to a self-driving car: sense the environment, evaluate it against a goal or set of rules, execute an action, and repeat.

Understanding the Physics: Why the Robot Behaves the Way It Does

The rover’s behavior emerges from simple physics and geometry. Understanding these relationships lets you predict behavior before building and explains the outcomes you observe during testing.

Differential Drive Steering

The rover uses differential drive — two independently controlled wheels on the same axle. Steering is achieved by running the wheels at different speeds, not by turning a front wheel. This is the same principle used in tanks, bulldozers, and most wheeled robots:

Differential drive motion modes:

Both wheels forward, equal speed → drive straight forward
Both wheels backward, equal speed → drive straight backward
Left wheel forward, right wheel stopped → gentle right curve
Left wheel forward, right wheel backward → pivot turn right in place
Left wheel stopped, right wheel forward → gentle left curve
Left wheel backward, right wheel forward → pivot turn left in place
Both wheels different forward speeds → gradual curve toward slower wheel

The TURN_TIME parameter in the code controls how long the pivot turn lasts. The angle turned in a pivot turn depends on the rotation speed (determined by TURN_SPEED) and duration (TURN_TIME). A rough formula:

Turn angle ≈ TURN_SPEED × TURN_TIME × wheel_speed_per_PWM_unit / wheel_base_distance

For a typical small chassis (wheel base ~13cm), TT motors:
At TURN_SPEED = 160:  wheel surface speed ≈ 18 cm/s
Pivot turn speed ≈ 2 × 18 / 13 ≈ 2.77 rad/s ≈ 159°/s

TURN_TIME = 700ms → 159°/s × 0.7s ≈ 111° (close to 90°, varies with surface)
TURN_TIME = 560ms → 159°/s × 0.56s ≈ 89° (near-perfect 90° turn)

Surface matters: carpet increases friction → slower actual wheel speed → larger TURN_TIME needed

This is why TURN_TIME needs tuning — the formula above gives an approximation, but actual wheel speed versus PWM is affected by battery voltage, motor variation, and surface friction.

Ultrasonic Distance Measurement Physics

The HC-SR04 works by timing the round-trip travel of a 40kHz ultrasound pulse. Sound travels at approximately 343 m/s at room temperature (20°C). Faster at higher temperatures — an effect that can be corrected if precision matters:

Speed of sound vs. temperature:
  v = 331.3 + 0.606 × T_celsius  (m/s)

At 20°C: v = 331.3 + 12.1 = 343.4 m/s (0.03434 cm/µs)
At 35°C: v = 331.3 + 21.2 = 352.5 m/s (0.03525 cm/µs)
Difference: 2.6% faster at 35°C than 20°C

For a reading at 20cm from a wall:
  At 20°C: actual distance = 20cm, measured correctly
  At 35°C: code uses 0.0343, actual speed is 0.03525
           Measured = duration × 0.03434 / 2 = duration × 0.01717
           Actual = duration × 0.03525 / 2 = duration × 0.01763
           Error: (0.01717 - 0.01763) / 0.01763 = -2.6% (reads shorter than actual)
           At 20cm actual: measured ≈ 19.5cm → within 0.5cm → acceptable for rover

Temperature correction for precision applications:
  float speedOfSound = 0.0001 * (331.3 + 0.606 * temperature_C);  // cm/µs
  float distanceCm = (duration / 2.0) * speedOfSound;

For a collision-avoiding rover, the 2.6% temperature error doesn’t matter — you don’t need sub-centimeter accuracy to decide whether to steer around a chair. For precision distance measurement applications (mapping, docking), temperature correction becomes relevant.

The Beam Angle and Blind Spots

The HC-SR04 transmits an ultrasound cone approximately 15° wide (±15° from the sensor axis). Objects outside this cone are not detected. This creates blind spots:

Sensor field of view (top view):

                    [HC-SR04]
                       │
               ← 15° ─┼─ 15° →
              /         │         \
             /          │          \
            /           │           \
          detected zone             undetected zone

Objects more than 15° to either side of center won't reflect ultrasound
back to the sensor — robot appears to see "nothing" even if object is close.

This is why the rover can sometimes drive into an obstacle at an angle — the obstacle is at the edge of or outside the detection cone. The fix is adding sensors at wider angles (as described in Enhancement 1 above) or reducing robot speed so there’s more time to react when an obstacle enters the main detection cone.

Power Budget for the Rover

Understanding how much current the rover draws helps you choose an appropriate battery and predict run time.

Component current draw at operating conditions:

Arduino Uno (running sketch):              ~80 mA
L298N motor driver (quiescent, motors off): ~40 mA
HC-SR04 (active sensing):                  ~15 mA
Two TT motors (driving forward, moderate load): ~200–400 mA total
Two TT motors (stalled):                   ~800–1200 mA total
L298N internal dissipation (at 6V, 400mA): ~200 mA equivalent loss
                                            (voltage drop across L298N at 400mA)

Typical average current during operation:
  Forward driving: 80 + 40 + 15 + 300 = ~435 mA
  During avoidance: ~200 mA (briefer, at lower speeds)
  Combined average: ~380 mA

Battery runtime estimates:
  4× AA Alkaline (2500mAh, 6V nominal, ~80% efficiency in discharge):
    Runtime ≈ 2500 × 0.80 / 380 ≈ 5.3 hours theoretical
    Practical runtime: ~2–3 hours (voltage sag reduces efficiency)

  6× AA Alkaline (2500mAh, 9V nominal):
    Runtime ≈ 2500 × 0.80 / 380 ≈ 5.3 hours (same mAh, more voltage means L298N waste)
    Practical runtime: ~2–3 hours

  2S LiPo 1000mAh (7.4V nominal):
    Runtime ≈ 1000 × 0.85 / 380 ≈ 2.2 hours at C-rate consideration
    Practical runtime: ~1–1.5 hours (1000mAh is modest for this application)

  2S LiPo 2200mAh (7.4V nominal):
    Runtime ≈ 2200 × 0.85 / 380 ≈ 4.9 hours
    Practical runtime: ~3–4 hours — best option for extended operation

Note: The L298N has ~2V voltage drop across its output stage.
At 6V input, motors receive ~4V (reduced torque vs. rated 6V).
At 9V input, motors receive ~7V (slightly over-voltage but acceptable briefly).
At 7.4V LiPo input, motors receive ~5.4V (good operating point).

Code Architecture: Why It’s Written This Way

The sketch is deliberately structured to demonstrate good robotics code architecture, not just to make the robot work. Understanding the architectural choices helps you write better code for future projects.

Functions for Every Action

Every motion (driveForward, driveBackward, turnRight, turnLeft, stopMotors) is its own function. This makes the main loop() readable — it describes behavior in terms of actions, not pin numbers. When you want to change how “turn right” works, you change it in one place and it’s correct everywhere.

Separation of Sensing and Acting

measureDistance() only measures. getSmoothedDistance() only filters. driveForward() only drives. loop() only makes decisions. Each function has one responsibility. This separation makes debugging easier — if the robot behaves wrong, you know whether to look at the sensing functions or the action functions.

Non-Blocking Is Not Used Here (On Purpose)

This beginner sketch uses delay() — it blocks the entire program during backup and turn phases. This is intentional: the delay-based approach is simpler to understand and works correctly for this simple behavior.

The consequence: during a delay(BACKUP_TIME), the sensor is not being read. If an obstacle appears behind the robot while it’s backing up, it won’t be detected. For this simple rover, this is acceptable. For more sophisticated robots, replacing delay() with millis()-based non-blocking code is the correct evolution — and the state machine pattern from article 70 is the right tool for that step.

The Static Turn Direction Variable

static bool turnDirection = true;

The static keyword inside a function means this variable persists between calls — it’s initialized only once. This makes the robot alternate turns (right, left, right, left…) which helps it find its way past obstacles that a consistent right-turn robot would circle forever. It’s a small detail that makes a meaningful behavior difference.

Hot this week

Analog-to-Digital Conversion: How Robots Read Sensor Values

Learn how analog-to-digital conversion works in robotics—understand ADC resolution, sampling rate, reference voltage, noise reduction, and sensor calibration techniques.

Input/Output Pins: Your Robot’s Connection to the World

Learn how microcontroller I/O pins work in robotics—digital vs analog pins, INPUT/OUTPUT modes, PWM, current limits, protection, and practical wiring for sensors and actuators.

Memory in Robotics: RAM, Flash, and EEPROM Explained

Understand RAM, Flash, and EEPROM memory in robotics—learn what each type stores, how much is available, how to avoid running out, and practical optimization techniques.

Understanding Clock Speed and Processing Power in Robot Brains

Learn how clock speed and processing power affect robot performance—understand MHz, instruction cycles, benchmarking, and matching processor specs to your robot's real needs.

C++ Performance Profiling and Optimization Techniques

Master C++ performance optimization. Learn how to profile code, eliminate bottlenecks, leverage CPU caches, use Google Benchmark, and apply modern C++ techniques for maximum speed.

Topics

Analog-to-Digital Conversion: How Robots Read Sensor Values

Learn how analog-to-digital conversion works in robotics—understand ADC resolution, sampling rate, reference voltage, noise reduction, and sensor calibration techniques.

Input/Output Pins: Your Robot’s Connection to the World

Learn how microcontroller I/O pins work in robotics—digital vs analog pins, INPUT/OUTPUT modes, PWM, current limits, protection, and practical wiring for sensors and actuators.

Memory in Robotics: RAM, Flash, and EEPROM Explained

Understand RAM, Flash, and EEPROM memory in robotics—learn what each type stores, how much is available, how to avoid running out, and practical optimization techniques.

Understanding Clock Speed and Processing Power in Robot Brains

Learn how clock speed and processing power affect robot performance—understand MHz, instruction cycles, benchmarking, and matching processor specs to your robot's real needs.

C++ Performance Profiling and Optimization Techniques

Master C++ performance optimization. Learn how to profile code, eliminate bottlenecks, leverage CPU caches, use Google Benchmark, and apply modern C++ techniques for maximum speed.

Implementing Design Patterns in Modern C++: A Complete Guide

Discover how modern C++ (C++11/14/17/20) revolutionizes classic GoF design patterns. Learn to write safer, cleaner, and more efficient code using smart pointers, lambdas, concepts, and std::variant.

SIMD Programming in C++: A Comprehensive Guide to Vectorization

SIMD (Single Instruction, Multiple Data) programming in C++ is...

Writing Cache-Friendly C++ Code

Learn to write cache-friendly C++ code — understand CPU caches, cache lines, spatial and temporal locality, data-oriented design, struct layout, false sharing, and how to measure cache performance.

Related Articles

Popular Categories