To connect an Arduino to another Arduino via Bluetooth, you do not use a native wireless bus. Instead, you bridge their hardware or software UART (serial) pins to a Bluetooth module—like the HC-05 for Classic SPP or the HM-10 for BLE 4.0—which acts as a transparent serial pass-through. The master Arduino sends standard serial bytes, the module packetizes them over the 2.4GHz RF spectrum, and the slave module unpacks them back into a serial stream. The Arduinos remain completely unaware they are talking over the air.
The direct answer for most hobbyist projects: use the HC-05 (Classic Bluetooth) if you need a simple point-to-point serial cable replacement with an Android phone or another Arduino, and use the HM-10 (BLE) or an ESP32 if you need iOS compatibility or lower power consumption.
Module Selection and UART Bridge Mechanics
Before wiring anything, you must select the right module for your distance, speed, and device count requirements. Classic Bluetooth (HC-05) establishes a continuous, high-throughput serial pipe but is blocked by iOS devices. BLE (HM-10/ESP32) uses a connection-oriented UUID system, draws milliamps instead of tens of milliamps, but has lower raw throughput and higher latency.
| Module | Protocol | Default UART Baud | Max RF Range | Est. Price (2026) | Best Use Case |
|---|---|---|---|---|---|
| HC-05 (ZG-B3) | Classic SPP 2.0 | 9600 bps (Data) / 38400 (AT) | 10m (Class 2) | $4.50 - $6.00 | Simple serial cable replacement, Android telemetry |
| HM-10 (CC2541) | BLE 4.0 | 9600 bps | 30m (Line of sight) | $5.50 - $7.00 | iOS sensor nodes, low-power battery applications |
| ESP32 (WROOM-32) | Native Classic + BLE 4.2 | 115200 bps (Native UART) | 50m+ (With PCB antenna) | $5.00 - $8.00 | High-speed data, mesh networks, native WiFi+BT |
| JDY-31 | Classic SPP 3.0 | 9600 bps | 15m | $3.50 - $4.50 | HC-05 alternative with better default Master/Slave switching |
Unlike I2C or SPI, Bluetooth modules do not sit on a shared multi-drop bus. They form a star topology (one master, up to seven active slaves in Classic piconets, though practically just 1-to-1 for UART bridges). Here are the physical bus mechanics of the UART-to-RF bridge:
| Wires (Physical UART) | 4 minimum (VCC, GND, TX, RX). Up to 6 if using EN/KEY (for AT command mode) and STATE (connection indicator). |
|---|---|
| Speed | UART side: 9600 to 115200 bps (configurable). RF Air side: 1 Mbps (BLE) to 3 Mbps (Classic EDR). |
| Addressing | Classic: MAC Address + 4-digit PIN (e.g., 1234). BLE: 128-bit Service/Characteristic UUIDs + MAC. |
| Distance | 10 meters for standard Class 2 modules through drywall; up to 50+ meters for ESP32 BLE in open air with high TX power settings. |
The Physical Layer: 5V to 3.3V Logic Shifting
The most common way to fry a Bluetooth module on the bench is ignoring logic levels. A standard Arduino Uno outputs 5V on its TX pin. While the HC-05’s VCC pin can usually accept 5V (because it has an onboard 3.3V LDO regulator), its RX data pin is strictly 3.3V tolerant. Feeding 5V serial data into the HC-05 RX pin will degrade the silicon and eventually kill the module.
Building a 1k/2k Voltage Divider:
To drop the 5V TX signal down to a safe ~3.3V for the HC-05 RX pin, use two resistors. Connect a 1kΩ resistor in series with the Arduino TX line, and a 2kΩ resistor from the HC-05 RX pin to GND. The junction between the two resistors connects to the HC-05 RX pin. This creates a ratio that safely scales 5V down to 3.33V.
Pull-up Resistors and I2C Confusion:
Beginners often ask if they need pull-up resistors for Bluetooth UART. You do not. Pull-ups (typically 4.7kΩ) are mandatory for open-drain buses like I2C. UART is a push-pull protocol; the lines are actively driven HIGH and LOW by the microcontroller. Adding pull-ups to UART TX/RX lines will only increase current draw and potentially cause logic threshold errors. Leave them off.
If you are using an HM-10 (BLE), the entire module is strictly 3.3V. You must power it from the Arduino’s 3.3V pin (ensure your Arduino's onboard 3.3V regulator can supply at least 50mA, which the Uno's LDO struggles with; a Nano is better, or use an external 3.3V LDO like the AMS1117-3.3). You still need the voltage divider on the RX pin if your Arduino logic is 5V.
The Classic Failures: Baud Mismatches and Debugging
When an Arduino-to-Arduino Bluetooth link fails, it is almost never an RF issue. It is a physical layer or configuration failure. Here is how to diagnose the invisible bus.
1. The HC-05 AT Command Baud Trap
To configure an HC-05 as a Master (so it auto-connects to a Slave without manual pairing), you must enter AT command mode. You do this by pulling the EN/KEY pin HIGH before applying power. The module's LED will blink slowly (once every 2 seconds).
The trap? In normal data mode, the HC-05 defaults to 9600 bps. But in AT command mode, the baud rate is hardcoded to 38400 bps, and it requires Carriage Return + New Line (CR+NL) terminators. If you try to send AT+ROLE=1 at 9600 bps, you will get garbage characters or no response. Set your Serial Monitor to 38400 bps and "Both NL & CR" when configuring.
2. Sniffing the Bus with an FTDI Adapter
If your Arduinos aren't talking, you need to verify if the bytes are actually leaving the master's UART. Do not rely solely on the SoftwareSerial monitor. Grab a $3 FT232RL USB-to-TTL adapter.
Connect the FTDI GND to your circuit GND. Connect the FTDI RX pin to the wire connecting your Arduino TX to the Bluetooth module RX. Open a terminal program like PuTTY or the Arduino IDE Serial Monitor on the FTDI's COM port. You will now see the exact raw bytes the Arduino is pushing to the Bluetooth module, bypassing any SoftwareSerial interrupt latency or buffer overflows.
3. BLE UUID Scanning Failures
If using HM-10 or ESP32 BLE, "pairing" via a PIN doesn't exist in the same way. The master must scan for the slave's specific Service UUID. If your master code is looking for 0000ffe0-0000-1000-8000-00805f9b34fb but your slave is broadcasting a custom UUID, the connection will time out. Always use a generic BLE scanner app (like nRF Connect on Android/iOS) to verify the exact UUIDs the slave is advertising before hardcoding them into your master Arduino sketch.
Minimal Working Exchange: Master-Slave Sensor Relay
Below is a complete, working example using two Arduino Unos and two HC-05 modules. The Master reads a potentiometer and sends the value over Bluetooth. The Slave receives it and dims an LED via PWM.
Note: For production or high-speed data, avoid SoftwareSerial and use an Arduino Mega or ESP32 with dedicated hardware UART pins (Serial1, Serial2). SoftwareSerial disables interrupts while transmitting, which can drop incoming bytes at baud rates above 38400.
| Arduino Uno Pin | HC-05 Pin | Notes |
|---|---|---|
| 5V | VCC | HC-05 has onboard 3.3V LDO |
| GND | GND | Common ground is mandatory |
| Pin 10 (Software RX) | TX | Direct connection (3.3V to 5V is safe) |
| Pin 11 (Software TX) | RX | Must use 1k/2k voltage divider! |
Master Arduino Code (Potentiometer Transmitter)
#include <SoftwareSerial.h>
// SoftwareSerial RX, TX
SoftwareSerial BTSerial(10, 11);
const int potPin = A0;
int potValue = 0;
byte mappedValue = 0;
void setup() {
Serial.begin(9600);
BTSerial.begin(9600); // Must match HC-05 default data baud
}
void loop() {
potValue = analogRead(potPin);
// Map 10-bit ADC (0-1023) to 8-bit PWM (0-255)
mappedValue = map(potValue, 0, 1023, 0, 255);
BTSerial.write(mappedValue); // Send as raw byte, not ASCII text
// Optional: mirror to hardware serial for debugging
Serial.print("Sending: ");
Serial.println(mappedValue);
delay(50); // 20Hz update rate
}
Slave Arduino Code (PWM LED Receiver)
#include <SoftwareSerial.h>
SoftwareSerial BTSerial(10, 11);
const int ledPin = 9; // PWM capable pin
byte incomingByte = 0;
void setup() {
Serial.begin(9600);
BTSerial.begin(9600);
pinMode(ledPin, OUTPUT);
}
void loop() {
if (BTSerial.available() > 0) {
incomingByte = BTSerial.read();
analogWrite(ledPin, incomingByte);
Serial.print("Received: ");
Serial.println(incomingByte);
}
}
BTSerial.write() instead of BTSerial.print(). print() converts the number 255 into three ASCII characters ('2', '5', '5'), which triples your bus traffic and requires string parsing on the slave. write() sends a single raw byte, making the exchange faster and the parsing trivial. For more on serial library mechanics, consult the official Arduino SoftwareSerial reference.
By treating Bluetooth simply as a wireless UART cable and respecting the 3.3V physical layer limits, you eliminate 90% of the headaches associated with wireless embedded projects. If you need deeper integration, consider migrating from the Uno and external modules to the ESP32, which handles the Bluetooth stack natively on-chip. For a deeper dive into RF module pairing, SparkFun's Bluetooth Basics tutorial remains an excellent bench reference.






