Guías · 17 min de lectura

ESP32 Bluetooth: Classic y BLE para controlarlo desde el móvil

ESP32 Bluetooth: LED desde el móvil con BluetoothSerial, servidor BLE con servicios y UUID, prueba con nRF Connect, datos de sensor, iPhone y errores comunes.

El Bluetooth del ESP32 aprovecha la radio inalámbrica integrada en el chip para conectar tu ESP32 a un móvil, una tablet o un ordenador sin ningún módulo extra. El ESP32 original admite a la vez dos tipos de Bluetooth: Bluetooth Classic, que funciona como un puerto serie, y Bluetooth Low Energy (BLE), que consume muy poca energía. Con un Arduino Uno necesitas un módulo HC-05 aparte para hablar con el móvil; con el ESP32 bastan unas pocas líneas de código. En esta guía configurarás los dos métodos paso a paso, encenderás un LED desde el móvil, escribirás tu propio servicio BLE y enviarás datos de un sensor al teléfono.

El método que elijas depende de tu móvil y de tu placa. Si tienes un teléfono Android y un ESP32 DevKit clásico, el comienzo más rápido es la librería BluetoothSerial. Si usas un iPhone o tu placa es un ESP32-S3 o un ESP32-C3, el Bluetooth Classic no te sirve; necesitas BLE. Al final encontrarás también el mismo proyecto BLE para la Raspberry Pi Pico W con MicroPython. Si es la primera vez que usas el ESP32, completa antes la instalación del Arduino IDE en ¿Qué es el ESP32?

Placa de desarrollo ESP32 con Bluetooth y BLE
Placa de desarrollo ESP32: el Wi-Fi, el Bluetooth Classic y el BLE están dentro del mismo chip, así que no necesitas un módulo Bluetooth externo para conectarte al móvil.

¿Qué es el Bluetooth del ESP32? Diferencias entre Classic y BLE

El Bluetooth Classic es el tipo más antiguo y conocido, el que usan los auriculares inalámbricos y módulos como el HC-05. El ESP32 lo usa mediante SPP (Serial Port Profile): los bytes fluyen uno tras otro como si hubiera un cable serie invisible entre el móvil y la placa. Primero emparejas el dispositivo en los ajustes de Bluetooth del teléfono y luego te conectas con una app de terminal. El BLE, en cambio, es un sistema distinto que llegó en 2010 con Bluetooth 4.0 y está pensado para dispositivos que funcionan meses con una pila. En BLE no hay un flujo continuo de datos; el ESP32 guarda una pequeña tabla de valores (GATT) y el móvil los lee, los escribe o se suscribe para recibir un aviso cuando cambian. Aunque los nombres se parezcan, los dos no pueden comunicarse entre sí: una app hecha para BLE no puede conectarse a un dispositivo Bluetooth Classic.

CaracterísticaBluetooth Classic (SPP)Bluetooth Low Energy (BLE)
Librería de ArduinoBluetoothSerial (incluida en el paquete ESP32)BLEDevice (incluida en el paquete ESP32) o NimBLE-Arduino
Modelo de datosFlujo continuo de bytes, como un puerto serieTabla GATT formada por servicios y características
Cantidad de datosAlta, apta para flujos continuosPaquetes pequeños; 20 bytes por notificación por defecto
ConsumoSiempre alto mientras está conectadoMuy bajo; puede funcionar meses con una pila
EmparejamientoEn los ajustes de Bluetooth del móvilNo hace falta; la conexión se hace desde la app
AndroidCompatibleCompatible
iPhone (iOS)No compatibleCompatible
ESP32 compatiblesSolo el ESP32 original (WROOM-32, WROVER)ESP32, S3, C3, C6 y H2 (no el S2)
Uso típicoTerminal serie, coche robot, sustituto del HC-05Sensores, wearables, domótica, balizas (beacons)

Una regla práctica: si quieres enviar y recibir texto desde un móvil Android y ver resultados rápido, usa Classic; si necesitas compatibilidad con iPhone, bajo consumo o una de las placas ESP32 más nuevas, usa BLE. Si empiezas un proyecto nuevo, merece la pena aprender BLE, porque funciona en los dos tipos de teléfono y está presente en todos los chips nuevos de Espressif con Bluetooth. Si ya has trabajado con el HC-05, verás que la lógica de comandos de Módulo Bluetooth HC-05 es exactamente la misma con BluetoothSerial.

¿Qué Bluetooth tiene cada placa ESP32?

No todos los chips de la familia ESP32 tienen el mismo hardware Bluetooth. El error más habitual es intentar subir un ejemplo de BluetoothSerial encontrado en internet a una placa ESP32-S3 o ESP32-C3. Espressif solo incluyó el Bluetooth Classic en el chip ESP32 original; los chips posteriores que tienen Bluetooth solo admiten BLE. Puedes saber qué chip lleva tu placa por el texto grabado en la tapa metálica del módulo (por ejemplo ESP32-WROOM-32 o ESP32-S3-WROOM-1).

Placa / chipBluetooth ClassicBLENota
ESP32 (WROOM-32, WROVER, DevKit V1)SíSí (4.2)La única familia ESP32 en la que funciona BluetoothSerial
ESP32-S2NoNoSolo Wi-Fi
ESP32-S3NoSí (Bluetooth 5)BluetoothSerial no compila, usa BLE
ESP32-C3NoSí (Bluetooth 5)Placas BLE pequeñas y baratas
ESP32-C6NoSí (Bluetooth 5)También Wi-Fi 6, Zigbee y Thread
ESP32-H2NoSí (Bluetooth 5)Sin Wi-Fi; orientado a Zigbee y Thread
ESP8266 (NodeMCU, Wemos D1)NoNoNecesita un HC-05 o HM-10 para tener Bluetooth
Raspberry Pi Pico W / Pico 2 WEn el hardwareSíMicroPython solo admite BLE
Raspberry Pi Pico / Pico 2NoNoNecesita un módulo Bluetooth externo
Si compilas código de BluetoothSerial con un ESP32-S3, C3, C6 o H2 seleccionado en el menú de placas, la línea de comprobación de los ejemplos de Espressif (y de nuestro código) detiene la compilación con "Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip." No es un problema de instalación ni de librería; el chip simplemente no tiene hardware Bluetooth Classic. Si tienes una de estas placas, pasa directamente al ejemplo BLE (Nordic UART) de más abajo.

Bluetooth Classic: controlar un LED desde el móvil con BluetoothSerial

La librería BluetoothSerial presenta el ESP32 ante el móvil como si fuera un módulo HC-05 y usa los mismos comandos que el objeto Serial: available(), read(), print() y println(). Viene incluida en el paquete de placas ESP32, así que no tienes que instalar nada más. Para el circuito basta con el LED azul integrado que la mayoría de las placas DevKit tienen en el GPIO 2; si usas un LED externo, conéctalo al GPIO 2 con una resistencia de 220 Ω. Puedes comprobar el valor con la calculadora de resistencia para LED y ver los demás pines seguros del ESP32 en la herramienta Pinout. Asegúrate de que ESP32 Dev Module está seleccionada en Herramientas > Placa y sube este código:

C++ (Arduino IDE)
1#include "BluetoothSerial.h"
2
3// Klasik Bluetooth yalnızca ilk ESP32'de var / Classic Bluetooth exists only on the original ESP32
4#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
5#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
6#endif
7#if !defined(CONFIG_BT_SPP_ENABLED)
8#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
9#endif
10
11BluetoothSerial SerialBT;
12const int LED_PIN = 2;                 // DevKit dahili LED / DevKit onboard LED
13bool ledAcik = false;
14
15void ledAyarla(bool acik) {
16  ledAcik = acik;
17  digitalWrite(LED_PIN, acik ? HIGH : LOW);
18  SerialBT.println(acik ? "LED acik / LED on" : "LED kapali / LED off");
19}
20
21void setup() {
22  Serial.begin(115200);
23  pinMode(LED_PIN, OUTPUT);
24  SerialBT.begin("BlueGrays-ESP32");   // telefonda görünecek ad / name shown on the phone
25  Serial.println("Eslesmeye hazir / Ready to pair");
26}
27
28void loop() {
29  if (SerialBT.available()) {
30    String komut = SerialBT.readStringUntil('\n');  // satır sonuna kadar oku / read until newline
31    komut.trim();                                   // CR ve boşlukları sil / strip CR and spaces
32    komut.toLowerCase();
33
34    if (komut == "1" || komut == "ac") {
35      ledAyarla(true);
36    } else if (komut == "0" || komut == "kapat") {
37      ledAyarla(false);
38    } else if (komut == "durum") {
39      SerialBT.println(ledAcik ? "LED acik / LED on" : "LED kapali / LED off");
40    } else if (komut.length() > 0) {
41      SerialBT.print("Bilinmeyen komut / Unknown command: ");
42      SerialBT.println(komut);
43    }
44  }
45
46  if (Serial.available()) {            // Seri Monitör'den telefona / Serial Monitor to phone
47    SerialBT.write(Serial.read());
48  }
49}

El código lee cada línea que llega del móvil como un comando. readStringUntil() espera hasta que llega el carácter de salto de línea y trim() elimina el retorno de carro (CR) que añade el teléfono y los espacios; si olvidas esta línea, al enviar "1" la placa recibe en realidad "1" más un CR invisible y no coincide ningún comando. Como los comandos se pasan a minúsculas, "AC" y "ac" hacen lo mismo. Las dos últimas líneas envían al móvil todo lo que escribas en el Monitor Serie, así que puedes mantener un chat inalámbrico entre el ordenador y el teléfono. Las comprobaciones #error del principio hacen que la compilación se detenga con un mensaje claro si eliges la placa equivocada.

Conectarse desde el móvil con Serial Bluetooth Terminal

  1. Después de subir el código, abre el Monitor Serie a 115200 baudios y comprueba que aparece el mensaje "Eslesmeye hazir / Ready to pair".
  2. En tu móvil Android, busca dispositivos nuevos en Ajustes > Bluetooth y empareja BlueGrays-ESP32. Por defecto el ESP32 se empareja sin pedir PIN; si el teléfono muestra un código de confirmación, acéptalo.
  3. Instala desde Google Play la app Serial Bluetooth Terminal de Kai Morich.
  4. En la app, entra en Devices desde el menú lateral y elige BlueGrays-ESP32 en la pestaña Bluetooth Classic.
  5. Toca el icono del enchufe de la barra superior; cuando aparezca "Connected", ya estás conectado.
  6. Escribe 1 en la caja inferior y envíalo: el LED se enciende y aparece la respuesta "LED acik / LED on". Con 0 se apaga y con durum conoces su estado actual.
Mantén pulsados los botones de macro M1 y M2 de la parte inferior de Serial Bluetooth Terminal y guarda en ellos los comandos "1" y "0" para encender y apagar el LED con un solo toque. La opción Send > Newline de los ajustes de la app es CR+LF por defecto; el trim() del código ya elimina el CR sobrante. Si conectas un relé en lugar del LED, el mismo código puede controlar una lámpara o un enchufe; tienes los detalles en ¿Qué es un módulo de relé?

¿Cómo funciona BLE? Servicios, características y UUID

En BLE hay dos papeles. El ESP32 actúa como servidor (periférico): guarda los valores y anuncia "estoy aquí" enviando a intervalos cortos (por defecto cada 20–40 ms en el ESP32) pequeños paquetes de advertising (anuncio) de 31 bytes como máximo. El móvil actúa como cliente (central): escanea el entorno, encuentra el dispositivo y se conecta. Tras la conexión, el móvil lee la tabla GATT (Generic Attribute Profile) del ESP32. Imagina esta tabla como un archivador: cada cajón es un servicio y cada carpeta del cajón es una característica. Una característica contiene un único valor (por ejemplo una temperatura o un comando para el LED) y unas propiedades que indican qué se puede hacer con él.

TérminoSignificadoEjemplo en esta guía
Advertising (anuncio)Paquete corto con el que el dispositivo anuncia su nombre y sus serviciosAnuncio con el nombre "BlueGrays-BLE"
ServicioGrupo que reúne características relacionadasServicio Nordic UART, Environmental Sensing (0x181A)
CaracterísticaUn único valor que se lee o se escribeComando del LED, temperatura (0x2A6E)
READEl móvil lee el valor cuando quiereÚltima temperatura medida
WRITE / WRITE_NREl móvil escribe un valor (con o sin respuesta)Escribir "1" enciende el LED
NOTIFYEl ESP32 avisa al móvil por sí solo cuando cambia el valorUna temperatura nueva cada 2 segundos
Descriptor 0x2902 (CCCD)Interruptor que activa y desactiva las notificacionesBotón de triple flecha de nRF Connect
UUIDNúmero de identificación de servicios y características6E400001-B5A3-F393-E0A9-E50E24DCCA9E

Cada servicio y cada característica se identifican con un UUID. Las funciones estándar definidas por el Bluetooth SIG usan números cortos de 16 bits, como 0x180F para el servicio de batería y 0x2A6E para la característica de temperatura. Para tus propios datos sin equivalente estándar, genera un UUID aleatorio de 128 bits; en Python basta con import uuid; print(uuid.uuid4()). Al elegir tu UUID no uses un valor que siga el patrón xxxxxxxx-0000-1000-8000-00805F9B34FB, porque ese patrón está reservado para los números de 16 bits del Bluetooth SIG. Si quieres que funcione sin problemas con apps ya hechas, es todavía más práctico usar UUID que todo el mundo reconoce:

Servicio / característicaUUIDFormato de datos
Battery Service / Battery Level0x180F / 0x2A191 byte, porcentaje (0–100)
Environmental Sensing / Temperature0x181A / 0x2A6EEntero con signo de 2 bytes, 0,01 °C
Environmental Sensing / Humidity0x181A / 0x2A6FEntero sin signo de 2 bytes, 0,01 %
Heart Rate / Heart Rate Measurement0x180D / 0x2A37Byte de banderas + valor del pulso
Nordic UART Service (NUS)6E400001-B5A3-F393-E0A9-E50E24DCCA9ERX 6E400002 (escritura), TX 6E400003 (notificación)

Servidor BLE con ESP32: controlar un LED desde el móvil (Nordic UART)

En nuestro primer proyecto BLE convertimos el ESP32 en un servidor Nordic UART Service (NUS). NUS es un servicio de "puerto serie sobre BLE" definido por Nordic Semiconductor que se ha convertido en un estándar de facto: el móvil escribe en la característica RX y el ESP32 envía notificaciones desde la característica TX. Su ventaja es que muchas apps ya hechas lo reconocen; nRF Connect, la pestaña Bluetooth LE de Serial Bluetooth Terminal y Bluefruit Connect en el iPhone funcionan con el mismo código. Así, el código de esta sección funciona tanto con Android como con iPhone, y también en placas solo BLE como el ESP32-S3 y el C3. La librería BLE forma parte del paquete de placas ESP32, así que no tienes que instalar nada más.

C++ (Arduino IDE)
1#include <BLEDevice.h>
2#include <BLEServer.h>
3#include <BLEUtils.h>
4#include <BLE2902.h>
5
6// Nordic UART Service UUID'leri / Nordic UART Service UUIDs
7#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
8#define RX_UUID      "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"  // telefon -> ESP32 / phone -> ESP32
9#define TX_UUID      "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"  // ESP32 -> telefon / ESP32 -> phone
10
11const int LED_PIN = 2;
12
13BLEServer *sunucu = nullptr;
14BLECharacteristic *txKarakteristik = nullptr;
15volatile bool bagli = false;
16bool oncekiBagli = false;
17volatile int bekleyenKomut = -1;          // -1: yeni komut yok / no new command
18
19class SunucuOlaylari : public BLEServerCallbacks {
20  void onConnect(BLEServer *s) { bagli = true; }
21  void onDisconnect(BLEServer *s) { bagli = false; }
22};
23
24class RxOlaylari : public BLECharacteristicCallbacks {
25  void onWrite(BLECharacteristic *c) {
26    String deger = c->getValue().c_str(); // çekirdek 2.x ve 3.x ile uyumlu / works on core 2.x and 3.x
27    deger.trim();
28    if (deger == "1") bekleyenKomut = 1;
29    else if (deger == "0") bekleyenKomut = 0;
30  }
31};
32
33void cevapGonder(const char *mesaj) {
34  txKarakteristik->setValue((uint8_t *)mesaj, strlen(mesaj));
35  txKarakteristik->notify();              // telefona bildirim / notify the phone
36}
37
38void setup() {
39  Serial.begin(115200);
40  pinMode(LED_PIN, OUTPUT);
41
42  BLEDevice::init("BlueGrays-BLE");       // yayında görünecek ad / advertised name
43  sunucu = BLEDevice::createServer();
44  sunucu->setCallbacks(new SunucuOlaylari());
45
46  BLEService *servis = sunucu->createService(SERVICE_UUID);
47
48  txKarakteristik = servis->createCharacteristic(TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
49  txKarakteristik->addDescriptor(new BLE2902());   // bildirim anahtarı (CCCD) / notification switch (CCCD)
50
51  BLECharacteristic *rx = servis->createCharacteristic(
52    RX_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
53  rx->setCallbacks(new RxOlaylari());
54
55  servis->start();
56
57  BLEAdvertising *yayin = BLEDevice::getAdvertising();
58  yayin->addServiceUUID(SERVICE_UUID);
59  yayin->setScanResponse(true);
60  yayin->setMinPreferred(0x06);           // iPhone bağlantısına yardımcı / helps iPhone connections
61  yayin->setMaxPreferred(0x12);
62  BLEDevice::startAdvertising();
63  Serial.println("BLE yayini basladi / BLE advertising started");
64}
65
66void loop() {
67  if (bekleyenKomut != -1) {              // telefondan komut geldi / command received
68    int komut = bekleyenKomut;
69    bekleyenKomut = -1;
70    digitalWrite(LED_PIN, komut == 1 ? HIGH : LOW);
71    if (bagli) cevapGonder(komut == 1 ? "LED acik" : "LED kapali");
72  }
73
74  if (!bagli && oncekiBagli) {            // bağlantı koptu / disconnected
75    delay(500);                           // yığına zaman tanı / give the stack time
76    sunucu->startAdvertising();           // yeniden görünür ol / advertise again
77    Serial.println("Yeniden yayin / Advertising again");
78    oncekiBagli = false;
79  }
80  if (bagli && !oncekiBagli) {            // yeni bağlantı / new connection
81    Serial.println("Telefon baglandi / Phone connected");
82    oncekiBagli = true;
83  }
84}

La parte más importante del código son las clases de callback. onConnect y onDisconnect siguen el estado de la conexión, y onWrite captura el valor que el móvil escribe en la característica RX. Estas funciones se ejecutan en la tarea propia de la pila Bluetooth, así que en lugar de hacer trabajos largos dentro de ellas solo cambiamos una variable; el trabajo real lo hace loop(). El descriptor BLE2902 añadido a la característica TX es necesario para que el móvil pueda activar las notificaciones. Cuando se corta la conexión, el ESP32 no vuelve a anunciarse por sí solo; sin la línea startAdvertising() de loop(), el dispositivo no se puede encontrar de nuevo tras la primera conexión. Las líneas setMinPreferred y setMaxPreferred añaden al anuncio el intervalo de conexión preferido, lo que facilita la conexión con iPhone.

Los códigos de este artículo se han compilado con la versión 2.0.11 del core de ESP32 para Arduino (paquete de placas). En esta versión, las funciones BLE como getValue() devuelven std::string, y la mayoría de los ejemplos de internet también usan ese tipo. Con el core 3.0, las funciones BLE pasaron al tipo String de Arduino, por eso el código escrito con std::string falla en 3.x con "conversion from 'String' to non-scalar type 'std::string' requested". Si asignas el resultado a una variable String y añades .c_str() al final, como en nuestro código, la línea compila tanto con el core 2.x como con el 3.x.

Probar BLE con nRF Connect paso a paso

  1. Instala en tu móvil la app gratuita nRF Connect for Mobile de Nordic Semiconductor; está disponible para Android y iPhone.
  2. En Android la app pide Bluetooth y el permiso de Dispositivos cercanos (en Android 11 y anteriores, el permiso de ubicación con la ubicación activada); sin él la lista de escaneo queda vacía.
  3. En la pestaña Scanner pulsa SCAN y busca BlueGrays-BLE en la lista. El valor RSSI que aparece al lado es la intensidad de la señal: -50 dBm es muy cerca y -90 dBm es el límite del alcance.
  4. Toca CONNECT. En la pantalla que se abre aparece Nordic UART Service; si hubieras usado UUID generados por ti, aquí pondría "Unknown Service".
  5. Toca el icono de triple flecha hacia abajo junto a la característica TX (6E400003) para activar las notificaciones.
  6. Toca el icono de flecha hacia arriba junto a la característica RX (6E400002), elige Text (UTF-8) como tipo de dato, escribe 1 y envíalo. El LED se enciende y en la línea TX aparece "LED acik".

Esta prueba es la forma más rápida de demostrar que la parte del ESP32 funciona antes de escribir una app para el móvil. Si algo falla, prueba primero con nRF Connect: si el dispositivo no está en la lista, el problema es el anuncio; si se conecta pero no aparece el servicio, falta la llamada start() del servicio; si escribes un valor y el LED no se enciende, revisa la comparación dentro de onWrite. También puedes probar el mismo código desde la pestaña Bluetooth LE de Serial Bluetooth Terminal; la app reconoce el servicio NUS y chateas igual que con el Bluetooth Classic. Si quieres crear tu propia app Android, echa un vistazo a App Inventor con Arduino y Bluetooth.

Enviar datos de un sensor por BLE (DHT22)

Sensor de temperatura y humedad DHT22
El DHT22 mide la temperatura con una resolución de 0,1 °C; como funciona a 3,3 V, se conecta directamente al ESP32.
Pin del DHT22ESP32Nota
VCC (+)3V3Funciona entre 3,3 y 5 V
DATA (OUT)GPIO 4Con el sensor suelto, pon una resistencia de 10 kΩ entre DATA y 3V3
GND (−)GNDTierra común

En este ejemplo usamos el servicio estándar Environmental Sensing (0x181A) y sus características de temperatura (0x2A6E) y humedad (0x2A6F). Lo bueno de los UUID estándar es que apps como nRF Connect reconocen el valor y lo muestran directamente como "23.45 °C". El estándar pide que los valores no se envíen como texto, sino como enteros multiplicados por 100: una temperatura de 23,45 °C se convierte en el número 2345 y se envía en 2 bytes, primero el byte bajo (little-endian). Para leer el sensor usamos la DHT sensor library de Adafruit, que explicamos en la guía del DHT11; puedes instalarla desde el Gestor de bibliotecas.

C++ (Arduino IDE)
1#include <BLEDevice.h>
2#include <BLEServer.h>
3#include <BLE2902.h>
4#include <DHT.h>
5
6#define DHT_PIN 4
7DHT dht(DHT_PIN, DHT22);
8
9BLEServer *sunucu = nullptr;
10BLECharacteristic *sicaklikKar = nullptr;
11BLECharacteristic *nemKar = nullptr;
12volatile bool bagli = false;
13bool oncekiBagli = false;
14unsigned long sonOlcum = 0;
15
16class SunucuOlaylari : public BLEServerCallbacks {
17  void onConnect(BLEServer *s) { bagli = true; }
18  void onDisconnect(BLEServer *s) { bagli = false; }
19};
20
21BLECharacteristic *karakteristikEkle(BLEService *servis, uint16_t uuid) {
22  BLECharacteristic *k = servis->createCharacteristic(
23    BLEUUID(uuid), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
24  k->addDescriptor(new BLE2902());        // bildirim için gerekli / needed for notify
25  return k;
26}
27
28void setup() {
29  Serial.begin(115200);
30  dht.begin();
31
32  BLEDevice::init("BlueGrays-Sensor");
33  sunucu = BLEDevice::createServer();
34  sunucu->setCallbacks(new SunucuOlaylari());
35
36  BLEService *servis = sunucu->createService(BLEUUID((uint16_t)0x181A));  // Environmental Sensing
37  sicaklikKar = karakteristikEkle(servis, 0x2A6E);  // sıcaklık, 0,01 °C / temperature, 0.01 °C
38  nemKar = karakteristikEkle(servis, 0x2A6F);       // nem, %0,01 / humidity, 0.01 %
39  servis->start();
40
41  BLEAdvertising *yayin = BLEDevice::getAdvertising();
42  yayin->addServiceUUID(BLEUUID((uint16_t)0x181A));
43  yayin->setScanResponse(true);
44  BLEDevice::startAdvertising();
45}
46
47void loop() {
48  if (millis() - sonOlcum >= 2000) {      // DHT22 en fazla 2 sn'de bir / DHT22 at most every 2 s
49    sonOlcum = millis();
50    float t = dht.readTemperature();
51    float h = dht.readHumidity();
52    if (!isnan(t) && !isnan(h)) {
53      int16_t t100 = (int16_t)lroundf(t * 100);    // 23,45 °C -> 2345
54      uint16_t h100 = (uint16_t)lroundf(h * 100);  // %41,2 -> 4120
55      sicaklikKar->setValue((uint8_t *)&t100, 2);  // 2 bayt, düşük bayt önce / 2 bytes, little-endian
56      nemKar->setValue((uint8_t *)&h100, 2);
57      if (bagli) {
58        sicaklikKar->notify();
59        nemKar->notify();
60      }
61      Serial.printf("%.2f C  %.2f %%\n", t, h);
62    }
63  }
64
65  if (!bagli && oncekiBagli) {            // bağlantı koptu / disconnected
66    delay(500);
67    sunucu->startAdvertising();           // yeniden yayın / advertise again
68    oncekiBagli = false;
69  }
70  if (bagli && !oncekiBagli) oncekiBagli = true;
71}

El código mide cada dos segundos, porque el DHT22 no se puede leer más a menudo. Primero se escriben los valores en la característica con setValue(); así el móvil también obtiene la última medida cuando hace una lectura (READ) sin esperar una notificación. Si el móvil está conectado, notify() envía el nuevo valor al instante. Si nRF Connect no puede descifrar el valor, verás los bytes en bruto: para 23,45 °C la pantalla muestra (0x) 29-09. Si inviertes los bytes obtienes 0x0929, que en decimal es 2345; al dividir entre 100 tienes la temperatura. Tendrás que hacer el mismo cálculo cuando escribas tu propia app. Enviar el valor como texto puede parecer más fácil, pero el formato estándar ocupa menos bytes y funciona con apps ya existentes.

También se pueden enviar datos sin conexión: el ESP32 puede meter la medida directamente en el campo de datos del fabricante (manufacturer data) de su paquete de anuncio, y cualquier móvil o pasarela cercana puede leerla sin conectarse. Este método se llama beacon (baliza), y el formato abierto BTHome que reconoce Home Assistant funciona igual. Si el ESP32 entra en sueño profundo después de cada medida, la batería dura meses; consulta ESP32 Deep Sleep para saber cómo.

Bluetooth del ESP32 con iPhone (iOS)

La pregunta más frecuente de quienes usan iPhone es por qué el ESP32 no aparece en los ajustes de Bluetooth. Apple solo abre el perfil de puerto serie (SPP) del Bluetooth Classic a los accesorios con certificación MFi, así que no puedes conectarte desde un iPhone a un ESP32 que use BluetoothSerial. La solución es BLE, y los ejemplos NUS y del sensor de arriba funcionan sin problemas en el iPhone. Ten en cuenta algunos detalles:

  • Buscar en Ajustes: los dispositivos BLE no aparecen en los ajustes de Bluetooth de iOS; haz la conexión desde una app como nRF Connect, LightBlue o Bluefruit Connect.
  • Caché: iOS recuerda el nombre y la lista de servicios del dispositivo. Si después de cambiar los servicios en el código sigues viendo la lista antigua, apaga y enciende el Bluetooth del móvil.
  • Sin dirección MAC: iOS no muestra a las apps la dirección MAC del dispositivo y asigna a cada uno un identificador propio; si usas varios ESP32, ponle a cada uno un nombre distinto.
  • Intervalo de conexión: no borres las líneas setMinPreferred(0x06) y setMaxPreferred(0x12); los ejemplos de Espressif también las recomiendan contra los problemas de conexión con iPhone.
  • Tu propia app: en iOS se usa Core Bluetooth con Swift; no hace falta emparejar, la app busca directamente el UUID del servicio.

BLE con la Raspberry Pi Pico W y MicroPython

Placa Raspberry Pi Pico
En la familia Raspberry Pi Pico, solo las placas con una W en el nombre (Pico W, Pico 2 W) tienen Bluetooth; la Pico estándar no tiene conexión inalámbrica.

En la familia Raspberry Pi Pico, el Bluetooth solo está en la Pico W y la Pico 2 W. El chip CYW43439 de la placa admite por hardware tanto el Bluetooth Classic como el BLE, pero MicroPython solo usa BLE (a partir de la versión 1.20). El código siguiente es el equivalente en MicroPython del ejemplo NUS del ESP32; como usa los mismos UUID, se prueba igual con nRF Connect y Serial Bluetooth Terminal. Debes tener instalada la versión de MicroPython preparada para la Pico W. Además, el código lee el sensor de temperatura interno de la placa y lo envía al móvil conectado cada 2 segundos.

MicroPython
1import bluetooth
2import struct
3import time
4from machine import Pin, ADC
5from micropython import const
6
7_IRQ_CENTRAL_CONNECT = const(1)
8_IRQ_CENTRAL_DISCONNECT = const(2)
9_IRQ_GATTS_WRITE = const(3)
10_FLAG_READ = const(0x0002)
11_FLAG_WRITE_NO_RESPONSE = const(0x0004)
12_FLAG_WRITE = const(0x0008)
13_FLAG_NOTIFY = const(0x0010)
14
15# Nordic UART Service: ESP32 örneğiyle aynı UUID'ler / same UUIDs as the ESP32 example
16NUS_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
17TX = (bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"), _FLAG_READ | _FLAG_NOTIFY)
18RX = (bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"), _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE)
19
20led = Pin("LED", Pin.OUT)       # Pico W dahili LED / Pico W onboard LED
21sensor = ADC(4)                 # dahili sıcaklık sensörü / internal temperature sensor
22
23ble = bluetooth.BLE()
24ble.active(True)
25((tx_h, rx_h),) = ble.gatts_register_services(((NUS_UUID, (TX, RX)),))
26baglanti = None
27
28
29def alan(tur, veri):            # uzunluk + tür + veri / length + type + data
30    return struct.pack("BB", len(veri) + 1, tur) + veri
31
32
33YAYIN = alan(0x01, b"\x06") + alan(0x09, b"BlueGrays-Pico")  # bayraklar + ad / flags + name
34YANIT = alan(0x07, bytes(NUS_UUID))                          # 128 bit UUID / 128-bit UUID
35
36
37def yayinla():
38    ble.gap_advertise(100000, adv_data=YAYIN, resp_data=YANIT)  # 100 ms aralık / interval
39
40
41def olay(event, data):
42    global baglanti
43    if event == _IRQ_CENTRAL_CONNECT:
44        baglanti = data[0]
45    elif event == _IRQ_CENTRAL_DISCONNECT:
46        baglanti = None
47        yayinla()                                 # yeniden görünür ol / advertise again
48    elif event == _IRQ_GATTS_WRITE and data[1] == rx_h:
49        komut = ble.gatts_read(rx_h).decode().strip()
50        if komut == "1":
51            led.on()
52        elif komut == "0":
53            led.off()
54
55
56ble.irq(olay)
57yayinla()
58
59while True:
60    if baglanti is not None:
61        volt = sensor.read_u16() * 3.3 / 65535
62        sicaklik = 27 - (volt - 0.706) / 0.001721   # veri sayfası formülü / datasheet formula
63        try:
64            ble.gatts_notify(baglanti, tx_h, "T:{:.1f}\n".format(sicaklik).encode())
65        except OSError:
66            pass                                    # bağlantı yeni koptu / just disconnected
67    time.sleep(2)

Como el módulo bluetooth de MicroPython es de bajo nivel, construimos nosotros mismos el paquete de anuncio. Cada campo consta de un byte de longitud, un byte de tipo y los datos: 0x01 son las banderas, 0x09 el nombre completo del dispositivo y 0x07 el UUID de servicio de 128 bits. Como un paquete de anuncio admite como máximo 31 bytes, pusimos el campo del UUID, de 18 bytes, en la respuesta de escaneo (resp_data). gatts_register_services() devuelve los identificadores (handles) de las características; con ellos leemos el valor que escribió el móvil y enviamos las notificaciones. Si prefieres un código más corto, puedes instalar con mip la librería aioble, basada en asyncio. Para ejecutar el código en el ESP32 con MicroPython, basta con cambiar la línea del LED a Pin(2, Pin.OUT) y leer, por ejemplo, un DHT11 en lugar del sensor de temperatura interno.

Wi-Fi y Bluetooth a la vez: memoria, alcance y alimentación

En el ESP32, el Wi-Fi y el Bluetooth comparten la misma radio de 2,4 GHz. El chip los hace funcionar repartiéndose el tiempo, así que puedes usarlos a la vez, aunque cada uno va algo más lento, y el Bluetooth Classic junto con mucho tráfico Wi-Fi es el escenario más exigente. Sin embargo, el problema real suele ser la memoria: la pila Bluetooth es grande y, junto con el Wi-Fi, el programa puede superar la partición de aplicación por defecto de 1,2 MB (1.310.720 bytes); el propio ejemplo BLE de esta guía ya ocupa por sí solo cerca del 80 % de ese espacio. Si la compilación termina con "Sketch too big" o "text section exceeds available space in board" y no usas actualizaciones inalámbricas (OTA), elige Huge APP (3MB No OTA/1MB SPIFFS) en Herramientas > Partition Scheme. La librería NimBLE-Arduino, que gasta menos flash y RAM, también es una buena alternativa.

El alcance depende del diseño de la antena de la placa y del entorno. Con un ESP32 con antena PCB integrada, cuenta con 10–20 metros en interiores según las paredes y algo más al aire libre; los módulos con conector para antena externa (por ejemplo el ESP32-WROOM-32U) llegan aún más lejos. Tapar la antena con una caja metálica, una batería o cables reduce mucho el alcance. Como la corriente sube un instante al encender la radio, un cable USB flojo puede hacer que la placa se reinicie con el mensaje "Brownout detector was triggered"; tienes las soluciones en ESP32 Brownout y Guru Meditation.

Ideas de proyectos con Bluetooth y ESP32

  • Coche robot por Bluetooth: pasa los comandos F, B, L y R recibidos por BluetoothSerial a un driver de motores L298N; para la parte mecánica consulta Cómo hacer un coche robot con Arduino.
  • Enchufe inteligente: sustituye el LED del ejemplo NUS por un módulo de relé y enciende una lámpara desde el iPhone.
  • Termómetro inalámbrico: añade una pantalla OLED al ejemplo del DHT22 para que el valor aparezca en la pantalla y en el móvil.
  • Luz ambiental de colores: cambia el color de una tira WS2812B NeoPixel con valores R, G y B enviados desde el móvil.
  • Tu propia app Android: crea en unas horas una app de mando con botones y deslizadores en App Inventor.
  • Comunicación entre dos ESP32: para que dos placas hablen sin móvil, prueba en lugar del Bluetooth el método ESP-NOW, más sencillo y rápido.

Errores frecuentes del Bluetooth del ESP32 y soluciones

Síntoma / errorCausaSolución
"Serial Bluetooth not available or not enabled"La placa elegida es un ESP32-S3, C3, C6 o H2, sin Bluetooth ClassicUsa un ESP32 clásico o pasa al ejemplo BLE (NUS)
"conversion from 'String' to non-scalar type 'std::string' requested"Código escrito para el core 2.x (std::string) compilado con un core 3.xAsigna getValue().c_str() a una variable String
"Sketch too big"Bluetooth y Wi-Fi juntos superan la partición de la aplicaciónPartition Scheme: elige Huge APP o usa NimBLE-Arduino
El iPhone no encuentra el ESP32El Bluetooth Classic (SPP) no es compatible con iOSUsa BLE y conéctate desde una app
El dispositivo desaparece tras la primera conexiónNo se reinició el anuncio después de la desconexiónLlama a startAdvertising() tras la desconexión
No llegan notificacionesNo se activaron en el móvil o falta el BLE2902Pulsa la triple flecha en nRF Connect; añade BLE2902 a la característica
"read failed, socket might closed or timeout"Datos de emparejamiento antiguos u otro móvil ya conectadoOlvida el dispositivo en el móvil y vuelve a emparejarlo; SPP solo admite una conexión a la vez
Se corta el final de los mensajes largosMTU por defecto de 23 bytes, es decir, 20 bytes de datos por notificaciónDivide el mensaje en trozos de 20 bytes o permite un MTU mayor con BLEDevice::setMTU(185)
Los cambios de servicios no aparecen en el móvilEl móvil guarda en caché la tabla GATT antiguaApaga y enciende el Bluetooth del móvil; en Android, olvida el dispositivo
Para depurar problemas de conexión, elige Debug o Verbose en Herramientas > Core Debug Level. En el Monitor Serie verás los registros de conexión, desconexión y errores de la pila Bluetooth, que indican rápidamente si el problema está en el móvil o en el ESP32. Repetir la misma prueba en el móvil con nRF Connect te ayuda además a saber si la causa es tu propia app.

Con el Bluetooth del ESP32, convertir el móvil en un mando y el ESP32 en un sensor inalámbrico o un dispositivo inteligente es cuestión de unas pocas líneas de código. BluetoothSerial es la mejor opción para empezar rápido con Android, y BLE para tener compatibilidad con iPhone y bajo consumo. Si quieres seguir con proyectos inalámbricos con ESP32, en la app BlueGrays encontrarás proyectos como LED, LED RGB, medición de temperatura con el DHT22 y un coche controlado por Bluetooth, cada uno con su esquema paso a paso y el código listo para usar.

Preguntas frecuentes

¿El ESP32 tiene Bluetooth?

Sí. El chip ESP32 original admite con Bluetooth 4.2 tanto el Bluetooth Classic (BR/EDR) como el BLE y no necesita ningún módulo extra. Los ESP32-S3, C3, C6 y H2 solo admiten BLE, y el ESP32-S2 no tiene Bluetooth.

¿Qué alcance tiene el Bluetooth del ESP32?

Con un ESP32 con antena PCB integrada se suelen conseguir 10–20 metros en interiores; las paredes, los objetos metálicos y la orientación de la antena influyen en la distancia. Al aire libre el alcance aumenta, y los módulos con antena externa (por ejemplo el WROOM-32U) llegan todavía más lejos.

¿El ESP32 se puede conectar a un iPhone por Bluetooth?

Sí por BLE, pero no por Bluetooth Classic (BluetoothSerial), porque iOS solo abre el perfil de puerto serie (SPP) a accesorios con certificación MFi. En el iPhone puedes conectarte al servicio BLE del ESP32 con una app como nRF Connect, LightBlue o Bluefruit Connect.

¿Funciona BluetoothSerial en el ESP32-S3 y el ESP32-C3?

No. Estos chips no tienen hardware Bluetooth Classic, así que BluetoothSerial no compila. Para tener la misma lógica de puerto serie sobre BLE puedes usar el Nordic UART Service (NUS); apps como Serial Bluetooth Terminal lo reconocen.

¿El ESP32 puede usar Wi-Fi y Bluetooth a la vez?

Sí, los dos funcionan juntos repartiéndose en el tiempo la misma radio de 2,4 GHz. Eso sí, la velocidad baja un poco y el programa ocupa más; si te aparece el error "Sketch too big", elige Huge APP en Partition Scheme o usa la librería NimBLE-Arduino, más ligera.

¿Hace falta un HC-05 con el ESP32?

Normalmente no. La librería BluetoothSerial del ESP32 clásico hace internamente el trabajo del HC-05 y funciona con las mismas apps de terminal. Solo necesitas un HC-05 en placas sin Bluetooth (Arduino Uno, ESP8266, la Pico estándar, ESP32-S2) o si un proyecto requiere Bluetooth Classic y usas un ESP32 solo BLE.

Ver todos los proyectos
Parpadeo del LED interno del ESP32
#106Principiante

Parpadeo del LED interno del ESP32

Parpadeo del LED azul incorporado en el ESP32 DevKit V1. ¡No se necesitan componentes externos! El primer proyecto que debe probar cuando compra un ESP32. GPIO 2 está conectado al LED interno.

4.8(29)
53
LED Parpadeante con ESP32
#103Principiante

LED Parpadeante con ESP32

Enseña el uso básico de la salida GPIO con ESP32. Conseguimos un efecto de parpadeo encendiendo y apagando un LED desde el pin digital. Versión para ESP32 del clásico proyecto blink en Arduino UNO; los comandos básicos de blink en el entorno Arduino son los mismos, pero la distribución de pines y los niveles GPIO de 3,3 V del ESP32 difieren de los del Arduino UNO.

4.7(37)
99
Control de LED con botón en ESP32 (Toggle)
#105Principiante

Control de LED con botón en ESP32 (Toggle)

Cada vez que se pulsa el botón, cambia el estado del LED: si está encendido se apaga, si está apagado se enciende. Patrón de conmutación clásico. Con debounce limpiamos el ruido mecánico del pulsador para garantizar una sola conmutación con una sola pulsación.

4.6(39)
57
Controlar un LED desde el móvil por Bluetooth
#34Principiante
Premium

Controlar un LED desde el móvil por Bluetooth

Un pequeño ensayo para encender y apagar las luces de tu habitación desde el móvil. El módulo Bluetooth pasa al Arduino las letras que envías desde el teléfono; una letra enciende el LED rojo, otra en

4.8(68)
89
Coche controlado por Bluetooth con L298N
#80Avanzado
Premium

Coche controlado por Bluetooth con L298N

Convierte tu móvil en un mando y conduce tu propio coche robot. El Arduino recibe por un módulo Bluetooth las órdenes de una app de control remoto en el móvil y mueve las ruedas con un controlador de

5.0(38)
65
Control de LED RGB con ESP32
#111Principiante

Control de LED RGB con ESP32

Generación ilimitada de colores con un solo LED RGB en lugar de 3 LEDs separados. Hacemos transición arco iris estableciendo valores R/G/B con analogWrite() como en Arduino. La teoría del color (como rojo+verde=amarillo) se ve de forma práctica.

4.6(29)
51
ESP32 DHT22 Temperatura y humedad (con pantalla LCD)
#117Intermedio

ESP32 DHT22 Temperatura y humedad (con pantalla LCD)

Se trata de un proyecto que mide la temperatura y la humedad ambientales mediante el sensor DHT22 y las muestra en tiempo real en una pantalla LCD I2C 16x2. Los datos del sensor se leen mediante el protocolo de un solo cable (one-wire) utilizando la biblioteca DHT de Adafruit. Este proyecto constituye la base para estaciones meteorológicas IoT, sistemas de automatización doméstica inteligente o sistemas de monitorización ambiental en invernaderos.

4.8(38)
56
Control de la tira NeoPixel WS2812B con ESP32
#126Intermedio
Premium

Control de la tira NeoPixel WS2812B con ESP32

Los LED direccionables WS2812B (NeoPixel) son LED RGB inteligentes que se pueden controlar en cadena a través de un único pin de datos. En este proyecto, controlaremos una tira de 8 LED WS2812B con la biblioteca Adafruit_NeoPixel para crear una animación fluida de arcoíris. Aunque el nivel lógico de 3,3 V del ESP32 funciona sin problemas en la mayoría de los componentes WS2812B, mostraremos la solución de una resistencia en serie de 470 Ω y un condensador de 1000 µF en caso de inestabilidad. La

4.9(19)
30