Integrating an Arduino with HC-05 Bluetooth remains one of the most reliable ways to add legacy Bluetooth 2.0 Serial Port Profile (SPP) communication to a microcontroller project. While newer modules like the ESP32 offer built-in BLE, the HC-05 is still the go-to for robust, point-to-point serial bridges with older Android devices, Windows PCs, and industrial HMIs. However, the most common point of failure on the bench isn't the code—it's the 5V-to-3.3V logic translation and baud rate mismatches.

This guide targets the Arduino Uno R3 (ATmega328P) and provides the exact wiring, voltage divider math, and compilable code required to get your serial bridge running, followed by a bench-tested debugging framework for when the serial monitor spits out garbage.

Parts List and HC-05 Spec Sheet

Before wiring, verify your exact module variant. The HC-05 comes on several breakout boards; the ZS-040 is the most common and includes a tactile button and a KEY pin required for entering AT command mode.

HC-05 (ZS-040 Breakout) Technical Specifications
Parameter Value / Rating Bench Notes
Operating Voltage (VCC) 3.6V to 6.0V Onboard LDO regulates 5V down to 3.3V for the SoC.
Logic Level (RX/TX) 3.3V Not 5V tolerant. Feeding 5V into RX will degrade or fry the trace.
Default Baud Rate (Data) 9600 bps Standard N, 8, 1 configuration.
Default Baud Rate (AT Mode) 38400 bps Fixed on most firmware versions; requires both NL and CR in Serial Monitor.
Bluetooth Profile SPP v2.0 Not compatible with iOS CoreBluetooth (iOS requires BLE).
Pairing Password 1234 or 0000 Configurable via AT+PSWD command.

Wiring the Arduino with HC-05: The 5V to 3.3V Translation

The Arduino Uno R3 operates at 5V logic. The HC-05's RX pin expects 3.3V. If you connect the Uno's TX pin directly to the HC-05's RX pin, you will eventually burn out the module's internal pull-up resistor or the UART trace. You must use a voltage divider on the TX-to-RX line.

Safety & Hardware Warning: Never connect 5V directly to the HC-05 RX, KEY, or EN pins. The VCC pin can accept 5V because the ZS-040 breakout has an onboard MIC5205 LDO regulator, but the data pins do not.

Using a standard 1kΩ and 2kΩ resistor pair yields a safe output voltage: Vout = 5V * (2000 / (1000 + 2000)) = 3.33V.

Arduino Uno R3 to HC-05 Pin Mapping
Arduino Uno R3 Pin HC-05 ZS-040 Pin Wiring Notes
5V VCC Direct connection. Powers the onboard LDO.
GND GND Common ground is mandatory for serial comms.
Pin 10 (Software RX) TX Direct connection. HC-05 TX outputs 3.3V, which the Uno safely reads as HIGH.
Pin 11 (Software TX) RX Via Voltage Divider. 1kΩ from Uno Pin 11 to HC-05 RX; 2kΩ from HC-05 RX to GND.
Not Connected KEY Leave floating for normal data mode. Pull to 3.3V (or hold button) for AT mode.

Complete Bluetooth Bridge Code (Target: Arduino Uno R3)

This sketch uses the SoftwareSerial library to create a transparent bridge between the Arduino's hardware USB serial and the HC-05. It includes explicit pin definitions and a buffer overflow check to prevent silent data drops during high-speed bursts.

#include <SoftwareSerial.h>

// --- Pin Definitions for Arduino Uno R3 ---
const int BT_RX = 10; // Uno RX pin connects to HC-05 TX
const int BT_TX = 11; // Uno TX pin connects to HC-05 RX (via voltage divider)

// Initialize SoftwareSerial on defined pins
SoftwareSerial BTSerial(BT_RX, BT_TX);

void setup() {
  // Initialize hardware serial for USB debugging
  Serial.begin(9600);
  while (!Serial) { ; } // Wait for serial port to connect (needed for native USB)
  
  // Initialize software serial for HC-05 (Default data mode baud is 9600)
  BTSerial.begin(9600);
  
  Serial.println(F("HC-05 Bridge Ready."));
  Serial.println(F("Type in Serial Monitor to send to Bluetooth."));
}

void loop() {
  // 1. Read from Bluetooth module, write to Serial Monitor
  if (BTSerial.available()) {
    Serial.write(BTSerial.read());
  }

  // 2. Read from Serial Monitor, write to Bluetooth module
  if (Serial.available()) {
    BTSerial.write(Serial.read());
  }

  // 3. Error Handling: Check for SoftwareSerial buffer overflow
  // The default buffer is 64 bytes. If data arrives faster than the loop reads it, we drop bytes.
  if (BTSerial.overflow()) {
    Serial.println(F("\n[ERROR] SoftwareSerial RX buffer overflow! Data lost."));
    Serial.println(F("Consider increasing _SS_MAX_RX_BUFF in SoftwareSerial.h or slowing the sender."));
  }
}

Debugging: First Three Things to Check When It Fails

When working with serial RF modules, failures usually manifest as garbage text or total silence. If your Arduino with HC-05 build isn't passing data, run through this ranked diagnostic sequence.

1. The Serial Monitor Shows Garbage (e.g., ⸮⸮⸮ or ????)

The Cause: Baud rate mismatch. The Serial Monitor is set to a different speed than the Serial.begin() or BTSerial.begin() values in your code, or the HC-05's internal baud rate was previously changed via AT commands.

The Fix: Ensure your Serial Monitor dropdown matches 9600. If it still shows ⸮⸮⸮, the HC-05 is likely stuck at 38400 or 115200. You must enter AT mode to reset it using AT+UART=9600,0,0.

2. AT Commands Return +CME ERROR: 4 or ERROR

The Cause: Incorrect AT command syntax or attempting to enter AT mode incorrectly. +CME ERROR: 4 specifically means "operation not supported" in the current state, often triggered if you send an AT command without the proper line endings.

The Fix: In the Arduino IDE Serial Monitor, you must set the line ending dropdown to "Both NL & CR". Furthermore, to enter AT mode on the ZS-040 board, you must hold down the tactile button while plugging in the 5V power, then release the button. The LED will blink slowly (once every 2 seconds). If it blinks rapidly, you are in data mode, and AT commands will be ignored.

3. Module Pairs but Data Only Flows One Way

The Cause: Missing common ground or a blown voltage divider resistor.

The Fix: Grab your multimeter. Measure continuity between the Uno GND and the HC-05 GND. Next, measure the voltage at the HC-05 RX pin while the Uno is powered. It should read roughly 3.3V when idle (HIGH). If it reads 0V or 5V, your 1k/2k resistor network is wired incorrectly or a resistor has failed open.

Bench Tip: The HC-05 LED states are your best diagnostic tool. Rapid double-blinking means it is in pairing mode (discoverable). A solid, unblinking light means it is connected to a master device via SPP. Slow, single blinks mean it is in AT command mode.

Extending and Simplifying the Build

Once you have the basic serial bridge working, you can adapt the hardware to fit your specific project constraints.

How to Simplify: Drop the Voltage Divider

If you want to eliminate the breadboard wiring and voltage divider resistors, switch your microcontroller to a native 3.3V logic board. The Arduino Pro Mini (3.3V / 8MHz variant) or the Adafruit Feather 32u4 Bluefruit LE (if you want to upgrade to BLE) operate at 3.3V. With a 3.3V board, you can wire the TX pin directly to the HC-05 RX pin without risking silicon damage, reducing your BOM and wiring complexity.

How to Extend: Add Relay Control and State Feedback

To turn this bridge into a home automation or robotics controller, parse the incoming serial characters in the loop() rather than just passing them through. For example, map the character '1' to trigger a 5V relay module via an optocoupler, and '0' to turn it off.

When extending to control high-current loads, always use a flyback diode across relay coils and ensure your power supply can handle the inrush current. The HC-05 can draw up to 30mA during transmission spikes; if your 5V rail sags below 3.6V during a relay click, the HC-05 will brownout and disconnect. Keep the microcontroller logic power and relay coil power on separate rails or use adequate bulk capacitance (e.g., a 470µF electrolytic capacitor near the HC-05 VCC pin).