Bluetooth Controlled Robot Car with HC-05 and Arduino Uno

Remote control is the quickest way to make a robot feel alive, and it is also a useful tool: you can drive a robot to test its motors, collect sensor data while steering it by hand, or use manual control as a fallback in a competition. The HC-05 Bluetooth module makes this easy because it appears to the Arduino as a plain serial port.

In this tutorial you will wire an HC-05 to the 2WD chassis, pair it with an Android phone, and write a sketch that turns single-character commands into motion with speed control. You will also add a safety timeout so the car stops when the connection drops, which is the feature most beginner projects forget.

About the HC-05 Module

The HC-05 is a Bluetooth Classic module using the Serial Port Profile (SPP). Once a phone pairs and connects, every byte the phone sends comes out of the module's TXD pin, and every byte you send to its RXD pin goes to the phone. Most HC-05 boards sold on a breakout have a regulator and accept 5 V on VCC, but their RXD input is a 3.3 V logic pin.

SettingCommon defaultNotes
Data mode baud rate9600Used for normal communication
AT command mode baud rate38400Entered by holding the EN or KEY button while powering up
Pairing PIN1234 (some modules 0000)Change with an AT command if needed
Status LED, not connectedFast blinkingSlow double blink when connected on many boards

Wiring

The Uno's hardware serial port on pins 0 and 1 is also used for USB uploads, and a module connected there blocks uploading. So this project uses SoftwareSerial on pins 2 and 3. The Arduino TX signal is 5 V, so it goes through a voltage divider before reaching the HC-05 RXD pin.

HC-05 VCC  --> Arduino 5V
HC-05 GND  --> Arduino GND
HC-05 TXD  --> Arduino D2   (SoftwareSerial RX)
Arduino D3 --> 1k --+--> HC-05 RXD
                    |
                    2k
                    |
                   GND        (5 V x 2k / 3k = 3.3 V)

Motor driver: TB6612FNG on pins 4, 5, 6, 7, 8, 9, 10 as in the 2WD chassis tutorial

Command Protocol

Many free Android Bluetooth controller apps let you assign a character to each button. Using single characters keeps parsing trivial and robust. Configure your app to send these characters, or type them in a Bluetooth serial terminal app to test.

CharacterAction
FForward
BBackward
LSpin left
RSpin right
G / IForward-left / forward-right curve
SStop
0 to 9Speed level, from slow (0) to full (9)

Arduino Code

HC-05 Bluetooth car with speed levels and connection-loss timeout
#include <SoftwareSerial.h>

SoftwareSerial bt(2, 3);  // RX = D2 (from HC-05 TXD), TX = D3 (to HC-05 RXD via divider)

const int PWMA = 5, AIN1 = 7, AIN2 = 8;    // left motor
const int PWMB = 6, BIN1 = 9, BIN2 = 10;   // right motor
const int STBY = 4;

int speedPwm = 180;                 // current speed, changed by '0'..'9'
unsigned long lastCmdMs = 0;
const unsigned long TIMEOUT_MS = 600; // stop if no command for this long
// Set to false if your app sends a command only once per button press
const bool USE_TIMEOUT = true;

void motor(int pwmPin, int in1, int in2, int s) {
  s = constrain(s, -255, 255);
  digitalWrite(in1, s > 0 ? HIGH : LOW);
  digitalWrite(in2, s < 0 ? HIGH : LOW);
  analogWrite(pwmPin, abs(s));
}
void drive(int l, int r) { motor(PWMA, AIN1, AIN2, l); motor(PWMB, BIN1, BIN2, r); }

void handleCommand(char c) {
  int s = speedPwm;
  switch (c) {
    case 'F': drive(s, s);          break;
    case 'B': drive(-s, -s);        break;
    case 'L': drive(-s, s);         break;
    case 'R': drive(s, -s);         break;
    case 'G': drive(s / 2, s);      break;   // curve left
    case 'I': drive(s, s / 2);      break;   // curve right
    case 'S': drive(0, 0);          break;
    default:
      if (c >= '0' && c <= '9') {
        // map level 0..9 to PWM 100..255 (below ~100 many gear motors stall)
        speedPwm = map(c - '0', 0, 9, 100, 255);
      }
      return;                                // ignore other bytes such as newlines
  }
  lastCmdMs = millis();
}

void setup() {
  int pins[] = {PWMA, AIN1, AIN2, PWMB, BIN1, BIN2, STBY};
  for (int p : pins) pinMode(p, OUTPUT);
  digitalWrite(STBY, HIGH);
  Serial.begin(9600);   // USB debug
  bt.begin(9600);       // HC-05 default data-mode baud rate
  Serial.println("Bluetooth car ready");
}

void loop() {
  while (bt.available()) {
    char c = bt.read();
    Serial.write(c);    // echo to Serial Monitor for debugging
    handleCommand(c);
  }

  // Failsafe: stop if the phone stops sending (out of range, app closed)
  if (USE_TIMEOUT && millis() - lastCmdMs > TIMEOUT_MS) {
    drive(0, 0);
  }
}

How It Works

SoftwareSerial creates a serial port in software on two ordinary pins. It is reliable at 9600 baud, which is plenty for single-character commands. The main loop reads every waiting byte and passes it to handleCommand, which either changes the motor outputs or stores a new speed level. Speed digits do not count as motion commands, so they do not reset the timeout.

The timeout assumes your controller app repeats the command while a button is held, which many apps do. If yours sends a character only when a button is pressed and an S when it is released, set USE_TIMEOUT to false, but then a lost connection leaves the car running until the HC-05 drops the link. Test this by walking away from the car with a button held.

Changing the name or PIN

To rename the module, enter AT mode by holding its button while applying power, connect at 38400 baud and send AT+NAME=MyRobot followed by carriage return and newline. AT+PSWD changes the PIN on many firmware versions, though the exact syntax varies between firmware releases, so check AT+VERSION and the documentation for your module.

Troubleshooting

  • Phone pairs but nothing arrives: TXD and RXD are crossed wrongly. HC-05 TXD must go to the Arduino RX pin.
  • Garbage characters in the Serial Monitor: the baud rate does not match. Try 9600, and if the module was changed earlier, 38400.
  • Upload fails: the module is connected to pins 0 and 1. Move it to SoftwareSerial pins.
  • Car keeps stopping and starting: the app sends commands too slowly for the timeout. Increase TIMEOUT_MS.
  • Module resets when motors start: motor noise on the 5 V rail; add capacitors and keep the module wires away from motor wires.

Summary

The HC-05 behaves like a wireless serial cable. With SoftwareSerial on pins 2 and 3, a divider on its RXD input and a single-character command set, an Arduino becomes a phone-controlled car in a few dozen lines. Always add a failsafe stop, and remember that iPhones need a BLE module instead.