Blog
Should I just use an ESP32 instead of an HC-05? Often yes — and here is exactly when not
Troniction

Starting a project from nothing, with no 5 V hardware to drive: yes, buy an original ESP32. It has Classic Bluetooth and BLE on the chip, so the module, its four wires, the divider, the module's baud rate and AT commands all disappear, and the dev board usually costs less than an Uno clone plus an HC-05 or HC-06. Two things can make it the wrong choice: only the original ESP32 has Classic Bluetooth (the S3, C3 and C6 are BLE only), and it's a 3.3 V board, so every 5 V signal coming into it needs a divider. If you already own an Uno and a module, finish the project with them.
That's the whole answer. The rest of this page is how to tell which ESP32 you're holding, the honest comparison, and the two procedures that turn an ESP32 into an HC-06 or an HM-10 with no wiring.
The question, as people ask it
You've spent an evening with an HC-05: the divider, the crossed wires, the baud rate that's 38400 in one mode and 9600 in the other. Somebody in a forum says "just use an ESP32". Is that real advice, or a way of not answering?
It's real advice. It's also incomplete, because "ESP32" isn't one chip.
First, ask it what it is
ESP32 is a family, and only one member of it has Classic Bluetooth. Classic is what an HC-05 and an HC-06 speak, and what Android serial apps such as Serial Bluetooth Terminal expect. BLE is what an HM-10 speaks, and what an iPhone or a web page needs.
| Chip | Classic Bluetooth (HC-05/HC-06 replacement) | BLE (HM-10 replacement) |
|---|---|---|
| ESP32, the original (ESP32-WROOM-32, "ESP32 DevKit", "DOIT") | Yes (Bluetooth 4.2 BR/EDR) | Yes |
| ESP32-S3, C3, C6, C2, C5, H2 | No | Yes (BLE 5) |
| ESP32-S2 | No | No: no Bluetooth at all |
| ESP32-P4 | No | No radio on the chip |
From Espressif's chip capability files and the arduino-esp32 documentation, checked 2026-10-01. Read the chip name printed on the metal can, or in the listing. A listing that just says "ESP32 board" can be any row of this table.
The sixty-second test
If the listing is vague and the board is already on your desk, let the compiler tell you.
- In the Arduino IDE, select your board.
- Open File → Examples → BluetoothSerial → SerialToSerialBT.
- Press Verify. Don't upload anything.
On a chip without Classic Bluetooth, the compile stops with these two lines:
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#error Serial Port Profile for Bluetooth is not available or not enabled. It is only available for the ESP32 chip.
I ran this on 2026-10-01 with core 3.3.12, and an S3, a C3 and an S2 all stop with both lines. The first one is misleading on an S3 or a C3: those chips do have Bluetooth, just not the Classic kind this example needs. Neither line is a broken install. It's the chip telling you it has no Classic radio. If it compiles, you have an original ESP32, and everything below applies.
The honest comparison
| Uno + HC-05 / HC-06 | ESP32 (original) | |
|---|---|---|
| Bluetooth | Classic, through the module | Classic and BLE, on the chip |
| Wi-Fi | none | built in |
| Processor | 1 core, 16 MHz | 2 cores on most modules, up to 240 MHz |
| Wiring for Bluetooth | four wires and a divider | none |
| Hardware serial ports | one, shared with USB | three |
| Logic level | 5 V | 3.3 V |
| Typical price (US, checked August 2026) | Uno clone + HC-06: 17–24 dollars | dev board: 8–12 dollars |
The prices are from Amazon US listings in August 2026. Prices for these parts move, so treat them as a ballpark, not a quote, and check today's listings before you decide on price alone.
What disappears on an ESP32: the module's wires, the divider, the module's baud rate, AT commands, the tree of HM-10 clones, and the upload trap where a module on pins 0 and 1 blocks the USB port.
What stays the same: the four boxes (phone, radio, board, motors or whatever you drive), single-letter commands, reading without blocking, and the iPhone rule.
The iPhone rule doesn't go away
It moves from the shop to your sketch. On an Uno, you choose Classic or BLE when you buy the module. On an ESP32, you choose it when you pick the library. Pick Classic, and the iPhone still can't see it:
Classic Bluetooth SPP — which is what an HC-05 and an HC-06 speak — is invisible to iOS at the operating-system level. No app fixes it, no library fixes it, and no wiring change fixes it. You need a BLE module: a genuine HM-10, or an ESP32. And Web Bluetooth is not the escape hatch — it is not in Safari, any version.
An ESP32 is on that list because it can do BLE, not because it does it by default. If an iPhone is involved, you want the BLE procedure further down, not BluetoothSerial.
When the Uno and the module still win
- You already own them. The project you finish beats the better board you order.
- Classic serial is the simpler radio. On an HC-06 you send letters and they arrive. Pick BLE on an ESP32 and you meet services and characteristics on day one.
- You have 5 V hardware. Sensors, displays and shields built for a 5 V Uno need a divider or a level shifter on every signal coming into an ESP32.
- Speed isn't a reason either way. One forum user timed an HC-05 returning from a write faster than the ESP32's built-in Classic; a published latency benchmark found the opposite. Neither of them is your project. If timing matters to you, measure your own link.
The 3.3 V catch, exactly
The ESP32's datasheet rates an input HIGH up to the supply plus 0.3 V, about 3.6 V. 5 V on an input is outside its rating. So anything 5 V that talks to the ESP32, such as a 5 V sensor's output or an Uno's TX pin, needs the same 1 kΩ / 2 kΩ divider you'd put on an HC-05's RXD. That's the same divider, for the same reason, just on a different pin.
The other direction is usually fine. A 3.3 V output from the ESP32 reads as HIGH on most 5 V parts: an Uno reads anything above 3.0 V as HIGH, and an L298 motor driver's inputs need only 2.3 V.
Make the ESP32 an HC-06: Classic serial
This is the ESP32 behaving like an HC-06 that lives inside the board. Original ESP32 only.
- Install esp32 by Espressif Systems in the Boards Manager. I compiled everything on this page with version 3.3.12, the newest in the Boards Manager on 2026-10-01.
- Select ESP32 Dev Module.
- Open File → Examples → BluetoothSerial → SerialToSerialBT and upload it unchanged.
- On an Android phone, pair with ESP32-BT-Slave in Bluetooth settings, then open Serial Bluetooth Terminal and connect.
- Open the Serial Monitor at 115200, and type in both directions.
You should see a letter typed in the app appear in the Serial Monitor, and a letter typed in the Serial Monitor appear in the app. It's the same bridge test you'd run with an HC-06, with no wires.
Why 115200 and not 9600? On an ESP32, Serial is only the USB link to your laptop, with no module at the other end to disagree with. The example sets Serial.begin(115200), so the monitor has to match it.
SerialBT then works like any serial port (available(), read(), write()), so a sketch that reads single-letter commands runs unchanged reading from it. Here is the one I use: it reads one byte at a time, never waits, skips the line ending the app adds, and echoes each command back so the app shows it arrived. The actions only print, so it's safe with nothing attached; put your motor or relay code inside them.
// esp32_classic_dispatcher.ino: an original ESP32 behaving like an HC-06
// with single-letter commands. Classic Bluetooth (SPP), Android only.
// Board: ESP32 Dev Module (esp32:esp32:esp32). Serial Monitor: 115200.
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
void forward() { Serial.println("forward"); }
void backward() { Serial.println("backward"); }
void left() { Serial.println("left"); }
void right() { Serial.println("right"); }
void halt() { Serial.println("halt"); }
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32-Troniction"); // the name the phone shows
Serial.println("Pair with ESP32-Troniction, then send F B L R S.");
}
void loop() {
if (!SerialBT.available()) return; // never wait for a byte
char c = SerialBT.read();
if (c == '\r' || c == '\n') return; // the app's line ending
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
switch (c) {
case 'F': forward(); break;
case 'B': backward(); break;
case 'L': left(); break;
case 'R': right(); break;
case 'S': halt(); break;
default:
Serial.print("ignored byte ");
Serial.println((int)c);
return;
}
SerialBT.write(c); // echo it back so the app shows it arrived
}
For a fixed pairing PIN, it's SerialBT.setPin(pin, length), two arguments in the 3.x core.
If it pairs but nothing arrives, the causes are the same family as on a module, and connected, but no data goes through them in order.
Make the ESP32 an HM-10: BLE serial
For an iPhone, a web page, or any ESP32 without Classic.
- Open File → Examples → BLE → UART. Older guides call it
BLE_uart. - Upload it. It shows up as UART Service and advertises the Nordic UART Service.
- Connect from nRF Connect or LightBlue, write
Fto the RX characteristic, and turn on notifications for TX.
| What | UUID | The phone… |
|---|---|---|
| Service | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E | finds this |
| RX characteristic | 6E400002-B5A3-F393-E0A9-E50E24DCCA9E | writes commands here |
| TX characteristic | 6E400003-B5A3-F393-E0A9-E50E24DCCA9E | listens here (notify) |
RX and TX are named from the ESP32's side: the phone writes to RX and listens on TX. It's the same crossing rule as the wires on a module, moved into software.
Answering like an HM-10
Apps and web pages written for the HM-10 look for a different service: FFE0, with one characteristic, FFE1, that the phone both writes to and listens on. The ESP32's BLE library takes any UUID, so the same idea works with the HM-10's numbers. This sketch advertises FFE0, takes single-letter commands written to FFE1, and echoes each one back as a notification. It runs on any ESP32 with BLE, including the S3 and the C3.
// esp32_hm10_ble.ino: an ESP32 answering like an HM-10. BLE service FFE0,
// one characteristic FFE1 (write + notify), single-letter commands.
// Any ESP32 with BLE: the original, S3, C3, C6. Serial Monitor: 115200.
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLE2902.h>
BLECharacteristic *ffe1;
bool connected = false;
volatile char pending = 0; // the last command received
class Link : public BLEServerCallbacks {
void onConnect(BLEServer *s) { connected = true; }
void onDisconnect(BLEServer *s) {
connected = false;
BLEDevice::startAdvertising(); // be findable again
}
};
class Rx : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *ch) {
String v = ch->getValue(); // whatever the phone wrote
for (size_t i = 0; i < v.length(); i++) {
char c = v[i];
if (c != '\r' && c != '\n') pending = c;
}
}
};
void setup() {
Serial.begin(115200);
BLEDevice::init("ESP32-HM10");
BLEServer *server = BLEDevice::createServer();
server->setCallbacks(new Link());
BLEService *svc = server->createService(BLEUUID((uint16_t)0xFFE0));
ffe1 = svc->createCharacteristic(
BLEUUID((uint16_t)0xFFE1),
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_WRITE_NR | BLECharacteristic::PROPERTY_NOTIFY);
ffe1->addDescriptor(new BLE2902());
ffe1->setCallbacks(new Rx());
svc->start();
BLEAdvertising *adv = BLEDevice::getAdvertising();
adv->addServiceUUID(BLEUUID((uint16_t)0xFFE0));
BLEDevice::startAdvertising();
Serial.println("Advertising as ESP32-HM10 (FFE0 / FFE1).");
}
void loop() {
char c = pending;
if (!c) return;
pending = 0;
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
switch (c) {
case 'F': Serial.println("forward"); break;
case 'B': Serial.println("backward"); break;
case 'L': Serial.println("left"); break;
case 'R': Serial.println("right"); break;
case 'S': Serial.println("halt"); break;
default: Serial.print("ignored byte "); Serial.println((int)c); return;
}
if (connected) { // echo it back on FFE1
ffe1->setValue((uint8_t *)&c, 1);
ffe1->notify();
}
}
The write arrives in a callback, outside loop(), so the callback only stores the letter and loop() acts on it. That keeps the slow work (motors, printing) out of the Bluetooth stack's way. It keeps the latest letter, which suits driving commands, where only the newest one matters.
What this sketch has and hasn't been through: it compiles for the original ESP32, the S3 and the C3 on core 3.3.12. I haven't yet run it against a phone, so treat it as a strong starting point rather than a tested recipe, and check it with nRF Connect before you point an HM-10 app at it.
How far this has been checked
The chip table, the example names, the UUIDs and the voltage ratings are from Espressif's and Nordic's own documentation and source, read on 2026-10-01. On the same day I installed core 3.3.12 with arduino-cli and compiled every sketch on this page:
| Sketch | ESP32 | S3, C3 | S2 |
|---|---|---|---|
SerialToSerialBT | yes | fails | fails |
UART (BLE) | yes | yes | — |
| Classic dispatcher (above) | yes | — | — |
| HM-10 sketch (above) | yes | yes | — |
"Yes" means it compiles. "Fails" means it stops at the two #error lines above, which is the point of the sixty-second test. A dash means I didn't try, because that chip lacks the radio the sketch needs.
Compiling isn't running. None of these has been on a board for this page yet; when I run them on hardware, the results will go here.
The ESP32 Arduino core is mid-change: version 4.0 rewrites the Bluetooth libraries (a release candidate came out on 23 September 2026, and the Boards Manager still offered 3.3.12 as the newest on 1 October). If a name here is missing from your IDE, check which core you have installed; 3.3.12 matches this page, and I'll update it when 4.0 is the stable release.
The verdict
Starting fresh, no board yet, no 5 V hardware to drive: buy an original ESP32, not an S3 or a C3 if you want Classic. It usually costs less than an Uno plus a module, and most of the beginner faults with a module can't happen on it: there's no divider to forget, no TX and RX to cross, no baud rate to match.
Already have an Uno and a module: finish the project. Everything you're learning is about serial communication, and it transfers whole: the crossing rule, the baud contract, line endings, reading without blocking, the iPhone rule. None of it is wasted when you move.
An iPhone anywhere in the picture: an ESP32 running BLE, or a genuine HM-10. If you're still weighing the three modules, HC-05 vs HC-06 vs HM-10 settles that in one question.
And if you do move to an ESP32 and wire it to something external, the pins are different from an Uno's: which pins for a Bluetooth module on each board covers the ESP32's, and the ones to avoid. Every other symptom, sorted by which part of the chain is misbehaving, is on the Arduino Bluetooth fixes index.
Common questions
- Is an ESP32 better than an Arduino with an HC-05?
- For a new project with no 5 V hardware to drive, usually yes. An original ESP32 has Classic Bluetooth and BLE on the chip, so there are no module wires, no divider, no module baud rate and no AT commands, and the dev board typically costs less than an Uno clone plus a module. If you already own the Uno and the module, finish the project with them: everything you learn transfers.
- Does every ESP32 have Classic Bluetooth?
- No. Only the original ESP32 has Classic Bluetooth, the kind an HC-05 or HC-06 speaks. The ESP32-S3, C3, C6, C2, C5 and H2 have BLE only, the S2 has no Bluetooth at all, and the P4 has no radio on the chip. Read the chip name on the metal can or in the listing before you plan anything.
- Can an iPhone connect to an ESP32 over Bluetooth?
- Only over BLE. An ESP32 running the BluetoothSerial library speaks Classic SPP, exactly like an HC-06, and an iPhone cannot see it. Use the BLE UART example instead, which advertises the Nordic UART Service that iPhone apps such as nRF Connect and LightBlue can open.
- Do I need a voltage divider with an ESP32?
- Not for Bluetooth, because the radio is inside the chip. You need one on any 5 V signal coming into the ESP32, because its inputs are rated to about 3.6 V. Signals going out of the ESP32 to 5 V parts usually work, as long as the 5 V part reads 3.3 V as HIGH.
- Is the ESP32's Bluetooth faster than an HC-05?
- It isn't settled. One forum user timed an HC-05 as faster than the ESP32's built-in Classic; a published latency benchmark found the opposite. Neither measured your project. If timing matters, measure your own link.
Still not connecting?
Arduino Bluetooth — Make It Connect is a 277-page book of every way the link fails, why, and the fix — HC-05, HC-06 and HM-10 BLE, including the clone family few sources cover. PDF and EPUB, $15.