If you need a simple serial passthrough to an Android app or PC terminal, use the **HC-05** (Classic Bluetooth). If your project requires iOS compatibility or low-power sensor beaconing, use the **HM-10** or **AT-09** (Bluetooth Low Energy). However, if you are starting a new build today, bypass external modules entirely and use an **ESP32-C3** or **ESP32-S3** with native BLE. Integrating Arduino and Bluetooth fundamentally relies on bridging a wired UART bus to a wireless RF protocol. Skipping the physical layer details—like logic level shifting and baud rate synchronization—is the primary reason these projects fail on the bench. This primer covers the exact wiring, bus mechanics, and debugging paths required to get your RF link online.

The Physical Layer: Wiring and Level Shifting

The most common mistake when wiring an Arduino to a Bluetooth module is ignoring logic levels. Standard Arduinos (Uno, Mega, Nano) operate at 5V logic. Almost all modern Bluetooth modules (HC-05, HM-10, ESP32) operate at 3.3V logic and are not 5V tolerant on their RX pins. Feeding 5V directly into the module's RX pin will degrade or destroy the silicon over time.

Mandatory Voltage Divider: You must step down the Arduino TX (5V) to the Bluetooth RX (3.3V). Use a simple resistor divider: a 1kΩ resistor in series from Arduino TX to Module RX, and a 2kΩ resistor from Module RX to GND. The Module TX to Arduino RX line can be connected directly, as the Arduino will reliably read 3.3V as a HIGH signal.

Regarding pull-up resistors: standard UART lines (TX/RX) do not require external pull-ups; they are actively driven push-pull lines. However, if you are using an I2C-to-UART bridge (like the SC16IS750) to save hardware serial pins, the I2C SDA and SCL lines require 4.7kΩ pull-up resistors to VCC, or the bus will float and drop packets.

Bus Mechanics and Protocol Specifications

Bluetooth for microcontrollers is a two-stage bus: the local wired UART bridge, and the remote wireless RF link. Understanding the constraints of both layers prevents buffer overflows and dropped connections.

Protocol LayerWires / PinsSpeed (Max Practical)Addressing SchemeMax Distance
UART (Local Bridge)TX, RX, VCC, GND115,200 bpsNone (Point-to-Point)< 1 meter (breadboard)
Bluetooth Classic (SPP)RF (2.4 GHz)~2.1 Mbps (RF) / 115k (UART)48-bit MAC Address10m (Class 2) to 100m (Class 1)
Bluetooth Low EnergyRF (2.4 GHz)1 Mbps or 2 Mbps PHYUUIDs (Service/Characteristic)Up to 100m (depending on Tx power)

Notice the speed bottleneck: even if BLE supports a 2 Mbps PHY over the air, your local UART bridge limits the actual payload throughput to your configured baud rate. If you stream sensor data at 115,200 baud, your maximum theoretical throughput is roughly 11.5 KB/s, regardless of the RF protocol's raw speed.

The Classic Failures (And How to Fix Them)

When an Arduino and Bluetooth module refuse to communicate, the failure almost always falls into one of three categories.

1. Baud Rate Mismatch

The HC-05 module has two distinct modes: Data Mode (default 9600 baud) and AT Command Mode (fixed at 38400 baud). A classic failure occurs when a user tries to send AT commands while the Arduino Serial Monitor is set to 9600 baud, or attempts to read sensor data while the monitor is stuck at 38400. Always verify the module's state (the LED blinks slowly in AT mode, rapidly in Data mode) and match your Serial.begin() accordingly.

2. Missing Level Shifting (Fried RX Pin)

If the module powers on and advertises, but the Arduino cannot send data to it, the module's RX pin is likely dead from 5V overvoltage. Test this by swapping the TX/RX lines and using a logic analyzer or oscilloscope to verify the 3.3V signal is actually leaving the module's TX pin.

3. Address Clash and iOS Rejection

Apple's iOS strictly prohibits Bluetooth Classic (SPP profile) connections for non-MFi certified devices. If your Android phone sees the HC-05 but your iPhone does not, this is not a hardware failure; it is an OS-level block. You must switch to a BLE module (HM-10) and use a BLE central app. Furthermore, BLE address clashes occur when multiple identical modules broadcast the same default MAC address; use the AT+ADDR command to assign unique addresses if deploying a mesh of sensors.

Minimal Working Exchange: UART to BLE

Below is a minimal, robust implementation for an Arduino Uno reading a potentiometer and transmitting the value over BLE using an HM-10 module. This assumes the voltage divider is physically wired as described in the Physical Layer section.

Wiring Map:

  • Arduino 5V → HM-10 VCC
  • Arduino GND → HM-10 GND
  • Arduino Pin 11 (SoftwareSerial RX) → HM-10 TX
  • Arduino Pin 10 (SoftwareSerial TX) → 1kΩ Resistor → HM-10 RX
  • HM-10 RX → 2kΩ Resistor → GND
#include <SoftwareSerial.h>

// Pin 10 is TX (to HM-10 RX via voltage divider)
// Pin 11 is RX (from HM-10 TX)
SoftwareSerial bleSerial(11, 10); 

const int POT_PIN = A0;
int lastVal = -1;

void setup() {
  Serial.begin(9600);    // Debug monitor
  bleSerial.begin(9600); // HM-10 default baud
  Serial.println('BLE HM-10 Initialized. Waiting for central...');
}

void loop() {
  int currentVal = analogRead(POT_PIN);
  
  // Only transmit if value changes by more than 2 to prevent bus flooding
  if (abs(currentVal - lastVal) > 2) {
    lastVal = currentVal;
    
    // Format as string with newline for easy parsing on the receiving app
    String payload = String('POT:') + String(currentVal) + '\n';
    bleSerial.print(payload);
    
    // Small delay to prevent overwhelming the HM-10 internal UART buffer
    delay(20); 
  }
  
  // Echo any incoming BLE commands to the debug monitor
  if (bleSerial.available()) {
    Serial.write(bleSerial.read());
  }
}

Sniffing and Debugging the RF Bus

When the code compiles and the wiring is correct, but the mobile app shows no data, you must sniff the bus to isolate the break in the chain.

  1. Verify the UART Bridge: Disconnect the Bluetooth module. Connect the Arduino TX line directly to the Arduino RX line (a local loopback). Open the Serial Monitor and type a message. If it echoes back, your microcontroller's UART and code are functioning perfectly. The fault lies in the RF layer or the module.
  2. Sniff the RF Layer (BLE): Download the nRF Connect for Mobile app (by Nordic Semiconductor). Scan for your module. If nRF Connect sees the device and can read the custom Service UUID, your RF transmission is working, and the bug is in your custom mobile app's parsing logic.
  3. Deep Packet Inspection: For advanced debugging of BLE advertising intervals or connection parameter rejections, use an ESP32 running the ESP-IDF Bluetooth stack to capture HCI snoop logs, or use a dedicated hardware sniffer like the nRF52840 Dongle running Wireshark.

Decision Tree: Which Bluetooth Path to Take

Do not default to the first module you find in a starter kit. Use this decision matrix to select the exact hardware for your architecture.

Project RequirementProtocol / ModuleConcrete Pick (Part Number)
Need simple serial terminal to Android/Windows PC; low cost; no custom app development.Bluetooth Classic (SPP)HC-05 (with ZS-040 breakout board)
Must connect to iOS/iPhone; battery powered; sending small sensor payloads.Bluetooth Low Energy (BLE)HM-10 or AT-09 (CC2541 based)
Need high throughput, native WiFi+BT, eliminating UART wiring and level shifters entirely.Native BLE 5.0 / ClassicESP32-C3 SuperMini or ESP32-S3 DevKit
Long range (100m+); outdoor telemetry; requires external antenna.BLE Long Range (Coded PHY)ESP32-C6 with u.FL antenna connector
The 2026 Default Recommendation: If you are building a new project today and have no strict legacy constraints, bypass external UART modules entirely. The default pick is the ESP32-C3 SuperMini (typically under $5). It features native BLE 5.0, eliminates the 5V-to-3.3V level-shifting headaches, runs the Arduino core natively, and allows you to use the official Espressif Arduino BLE library for robust, crash-free RF communication.