Lesson 6 of 10

Building Your First Robot

The Plan: an Obstacle-Avoiding Car

This is the first machine that puts everything together. The robot drives forward on two wheels, watches ahead with an ultrasonic sensor, and when something gets close it stops, backs off, turns, and carries on. It is the classic first robot for a good reason: it uses the complete sense-think-act loop, it fails in instructive ways, and every fix teaches something you will reuse.

Build it in stages and test after each one. This single piece of advice separates an evening's satisfying work from a week of frustration. If you wire the whole thing, upload the full sketch and nothing happens, you have a dozen candidate causes and no way to narrow them down. If you test the motors alone, then the sensor alone, then combine them, each stage has one possible fault and you find it in a minute.

The chassis does not have to be bought. Stiff cardboard, foam board or thin plywood works perfectly well. What matters is that the two drive wheels are level with each other and that the third support point — a castor wheel, a furniture slider, or even a smooth bottle cap — slides freely. An uneven chassis makes the robot veer, and you will waste an hour blaming the code.

  • Arduino Uno with USB cable
  • L298N motor driver module
  • Two DC gear motors with wheels
  • One HC-SR04 ultrasonic distance sensor
  • A castor wheel or any smooth third support point
  • Battery pack for the motors, plus a switch if you can get one
  • Chassis material, double-sided tape or cable ties, and jumper wires
Notes
  • Add an on/off switch between the battery and the driver if you possibly can. Repeatedly pulling a battery connector apart to stop a runaway robot loosens the wires and eventually breaks them, usually the evening before you have to demonstrate the project.

Assembly, and the Order That Saves Time

Mount the two motors on opposite sides at the same height, then the third support point at the front or back so the chassis sits level. Fix the Arduino and the driver module on top with double-sided tape or cable ties, leaving the USB socket reachable — you will be uploading revised code many times, and a board you have to unmount each time is a board you will stop improving.

Mount the ultrasonic sensor at the front, facing straight ahead and level with the ground. Aim it downwards even slightly and it will detect the floor as an obstacle, which produces a robot that refuses to move and reports a small constant distance. Aim it too high and it will miss low objects like a table leg's base. It should also sit clear of the wheels; sound reflecting off your own robot gives readings that make no sense.

Route the wires deliberately rather than letting them fall where they will. Wheels catch loose wires, and a wire pulled out of a breadboard mid-run produces a fault that appears and disappears at random — the worst kind to diagnose. Cable ties or even a twist of tape every few centimetres is enough.

Then test in this order, before uploading the full sketch: first power the driver and confirm both motors turn in both directions with a simple sketch; second confirm the sensor prints sensible distances to the Serial Monitor while you move your hand in front of it; third, and only then, upload the complete program with the wheels lifted off the table.

  • Fix the motors level, on opposite sides, with the third support point free-running
  • Mount the board and driver where the USB socket stays reachable
  • Mount the sensor at the front, pointing level and clear of the robot's own body
  • Route and tie down every wire so wheels cannot catch them
  • Test motors, then sensor, then the combined sketch — never all three at once
  • Run the first full test with the wheels off the ground
Notes
  • The first upload of a driving sketch should always happen with the robot on a book or a mug so its wheels spin in the air. A robot that drives off a table at full speed on its first run is a common, avoidable and expensive way to start.

The Complete Sketch

The sketch below is organised the way robot code should be: named pins at the top, one small function per physical action, and a loop() short enough to read at a glance. Notice that loop() contains no motor commands at all — it senses, it decides, and it calls a named action. That structure is what lets you change the behaviour later without touching the wiring code.

Two details are worth pausing on. First, the sensor reading is checked for validity before it is trusted. pulseIn() returns 0 when no echo comes back, and the arithmetic turns that into a distance of zero, which the robot would read as "something is touching me" and react to by turning away in an empty room. Treating a zero as "no reading" and carrying on is more sensible than panicking.

Second, the robot reverses briefly before turning. A robot that simply turns on the spot when it is already almost touching a wall will often clip the obstacle with its outer wheel and stay stuck against it, trying the same failing manoeuvre forever. Backing up first gives it room.

Example
// --- ultrasonic sensor ---
const int trigPin = 12;
const int echoPin = 11;

// --- right motor (driver channel A) ---
const int enA = 9;
const int in1 = 8;
const int in2 = 7;

// --- left motor (driver channel B) ---
const int enB = 3;
const int in3 = 5;
const int in4 = 4;

// --- tuning ---
const int driveSpeed  = 170;   // found by testing on the real floor
const int turnSpeed   = 190;   // turning needs a little more push
const int safeDistance = 20;   // centimetres

void setup() {
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

  pinMode(enA, OUTPUT); pinMode(in1, OUTPUT); pinMode(in2, OUTPUT);
  pinMode(enB, OUTPUT); pinMode(in3, OUTPUT); pinMode(in4, OUTPUT);

  Serial.begin(9600);
  stopRobot();
  delay(2000);        // time to put the robot down and step back
}

float measureDistance() {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  long duration = pulseIn(echoPin, HIGH, 30000);   // give up after 30 ms
  if (duration == 0) return -1;                    // no echo came back
  return duration * 0.034 / 2;
}

void loop() {
  float distance = measureDistance();
  Serial.println(distance);

  if (distance < 0) {
    moveForward();               // no reading: assume the path is clear
  } else if (distance > safeDistance) {
    moveForward();
  } else {
    stopRobot();
    delay(200);
    moveBackward();
    delay(400);
    turnRight();
    delay(500);                  // tune this until the turn is about 90 degrees
    stopRobot();
  }
}

void moveForward() {
  digitalWrite(in1, HIGH); digitalWrite(in2, LOW);
  digitalWrite(in3, HIGH); digitalWrite(in4, LOW);
  analogWrite(enA, driveSpeed);
  analogWrite(enB, driveSpeed);
}

void moveBackward() {
  digitalWrite(in1, LOW); digitalWrite(in2, HIGH);
  digitalWrite(in3, LOW); digitalWrite(in4, HIGH);
  analogWrite(enA, driveSpeed);
  analogWrite(enB, driveSpeed);
}

void turnRight() {
  digitalWrite(in1, HIGH); digitalWrite(in2, LOW);   // right wheel forward
  digitalWrite(in3, LOW);  digitalWrite(in4, HIGH);  // left wheel backward
  analogWrite(enA, turnSpeed);
  analogWrite(enB, turnSpeed);
}

void stopRobot() {
  digitalWrite(in1, LOW); digitalWrite(in2, LOW);
  digitalWrite(in3, LOW); digitalWrite(in4, LOW);
  analogWrite(enA, 0);
  analogWrite(enB, 0);
}
Notes
  • The 30000 passed to pulseIn() is a timeout in microseconds. Without it, a missing echo makes the board wait far longer than you want, and while it waits the wheels keep turning. Any timeout is better than none here.
  • The two-second delay in setup() is deliberate. Uploading code restarts the sketch immediately, and without that pause the robot lunges off the table while your hands are still on it.

Making It Actually Work: Tuning and Debugging

Your robot will not behave perfectly the first time, and that is the normal and useful part of the project. Almost every problem falls into one of a few families, and each has a specific check.

It drifts instead of driving straight. The two motors are never identical, so equal PWM values give slightly unequal speeds. Fix it in code by using a slightly different value for the slower side, found by trial. Before doing that, check the mechanical causes: a wheel not pushed fully onto its shaft, a chassis that is not level, or a dragging third support point.

It stops or resets when the motors start. This is almost always power. The motors are pulling the supply down far enough to reset the board. Use fresh or charged batteries, and give the motors their own supply rather than sharing the board's.

It reacts to obstacles that are not there. Print the raw distances to the Serial Monitor and watch. If they read very small constantly, the sensor is probably seeing the floor or part of the robot. If they jump wildly, the readings are being taken too fast, or the sensor is loose and vibrating.

It hits things it should have seen. Either it is too fast for the distance you chose, or the obstacle was at an angle that reflected the sound away. Slow the robot down and increase the safe distance first; those are one-line changes.

  • Drifts sideways — unequal motors, an unlevel chassis, or a wheel loose on its shaft
  • Resets when moving — the motor supply is sagging; use a separate or fresher battery
  • Sees phantom obstacles — sensor aimed at the floor, or readings taken too rapidly
  • Misses real obstacles — too fast, or an angled surface reflecting the pulse away
  • Turns too far or not far enough — adjust the delay inside the turn, on the real floor surface
  • Works on tiles, fails on carpet — carpet needs more turning force, so raise the speed values
Notes
  • Change one thing at a time and write down what you changed. Adjusting the speed, the safe distance and the turn duration together tells you nothing about which of them helped — and this is exactly the discipline that engineering projects are actually assessed on.
  • When you are ready for a challenge, replace the delay() calls in the avoid manoeuvre with the millis() pattern from the sensors lesson, so the robot keeps sensing all the way through its turn instead of being blind for a second.
Ask AI