To establish a reliable Bluetooth Arduino to Arduino link, you are not actually writing a wireless protocol from scratch; you are building a transparent wireless UART bridge. By using Classic Serial Port Profile (SPP) modules like the HC-05, the microcontrollers communicate via standard hardware serial (TX/RX), while the module handles the 2.4 GHz ISM band RF modulation and packetization. The direct answer for a robust point-to-point link is to use two HC-05 modules (one configured as Master, one as Slave), bind them via their MAC addresses using AT commands, and shift the 5V Arduino logic down to the module's 3.3V RX threshold using a simple resistor divider.
The Physical Layer: UART Bridge Mechanics & Wiring
Before writing a single line of code, you must understand that Bluetooth SPP modules act as dumb serial pipes. The "bus" is actually two distinct layers: the local wired UART bus between the ATmega328P and the HC-05, and the wireless RF link between the two HC-05 modules. If the physical UART layer fails, the RF layer never gets the data to transmit.
| Parameter | Local UART (Wired) | Classic SPP (HC-05 RF) | BLE 4.0 (JDY-31 RF) |
|---|---|---|---|
| Physical Wires | 4 (VCC, GND, TX, RX) | N/A (Wireless) | N/A (Wireless) |
| Max Speed (Baud) | Up to 115,200 bps | ~1380 bps effective payload | Variable, MTU dependent |
| Addressing | None (Point-to-Point) | 48-bit MAC Address | UUID / MAC Address |
| Max Distance | ~15 meters (cable) | ~10 meters (Class 2) | ~30 meters (Line of sight) |
| Logic Level | 5V (Arduino) / 3.3V (Module) | N/A | N/A |
Mandatory Voltage Division (The Pull-Up/Logic Shift)
The most common hardware failure in these projects is frying the HC-05's RX pin. The Arduino Uno outputs 5V on its TX pin, but the HC-05 operates at 3.3V logic. While the HC-05's VCC pin can tolerate 5V (due to an onboard regulator), its data pins cannot. You must drop the 5V TX signal to ~3.3V.
- Arduino TX to HC-05 RX: Pass through a voltage divider. Use a 1kΩ resistor in series from Arduino TX, and a 2kΩ resistor pulling down from the HC-05 RX pin to GND. This yields exactly 3.33V.
- HC-05 TX to Arduino RX: Connect directly. The Arduino's ATmega328P reads any voltage above 2.5V as a logical HIGH, so the HC-05's 3.3V output is perfectly safe and readable without a pull-up.
Protocol Selection: Classic SPP vs. BLE for Arduino Links
Which protocol fits your distance, speed, and device count requirements? For a simple Arduino-to-Arduino link where you want to replace a physical serial cable, Classic SPP (HC-05) is the undisputed winner. It requires no custom mobile app or complex GATT characteristic mapping; it just passes raw bytes. However, if you need to connect to modern iOS devices or require lower power consumption, you must pivot to BLE.
| Criteria | Classic SPP (HC-05) | BLE (JDY-31 / HM-10) | ESP-NOW (ESP32 Native) |
|---|---|---|---|
| Best For | Arduino-to-Arduino cable replacement | Arduino-to-Smartphone telemetry | High-speed, multi-node mesh |
| Device Count | 1 Master to 1 Slave (Bound) | 1 Central to multiple Peripherals | Up to 20 peers |
| Pairing Method | AT Command MAC Binding | OS-level BLE Pairing / App | MAC Address Registration in Code |
| iOS Compatibility | No (Apple blocks SPP) | Yes | No (Proprietary) |
Configuration, Pairing, and the Minimal Working Exchange
To create a dedicated Arduino-to-Arduino link, you must bind the Master HC-05 to the Slave HC-05's MAC address. This prevents the Master from connecting to random nearby Bluetooth devices.
Step 1: AT Command Binding
Connect the Slave HC-05 to a USB-Serial adapter, pull the EN/KEY pin HIGH, and power it on to enter AT mode (default baud 38400). Send AT+ADDR? to get its MAC address (e.g., 98:D3:34:90:5C:33).
Next, configure the Master HC-05 in AT mode:
AT+ROLE=1 // Set as Master
AT+BIND=98d3,34,905c33 // Bind to Slave MAC (Note: use commas, not colons)
AT+CMODE=0 // Connect only to bound address
AT+RESET // Reboot into transparent mode
98:D3...), the module will return an ERROR and fail to bind.
Step 2: Minimal Working Exchange Code
Once paired, the modules act as a transparent wire. We use SoftwareSerial on the Uno to leave the hardware UART free for debugging via the Serial Monitor. This code sends a counter from the Master and expects an acknowledgment from the Slave.
// MASTER ARDUINO CODE
#include <SoftwareSerial.h>
// Pin 10 = RX (from HC-05 TX), Pin 11 = TX (to HC-05 RX via voltage divider)
SoftwareSerial BTSerial(10, 11);
int packetCount = 0;
void setup() {
Serial.begin(9600); // Debug monitor
BTSerial.begin(9600); // HC-05 default transparent baud
Serial.println("Master initialized. Waiting for link...");
}
void loop() {
// Send payload
BTSerial.print("PING:");
BTSerial.println(packetCount);
Serial.print("Sent PING: ");
Serial.println(packetCount);
// Wait for ACK with timeout
unsigned long timer = millis();
while(millis() - timer < 1000) {
if(BTSerial.available()) {
String response = BTSerial.readStringUntil('\n');
Serial.print("Received: ");
Serial.println(response);
break;
}
}
packetCount++;
delay(2000);
}
// SLAVE ARDUINO CODE
#include <SoftwareSerial.h>
SoftwareSerial BTSerial(10, 11);
void setup() {
Serial.begin(9600);
BTSerial.begin(9600);
Serial.println("Slave listening...");
}
void loop() {
if(BTSerial.available()) {
String incoming = BTSerial.readStringUntil('\n');
Serial.print("Got: ");
Serial.println(incoming);
// Send Acknowledgment
BTSerial.println("ACK:OK");
}
}
Debugging the Airwaves: Classic Failures and Bus Sniffing
When your Bluetooth link drops packets or refuses to connect, the issue is almost never "Bluetooth interference." It is almost always a local UART configuration error. Here is how to diagnose the classic failures.
The Classic Failures
- Baud Rate Mismatch: The HC-05 defaults to 9600 bps in transparent (data) mode, but switches to 38400 bps when entering AT command mode. If your Arduino code initializes
SoftwareSerial.begin(38400)for normal data transfer, you will receive garbage characters. Always use 9600 for the sketch, and 38400 only for AT configuration. - Unbound MAC Addresses: If you set
AT+ROLE=1but forgetAT+CMODE=0, the Master will attempt to connect to the first SPP device it finds (often a nearby laptop or phone), leaving your Slave Arduino ignored. - Power Supply Brownouts: The HC-05 draws up to 50mA during transmission bursts. If powered directly from the Arduino Uno's 3.3V regulator (which is only rated for ~50mA), the voltage will sag, causing the module to reset mid-packet. Always power the HC-05 VCC pin from the Arduino's 5V pin.
How to Sniff and Debug the Bus
Because Classic SPP uses proprietary RF hopping sequences, you cannot easily sniff the 2.4 GHz airwaves without a $500 spectrum analyzer. Instead, sniff the local UART bus.
To debug, take a third Arduino or a $12 USB-to-TTL adapter (like an FT232RL breakout). Connect its RX pin directly to the wire bridging your transmitting Arduino's TX pin and the HC-05's RX pin. Open a serial terminal on your PC at 9600 baud. You will now see exactly what the Arduino is pushing to the Bluetooth module. If the data looks correct on your PC sniff, but doesn't arrive at the remote Arduino, the issue is RF pairing (check your AT+BIND MAC address). If the data looks like garbage on your PC sniff, your local baud rate or voltage divider is at fault.
For deeper module diagnostics, you can temporarily wire the HC-05's STATE pin to an Arduino input. The STATE pin goes HIGH when a Bluetooth link is successfully established. Polling this pin in your setup() loop prevents your code from firing serial data into the void before the RF handshake is complete.
For more on the underlying serial mechanics, refer to the Arduino SoftwareSerial documentation and the foundational UART theory guide on All About Circuits.






