What the HC-05 Module Actually Does
It is easy to imagine that adding Bluetooth to a robot means teaching the Arduino about wireless protocols. It does not. An HC-05 module is a wireless serial cable, and that is the whole mental model you need. Your phone sends a character over Bluetooth, the module receives it and hands it to the Arduino down a wire as ordinary serial data. As far as your sketch is concerned, characters simply arrive — exactly as if someone had typed them into the Serial Monitor.
That framing is liberating, because it means you already know how to write this program. You read a character, you decide what it means, you drive the motors. The wireless part is somebody else's problem, solved inside a module that costs very little.
Serial communication needs both sides to agree on a speed, called the baud rate. HC-05 modules commonly arrive configured at 9600 baud for ordinary data, so begin(9600) in your sketch usually matches. If characters arrive as meaningless symbols, a baud rate mismatch is the first thing to suspect.
One limitation worth knowing before you buy: the HC-05 uses classic Bluetooth, which Android phones support through simple terminal and controller apps but Apple devices generally do not open to third-party apps in this way. If you need to control a robot from an iPhone, look at a Bluetooth Low Energy module or an ESP32 instead. Check what your own phone supports before ordering parts.
- VCC — power for the module, from the Arduino's 5 V pin
- GND — ground, shared with the Arduino and with the motor supply
- TXD — the module transmits here; it connects to an Arduino receive pin
- RXD — the module receives here; it connects to an Arduino transmit pin, through a voltage divider
- STATE / EN — extra pins for status and configuration mode; not needed for this project
- Ordinary Bluetooth range is short and it does not go through walls well. This is a line-of-sight toy, not a remote control for another room — and that limit is a safety feature, since a robot you cannot see is a robot you cannot stop.
Wiring: Cross the Lines, and Mind the Logic Level
Serial connections always cross. Transmit on one device goes to receive on the other, because a wire carrying data out of one chip must carry it into the next. So the module's TXD goes to an Arduino receive pin and the module's RXD comes from an Arduino transmit pin. Connecting TX to TX is the commonest wiring mistake here, and it produces a setup that powers on happily, pairs successfully and never transfers a single character.
Then the electrical detail that matters. An Arduino Uno's output pins swing to about 5 V, while the HC-05's own logic is 3.3 V. The module's power input generally tolerates 5 V because the breakout board carries a regulator, but its RXD data pin does not — feeding 5 V into it stresses the module and can shorten its life or damage it. The standard fix is a voltage divider: two resistors between the Arduino's transmit pin and the module's RXD, tapped in the middle, so the module sees a reduced voltage.
The other direction needs nothing. The module transmits at 3.3 V, and an Arduino input reads that comfortably as HIGH. So the divider goes on one line only, from the Arduino towards the module. A ready-made logic level converter does the same job on both lines if you would rather buy than build.
Finally, do not put the module on pins 0 and 1. Those are the Arduino's own hardware serial pins, shared with the USB link — a module attached there blocks code uploads and fights with the Serial Monitor. Use SoftwareSerial to create a second serial port on two ordinary digital pins instead. That keeps the USB link free for printing debug messages while the robot is running, which is exactly what you want while you are still getting it working.
// HC-05 wiring for an Arduino Uno
//
// HC-05 VCC ---> Arduino 5V
// HC-05 GND ---> Arduino GND (shared with the motor supply ground)
// HC-05 TXD ---> Arduino pin 10 (SoftwareSerial RX) - no divider needed
// HC-05 RXD <--- voltage divider <--- Arduino pin 11 (SoftwareSerial TX)
//
// The divider steps the Arduino's ~5 V output down for the module's
// 3.3 V input. Two resistors in series from pin 11 to GND, with the
// module's RXD taken from the junction between them.
//
// Do NOT use pins 0 and 1 - they belong to the USB serial link and
// anything connected there will block your uploads. - If uploads suddenly start failing after you add this module, the module is on pins 0 or 1. Disconnect it, upload, reconnect — or better, move it to the SoftwareSerial pins for good.
SoftwareSerialis created in software rather than by dedicated hardware, so it is less reliable at high baud rates and only one such port can listen at a time. At 9600 baud for a few command characters it is entirely fine.
The Sketch, With a Failsafe
The program is a command interpreter. Each character the phone sends stands for an action: F for forward, B for back, L and R for turns, S for stop. A switch statement maps each one to a function, and the motor functions are the same ones you already wrote for the obstacle-avoiding robot.
The important addition is a failsafe, and it is not optional in a robot you control remotely. Consider what happens with the naive version: you press forward, the robot starts driving, and then you walk out of Bluetooth range or your phone's battery dies. No stop command ever arrives. The robot keeps driving forward — into a wall, down a staircase, or into somebody's ankles — until its battery runs flat.
The fix is to require the controller to keep proving it is still there. Record the time each command arrives, and in every pass of loop() check how long it has been. If nothing has arrived for longer than a short timeout, stop the motors regardless of what the last command said. A phone that is still connected sends commands often enough to keep the robot alive; a phone that has vanished stops it within a fraction of a second.
This is a genuine professional pattern with a name — a watchdog or deadman timer — and it appears in industrial machines, drones and vehicles for exactly the same reason. Any machine that moves under remote command should stop when the command stops.
#include <SoftwareSerial.h>
SoftwareSerial bluetooth(10, 11); // (RX pin, TX pin)
// --- motors (same wiring as the earlier robots) ---
const int enA = 9; const int in1 = 8; const int in2 = 7; // right
const int enB = 3; const int in3 = 5; const int in4 = 4; // left
int motorSpeed = 180; // changed live by the 1-5 keys
unsigned long lastCommandTime = 0;
const unsigned long commandTimeout = 600; // ms before the failsafe fires
void setup() {
pinMode(enA, OUTPUT); pinMode(in1, OUTPUT); pinMode(in2, OUTPUT);
pinMode(enB, OUTPUT); pinMode(in3, OUTPUT); pinMode(in4, OUTPUT);
bluetooth.begin(9600); // to the phone
Serial.begin(9600); // to your computer, for debugging
Serial.println("ready");
stopRobot();
}
void loop() {
if (bluetooth.available()) {
char command = bluetooth.read();
lastCommandTime = millis();
Serial.println(command); // watch what the phone is really sending
switch (command) {
case 'F': moveForward(); break;
case 'B': moveBackward(); break;
case 'L': turnLeft(); break;
case 'R': turnRight(); break;
case 'S': stopRobot(); break;
// speed presets
case '1': motorSpeed = 120; break;
case '3': motorSpeed = 180; break;
case '5': motorSpeed = 250; break;
}
}
// FAILSAFE: nothing heard recently means stop, whatever we were doing
if (millis() - lastCommandTime > commandTimeout) {
stopRobot();
}
}
void moveForward() {
digitalWrite(in1, HIGH); digitalWrite(in2, LOW);
digitalWrite(in3, HIGH); digitalWrite(in4, LOW);
analogWrite(enA, motorSpeed); analogWrite(enB, motorSpeed);
}
void moveBackward() {
digitalWrite(in1, LOW); digitalWrite(in2, HIGH);
digitalWrite(in3, LOW); digitalWrite(in4, HIGH);
analogWrite(enA, motorSpeed); analogWrite(enB, motorSpeed);
}
void turnLeft() {
digitalWrite(in1, HIGH); digitalWrite(in2, LOW); // right wheel forward
digitalWrite(in3, LOW); digitalWrite(in4, HIGH); // left wheel back
analogWrite(enA, motorSpeed); analogWrite(enB, motorSpeed);
}
void turnRight() {
digitalWrite(in1, LOW); digitalWrite(in2, HIGH);
digitalWrite(in3, HIGH); digitalWrite(in4, LOW);
analogWrite(enA, motorSpeed); analogWrite(enB, motorSpeed);
}
void stopRobot() {
digitalWrite(in1, LOW); digitalWrite(in2, LOW);
digitalWrite(in3, LOW); digitalWrite(in4, LOW);
analogWrite(enA, 0); analogWrite(enB, 0);
} - Set
commandTimeoutto comfortably longer than the gap between the commands your app sends when a button is held, or the robot will stutter as the failsafe keeps cutting in. If your app sends a command only once per press, either choose an app that repeats while held, or lengthen the timeout — but never remove it. - Printing every received character to the Serial Monitor is the fastest way to debug this project. If the characters are wrong, the problem is the app's configuration; if nothing appears at all, the problem is the wiring or the baud rate.
Pairing the Phone and Testing Safely
Pairing happens in the phone's own Bluetooth settings, not in the app. Power the robot, look for the module in the list of available devices, and pair with it. These modules commonly ship with a PIN of 1234 or 0000; if neither is accepted, check the documentation for the module you bought. Pairing is a one-time step — afterwards the app connects without asking again.
Then install any Bluetooth serial terminal or Arduino controller app from your phone's app store. Configure its buttons to send exactly the characters your sketch expects, one character each, with no extra newline if the app offers a choice. Many puzzling failures are simply an app sending F followed by a line ending, or sending the word Forward instead of a single letter.
Test in this order. Keep the robot's wheels off the ground for the first run and watch the Serial Monitor while you press buttons — you should see each character appear. Only when the right characters are arriving and the wheels are turning in the right directions should you put the robot on the floor. Test in an open space away from stairs, and keep it where you can reach it.
Once it works, the interesting extensions are easy. Send speed presets, as the sketch already does. Send a character that makes the robot beep so you can find it. Or, best of all, combine this project with the earlier one: let the phone steer, but keep the ultrasonic sensor active and let the robot refuse a forward command when there is a wall in front of it. That is how real remote-operated machines are built — the operator gives intent, the machine keeps itself out of trouble.
- Pair in the phone's Bluetooth settings first; the PIN is commonly 1234 or 0000
- Configure the app to send single characters that match your
switchexactly - Watch the Serial Monitor to confirm which characters are actually arriving
- First run with the wheels off the ground, then on an open floor away from stairs
- Confirm the failsafe by walking out of range on purpose and checking that it stops
- Deliberately test the failsafe before you trust it. Drive the robot, then close the app or switch off Bluetooth on the phone, and confirm the robot stops within a second. A failsafe you have never tested is not a failsafe.
