Building an Advanced Autonomous and Bluetooth-Controlled Robot Car with Arduino Mega, Ultrasonic Sensors, and Adafruit Motor Shield

This post contains affiliate links, marked with *. As an Amazon Associate I earn from qualifying purchases, at no extra cost to you. It helps keep the tutorials free.

The video loads from YouTube (Google), which may set cookies. Privacy policy

Quick summary

This robot car avoids obstacles on its own. It uses four HC-SR04 ultrasonic sensors, one on each side. From an Android app you can switch it to manual control over Bluetooth.

The car runs on an Arduino Mega 2560. An Adafruit Motor Shield V1.2 drives the four DC motors, and an HC-05 Bluetooth module receives the commands from the phone. The chassis consists of two acrylic (Plexiglas) plates with a 3D printed frame between them, plus printed motor mounts and an Arduino holder. The phone app is built with MIT App Inventor, a free browser tool in which you program by snapping blocks together instead of typing code. The project extends my earlier Bluetooth-Controlled Robot Car with Arduino, HC-05, and Adafruit Motor Shield with obstacle detection in all four directions.

3D render of the robot car: white 3D printed frame with openings for the HC-SR04 sensors, the upper plate and four wheels

How the robot car works

Arduino Mega is the main controller. In a continuous loop, it processes the Bluetooth commands, reads the four sensors in autonomous mode and sends the signals to the motor shield. I use the Arduino Mega instead of an Arduino Uno because it has enough free digital pins for the four sensors (pins 22 to 29). A hardware serial port is a built-in circuit (UART) that sends and receives serial data. The Arduino Mega has three extra ones (Serial1 to Serial3), so the HC-05 can get a port of its own (see the note in Step 4).

Components and their roles

HC-05 Bluetooth module connects to the Arduino Mega’s hardware serial port on pin 0 (RX, receive) and pin 1 (TX, transmit). The wires cross: HC-05 TXD (transmit) goes to pin 0, and HC-05 RXD (receive) goes to pin 1. The module receives single-character commands from the Android app: F (forward), B (backward), L (left), R (right), S (stop), A (autonomous mode on) and M (autonomous mode off).

Adafruit Motor Shield V1.2 sits on top of the Arduino Mega. It uses two L293D motor driver chips (each contains two H-bridges, circuits that can run a motor in both directions) to control up to four DC motors. Motors M1 and M2 drive the left wheels, M3 and M4 the right wheels. The shield also carries the motor current, which would overload the Arduino Mega pins.

Four HC-SR04 ultrasonic sensors sit in the front, back, left and right walls of the 3D printed frame. Each sensor measures the distance to the nearest obstacle by sending an ultrasonic pulse and timing the echo. In autonomous mode, the Arduino Mega reads all four sensors and decides whether to drive forward, turn or reverse. My HC-SR04 tutorial explains how the measurement works.

Operating modes

Manual Bluetooth mode: When you press a direction button in the Android app, the app sends one character over Bluetooth. The Arduino Mega reads it and drives the motors. Releasing the button sends S (stop).

Autonomous mode: When you set the mode switch to Autonomous, the app sends A. The Arduino Mega then reads all four sensors in a loop. If nothing is within 20 cm in front, the car drives forward. Its speed rises with the front distance up to 160 cm. When the front path is blocked, it compares the left and right distances and turns toward the side with the larger distance. If both sides are also blocked, it reverses while scanning for an opening. Any direction or stop command from the app (F, B, L, R, S) ends autonomous mode as soon as the Arduino sketch (Arduino’s name for a program) finishes its current move. Setting the switch back to manual sends M, which stops the car and ends autonomous mode too.

Components needed

ComponentQtyPurposeNotes
Arduino Mega 25601Main controller: runs the sketch and reads the sensorsEnough free digital pins (22 to 29) for the four sensors, plus Serial1 to Serial3. I used a Mega-compatible board, which works the same way
Adafruit Motor Shield V1.21Drives the four DC motors via the M1 to M4 terminalsStacks directly on the Arduino Mega’s headers (the rows of pin sockets along the board edges)
HC-05 Bluetooth module1Wireless link to the Android appConnects to Serial (pin 0 and pin 1); default pairing PIN is 1234
HC-SR04 ultrasonic sensor4Obstacle detection at the front, back, left and rightNeeds two pins each: Trig (trigger) starts an ultrasonic pulse, and Echo stays HIGH (at 5 V) until the echo returns
Geared DC motor4Drives the four wheelsStandard 3 to 6 V geared DC motors
Acrylic plate (Plexiglas)2Lower plate (carries the motors and the Arduino holder) and upper plate (closes the frame at the top)200 × 150 mm, 3 mm thick each
3D printed frame, motor mounts and Arduino holder1 setThe frame carries the four sensors; the mounts and the holder fix the motors and the Arduino MegaSTL files (3D print files) in the OmArTronics shop, see below
Resistors 1 kΩ and 2 kΩ1 eachVoltage divider for the HC-05 RXD lineSee the note under the HC-05 wiring table
Jumper wiresAssortedAll signal and power connectionsMale-to-male (plug at both ends) and male-to-female (plug and socket)
Battery pack1Powers the motors (and the Arduino Mega, see Power connections)6 to 7.4 V for 3 to 6 V motors; connect to the shield’s motor power terminal (EXT_PWR)
Power switch1Switches the battery on and offSoldered into the positive battery lead
Android smartphone1Runs the MIT App Inventor control appAndroid with Bluetooth
Wheels4Mount on the gearbox motor shaftsStandard 65 mm robot car wheels
Screws, nuts, standoffsAssortedMechanical assemblyM3 hardware recommended (metric screws with a 3 mm thread), plus threaded heat-set inserts for the printed parts; standoffs are spacer posts that hold a board above the plate

OmArTronics Shop: STL files as a free download: the chassis frame, the lower and upper plates, the Arduino holder, two motor mounts and a USB access cover (7 files). The code, wiring diagram and parts list are on this page.

Step 1: Building the chassis for your robot car

If 3D printing is new to you, my 3D Printing beginner guide covers the basics.

  • Cut the acrylic plates: Cut two plates of 200 × 150 mm from 3 mm acrylic sheet, one as the lower plate and one as the upper plate. I bought the acrylic plates, cut them to this size and drilled the mounting holes before the assembly.
  • 3D print the frame and motor mounts: Print the frame and four motor mounts. The STL set has two mirrored mount files (motor_holder1 and motor_holder2); print each twice. Position the mounts so the motors sit symmetrically on both sides.
  • 3D print the Arduino holder: Print the Arduino holder from the same STL set. Leave enough clearance for the motor shield that sits on top of the Arduino Mega.
  • Press in the threaded inserts: I heated threaded inserts and pressed them into all printed parts, so the screws hold without extra nuts.

Step 2: Mounting the motors

  • Attach the motors: Screw each DC motor to its 3D printed motor mount.
  • Mount the motors on the chassis: Fix the motor mounts to the lower acrylic plate and tighten the screws firmly. Align the left pair (M1 and M2) and the right pair (M3 and M4) so the car drives straight.

Step 3: Mounting the Arduino Mega and motor shield

  • Attach the Arduino holder: Screw the 3D printed Arduino holder to the lower plate.
  • Mount the Arduino Mega: Place the Arduino Mega in the holder and secure it with screws.
  • Attach the motor shield: Place the Adafruit Motor Shield onto the Arduino Mega, check that all header pins are aligned, and press it down firmly but carefully.

Step 4: Wiring the components

If you are new to Bluetooth modules, my HC-05/HC-06 Bluetooth Module Complete Tutorial covers the basics in detail.

Motor connections

MotorMotor shield terminalSide
Left front motorM1Left
Left rear motorM2Left
Right front motorM3Right
Right rear motorM4Right

HC-05 Bluetooth module connections

HC-05 pinArduino Mega pinNotes
VCC5 VPower supply for the module
GNDGNDCommon ground
TXDRX (pin 0)HC-05 transmit to Arduino Mega receive
RXDTX (pin 1)Arduino Mega transmit to HC-05 receive (through a voltage divider, see note)

Voltage divider on the RXD line: The HC-05 RXD pin expects 3.3 V signals, but the Arduino Mega sends 5 V signals. Put a voltage divider in this line: a 1 kΩ resistor from Arduino Mega TX (pin 1) to HC-05 RXD, and a 2 kΩ resistor from HC-05 RXD to GND. This lowers the signal to about 3.3 V. My wiring diagram below connects RXD directly. Add the divider anyway to keep the RXD pin at 3.3 V.

Serial pins 0 and 1: The HC-05 uses the Arduino Mega’s hardware Serial on pin 0 (RX) and pin 1 (TX). The motor shield covers these pins, so my wiring diagram connects the HC-05 wires to pin 0 and pin 1 on the shield’s digital header row, which passes them through to the Arduino Mega. The USB connection uses the same two pins, so disconnect the HC-05 TXD and RXD wires before uploading a sketch and reconnect them afterwards. The alternative is Serial1: connect HC-05 TXD to pin 19 (RX1) and HC-05 RXD to pin 18 (TX1), again through the voltage divider. Replace every Serial call in the sketch (Serial.begin, Serial.available, Serial.read) with Serial1. The module can then stay connected during uploads.

Ultrasonic sensor connections

SensorPositionTrig pinEcho pin
Sensor_FFrontPin 23Pin 22
Sensor_BBackPin 25Pin 24
Sensor_LLeftPin 27Pin 26
Sensor_RRightPin 29Pin 28

Each HC-SR04 also needs 5 V on VCC and a GND connection.

Power connections

Connect the battery pack’s positive and negative terminals to the motor power terminal (EXT_PWR) on the Adafruit Motor Shield. The shield has a PWR jumper, a small plug that connects two pins. With the jumper fitted, the same battery also feeds the Arduino Mega through its VIN pin (the raw voltage input), which needs at least 7 V. A 6 V pack (4 × AA) is too low for that. Remove the jumper when the Arduino Mega has its own supply (USB or a second battery). The stacked shield already shares GND with the Arduino Mega.

The pack must deliver at least 2 A for the four motors. Match the voltage to the motors. The L293D driver chips on the shield lose about 2.6 V at 0.6 A (typical datasheet values: 1.4 V on the high side, 1.2 V on the low side). So 3 to 6 V geared DC motors run well on 6 to 7.4 V. At 12 V they can overheat. Suitable packs are 4 × AA alkaline cells (6 V, motors only) or a 2S LiPo (lithium polymer, two cells in series, 7.4 V). A 9 V (PP3) battery cannot supply the current of four motors under load.

I fixed the battery pack to the lower plate with double-sided tape and soldered a power switch into its positive lead. The switch sits in the upper plate.

Wiring diagram: Arduino Mega, Adafruit Motor Shield, four HC-SR04 sensors, HC-05 module, four DC motors, two battery holders and a power switch

The diagram shows two 4 × AAA holders in series and the label 6V-12V. With 3 to 6 V motors, stay at 6 to 7.4 V as described under Power connections.

Step 5: Programming the Arduino Mega for autonomous and Bluetooth control

The sketch handles both the manual Bluetooth commands and the autonomous obstacle avoidance. If you have never written a sketch, start with my Arduino Programming Basics: Complete Beginner Guide. The sketch and the app files are also in the GitHub repository.

Full Arduino sketch

The sketch needs Adafruit’s AFMotor library for the V1 shield. Install it in the Arduino IDE (the free editor that compiles and uploads sketches) under Sketch > Include Library > Manage Libraries: search for “Adafruit Motor Shield library” (not the V2 library). Copy the sketch into the Arduino IDE, click Verify, then Upload to the Arduino Mega. Disconnect the HC-05 TXD and RXD wires before uploading.

/**
 * Author: Omar Draidrya
 * Date: 2024/07/20
 * This sketch controls an advanced autonomous and Bluetooth-controlled robot car using Arduino Mega,
 * four HC-SR04 ultrasonic sensors, HC-05 Bluetooth module, and Adafruit Motor Shield V1.
 * The robot supports manual Bluetooth control (Forward, Backward, Left, Right, Stop) and
 * autonomous obstacle-avoidance navigation with front, rear, left, and right distance sensing.
 */
#include <AFMotor.h>
// Motor setup
AF_DCMotor motor1(1);
AF_DCMotor motor2(2);
AF_DCMotor motor3(3);
AF_DCMotor motor4(4);
// Ultrasonic sensor pins
const int trigF = 23;
const int echoF = 22;
const int trigL = 27;
const int echoL = 26;
const int trigR = 29;
const int echoR = 28;
const int trigB = 25;
const int echoB = 24;
char command;
bool autonomousModeEnabled = false; // Variable to track autonomous mode
// Function prototypes
void handleCommand(char command);
void forward(int speed = 160);
void backward(int speed = 160);
void turnLeft(int speed = 160);
void turnRight(int speed = 160);
void stop();
void autonomousMode();
int getDistance(int trigPin, int echoPin);
void setup() {
  Serial.begin(9600);
  pinMode(trigF, OUTPUT);
  pinMode(echoF, INPUT);
  pinMode(trigL, OUTPUT);
  pinMode(echoL, INPUT);
  pinMode(trigR, OUTPUT);
  pinMode(echoR, INPUT);
  pinMode(trigB, OUTPUT);
  pinMode(echoB, INPUT);
}
void loop() {
  if (Serial.available() > 0) {
    command = Serial.read();
    handleCommand(command);
  }
  if (autonomousModeEnabled) {
    autonomousMode();
  }
}
void handleCommand(char command) {
  if (command == 'F') {
    forward();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'B') {
    backward();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'L') {
    turnLeft();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'R') {
    turnRight();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'S') {
    stop();
    autonomousModeEnabled = false; // Disable autonomous mode
  } else if (command == 'A') {
    autonomousModeEnabled = true; // Enable autonomous mode
  } else if (command == 'M') {
    stop();
    autonomousModeEnabled = false; // Mode switch OFF: leave autonomous mode
  }
}
void forward(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(FORWARD);
  motor2.run(FORWARD);
  motor3.run(FORWARD);
  motor4.run(FORWARD);
}
void backward(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(BACKWARD);
  motor2.run(BACKWARD);
  motor3.run(BACKWARD);
  motor4.run(BACKWARD);
}
void turnLeft(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(BACKWARD);
  motor2.run(BACKWARD);
  motor3.run(FORWARD);
  motor4.run(FORWARD);
}
void turnRight(int speed) {
  motor1.setSpeed(speed);
  motor2.setSpeed(speed);
  motor3.setSpeed(speed);
  motor4.setSpeed(speed);
  motor1.run(FORWARD);
  motor2.run(FORWARD);
  motor3.run(BACKWARD);
  motor4.run(BACKWARD);
}
void stop() {
  motor1.run(RELEASE);
  motor2.run(RELEASE);
  motor3.run(RELEASE);
  motor4.run(RELEASE);
}
void autonomousMode() {
  int distanceF = getDistance(trigF, echoF);
  int distanceL = getDistance(trigL, echoL);
  int distanceR = getDistance(trigR, echoR);
  int distanceB = getDistance(trigB, echoB);
  // Check for an obstacle in front
  if (distanceF < 20) {
    stop();
    delay(100); // Short pause to make sure the robot has stopped
    // Obstacle avoidance
    if (distanceR > distanceL && distanceR > 20) {
      turnRight(180);
      delay(300);
    } else if (distanceL > distanceR && distanceL > 20) {
      turnLeft(180);
      delay(300);
    } else if (distanceB > 20) {
      backward(160);
      while (distanceB > 20) {
        distanceB = getDistance(trigB, echoB);
        distanceL = getDistance(trigL, echoL);
        distanceR = getDistance(trigR, echoR);
        if (distanceR > 20 || distanceL > 20) {
          stop();
          delay(100);
          if (distanceR > distanceL) {
            turnRight(180);
          } else {
            turnLeft(180);
          }
          delay(300);
          break;
        }
        delay(100);
      }
      stop();
    } else {
      stop();
    }
  } else {
    adjustDirection(distanceL, distanceR);
    forward(constrain(map(distanceF, 20, 160, 64, 160), 64, 160));
  }
}
void adjustDirection(int distanceL, int distanceR) {
  if (distanceL < 20) {
    turnRight(180);
    delay(100);
    forward(160);
  } else if (distanceR < 20) {
    turnLeft(180);
    delay(100);
    forward(160);
  }
}
int getDistance(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 30000); // Wait at most 30 ms for the echo
  if (duration == 0) return 400; // No echo: treat as a free path
  int distance = duration * 0.034 / 2;
  return distance;
}

Detailed code walkthrough

Library and motor setup

The sketch uses Adafruit’s AFMotor.h library for the V1 shield (installation: see Full Arduino sketch). The library provides the AF_DCMotor class; each AF_DCMotor object stands for one motor terminal (M1 to M4). motor1 and motor2 control the left wheels, motor3 and motor4 the right wheels.

Ultrasonic sensor pin mapping

Eight pin constants define the Trig and Echo connections for the four HC-SR04 sensors. Front: trigF and echoF on pins 23 and 22. Back: trigB and echoB on pins 25 and 24. Left: trigL and echoL on pins 27 and 26. Right: trigR and echoR on pins 29 and 28. The setup() function configures all Trig pins as OUTPUT and all Echo pins as INPUT.

Bluetooth command handling

In each pass through loop(), the sketch checks Serial.available() for incoming Bluetooth data. When a character arrives, handleCommand() calls the matching movement function: F calls forward(), B backward(), L turnLeft(), R turnRight() and S stop(). Each of these commands also sets autonomousModeEnabled = false, so the car leaves autonomous mode as soon as it finishes its current move. A sets autonomousModeEnabled = true. M, sent when you switch the app back to manual, stops the car and sets autonomousModeEnabled = false.

Manual mode movement functions

The forward(), backward(), turnLeft() and turnRight() functions set the speed of all four motors and then run them in the required direction. Turning works through differential drive: the car has no steerable wheels and turns by running the left and right wheels in opposite directions. turnLeft() runs the left motors backward and the right motors forward, turnRight() does the opposite. The stop() function calls run(RELEASE) for all four motors, which switches the motor current off. The four movement functions accept an optional speed value from 0 to 255 (default 160). The value sets a PWM signal (pulse-width modulation): the motor voltage is switched on and off many times per second. A higher value keeps it on for a larger part of each cycle, so the motor turns faster.

Autonomous mode and front obstacle logic

The autonomousMode() function runs while autonomousModeEnabled is true. It reads the distances from all four sensors. If the front distance is below 20 cm, the car stops and starts the obstacle avoidance. It then compares the left and right distances. If the right distance is larger and above 20 cm, the car turns right for 300 ms; if the left distance is larger and above 20 cm, it turns left. If both sides are blocked, it reverses.

Side correction logic

The adjustDirection() function runs when there is no front obstacle. If the left distance is below 20 cm (the car is drifting toward a wall), the car turns right for 100 ms and drives on. The same applies on the other side. This keeps the car roughly centered between obstacles.

Reverse and escape logic

When the front, left and right are all blocked but the rear is clear (distance above 20 cm), the car reverses. During this maneuver, the sketch keeps reading the left and right sensors. As soon as the left or right distance rises above 20 cm, the car stops, turns toward the side with the larger distance and leaves the reverse loop. If the rear becomes blocked first, the car stops.

Distance measurement function

The getDistance() function sends a 10 µs pulse on the Trig pin and measures the echo time on the Echo pin with pulseIn(). It converts the echo time to centimeters with distance = duration * 0.034 / 2. Sound travels about 0.034 cm per microsecond (343 m/s), and the pulse travels to the obstacle and back, so the result is divided by 2. The sketch calls getDistance() for each of the four sensors whenever it needs a distance reading. Without a timeout, pulseIn() waits up to one second for an echo and returns 0 if none arrives, and the sketch would treat that as an obstacle at 0 cm. The third argument in pulseIn(echoPin, HIGH, 30000) limits the wait to 30 ms (about 5 m of range), and if (duration == 0) return 400; counts a missing echo as a free path.

Speed mapping logic

When the path ahead is clear, the code sets the forward speed with constrain(map(distanceF, 20, 160, 64, 160), 64, 160). map() converts the front distance (20 to 160 cm) into a speed (64 to 160). The car drives slowly when the obstacle is close and at the default speed of 160 when the path ahead is clear for 160 cm or more. map() alone does not limit its output: from 300 cm on it returns 256 or more, and setSpeed() then wraps around to a low speed, so the car would slow down in open space. constrain() caps the value to the range 64 to 160. The minimum of 64 keeps the motors turning: below that value, the average voltage is too low to overcome friction and the motor stalls.

Step 6: Building the Android app for Bluetooth control

The app is built in MIT App Inventor and sends the single-character commands to the HC-05 over Bluetooth.

Setting up the project

  • Register and log in: Go to MIT App Inventor and log in with your Google account.
  • Create a new project: Click “Start New Project” and name it “RobotCarControl”.

Required UI components

  • ListPicker1: Set the text to “Select Bluetooth Device”. It lets you pick the paired HC-05 from a list of Bluetooth devices.
  • BluetoothClient1: Add it from the Connectivity palette. It is a non-visible component that handles the Bluetooth serial connection.
  • Label1: A text label that shows the connection status (“Connected” or “Not Connected”). This label must exist in the UI because the blocks reference it.
  • ButtonForward, ButtonBackward, ButtonLeft, ButtonRight: Four direction buttons. Set their colors for visual clarity (for example, green for forward, red for backward).
  • ModeSwitch: A Switch component that toggles between manual and autonomous mode. When toggled ON, it sends “A” to enable autonomous mode. When toggled OFF, it sends “M”, and the sketch reacts in handleCommand() with else if (command == 'M') { stop(); autonomousModeEnabled = false; }. Without this branch, the car keeps driving on its own until you press a direction button.

How the ListPicker Bluetooth connection works

When you tap the ListPicker, the BeforePicking event fills the list with the paired Bluetooth devices from BluetoothClient1.AddressesAndNames. After you select a device, the AfterPicking event tries to connect with BluetoothClient1.Connect. If the connection succeeds, Label1 shows “Connected”; otherwise it shows “Not Connected”. On Android 12 and newer, the app needs the Nearby devices permission to read this list. If the list stays empty, allow it under Settings > Apps > RobotCarControl > Permissions, or add a Screen1.Initialize block that calls Screen1.AskForPermission with BluetoothConnect.

How TouchDown and TouchUp commands work

Each direction button uses TouchDown to send the movement command (F, B, L or R) when pressed, and TouchUp to send “S” (stop) when released. The car moves as long as you hold the button and stops when you release it.

Programming the blocks

Add these blocks in the Blocks editor of MIT App Inventor.

when ListPicker1.BeforePicking
  set ListPicker1.Elements to BluetoothClient1.AddressesAndNames
when ListPicker1.AfterPicking
  if call BluetoothClient1.Connect address ListPicker1.Selection
    then set Label1.Text to "Connected"
    else set Label1.Text to "Not Connected"
when ButtonForward.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "F"
when ButtonBackward.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "B"
when ButtonLeft.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "L"
when ButtonRight.TouchDown
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "R"
when ButtonForward.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ButtonBackward.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ButtonLeft.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ButtonRight.TouchUp
  if BluetoothClient1.IsConnected
    call BluetoothClient1.SendText text "S"
when ModeSwitch.Changed
  if ModeSwitch.On
    call BluetoothClient1.SendText text "A"
  else
    call BluetoothClient1.SendText text "M"
MIT App Inventor block programming interface showing Bluetooth connection, directional button commands, and mode switch logic for the robot car control app

Step 7: Testing and tuning the robot car

  • Pair the Bluetooth device: Turn on Bluetooth on your Android phone and pair it with the HC-05. The default pairing PIN is 1234 (0000 on some modules).
  • Install the app: In App Inventor, choose Build > Android App (.apk), open the download link or QR code on your phone and install the APK (the Android app file). For quick tests, the MIT AI2 Companion app runs the project without installing it (Connect > AI Companion). I installed the APK on my phone for the test drive in the video. The finished APK and the App Inventor project file (.aia) are also in the GitHub repository.
  • Connect via app: Open the RobotCarControl app, tap the ListPicker and select your HC-05. If the connection succeeds, Label1 changes to “Connected”.
  • Test manual control before autonomous mode: Each button (F, B, L, R) must move the car the right way, and releasing it (S) must stop it.
  • Verify motor direction: If a motor spins the wrong way, swap its two wires at the motor shield terminal. When you press the forward button, all four wheels must turn forward.
  • Verify each ultrasonic sensor individually: Add temporary Serial.println() statements that print each sensor’s distance to the Serial Monitor (the window in the Arduino IDE that shows the data the board sends over USB). Front, back, left and right must show plausible values and react to an object placed in front of them.
  • Test autonomous mode: Set the mode switch to Autonomous. Place an obstacle in front of the car and watch whether it stops and turns away.

Tune in this order:

  • Obstacle distance threshold: The code uses 20 cm as the threshold, the distance below which a reading counts as an obstacle. If your car reacts too late (crashes) or too early (stops far from obstacles), change the value 20 in autonomousMode(), adjustDirection() and the map() call. Or define one constant such as const int SAFE_DIST = 20; and use it in all three places.
  • Speed values: The default speed is 160 (out of 255). If your motors are too fast or too slow, change the default speed and the map() range. A heavier car or weaker motors may need a higher minimum speed.
  • Turn timing: The turn duration is set by delay(300) after the turn commands. If the car does not turn enough (or turns too far), increase or decrease this delay.
  • My values: The sketch above holds the values my car drives with in the video: threshold 20 cm, forward speed 64 to 160, turns at speed 180 for 300 ms. Start with these and change one value at a time.

Troubleshooting

ProblemLikely causeSolution
HC-05 pairs but car does not moveApp is not sending commands, or wiring is incorrectCheck that the app shows “Connected”. Plug the HC-05 TXD and RXD wires back in if you removed them for the upload. If it still does not move, check the motor shield wiring and the battery.
Upload fails with an error on SerialHC-05 TXD and RXD wires occupy pin 0 and pin 1Disconnect the HC-05 TXD and RXD wires before uploading. Reconnect them after the upload.
Car moves in the wrong directionMotor wires are swappedSwap the two wires of the affected motor at the shield terminal, or swap the motor objects in the code.
Ultrasonic sensor gives unstable readingsLoose wiring, electrical noise, or the sensor faces a soft or angled surfaceSecure all jumper wires. Add a small delay between sensor readings. Test with a flat, hard surface.
Car keeps stopping unexpectedlyFront sensor reads false short distances, or no echo returns, so pulseIn() returns 0 and getDistance() reports 0 cmCheck for loose Trig and Echo wires. Mount the sensor firmly so it does not vibrate. Keep the pulseIn() timeout and the duration == 0 check from the sketch.
Autonomous mode gets stuck in a cornerAll four directions are blocked within 20 cmThe sketch stops when all four directions are blocked. Add an escape move: turn in place with turnLeft(180) and a delay longer than 300 ms, then measure again. A lower threshold also lets the car drive out of tighter spaces.
Motors weak, or Arduino Mega resets when the motors startBattery voltage drops under loadUse fresh or larger batteries. Power the motors from the shield’s EXT_PWR terminal and the Arduino Mega from USB or its own supply (remove the PWR jumper).
Car turns the wrong way in autonomous modeLeft and right sensors are swapped in the wiring or the codeCheck that Sensor_L sits on the left and Sensor_R on the right, and that the pin numbers match the layout.

If your car behaves differently, leave a comment with your battery type and the four sensor readings from the Serial Monitor.

Frequently asked questions

Can I use an Arduino Uno instead of an Arduino Mega?

Only with compromises. With four DC motors, the Adafruit Motor Shield V1 uses pins 3, 4, 5, 6, 7, 8, 11 and 12, and the HC-05 takes pin 0 and pin 1. That leaves pins 2, 9, 10, 13 and A0 to A5 for the eight Trig and Echo lines (the analog pins also work as digital pins). You must change the pin numbers in the sketch to match. The Arduino Uno has no second hardware serial port, so the HC-05 must be unplugged for every upload. The Arduino Mega has enough free pins, and on Serial1 (see Step 4) the HC-05 can stay connected during uploads.

What is the difference between the Adafruit Motor Shield V1 and V2?

The V1 uses the AFMotor.h library: a 74HC595 shift register sets the motor directions, and two L293D driver chips supply the motor current. A shift register is a chip that turns a few Arduino pins into eight outputs. The V2 uses I2C (a two-wire bus) and the Adafruit_MotorShield library. This tutorial uses the V1; with a V2 you need to change the library and the motor setup code.

Can I use an HC-06 module instead of the HC-05?

Yes. The HC-06 only works in slave mode: it waits for another device to connect and cannot start a connection itself. That is enough here, because the phone opens the connection. Wiring and code stay the same.

Why does my car not drive straight?

Uneven motor speeds, different wheel friction or small differences between the left and right motors make the car drift. Give the faster side a slightly lower speed: forward() uses one value for all four motors, so add a second value for M3 and M4. For accurate straight driving you need wheel encoders (sensors that count wheel turns) and PID control, a feedback loop that corrects the motor speed from the measured error.

Resources and next projects

Related OmArTronics tutorials

Upgrade ideas

  • Add a servo-mounted front ultrasonic sensor for 180-degree scanning (see my Arduino Radar with HC-SR04 and Servo Motor guide).
  • Add line-following sensors for track-based navigation (see my Line Following Robot with KY-033 Sensors tutorial).
  • Add an OLED or LCD display that shows the sensor readings and the connection status.
  • Add wheel encoders and PID motor control for speed matching and straight-line driving (see the FAQ above).

Leave a Comment