Robotics Project · Arduino

MEETGOOGLY

A sensor-driven robot car that drives itself around obstacles — and takes IR remote commands when it'd rather be told where to go. Built on an Arduino Uno, an ultrasonic rangefinder, and a servo-mounted "head" that scans the room like it's actually looking for trouble.

Elegoo Arduino Uno R3 Custom Sensor Shield L298N Motor Controller HC-SR04 Ultrasonic 3-Element Line Tracker Bluetooth + IR Remote
01 · Overview

Two brains, one chassis

Googly runs in two modes off the same loop. In manual mode, an IR remote sends hex codes straight to the motor functions — forward, back, left, right, stop. Flip to auto mode and the ultrasonic sensor takes over, sweeping the servo head to measure distance and steering around whatever it finds in front of it.

Brain

Elegoo Arduino Uno R3, paired with a custom sensor expansion shield that breaks out clean headers for every sensor and motor lead.

Drive

Dual DC gear motors through an L298N motor controller board, PWM speed control on ENA/ENB.

Distance Sensing

HC-SR04 ultrasonic sensor on a servo mount — measures distance and picks a clear direction.

Line Tracking

A 3-element line tracking sensor module reads reflected IR light to follow a marked path or edge.

Wireless Control

A Bluetooth module and IR receiver/remote control give two independent ways to drive Googly manually.

Logic

Arduino Uno loop checks for incoming remote/Bluetooth input first, falls back to autonomous avoidance and line following.

i

Right level for you? This page is aimed at the middle ground — not a first "blink an LED" project, and not a full research-grade robotics build either. If you want to learn real code and the correct parts for a robot with actual sensing and control, without diving into something overly complex, the parts list above and the Code Walkthrough section below are meant to be a complete, adaptable reference for exactly that.

02 · The Build

Googly, in the flesh

The finished chassis — 4WD platform, servo-mounted ultrasonic sensor up front, motor driver and receiver wired in on top.

Googly, the finished 4WD Arduino robot car with an ultrasonic sensor mounted on a servo up front
03 · Build Log

Getting it to actually work

ISSUE 01

The IR receiver only worked half the time

Serial monitor showed decoded values dropping randomly, especially right after a motor direction change. Turned out the motor driver's switching noise was interfering with the receiver on the same rail — moving the IR receiver's ground further from the motor leads and adding a small delay after irrecv.resume() cleaned up the signal.

Fixed
ISSUE 02

Auto mode kept driving into low obstacles

The ultrasonic sensor was mounted level, so anything below its beam height went undetected. Angling the servo sweep slightly downward and averaging three getDistance() readings per position got rid of the false "clear path" calls.

Fixed
ISSUE 03

Turning radius is still wider than I want

Sharp turns rely on stopping one side's motor rather than reversing it, so Googly needs more room to pivot than the room usually gives it. Next revision: reverse the inner wheel during turns for a tighter radius.

Open
04 · The Code

Arduino sketch

Full loop below: check for an IR command first, otherwise run the obstacle-avoidance sweep. Placeholder note: the IR hex codes in switch(results.value) below are stand-ins — replace them with the values your own remote prints to Serial.

googly.ino
#include <IRremote.h>
#include <Servo.h>

#define ENA 5
#define ENB 6
#define IN1 7
#define IN2 8
#define IN3 9
#define IN4 11
#define trigPin A5
#define echoPin A4
#define IR_RECEIVE_PIN 12

// Replace with the hex values your remote actually sends
#define IR_FORWARD  0xFF18E7
#define IR_BACKWARD 0xFF4AB5
#define IR_LEFT     0xFF10EF
#define IR_RIGHT    0xFF5AA5
#define IR_STOP     0xFF38C7

Servo myservo;
IRrecv irrecv(IR_RECEIVE_PIN);
decode_results results;
int motorSpeed = 200;
bool autoMode = true;

void setup() {
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
  pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT);
  myservo.attach(3);
  myservo.write(90);
  Serial.begin(9600);
  irrecv.enableIRIn();
}

void loop() {
  if (irrecv.decode(&results)) {
    autoMode = false;
    switch (results.value) {
      case IR_FORWARD:  forward();   break;
      case IR_BACKWARD: backward();  break;
      case IR_LEFT:     turnLeft();  break;
      case IR_RIGHT:    turnRight(); break;
      case IR_STOP:     brake(); autoMode = true; break;
    }
    irrecv.resume();
    delay(15); // settle time to avoid noise re-triggering the receiver
  } else if (autoMode) {
    automaticAvoidance();
  }
}

void forward() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
  analogWrite(ENA, motorSpeed); analogWrite(ENB, motorSpeed);
}

void backward() {
  digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
  analogWrite(ENA, motorSpeed); analogWrite(ENB, motorSpeed);
}

void turnLeft() {
  digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);   // left side reverse
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);   // right side forward
  analogWrite(ENA, motorSpeed); analogWrite(ENB, motorSpeed);
}

void turnRight() {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);   // left side forward
  digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);   // right side reverse
  analogWrite(ENA, motorSpeed); analogWrite(ENB, motorSpeed);
}

void brake() {
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
  analogWrite(ENA, 0); analogWrite(ENB, 0);
}

int getDistance() {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000); // 30ms timeout
  if (duration == 0) return 999; // no echo = treat as clear
  return duration * 0.034 / 2;   // convert to cm
}

void automaticAvoidance() {
  int distanceAhead = getDistance();

  if (distanceAhead > 25) {
    forward();
    return;
  }

  brake();
  delay(150);

  myservo.write(150); // look left
  delay(400);
  int distanceLeft = getDistance();

  myservo.write(30);  // look right
  delay(600);
  int distanceRight = getDistance();

  myservo.write(90);  // recenter
  delay(300);

  if (distanceLeft > distanceRight) {
    turnLeft();
  } else {
    turnRight();
  }
  delay(400);
  brake();
}
05 · Code Walkthrough

How the two modes actually work

A quick tour through the logic above, function by function.

if (irrecv.decode(&results)) {
  autoMode = false;
  switch (results.value) {
    case IR_FORWARD: forward(); break;
    ...
  }
  irrecv.resume();
}
The main loop

Every pass through loop() checks the IR receiver first. If a code came in, Googly switches to manual mode and runs whatever motor function matches that button. irrecv.resume() resets the receiver so it's ready for the next signal. If nothing came in and Googly is still in auto mode, control falls through to automaticAvoidance().

digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
analogWrite(ENA, motorSpeed);
analogWrite(ENB, motorSpeed);
Driving forward

The L298N takes two direction pins per side (IN1/IN2 for the left motors, IN3/IN4 for the right) plus a PWM enable pin (ENA/ENB) that sets speed. Setting each side's pair to opposite HIGH/LOW spins both sides the same direction — forward. Turning just flips one side's pair instead.

digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH, 30000);
return duration * 0.034 / 2;
Measuring distance

The HC-SR04 sends a 10-microsecond pulse out the trigger pin, which fires an ultrasonic burst. pulseIn() times how long the echo pin stays high while the sound wave travels out and bounces back. Multiplying by the speed of sound (in cm/µs) and dividing by two — since the sound makes a round trip — gives the distance in centimeters.

myservo.write(150); // look left
int distanceLeft = getDistance();
myservo.write(30);  // look right
int distanceRight = getDistance();

if (distanceLeft > distanceRight) turnLeft();
else turnRight();
Deciding which way to turn

When something's within 25cm ahead, Googly stops, sweeps the servo-mounted sensor left then right, and compares the two readings. Whichever side has more open space wins — that's the direction it turns. It's a simple heuristic, but it's enough to route around most obstacles without needing a full map of the room.