Bluetooth controlled robot car with Arduino and HC-05

Wire an HC-05 to D2 and D3, drive an L298N, pair at 9600 baud and add the failsafe that stops the car when the phone goes quiet.

Reference
RE-134
Published
Read
10 min

About the author · Sources, testing and AI policy

Two-wheel robot car controlled from a phone over Bluetooth

Wire the HC-05 to D2 and D3, the L298N to four direction pins and two PWM pins, pair the phone at 9600 baud, and send single letters. The car moves on F, B, L, R and S, and stops by itself when the phone goes quiet. Three faults cause most failures: a missing voltage divider on the module’s RX pin, a 5 V rail shared with the motors, and ENA or ENB jumpers left in place.

This is a first Bluetooth car, not a competition robot. No encoders, no closed-loop speed control, no app to write. The job is a short parser, a motor driver and power wiring that survives a stall.

Parts you need

  • Arduino Uno, or a Nano if you want a lighter board. The Uno guide lists the PWM pins; you need two here.
  • HC-05 Bluetooth module. An HC-06 also works; the HC-05 lets you change the name, PIN, baud and role in AT mode.
  • L298N module with heatsink. Two channels, screw terminals, onboard 5 V regulator.
  • Two TT gear motors with wheels and a 2WD chassis with a caster.
  • Battery: a 2S 18650 holder with protected cells, or 4×AA NiMH for a slow first test. A 9 V battery will not drive the motors.
  • Switch, 1 kΩ and 2 kΩ resistors, jumper wires, a 100 µF capacitor.
  • Optional 5 V 2 A buck converter for a logic rail that does not sag when the motors start.

Wiring the HC-05 and the L298N to an Uno

SoftwareSerial gives the HC-05 its own pair of pins, so the USB port stays free for debugging and the parser never fights with the upload.

Module pinConnects toNote
HC-05 VCC5VBreakout has a 3.3 V regulator; the radio is not 5 V tolerant
HC-05 GNDGNDCommon ground with the L298N and the battery
HC-05 TXDD3Straight wire; 3.3 V still reads HIGH on a 5 V Uno
HC-05 RXDD2 through a dividerD2 → 1 kΩ → RXD, and RXD → 2 kΩ → GND. This brings 5 V down to about 3.3 V
L298N ENAD5 (PWM)Remove the ENA jumper or the left motor runs at full speed only
L298N IN1, IN2D7, D8Left motor direction. Swapping these two reverses that motor
L298N IN3, IN4D9, D10Right motor direction
L298N ENBD6 (PWM)Remove the ENB jumper
L298N +12VBattery + through the switchCheck polarity twice; this input has no reverse protection
L298N GNDBattery − and Uno GNDTwo ground paths, not one
L298N 5V outOptional source for the HC-05Small onboard regulator. Do not feed the Uno 5V pin from it while USB is plugged in
Pin map for the sketch below. The Uno’s PWM pins are 3, 5, 6, 9, 10 and 11.

Why the module’s RX pin needs a divider

The HC-05 radio runs at 3.3 V. Its TXD output is safe to feed into a 5 V Arduino input, because 3.3 V sits above the Uno’s HIGH threshold. Its RXD input is not safe to drive at 5 V. A 1 kΩ series resistor with 2 kΩ to ground puts about 3.3 V on that pin. Without it the module can appear paired and then drop commands under load.

AT mode, baud and pairing

The factory defaults are usually 9600 baud, PIN 1234 or 0000, and a name like HC-05. AT mode runs at its own baud, commonly 38400, separate from the 9600 data baud. You only need AT mode if a default is wrong or you want to rename the module.

  1. Unplug power. Hold the small button on the module, or tie the EN/KEY pin high, then apply power. The LED slows to a regular blink.
  2. The module talks to D2 and D3, not the USB port, so upload a short passthrough sketch that copies the hardware serial to SoftwareSerial and back. Open the Serial Monitor at 38400 with “Both NL & CR”. A few clones use 9600 in AT mode, so try that next if you get nothing.
  3. Type AT. You should get OK. If you do not, the RX divider is wrong or the pins are swapped.
  4. Set what you need: AT+NAME=ROBOT1, AT+PSWD="1234", AT+UART=9600,0,0. Each command replies OK.
  5. Power cycle without the button held. The module is now in data mode at 9600 baud, ready for the sketch.

Pair from the phone’s Bluetooth settings, then open a serial Bluetooth app that sends characters. Apps that send “F” on a button press match this sketch directly. If the app sends words such as FORWARD, either change the app or extend the parser; the sketch ignores anything that is not a single command letter, including carriage returns and line feeds.

The sketch, with a failsafe stop

The parser keeps one command, not a queue, so stale characters cannot build up and keep the car moving after the button is released. The timeout is the real safety feature: if no valid character arrives for 600 ms, the motors stop.

// Bluetooth robot car: Arduino Uno + HC-05 + L298N + two TT motors
// Send F, B, L, R or S from a Bluetooth serial app at 9600 baud.

#include <SoftwareSerial.h>
SoftwareSerial bt(3, 2);        // RX = D3 (from HC-05 TXD), TX = D2 (to HC-05 RXD via divider)

const int ENA = 5, IN1 = 7, IN2 = 8;
const int ENB = 6, IN3 = 9, IN4 = 10;

const int START_PWM = 95;       // below this a TT motor on an L298N usually just buzzes
const int RUN_PWM   = 190;
const unsigned long FAILSAFE_MS = 600;

char cmd = 'S';
unsigned long lastCmdAt = 0;

void setup() {
  bt.begin(9600);               // data-mode baud, set with AT+UART
  Serial.begin(9600);           // USB mirror; comment out for a battery-only run
  pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  brake();
}

void loop() {
  while (bt.available()) {
    char c = toupper(bt.read());
    if (c == 'F' || c == 'B' || c == 'L' || c == 'R' || c == 'S') {
      cmd = c;
      lastCmdAt = millis();
      Serial.println(c);        // proves the command arrived before you blame the motors
    }
  }
  if (millis() - lastCmdAt > FAILSAFE_MS) cmd = 'S';
  drive(cmd);
}

void drive(char c) {
  switch (c) {
    case 'F': motors(RUN_PWM, true,  RUN_PWM, true);   break;
    case 'B': motors(RUN_PWM, false, RUN_PWM, false);  break;
    case 'L': motors(START_PWM, false, RUN_PWM, true); break;
    case 'R': motors(RUN_PWM, true, START_PWM, false); break;
    default:  brake(); return;
  }
}

void motors(int leftPwm, bool leftFwd, int rightPwm, bool rightFwd) {
  digitalWrite(IN1, leftFwd ? HIGH : LOW);
  digitalWrite(IN2, leftFwd ? LOW : HIGH);
  digitalWrite(IN3, rightFwd ? HIGH : LOW);
  digitalWrite(IN4, rightFwd ? LOW : HIGH);
  analogWrite(ENA, leftPwm);
  analogWrite(ENB, rightPwm);
}

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

Motor deadband: why 80 on the PWM pin does nothing

Two effects stack up. The L298N drops roughly 1.4–2 V across its output transistors, so the motor never sees the pack voltage. A TT motor also needs a minimum voltage to overcome gearbox friction. On a 7.4 V pack most TT motors begin to turn somewhere between 60 and 100 on the 0–255 PWM scale, and the number shifts with charge, load and floor surface.

Measure your own figure: run a test sketch that steps the PWM up from 0 in tens, with the robot lifted, and note the value where both wheels start cleanly. Use that as START_PWM for pivots. Turning below the deadband does not make the car go slower; it makes one wheel stall and the car lurch.

Which driver should you use?

DriverVoltage lost as heatCurrent per channelPins neededPick it when
L298N moduleAbout 1.4–2 V2 A rated, needs the heatsink2 PWM + 4 directionYou already own one and run a 7.4 V or higher pack
TB6612FNG moduleAbout 0.5 V1.2 A continuous, 3.2 A peak per the datasheet2 PWM + 4 direction + standbyYou run a 6 V pack, or want longer runtime from the same cells
Both are single-channel-per-motor H-bridges. The wiring and code above transfer to the TB6612 with the pin names renamed. More detail in the L298N guide.

When it pairs but the car will not move

SymptomLikely causeCheck this first
Phone pairs, wheels never turnTXD and RXD not crossed, or the app sends wordsD3 must go to TXD and D2 to RXD. Watch the USB serial mirror: does a letter appear?
One wheel onlyENA or ENB jumper still fitted, or that motor’s pair is wired to one terminalRemove both jumpers; confirm both motor pairs on OUT1/OUT2 and OUT3/OUT4
Uno resets when the car startsShared 5 V rail sagging under motor currentPower logic from a buck converter; keep grounds common; add 100 µF across the 5 V rail
Module disconnects under load5 V on RXD, or the module fed from a noisy railFit the divider; feed the module from the L298N 5 V output, not the motor terminals
Car keeps rolling after the app closesNo timeout in the parserKeep the 600 ms failsafe; test it by switching the phone’s Bluetooth off mid-run
Commands arrive as garbageBaud mismatch between the module’s data setting and the sketchAT+UART=9600,0,0, then bt.begin(9600)
Steering works backwardsOne motor pair reversedSwap that motor’s two leads at the terminal block, not the code

What it costs

ItemPlanning price (INR)
Arduino Uno clone, or Nano clone₹450–700 (Nano ₹300–450)
HC-05 module₹250–450
L298N module₹150–250
TT gear motors with wheels, pair₹250–350
2WD chassis with caster₹150–300
2S 18650 holder with two protected cells₹350–600
Switch, wires, resistors, capacitor₹100–200
5 V 2 A buck converter₹200–300
Total₹1,900–3,150
Planning ranges, October 2026 (planning range; check current prices). Excludes charger, shipping, tools and a spare motor driver.

Runtime is the number students forget to plan. Two TT motors plus the Uno and the module usually average a few hundred milliamps, so a 2,000 mAh pack gives roughly an hour and a half to two hours with reserve. Put your own measured figures into the battery runtime calculator before you promise a demo length.

This build sheet is reviewed against datasheets and vendor listings; test your own robot before relying on it. Check wheel direction with the car lifted, then the failsafe with the phone out of range.

Common questions

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

Yes. The HC-06 is slave-only and usually cheaper, which is all a car needs. You lose the ability to change its role, and some clones accept fewer AT commands. The wiring, divider and sketch are the same.

Why does the car steer the wrong way?

One motor pair is reversed or the IN pins are swapped. Lift the car, send F, watch which wheel runs backwards, then swap that motor’s two leads at the terminal block. Changing the code to compensate hides a wiring fault you will meet again.

Can I control it from an iPhone?

Not with an HC-05. iOS does not expose the Bluetooth SPP profile the module uses, so Android or a laptop is the practical route. If your phone is an iPhone, build the ESP32 robot car instead; it serves a web page over Wi-Fi and needs no app.

Adding a camera stream to the same chassis turns it into a remote-view rover.

When Bluetooth range is not enough, control the robot over Wi-Fi from an app.

When Bluetooth range is not enough, move the same robot to Wi-Fi control on an ESP32.

Sources and further reading

Documents used to check the numbers on this page:

Newer builds and guides

Pass it onWhatsAppLinkedIn

Keep reading

Continue learning

The Build Note

One short email when something is worth your bench time.

A new build sheet, a part price that moved, or a mistake worth avoiding. No daily drip, no recycled news.

One short email when a build or a price is worth your time. Unsubscribe in one click.