To build a reliable Bluetooth Arduino controlled car, you need an Arduino Uno R3, an L298N dual H-bridge motor driver, and an HC-05 Bluetooth module, powered by a 2S LiPo battery. While many starter kits include 4x AA battery holders and 9V alkaline clips, these power sources will fail under the load of four TT gearmotors due to the L298N's inherent voltage drop. This guide provides the exact bench-tested wiring, pin mappings, and C++ code to get your rover moving, along with the specific debugging steps to resolve the most common serial and motor faults.
Bill of Materials & Component Specifications
Before ordering parts, understand the power budget. Four standard 1:48 TT gearmotors draw roughly 200mA each under normal load, spiking to 800mA+ at stall. The Arduino Uno and HC-05 add another 100mA. You need a battery chemistry that can deliver >1A continuous without severe voltage sag.
| Component | Exact Model / Variant | Nominal Voltage | Current Draw / Limit | Est. Price (2026) |
|---|---|---|---|---|
| Microcontroller | Arduino Uno R3 (ATmega328P) | 5V Logic | ~50mA (board only) | $24.00 |
| Motor Driver | L298N Dual H-Bridge Module | 5V to 35V DC | 2A per channel (3A peak) | $6.50 |
| Bluetooth Module | HC-05 (Zigbee/Classic BT) | 3.3V Logic (5V tolerant w/ divider) | 8mA idle / 50mA peak | $8.00 |
| Motors (x4) | TT Gearmotor 1:48 Ratio | 3V to 6V DC | 200mA nominal / 800mA stall | $12.00 (pack of 4) |
| Power Source | 2S LiPo Battery (1500mAh+) | 7.4V (8.4V fully charged) | 20C discharge (30A+ capable) | $18.00 |
| Chassis | 4WD Acrylic/Tank Tread Kit | N/A | N/A | $15.00 |
The L298N uses bipolar junction transistors (BJTs) in a Darlington pair configuration. According to the STMicroelectronics L298N datasheet, this topology introduces a saturation voltage drop (Vce) of roughly 2V to 3V. If you power the L298N with 5V from the Arduino's USB port, your 6V motors will only receive ~2.5V, resulting in sluggish movement or stalling. Always use a 7.4V 2S LiPo to ensure the motors receive a healthy 5V+ after the driver's internal drop.
Pin Mapping & Wiring Procedure
Proper wire gauge selection is critical here. Use 18 AWG silicone stranded wire for the battery-to-driver power connections to handle the current without heating up. Use 22 AWG solid core for the logic pins to the Arduino.
| Arduino Uno Pin | Component | Function | Notes / Constraints |
|---|---|---|---|
| D5 (PWM) | L298N ENA | Left Motor Speed | Must be a PWM-capable pin (~) |
| D4 | L298N IN1 | Left Motor Dir A | Digital OUT |
| D7 | L298N IN2 | Left Motor Dir B | Digital OUT |
| D8 | L298N IN3 | Right Motor Dir A | Digital OUT |
| D9 | L298N IN4 | Right Motor Dir B | Digital OUT |
| D6 (PWM) | L298N ENB | Right Motor Speed | Must be a PWM-capable pin (~) |
| D10 | HC-05 TX | SoftwareSerial RX | Receives 3.3V logic directly |
| D11 | HC-05 RX | SoftwareSerial TX | Requires 5V to 3.3V voltage divider |
| 5V | HC-05 VCC | Module Power | Do NOT power HC-05 from 3.3V pin |
| GND | L298N GND / HC-05 GND | Common Ground | Crucial: All grounds must tie together |
Numbered Wiring Steps
- Prepare the Voltage Divider: The Arduino Uno outputs 5V on its TX pin (D11), but the HC-05 RX pin expects 3.3V logic. Build a voltage divider using a 1kΩ resistor in series with the signal wire, and a 2kΩ resistor pulling down to GND. This drops the 5V signal to a safe ~3.3V.
- Wire the Motor Power: Connect the 2S LiPo's positive lead (red) to the L298N's 12V terminal (which accepts up to 35V). Connect the negative lead (black) to the L298N's GND terminal.
- Set the L298N 5V Jumper: Locate the small jumper cap near the L298N's power screw terminals. Because your input voltage is under 12V (7.4V nominal), leave this jumper ON. This enables the board's internal 7805 regulator to output 5V, which you can use to power the Arduino Uno via its Vin or 5V pin if you want to run off a single battery switch.
- Establish Common Ground: Run a jumper wire from the L298N GND terminal to the Arduino Uno GND pin, and another to the HC-05 GND pin. Without a shared ground reference, the logic signals will float and the motors will behave erratically.
- Secure Screw Terminals: The L298N's green screw terminals have a shallow bite. If using stranded wire, strip 6mm, twist tightly, and tin with a soldering iron before clamping to prevent pull-out under chassis vibration.
Complete Arduino C++ Control Code
The following code targets the Arduino Uno R3. It utilizes the SoftwareSerial library to communicate with the HC-05, leaving the hardware Serial (pins 0 and 1) free for USB debugging. The code includes a watchdog-style heartbeat check to stop the motors if the Bluetooth connection drops or the phone app crashes.
#include <SoftwareSerial.h>
// --- PIN DEFINITIONS ---
#define ENA 5 // PWM for Motor A (Left)
#define IN1 4 // Direction Motor A
#define IN2 7 // Direction Motor A
#define IN3 8 // Direction Motor B
#define IN4 9 // Direction Motor B
#define ENB 6 // PWM for Motor B (Right)
#define BT_RX 10 // HC-05 TX -> Arduino Pin 10
#define BT_TX 11 // HC-05 RX -> Arduino Pin 11 (via voltage divider)
SoftwareSerial BTSerial(BT_RX, BT_TX);
const int MOTOR_SPEED = 220; // 0-255 PWM (220 provides good torque without maxing out)
unsigned long lastCommandTime = 0;
const unsigned long TIMEOUT_MS = 500; // Stop motors if no signal for 500ms
void setup() {
Serial.begin(9600); // Hardware serial for USB debugging
BTSerial.begin(9600); // HC-05 default baud rate is 9600
pinMode(ENA, OUTPUT);
pinMode(ENB, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
stopMotors();
Serial.println("System Ready. Awaiting Bluetooth commands...");
}
void loop() {
if (BTSerial.available()) {
char command = BTSerial.read();
lastCommandTime = millis(); // Reset timeout timer
switch(command) {
case 'F': moveForward(); break;
case 'B': moveBackward(); break;
case 'L': turnLeft(); break;
case 'R': turnRight(); break;
case 'S': stopMotors(); break;
default:
Serial.print("Unknown command received: ");
Serial.println(command);
break;
}
}
// Safety timeout: Stop car if connection drops
if (millis() - lastCommandTime > TIMEOUT_MS) {
stopMotors();
}
}
// --- MOTOR CONTROL FUNCTIONS ---
void moveForward() {
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
analogWrite(ENA, MOTOR_SPEED); analogWrite(ENB, MOTOR_SPEED);
}
void moveBackward() {
digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
analogWrite(ENA, MOTOR_SPEED); analogWrite(ENB, MOTOR_SPEED);
}
void turnLeft() {
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW); // Stop left
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW); // Right forward
analogWrite(ENA, 0); analogWrite(ENB, MOTOR_SPEED);
}
void turnRight() {
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW); // Left forward
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW); // Stop right
analogWrite(ENA, MOTOR_SPEED); analogWrite(ENB, 0);
}
void stopMotors() {
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
analogWrite(ENA, 0); analogWrite(ENB, 0);
}
Debugging: The First 3 Things to Check When It Fails
Embedded hardware rarely works perfectly on the first power-up. If your Arduino controlled car is misbehaving, follow this ranked decision path based on the exact symptoms and serial monitor outputs.
1. The Baud Rate Mismatch
Symptom: You open the Arduino IDE Serial Monitor and see gibberish characters like ⸮⸮⸮ or ? when the HC-05 receives data.
Ranked Causes:
- Clone HC-05 Module: While genuine modules default to 9600 baud, many cheap clones ship configured to 38400 baud.
- App Mismatch: Your smartphone Bluetooth RC controller app is hardcoded to a different baud rate.
The Fix: Put the HC-05 into AT command mode by holding the micro-button while powering it on (the LED will blink slowly, once every 2 seconds). Send AT+UART? via the Serial Monitor (with 'Both NL & CR' enabled). If it returns +UART:38400,0,0, you must either change your BTSerial.begin(38400) in the C++ code, or reconfigure the module to 9600 using the command AT+UART=9600,0,0.
2. The Voltage Drop Brownout
Symptom: The car is on, the HC-05 connects to your phone, but when you press 'Forward', the motors emit a high-pitched hum or click, the wheels don't turn, and the Arduino Serial monitor suddenly resets or prints garbage.
Ranked Causes:
- Insufficient Battery C-Rating: You are using a standard 9V alkaline or 4x AA NiMH pack. When the motors draw 1A+ at startup, the battery voltage sags below the Arduino's brownout detection threshold (approx 2.7V on the ATmega328P), causing a hard reset.
- L298N Vce Saturation: As noted in the BOM section, feeding the L298N with 5V leaves only ~2.5V for the motors, which is below the TT motor's starting torque threshold.
The Fix: Switch to a 2S LiPo (7.4V) with at least a 20C discharge rating. Ensure your battery wires are 18 AWG or thicker to minimize resistance.
3. The Bluetooth State Trap
Symptom: Your phone app shows 'Connected', but the car ignores all commands. Alternatively, querying the module returns +STATE: INIT or +STATE: INQUIRING.
Ranked Causes:
- Stuck in AT Mode: The HC-05 'KEY' pin is being pulled HIGH, or the button is stuck, forcing the module into configuration mode rather than transparent data mode.
- TX/RX Crossed Incorrectly: You connected Arduino TX to HC-05 TX instead of RX.
The Fix: Ensure the KEY pin on the HC-05 is left completely floating (disconnected) during normal driving operation. Verify that Arduino Pin 11 (TX) goes to HC-05 RX, and Arduino Pin 10 (RX) goes to HC-05 TX. Serial communication must always cross over (TX to RX, RX to TX).
Extending and Simplifying the Build
Once the base Bluetooth Arduino controlled car is navigating your living room, you'll likely want to modify the platform. Here is how to scale the project up or down based on your goals.
How to Simplify (Line Follower / Obstacle Avoidance)
If Bluetooth pairing and smartphone apps are introducing too much latency or frustration, strip the HC-05 module out entirely. Replace it with an HC-SR04 Ultrasonic Sensor mounted on a micro-servo for autonomous obstacle avoidance, or wire up three TCRT5000 IR Reflectance Sensors to the underside for line following. You can use the standard IRremote library to control the car with a cheap 38kHz NEC TV remote, bypassing the need for software serial and smartphone dependencies entirely.
How to Extend (FPV and Telemetry)
To upgrade from basic line-of-sight driving to First Person View (FPV), the Arduino Uno R3 lacks the processing power and native WiFi required for video streaming. Swap the Uno for an ESP32-CAM (AI-Thinker variant). The ESP32 handles the motor PWM and hosts a WebSocket server for low-latency browser-based controls, while simultaneously streaming an MJPEG video feed over 2.4GHz WiFi. For advanced telemetry, add an MPU6050 IMU via I2C (pins A4/A5) to implement PID control loops, allowing the car to correct for wheel slip and maintain perfectly straight trajectories over uneven terrain.






