To build a reliable bluetooth proximity sensor, bypass external UART modules (like the HC-05 or HM-10) and use an ESP32 with native BLE (Bluetooth Low Energy). If you must use an external BLE module on a 5V Arduino, wire it via the UART bus at 9600 baud using a BSS138 logic level shifter, never direct 5V. For short-range wired alternatives, I2C proximity sensors (like the VL53L0X Time-of-Flight) require 4.7kΩ pull-up resistors to 3.3V and operate at 400kHz. The physical layer dictates your success; ignoring voltage thresholds or bus capacitance will brick your module or yield phantom readings.
Bus Mechanics: UART (Wireless Bridge) vs I2C (Wired Proximity)
When designing a proximity system, you are choosing between a wireless bridge (UART to BLE module) or a direct wired sensor (I2C). Understanding the physical layer of both buses prevents 90% of bench failures.
| Feature | UART (for HM-10/HC-05 BLE Modules) | I2C (for VL53L0X / APDS-9960 Wired Sensors) | Native BLE (ESP32 / nRF52 SoC Internal) |
|---|---|---|---|
| Wires Required | 2 (TX, RX) + Power/GND | 2 (SDA, SCL) + Power/GND | 0 (Antenna trace only) |
| Max Speed | 115,200 baud (typical), up to 1Mbps | 100kHz (Standard), 400kHz (Fast) | 1 Mbps (BLE 4.2), 2 Mbps (BLE 5.0) |
| Addressing | None (Point-to-Point serial) | 7-bit hardware address (e.g., 0x29) | MAC Address / UUID |
| Max Distance | ~15cm (inter-MCU to module trace) | ~30cm (bus capacitance limit) | 10m - 50m (wireless RSSI range) |
| Voltage Logic | 3.3V strictly (5V destroys HM-10) | 3.3V or 5V (depends on pull-up rail) | 3.3V internal |
Physical Wiring and Pull-Up Requirements
The physical layer is where most proximity projects fail. A bluetooth proximity sensor relying on an external HM-10 module communicates via UART, but the HM-10 is strictly a 3.3V device. Feeding it 5V from an Arduino Uno's TX pin will fry the module's internal voltage regulator within seconds.
If you pivot to a wired I2C proximity sensor (like the STMicroelectronics VL53L0X Time-of-Flight sensor) for sub-millimeter accuracy, the bus requires open-drain pull-up resistors. The I2C spec mandates pull-ups to establish the logic HIGH state. For a 100kHz bus with standard capacitance (<200pF), use 4.7kΩ resistors tied to the 3.3V rail. If you push the bus to 400kHz (Fast Mode), drop the pull-ups to 2.2kΩ to decrease the RC rise time of the square wave edges.
Decision Path: Which Protocol Fits Your Proximity Project?
Use this decision matrix to lock in your hardware architecture. Do not mix wired I2C for room-scale presence detection; use BLE RSSI (Received Signal Strength Indicator) instead.
| Project Constraint | Choose UART + External BLE Module | Choose I2C Wired Sensor | Choose Native BLE SoC |
|---|---|---|---|
| Detection Range | 1m - 10m (RSSI based) | 1cm - 200cm (Time-of-Flight / IR) | 1m - 50m (RSSI based) |
| Device Count | 1 module per UART port | Up to 110+ on one bus (if addresses differ) | Up to 20 simultaneous connections |
| Host MCU | Arduino Uno / Mega (5V) | Any MCU with I2C peripheral | ESP32 / nRF52840 / Raspberry Pi Pico W |
| PCB Space | High (Module + Level Shifter) | Low (Single IC + 2 resistors) | Lowest (Integrated RF) |
The Final Verdict: If you are building a room-scale bluetooth proximity sensor in 2026, buy the ESP32-C3 SuperMini (typically $3.50 USD). It has native BLE 5.0, operates at 3.3V, eliminates the need for external UART modules and level shifters, and allows you to scan for iBeacons or BLE tags directly using the NimBLE stack. Only use the UART + HM-10 route if you are retrofitting a legacy 5V Arduino Mega system that cannot be replaced.
The Classic Failures: Baud Mismatches, Missing Pull-Ups, and Address Clashes
When your proximity sensor returns garbage data or flatlines, check these three physical and link-layer failures first.
- UART Baud Mismatch (The HM-10 Trap): The HM-10 defaults to 9600 baud, but many clones ship at 115200. If your Serial monitor shows inverted question marks or nothing at all, send the AT command
AT+BAUD?at both 9600 and 115200 to query the actual rate. Mismatched baud rates cause framing errors that the MCU silently drops. - Missing I2C Pull-Ups (Floating Bus): If your wired VL53L0X fails to initialize (
Wire.endTransmission()returns 2), measure the SDA and SCL lines with a multimeter. If they read 0.0V or float randomly, you forgot the pull-up resistors. The I2C peripheral can pull the line LOW, but without pull-ups, it can never drive it HIGH. - I2C Address Clash: If you wire two identical proximity sensors to the same I2C bus, they will both respond to address
0x29, causing data collisions and bus lockups. You must use a sensor breakout with an address-select jumper, or use an I2C multiplexer (like the TCA9548A) to isolate the buses.
Minimal Working Exchange: UART AT Commands for BLE Discovery
Before writing complex proximity logic, verify the physical UART bus by sending a raw AT command to the BLE module. This minimal exchange configures the HM-10 as a central device, scans for nearby BLE beacons, and returns their MAC addresses and RSSI (signal strength) values. Lower RSSI (e.g., -90dBm) means further away; higher RSSI (e.g., -40dBm) means closer.
Wiring assumption: Arduino Uno (5V). HM-10 VCC to 3.3V, GND to GND. Uno TX (Pin 11) through BSS138 level shifter to HM-10 RX. HM-10 TX directly to Uno RX (Pin 10). Baud: 9600.
#include <SoftwareSerial.h>
// SoftwareSerial on pins 10 (RX) and 11 (TX)
SoftwareSerial bleSerial(10, 11);
void setup() {
Serial.begin(9600); // Debug console
bleSerial.begin(9600); // UART bus to HM-10
// Wait for module boot
delay(1000);
// Set HM-10 to Central Mode (Role 1) to scan for other BLE devices
bleSerial.print("AT+ROLE1");
delay(500);
// Clear any previous serial buffer
while(bleSerial.available()) bleSerial.read();
}
void loop() {
// Send discovery command to scan for nearby BLE proximity beacons
bleSerial.print("AT+DISC?");
unsigned long startTime = millis();
// Read responses for 5 seconds
while(millis() - startTime < 5000) {
if(bleSerial.available()) {
char c = bleSerial.read();
Serial.print(c); // Prints OK+DISC:MAC:RSSI to console
}
}
Serial.println("\n--- Scan Cycle Complete ---");
delay(5000); // Wait 5 seconds before next scan
}
How to Sniff and Debug the Bus
When code and wiring look correct but the bus is dead, you must look at the physical signals. Relying solely on Serial.print() hides physical layer faults.
Sniffing the UART Bus: Connect a $15 USB Logic Analyzer (like the Saleae Logic clone) to the TX and RX lines. Use PulseView or the Saleae software to decode the UART protocol. Set the decoder to 9600 baud, 8N1. If you see perfect square waves on the TX line but no response on the RX line, your module is dead or unpowered. If the square waves look like sloped triangles, your baud rate is too high for the wire capacitance.
Sniffing the I2C Bus: Hook an oscilloscope to SDA and SCL. A healthy I2C bus shows crisp 3.3V square waves with slight rounding on the rising edges (due to the RC curve of the pull-ups). If the HIGH level only reaches 1.5V, your pull-up resistors are too weak, or another device is actively dragging the bus low (a classic sign of a shorted sensor IC).
Sniffing the BLE Proximity Link: To verify that your bluetooth proximity sensor is actually broadcasting or scanning correctly without writing MCU code, use the nRF Connect for Mobile app on your smartphone. This app acts as an independent BLE sniffer. If your ESP32 or HM-10 is broadcasting an iBeacon, nRF Connect will show the exact UUID, Major/Minor values, and the raw RSSI in dBm. If the app sees it but your MCU doesn't, the fault is in your firmware's parsing logic, not the RF environment.






