The Verdict: Which Chassis and Board to Choose

Building an RC car with Arduino is a rite of passage for embedded hobbyists, but most tutorials leave you with a car that stutters, drifts, or runs away when the Bluetooth connection drops. The root cause is almost always underspecified power delivery and lack of software watchdogs. Before buying parts, use this decision path to lock in your hardware.

If your goal is...Choose this Board & DriverChoose this Power Source
Learning basic logic and PWMArduino Uno R3 + L298N4x 1.5V AA (6V) - Expect slow speed
Reliable outdoor driving (Default Pick)Arduino Uno R3 + L298N2S 7.4V LiPo (1500mAh+)
High efficiency / minimal heatArduino Nano + TB6612FNG2S or 3S LiPo
Computer vision / autonomousRaspberry Pi 4 + ESP32 co-processor3S 11.1V LiPo + Step-down buck

The Concrete Pick: For a robust, debuggable first build, we are terminating on the Arduino Uno R3 (ATmega328P DIP variant) paired with an L298N Dual H-Bridge and a 2S 7.4V LiPo battery. The Uno gives you full-size headers for easy probing, and the 2S LiPo provides the 8.4V peak needed to overcome the L298N's internal voltage drop.

Project Difficulty: Intermediate (3/5)
Time to Build: 2-3 hours (excluding 3D printing/custom chassis)
Core Skills: PWM logic, SoftwareSerial UART, LiPo safety handling.

Exact Parts List and Spec Sheet

Do not substitute the motor driver without adjusting the code logic. The L298N uses bipolar junction transistors (BJTs), which drop about 2V to 3V across the H-bridge. If you feed it 6V from AA batteries, your motors only see ~3.5V and will barely turn. A 2S LiPo (nominal 7.4V, fully charged 8.4V) ensures your motors get a healthy 5.5V to 6V.

ComponentExact Variant / ModelEst. Price (2026)Critical Notes
MicrocontrollerArduino Uno R3 (ATmega328P)$24.00Use official or high-quality clone with CH340/ATmega16U2 USB IC.
Motor DriverL298N Dual H-Bridge Module$6.50Must have the 5V output terminal block. STMicroelectronics L298 Datasheet.
Bluetooth ModuleHC-05 (SPP Profile, 6-pin)$8.00Ensure it has the 'KEY' or 'EN' pin for AT command mode access.
Chassis Kit4WD Acrylic Smart Car Chassis$18.00Includes 4x TT gearmotors, wheels, and battery strap. Wire motors in parallel pairs.
Power Source2S 7.4V 1500mAh LiPo (XT60)$16.00Minimum 20C discharge rating. Never use unprotected 18650s in parallel without a BMS.
SwitchDPDT Rocker Switch (10A)$2.00Wired on the main positive LiPo lead to prevent spark-on-connect.

Pin Mapping and Wiring Steps

Wiring an RC car with Arduino requires separating your logic ground from your high-current motor ground, while maintaining a common reference. The L298N module handles this via its GND terminal, which must tie back to the Arduino GND.

Arduino Uno PinDestinationFunction
5VL298N 5V (if jumper is ON)Powering the Arduino via the driver's onboard 7805 regulator.
GNDL298N GND & HC-05 GNDCommon logic ground reference.
D5 (PWM)L298N ENASpeed control for Left Motors.
D6 (PWM)L298N ENBSpeed control for Right Motors.
D7L298N IN1Left Motor Direction A.
D8L298N IN2Left Motor Direction B.
D9L298N IN3Right Motor Direction A.
D10L298N IN4Right Motor Direction B.
D10 (RX)HC-05 TXDSoftwareSerial Receive (Cross-wired).
D11 (TX)HC-05 RXDSoftwareSerial Transmit (Cross-wired).
Bench Tip: The L298N has a jumper cap near the 5V terminal. If your input voltage (VCC) is under 12V, leave the jumper ON to power the Arduino from the board's internal regulator. If you are using a 3S LiPo (12.6V peak), REMOVE the jumper and power the Arduino via its own DC barrel jack or VIN pin, or you will fry the 7805 regulator.
  1. Mount the L298N: Bolt the driver to the rear of the acrylic chassis. Ensure the heatsink faces backward for airflow.
  2. Wire the Motors: Connect the two left TT motors in parallel to OUT1 and OUT2. Connect the two right TT motors in parallel to OUT3 and OUT4.
  3. Connect Power: Wire the LiPo positive (red) through the DPDT switch to the L298N 12V terminal. Wire the LiPo negative (black) directly to the L298N GND terminal.
  4. Establish Common Ground: Run a jumper wire from the L298N GND terminal to the Arduino Uno GND pin. Skip this, and your PWM signals will float, causing erratic motor spasms.
  5. Wire Bluetooth: Connect HC-05 VCC to Arduino 5V, GND to GND. Cross-wire TX to D10 and RX to D11. Add a 1k/2k resistor voltage divider on the HC-05 RX line if your module is strictly 3.3V logic (most standard HC-05s tolerate 5V on RX, but check your breakout).

Complete Arduino C++ Code with Error Handling

This code targets the Arduino Uno R3. It uses the SoftwareSerial library to communicate with the HC-05, leaving the hardware Serial (pins 0/1) free for USB debugging. Crucially, it implements a watchdog timeout: if the Bluetooth connection drops or the phone app crashes, the car stops automatically after 500ms to prevent runaways.

#include <SoftwareSerial.h>

// --- PIN DEFINITIONS ---
const int ENA = 5;   // Left motor PWM
const int IN1 = 7;   // Left motor Dir A
const int IN2 = 8;   // Left motor Dir B
const int ENB = 6;   // Right motor PWM
const int IN3 = 9;   // Right motor Dir A
const int IN4 = 10;  // Right motor Dir B

// Bluetooth on SoftwareSerial (RX=10, TX=11) - Wait, D10 is IN4!
// Correction for Uno: Move BT RX to D12, BT TX to D13 to avoid conflict with ENB/IN4.
const int BT_RX = 12;
const int BT_TX = 13;
SoftwareSerial bluetooth(BT_RX, BT_TX);

// --- STATE VARIABLES ---
int baseSpeed = 200; // 0-255 PWM value
unsigned long lastCommandTime = 0;
const unsigned long TIMEOUT_MS = 500; // Safety watchdog timeout

void setup() {
  Serial.begin(9600);  // Hardware serial for USB debugging
  bluetooth.begin(9600); // HC-05 default baud rate
  
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(ENB, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  
  stopMotors();
  Serial.println(F("RC Car Initialized. Waiting for Bluetooth commands..."));
}

void loop() {
  // 1. Watchdog Timeout Check (Safety Critical)
  if (millis() - lastCommandTime > TIMEOUT_MS) {
    stopMotors();
    // Prevent serial spam by only printing once per timeout event
    if (lastCommandTime != 0) {
      Serial.println(F("Error: BT Timeout - Motors stopped"));
      lastCommandTime = 0; 
    }
  }

  // 2. Process Incoming Commands
  if (bluetooth.available() > 0) {
    char cmd = bluetooth.read();
    lastCommandTime = millis(); // Reset watchdog
    
    // Error handling: Bounds checking for speed commands
    if (cmd >= '0' && cmd <= '9') {
      baseSpeed = map(cmd - '0', 0, 9, 0, 255);
      baseSpeed = constrain(baseSpeed, 0, 255); // Hard limit
      Serial.print(F("Speed set to: ")); Serial.println(baseSpeed);
    }
    
    // Directional commands (F=Forward, B=Back, L=Left, R=Right, S=Stop)
    switch (cmd) {
      case 'F': moveForward(baseSpeed); break;
      case 'B': moveBackward(baseSpeed); break;
      case 'L': turnLeft(baseSpeed); break;
      case 'R': turnRight(baseSpeed); break;
      case 'S': stopMotors(); break;
      default:
        Serial.print(F("Warning: Unknown command char: ")); 
        Serial.println(cmd, HEX);
        break;
    }
  }
}

// --- MOTOR CONTROL FUNCTIONS ---
void moveForward(int speed) {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
  analogWrite(ENA, speed); analogWrite(ENB, speed);
}

void moveBackward(int speed) {
  digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
  analogWrite(ENA, speed); analogWrite(ENB, speed);
}

void turnLeft(int speed) {
  digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH); // Left backward
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);  // Right forward
  analogWrite(ENA, speed); analogWrite(ENB, speed);
}

void turnRight(int speed) {
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);  // Left forward
  digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);   // Right backward
  analogWrite(ENA, speed); analogWrite(ENB, speed);
}

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

Troubleshooting: When the RC Car with Arduino Fails

When your build fails, do not start rewriting code. Hardware and UART mismatches cause 90% of issues. Here are the first three things to check, followed by exact error strings and their fixes.

The First 3 Things to Check:

  1. Common Ground: Verify the Arduino GND is physically wired to the L298N GND block. A missing ground causes the L298N to ignore logic signals.
  2. L298N Jumper Cap: If the Arduino is powered via USB but the L298N 5V terminal is connected to the Arduino 5V pin (with the jumper ON), you are back-feeding the USB bus. Remove the jumper or disconnect the 5V wire when testing on USB.
  3. TX/RX Cross-Wiring: Bluetooth TX must go to Arduino RX (D12), and Bluetooth RX to Arduino TX (D13). If they are straight-through, the car will never receive data.
Exact Error String / SymptomRanked CausesThe Fix
avrdude: stk500_recv(): programmer is not responding 1. HC-05 TX/RX connected to Uno Pins 0/1 during upload.
2. Corrupted ATmega16U2 USB IC.
Always unplug the Bluetooth module from pins 0 and 1 before clicking 'Upload'. Hardware Serial cannot share the USB bus during flashing.
Serial Monitor outputs: ⸮⸮⸮⸮⸮ (Garbage characters) 1. Baud rate mismatch in Serial Monitor.
2. HC-05 stuck in AT mode (38400 baud).
Set Serial Monitor to 9600 baud. If HC-05 LED is blinking slowly (2 sec intervals), it is in AT mode. Power cycle it without holding the KEY button to enter 9600 baud data mode.
Motors hum loudly but wheels do not turn 1. Insufficient current (Brownout).
2. PWM frequency too low for TT motors.
Check LiPo voltage under load. If it drops below 6.5V, the L298N logic resets. Upgrade to a higher C-rating LiPo or swap to a TB6612FNG MOSFET driver.

How to Extend or Simplify the Build

Once the baseline RC car with Arduino is driving reliably, you have two distinct paths depending on your end goal.

To Simplify (For younger makers or quick demos):
Drop the HC-05 Bluetooth module and the smartphone app dependency. Swap in an IR Receiver (HX1838) and a 24-button IR remote. It costs under $3, requires only one digital pin, and eliminates all UART baud-rate debugging. You lose range (line-of-sight only), but you gain 100% reliability indoors.

To Extend (For robotics and autonomous navigation):
The L298N is a legacy BJT driver that wastes ~2V as heat. For serious outdoor runs, upgrade to a TB6612FNG MOSFET motor driver. It handles up to 1.2A continuous per channel with almost zero voltage drop, meaning your motors get the full battery voltage. Next, add an MPU6050 IMU (I2C) to the chassis. By reading the accelerometer and gyroscope data, you can implement software traction control, drift detection, or basic dead-reckoning navigation when the Bluetooth signal drops.