Building a Bluetooth-Controlled Robot Car with Arduino, HC-05, 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

Watch the full build: Bluetooth-controlled robot car with Arduino, HC-05, and Adafruit Motor Shield

Update: this car has a 3D printed successor, OmObi. OmObi uses the same code and app; its STL files are in the shop. This post keeps the original build on a plate cut from an acrylic (Plexiglas) sheet.

Quick summary

This four-wheel-drive car takes commands from an Android app over Bluetooth. It runs on an Arduino Uno with an Adafruit Motor Shield V1.2 and an HC-05 Bluetooth module. You cut the acrylic plate, wire the motors and the HC-05, upload the sketch (the Arduino program) and build the control app in MIT App Inventor.

I built this car as the follow-up project to my HC-05/HC-06 Bluetooth tutorial. If you are new to Arduino, start with my Arduino programming basics guide.

How the car works

The system has four parts: the Android phone, the HC-05 Bluetooth module, the Arduino Uno and the Adafruit Motor Shield with four DC motors.

The Android phone runs a control app built with MIT App Inventor, a free browser tool that builds Android apps from drag-and-drop blocks. When you press a direction button, the app sends F, B, L or R over Bluetooth. When you release it, the app sends S (stop).

The HC-05 receives the command and passes it to the Arduino over a serial connection: the bits travel one after another on two wires, RX (receive, pin 0) and TX (transmit, pin 1). The HC-05 acts as a wireless serial bridge between the phone and the Arduino. My HC-05/HC-06 Arduino Bluetooth tutorial explains the module in detail.

The Arduino reads the character from the serial buffer, a small memory area where received bytes wait until the sketch reads them. It then calls the matching movement function: forward, backward, turnLeft, turnRight or stop.

The Adafruit Motor Shield V1.2 is a shield, an add-on board that plugs onto the Arduino’s pin headers. The Arduino pins cannot supply the current a motor needs. The shield’s two L293D motor driver chips switch the battery current to the four motors, up to 0.6 A per motor. Each motor connects to one of the motor terminals M1 to M4, screw terminals that clamp the motor wires. The sketch controls the shield with the AFMotor library, a package of ready-made code that a sketch can include. The library sets the direction (FORWARD, BACKWARD, RELEASE) and the speed (0 to 255). For turning, one side runs forward while the other runs backward, a tank-style turn. For motor driver principles, see my L298N motor driver tutorial.

Components (bill of materials)

ComponentQtyRequired?PurposeNotes
Arduino Uno1RequiredMain microcontrollerAny Uno-compatible board works
Adafruit Motor Shield V1.21RequiredDrives four DC motorsStacks on top of the Arduino. Use V1.2 or an L293D clone of it: the newer V2 shield needs the Adafruit_MotorShield library and different code
HC-05 Bluetooth module1RequiredWireless serial link to the phoneHC-06 also works: it can only accept incoming connections (slave mode), which is all this project needs
DC motors with gearboxes4RequiredDrive the four wheelsTypically 3 to 6 V geared motors
Wheels4RequiredFit on the motor shaftsMatch them to your gearbox shaft size
Acrylic plate1RequiredCarries the motors, electronics and batteryCut to size; a similar sheet material also works
3D printed motor mounts4OptionalHold the motors on the acrylic plateMetal L-brackets also work
3D printed Arduino holder1OptionalHolds the Arduino on the acrylic plateDouble-sided foam tape or standoffs (threaded spacers between board and acrylic plate) also work
Battery pack, 4 × AA or similar1RequiredPowers the motors (and the Arduino via the PWR jumper, see Step 4)6 V; use fresh batteries
Resistors, 1 kΩ and 2 kΩ1 eachRecommendedVoltage divider on the HC-05 RXD pinSee Step 3
On/off switch1OptionalSwitches the car off without unplugging the packGoes in the red battery wire
Jumper wiresSeveralRequiredConnect the HC-05 to the ArduinoFemale-to-female, plugged onto header pins soldered to the shield (see Step 3)
Screws, nuts, standoffsAssortedRequiredMechanical assemblyM3 is common
Android phone1RequiredRuns the control appThe HC-05 uses Bluetooth Classic (not Bluetooth Low Energy); iPhones cannot connect to it, and App Inventor builds Android apps only
3D rendering of the four-wheel-drive chassis: acrylic plate, four yellow geared DC motors and wheels, before the electronics are mounted

Step 1: Building the chassis

The chassis, the frame of the car, is a flat acrylic plate. It is light, easy to cut and stiff enough to carry four motors, the electronics and the battery pack.

Cut the acrylic plate

Measure and cut the acrylic plate. A typical size is about 20 cm × 15 cm; adjust it to your motors and battery pack. Use a saw or an acrylic cutter (a hooked scoring knife for plastic sheets) for clean edges. After cutting, I peeled off the protective film, marked the hole positions for the components and drilled the holes. I drilled one more hole to pass the motor wires through the plate.

Print the motor mounts

Print four motor mounts that grip the motor body and let the gearbox shaft point outward through the edge of the acrylic plate. I designed my mounts in Autodesk Inventor. After printing, I heated threaded inserts (small metal sleeves with an inner thread) and pressed them into the plastic, so the screws hold directly in the mounts. Without a 3D printer, use metal L-brackets; hot glue holds only for first tests.

Prepare the Arduino holder

Hold the board with a 3D printed holder or with standoffs and M3 screws through the Arduino’s mounting holes. Double-sided foam tape also works. The holder keeps the board in place while the car drives and keeps the wiring from pulling loose.

Step 2: Mounting the motors and electronics

Attach the motors to the mounts

Fix each motor into its mount (or L-bracket) with screws and nuts. Mount the motors with all shafts pointing outward. The left and right motors are mirror images, so with identical wiring one side may spin backward; you fix that by swapping the two wires of those motors (see Code explanation).

Mount the motor assemblies on the acrylic plate

Screw the motor mounts to the acrylic plate, two motors on each side, as far apart as possible for stability. Check that the wheels spin freely without rubbing on the acrylic plate or on each other.

Mount the Arduino and the shield

Fix the Arduino holder (or the standoffs) to the center or rear of the acrylic plate and leave room at the front or sides for the battery pack. Set the Uno into the holder and screw it down. Press the Motor Shield onto the Arduino headers until it sits flush, and check that no pin is bent or outside its socket.

Step 3: Wiring

The wiring diagram below shows the motors, the battery and the on/off switch; the HC-05 connections follow in their own table. Before connecting the motors, I labeled each motor wire with the name of its motor, so I could match the four pairs to M1 to M4.

Wiring diagram: four 3 to 6 V DC motors on the motor terminals M1 to M4 of the Adafruit Motor Shield V1.2, and two 4 × AAA battery holders in series, with an on/off switch in the red wire, on the EXT_PWR terminal

Motor connections

MotorMotor terminalPosition on the car
Motor 1M1Left side (front or rear, order does not matter)
Motor 2M2Left side (front or rear, order does not matter)
Motor 3M3Right side (front or rear, order does not matter)
Motor 4M4Right side (front or rear, order does not matter)

Insert the two wires of each motor into its motor terminal and tighten the screws.

HC-05 connections

HC-05 pinArduino pinNotes
VCC5 VPowers the HC-05
GNDGNDCommon ground (the shared 0 V reference of both boards)
TXDRX (pin 0)HC-05 transmits data to the Arduino
RXDTX (pin 1)Arduino transmits data to the HC-05

The HC-05 RXD pin uses 3.3 V logic, but the Arduino TX pin sends 5 V. Add a voltage divider, two resistors that lower the 5 V signal to about 3.3 V: 1 kΩ from Arduino TX to RXD, 2 kΩ from RXD to GND. You can skip it if the breakout board of your HC-05 (the small carrier board with the pin header) has a level shifter, a circuit that converts 5 V signals to 3.3 V. The Motor Shield covers the Arduino headers. On my car, I soldered short header pins onto the shield, two for pins 0 and 1 and two for 5 V and GND, and plugged the HC-05 wires onto them. I connected the module directly, without a divider; it worked, but the divider is the safer way. I fixed the HC-05 to the acrylic plate with adhesive.

Pins 0/1 and uploads

Warning: pins 0 (RX) and 1 (TX) are also the USB serial pins the Arduino uses for uploads and the Serial Monitor. The Serial Monitor is the window in the Arduino IDE (the program you write and upload sketches with) that shows serial text. Disconnect at least the HC-05 TXD and RXD wires before every upload, otherwise the upload fails. Uploading with the HC-05 still connected caused me problems on this car. Reconnect the wires afterwards and press reset. While the HC-05 is on pins 0/1, the Serial Monitor is not usable.

If you need Bluetooth and the Serial Monitor at the same time, connect the HC-05 through SoftwareSerial, a library that creates a second serial port on other pins. Use pins the Motor Shield leaves free, for example A0 and A1. In the sketch, create a SoftwareSerial port on these pins and use it in place of Serial. Do not use pin 3: the shield uses it for motor M2.

Step 4: Power

Connect the 4 × AA pack (6 V) to the EXT_PWR terminal on the Motor Shield: red wire to the + terminal, black wire to the – terminal. The shield powers the motors from this terminal. Its ground is connected to the Arduino’s ground through the header pins. Do not power the motors from a 9 V (PP3) battery: it cannot deliver the current four DC motors draw under load.

The shield has a jumper labeled PWR, a small removable plug that connects two pins. With the jumper installed, the battery on EXT_PWR also feeds the Arduino’s VIN pin (its raw power input). The Uno’s onboard voltage regulator (a circuit that keeps the output voltage constant) needs about 7 V at VIN for a stable 5 V, so a 6 V pack is too low for that. If the Arduino or the HC-05 resets when the motors start, remove the PWR jumper and power the Uno separately. Use a USB power bank or a 9 V (PP3) battery on the DC jack (the round power socket of the Uno). In this setup the 9 V battery supplies only the Uno and the HC-05; the motors still run from the 4 × AA pack.

I added an on/off switch to the battery line and fixed it to the frame, so I can switch the car off without unplugging the pack. The switch goes in the red wire.

On my car, the battery is a 3S LiPo pack (three lithium-polymer cells in series, 11.1 V, 5200 mAh), held on the acrylic plate with cable ties. With the PWR jumper installed, it gives the Uno enough voltage at VIN. The wiring diagram shows a similar voltage: two 4 × AAA holders in series, about 12 V. The motors are rated 3 to 6 V, though. The L293D drops about 2.6 V, so at full speed they get roughly 8.5 V from the LiPo. OmObi, the 3D printed successor, runs on a 2S LiPo (7.4 V). For a first build, the 4 × AA pack is the safer choice. For longer run time, a rechargeable 2S Li-ion pack also works (two cells in series, 7.4 V nominal, 8.4 V fully charged). Use it with a BMS (battery management system), a small board that protects the cells from overcharge and deep discharge. After the L293D drop, at most about 5.8 V reach the motors.

Step 5: Programming the Arduino

The sketch below reads single-character commands from the HC-05 and drives the four motors. It uses the AFMotor library, Adafruit’s library for the V1 Motor Shield. Install it in the Arduino IDE under Sketch > Include Library > Manage Libraries: search for “Adafruit Motor Shield library” (not the V2 library).

To upload the sketch, connect the Uno by USB, select Tools > Board > Arduino Uno and the matching port under Tools > Port, then click Upload.

Important: disconnect the HC-05 from pins 0 and 1 before uploading this sketch. Reconnect it after the upload completes, then reset the Arduino.

/**
 * Author: Omar Draidrya
 * Date: 2024/07/03
 * Bluetooth-controlled 4WD robot car using Adafruit Motor Shield V1.2 and HC-05.
 * Receives single-character commands (F/B/L/R/S) via serial from the HC-05
 * Bluetooth module and drives four DC motors on terminals M1-M4 accordingly.
 */
#include <AFMotor.h>
AF_DCMotor motor1(1);  // Create motor #1 using M1 connector
AF_DCMotor motor2(2);  // Create motor #2 using M2 connector
AF_DCMotor motor3(3);  // Create motor #3 using M3 connector
AF_DCMotor motor4(4);  // Create motor #4 using M4 connector
char command;
void setup() {
    Serial.begin(9600);  // Start serial communication at 9600 baud rate
    motor1.setSpeed(255);  // Set initial motor speeds
    motor2.setSpeed(255);
    motor3.setSpeed(255);
    motor4.setSpeed(255);
}
void loop() {
    if (Serial.available() > 0) {
        command = Serial.read();  // Read the incoming command
        if (command == 'F') {
            forward();
        } else if (command == 'B') {
            backward();
        } else if (command == 'L') {
            turnLeft();
        } else if (command == 'R') {
            turnRight();
        } else if (command == 'S') {
            stop();
        }
    }
}
void forward() {
    motor1.run(FORWARD);
    motor2.run(FORWARD);
    motor3.run(FORWARD);
    motor4.run(FORWARD);
}
void backward() {
    motor1.run(BACKWARD);
    motor2.run(BACKWARD);
    motor3.run(BACKWARD);
    motor4.run(BACKWARD);
}
void turnLeft() {
    motor1.run(BACKWARD);
    motor2.run(BACKWARD);
    motor3.run(FORWARD);
    motor4.run(FORWARD);
}
void turnRight() {
    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);
}

Code explanation

The sketch creates four motor objects, motor1(1) to motor4(4), one for each motor terminal M1 to M4. In setup(), serial communication starts at 9600 baud (bits per second). Both sides must use the same rate; 9600 is the HC-05 default in data mode (its normal mode, in which it passes data through). All four motors are set to full speed (255 of 255).

Each command is one ASCII character (a letter stored as one byte), so the sketch needs no text parsing. Characters other than F, B, L, R and S are ignored.

forward() runs all four motors FORWARD, so the car drives straight ahead. backward() runs them BACKWARD. turnLeft() runs the left motors backward and the right motors forward, a tank-style left turn. turnRight() does the opposite. stop() calls RELEASE on all motors, which cuts the power; the car rolls out and stops.

The turn functions rely on the left/right split from the motor table in Step 3. If one wheel spins the wrong way, swap the two wires of that motor at its motor terminal. If the whole car drives backward on F, swap the wires of all four motors, or swap FORWARD and BACKWARD in the code.

How to change speed

The speed is set in setup() with motor1.setSpeed(255). The value ranges from 0 (stopped) to 255 (full speed). To slow the car down, reduce this value. A value of 150 switches the motor on for about 59 % of each PWM cycle. PWM (pulse-width modulation) means the shield turns the motor on and off very fast, and the on-time share sets the speed. The real wheel speed also depends on battery voltage and load, and very low values may not start the motors at all. You can set different speeds for turning and straight driving by calling setSpeed() inside the movement functions.

How to test the code safely

For the first test, place the car on a box or a stack of books so the wheels do not touch the ground. That way you can check each direction without the car driving off the table. Connect over Bluetooth, send each command from the app and check that all four wheels spin in the expected direction. When they do, set the car on the floor and test at low speed with setSpeed() around 120.

Step 6: Building the Android app

The phone app is built in MIT App Inventor. It connects to the HC-05 over Bluetooth and sends the direction commands. You build the app from blocks, so you do not need to write any Java or Kotlin, the usual programming languages for Android apps.

Register and create a project

  • Go to MIT App Inventor (ai2.appinventor.mit.edu) and log in with your Google account.
  • Click “Start New Project” and name it “RobotCarControl”.

Design the user interface

In the Designer view (the App Inventor screen where you place the app’s components), add these components:

  • ListPicker: set its text to “Select Bluetooth Device”. The user picks the paired Bluetooth device from this list.
  • Label: shows the connection status. The blocks set it to “Connected” or “Not Connected”.
  • BluetoothClient: from the Connectivity palette. It is a non-visible component that handles the Bluetooth connection.
  • Four Buttons: one for each direction. Set their text and colors so the directions are clear.

App Inventor names new components automatically (ListPicker1, Label1, BluetoothClient1). Keep these default names, because the blocks below use them. Rename only the four buttons (ButtonForward, ButtonBackward, ButtonLeft, ButtonRight) with the Rename button in the Components panel.

Program the blocks

Switch to the Blocks view, the screen where you program the components with blocks. The listing below is a text version of the blocks, not code you can type. Click a component in the Blocks panel (for example ListPicker1) and drag the matching block, such as “when ListPicker1.BeforePicking”, into the workspace. The screenshot below shows the finished blocks. In it, the buttons are named Forward_Button, Backward_Button, Left_Button and Right_Button; any names work if the blocks match. The connect blocks there also set the background of Label1 to green or red.

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"
MIT App Inventor blocks: ListPicker connection logic and TouchDown/TouchUp handlers for the four direction buttons

Understanding the block logic

ListPicker1.BeforePicking: when the user taps the ListPicker, it fills the list with all paired Bluetooth devices (addresses and names) from BluetoothClient1.AddressesAndNames.

ListPicker1.AfterPicking: once the user selects a device, the app connects with BluetoothClient1.Connect and the selected address. On success, Label1.Text shows “Connected”, otherwise “Not Connected”.

TouchDown events: when the user presses (touches down on) a direction button, the app sends the matching command character (F, B, L or R) with BluetoothClient1.SendText, but only while the Bluetooth connection is active.

TouchUp events: when the user releases (lifts their finger from) any direction button, the app sends “S”. The car therefore moves only while you hold a button. On S the motors switch to RELEASE and the car rolls out for a short distance.

Step 7: Connecting and driving

Pair the HC-05

Turn on Bluetooth on the phone, open the Bluetooth settings and search for new devices. Pair with HC-05 using PIN 1234 (0000 on some modules). The phone remembers the pairing, so you do this only once.

Install the app and connect

Install the app on the phone first. In App Inventor, choose Build > Android App (.apk). Download the .apk file (the Android installation file) to the phone and install it; Android asks you to allow installs from this source. For quick tests, you can run the project live with the MIT AI2 Companion app instead. If Android asks for the “Nearby devices” permission, allow it, otherwise the app cannot list or connect to the HC-05. I used the app on an Android tablet and installed it from the downloaded .apk.

Open RobotCarControl, tap “Select Bluetooth Device” and choose HC-05. Label1 should show “Connected”. Press the direction buttons: the car drives forward and backward, turns left and right, and stops when you release the button.

Testing, troubleshooting and FAQ

This car uses the same code, app and wiring as its 3D printed successor, OmObi. The first test, troubleshooting and FAQ are maintained in the OmObi post: first test, troubleshooting and FAQ.

Related tutorials

Possible upgrades: an ultrasonic sensor for obstacle avoidance, a camera for FPV (first-person view) driving, or an ESP32 for Wi-Fi control.

1 thought on “Building a Bluetooth-Controlled Robot Car with Arduino, HC-05, and Adafruit Motor Shield”

Leave a Comment