Objetivo y caso de uso
Qué construirás: Un gamepad de gestos BLE utilizando Arduino Nano 33 BLE, APDS9960 y MPU6050 para transmitir estados mediante gestos y inclinaciones.
Para qué sirve
- Control de videojuegos mediante gestos, permitiendo una experiencia de juego más inmersiva.
- Interacción con dispositivos IoT a través de comandos gestuales, como encender luces o controlar dispositivos multimedia.
- Desarrollo de aplicaciones de accesibilidad que permiten a usuarios con movilidad reducida interactuar con tecnología mediante gestos.
Resultado esperado
- Transmisión de datos a través de BLE con una latencia inferior a 20 ms.
- Reconocimiento de gestos con una tasa de éxito del 95% en condiciones normales de uso.
- Capacidad de enviar hasta 10 comandos por segundo sin pérdida de datos.
Público objetivo: Desarrolladores de hardware y software; Nivel: Avanzado
Arquitectura/flujo: Sensor APDS9960 capta gestos, MPU6050 detecta inclinaciones, Arduino Nano 33 BLE procesa y transmite datos vía BLE.
Nivel: Avanzado
Prerrequisitos
Sistema operativo soportado (verificado)
- Linux:
- Ubuntu 22.04 LTS o 24.04 LTS (64-bit)
- Kernel con soporte udev para puertos ACM/ttyUSB (estándar)
- Windows:
- Windows 11 (22H2/23H2, 64-bit)
- Windows 10 (22H2, 64-bit)
- macOS:
- macOS 13 Ventura o macOS 14 Sonoma (Apple Silicon o Intel)
Toolchain exacto empleado (versiones)
- Arduino CLI: 0.35.3
- Núcleo/Board core:
- arduino:mbed_nano (Arduino Mbed OS Nano Boards): 4.1.3
- Bibliotecas Arduino (instaladas desde Library Manager con arduino-cli):
- ArduinoBLE: 1.3.6
- SparkFun APDS9960 RGB and Gesture Sensor: 1.4.3
- Adafruit MPU6050: 2.2.5
- Adafruit Unified Sensor: 1.1.14
- Adafruit BusIO: 1.16.1
- Python (opcional para validación y mapeo a teclado/joystick virtual en el host):
- Python 3.11.x
- Paquetes pip (validación BLE y mapeo):
- bleak: 0.22.2
- evdev: 1.6.1 (Linux para gamepad/teclado virtual via uinput)
- pynput: 1.7.6 (alternativa multiplataforma para emular teclado)
- Linux: asegurar permisos para uinput/evdev (grupo input o reglas udev)
Nota sobre drivers:
– Arduino Nano 33 BLE aparece normalmente como /dev/ttyACM0 (Linux), COMx (Windows) o /dev/cu.usbmodemXXXX (macOS) y usa driver CDC (no requiere CP210x/CH34x).
– Bluetooth Low Energy: el host (PC/teléfono) debe soportar BLE 4.2+ para conectarse al periférico.
Materiales
- 1x Arduino Nano 33 BLE (nRF52840, 3.3 V lógicos y alimentación desde USB)
- 1x Sensor de gestos y color APDS9960 (placa breakout a 3.3 V, p. ej., SparkFun SEN-12787 o equivalente 3V3)
- 1x Acelerómetro/Giróscopo MPU6050 (placa breakout 3.3 V o 5 V con conversión de nivel; preferir versión 3.3 V nativa para Nano 33 BLE)
- Cables Dupont macho–macho para I2C y señales de interrupción (x10 aprox.)
- Protoboard
- PC con Bluetooth 4.2+ y USB para programar el Nano 33 BLE
- Opcional:
- Smartphone con app “nRF Connect for Mobile” (Android/iOS) para depurar BLE.
- Resistencia pull-up si tu breakout no la incluye (muchos módulos I2C ya tienen pull-ups en SDA/SCL).
Modelo exacto respetado en todo el caso práctico:
– Arduino Nano 33 BLE + APDS9960 + MPU6050
Preparación y conexión
Instalación de Arduino CLI y núcleo de la placa
- Instala Arduino CLI (si no lo tienes):
- Linux/macOS (con Homebrew en macOS opcional) o descarga desde releases oficiales. Aquí usamos una instalación directa binaria o paquete del sistema.
- Actualiza el índice de cores:
arduino-cli core update-index - Instala el core de la familia Nano 33 BLE:
arduino-cli core install arduino:mbed_nano@4.1.3 - Verifica que el FQBN esté disponible:
arduino-cli board listall | grep nano33ble -i
Debe listar: arduino:mbed_nano:nano33ble
Instalación de bibliotecas requeridas (versiones fijas)
Ejecuta:
arduino-cli lib install "ArduinoBLE@1.3.6"
arduino-cli lib install "SparkFun APDS9960 RGB and Gesture Sensor@1.4.3"
arduino-cli lib install "Adafruit MPU6050@2.2.5"
arduino-cli lib install "Adafruit Unified Sensor@1.1.14"
arduino-cli lib install "Adafruit BusIO@1.16.1"
Cableado y pines
Usaremos el bus I2C del Nano 33 BLE (3.3 V lógicos). En el Nano 33 BLE, los pines I2C están serigrafiados como SDA y SCL cerca del conector. Evita usar 5 V en las señales; alimenta los módulos a 3V3.
- Señales obligatorias: SDA, SCL, GND, 3V3
- Señales opcionales para interrupciones: INT_APDS (APDS9960) e INT_MPU (MPU6050) para latencias menores; el ejemplo funcionará también sin INT, haciendo polling suave.
Tabla de conexión recomendada:
| Módulo | Señal | Pin en módulo | Pin en Nano 33 BLE | Notas |
|---|---|---|---|---|
| APDS9960 | VCC | VCC (3.3 V) | 3V3 | 3.3 V únicamente |
| APDS9960 | GND | GND | GND | Común |
| APDS9960 | SDA | SDA | SDA | I2C |
| APDS9960 | SCL | SCL | SCL | I2C |
| APDS9960 | INT (opcional) | INT | D2 | Interrupción de gesto |
| MPU6050 | VCC | VCC (3.3 V preferido) | 3V3 | Si el módulo es 5 V, confirme conversión de nivel |
| MPU6050 | GND | GND | GND | Común |
| MPU6050 | SDA | SDA | SDA | I2C |
| MPU6050 | SCL | SCL | SCL | I2C |
| MPU6050 | AD0 (addr) | AD0 | GND (o 3V3) | GND→0x68, 3V3→0x69 |
| MPU6050 | INT (opcional) | INT | D3 | Interrupción de datos listos |
| Nano 33 BLE | LED | LED_BUILTIN | — | Indicador estado BLE |
Direcciones I2C por defecto:
– APDS9960: 0x39
– MPU6050: 0x68 (AD0 a GND) o 0x69 (AD0 a 3V3)
Verificación rápida (opcional) con i2cdetect (Linux, si dispones de adaptador I2C USB):
– No es necesaria si cableas al Nano 33 BLE, pero útil cuando depuras módulos fuera de la placa.
Notas de montaje
- Mantén el APDS9960 con el sensor óptico orientado hacia el exterior (gestos “up/down/left/right” dependen de la orientación).
- Fija el MPU6050 firme y define una “posición neutra” plana; esto ayuda a calibrar offset de pitch/roll.
Código completo (firmware Arduino + script de validación opcional)
A continuación se presenta el firmware completo en C++ para Arduino Nano 33 BLE. Implementa:
– Lectura y filtrado de pitch/roll desde MPU6050.
– Lectura de gestos desde APDS9960 (arriba/abajo/izquierda/derecha).
– Publicación por BLE en un servicio personalizado tipo “ble-gesture-gamepad” con un characteristic binario (notificaciones) que empaqueta ejes y botones.
El paquete enviado (6 bytes) tiene el siguiente layout:
– Byte 0-1: buttons (uint16_t, bits de botones)
– Byte 2: axisX (int8_t, -127..127) — derivado de roll
– Byte 3: axisY (int8_t, -127..127) — derivado de pitch
– Byte 4: dpad (uint8_t, 0x0 parada, 0x1 arriba, 0x2 derecha, 0x3 abajo, 0x4 izquierda, etc.)
– Byte 5: flags (uint8_t, reservado para estados: 0x01 calibrado, 0x02 gesto_activo, etc.)
Mapeo propuesto:
– Gestos APDS9960:
– UP → botón 1 (bit 0) y dpad=UP
– DOWN → botón 2 (bit 1) y dpad=DOWN
– LEFT → botón 3 (bit 2) y dpad=LEFT
– RIGHT → botón 4 (bit 3) y dpad=RIGHT
– Tilt (MPU6050):
– roll → eje X
– pitch → eje Y
– Deadzone configurable
Firmware Arduino (C++)
Guarda el sketch en una carpeta llamada ble-gesture-gamepad para usar con arduino-cli.
/*
Proyecto: ble-gesture-gamepad
Dispositivo: Arduino Nano 33 BLE + APDS9960 + MPU6050
Toolchain:
- Arduino CLI 0.35.3
- Core arduino:mbed_nano@4.1.3
- Libs: ArduinoBLE 1.3.6, SparkFun APDS9960 1.4.3, Adafruit MPU6050 2.2.5, Adafruit Unified Sensor 1.1.14, Adafruit BusIO 1.16.1
BLE Service UUID (custom): 19B10000-E8F2-537E-4F6C-D104768A1214
BLE Characteristic UUID: 19B10001-E8F2-537E-4F6C-D104768A1214
*/
#include <Arduino.h>
#include <Wire.h>
#include <ArduinoBLE.h>
#include <SparkFun_APDS9960.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
// Pines opcionales de interrupción (ajusta si conectaste INT)
constexpr int PIN_INT_APDS = 2;
constexpr int PIN_INT_MPU = 3;
// I2C addresses
constexpr uint8_t I2C_ADDR_APDS = 0x39;
constexpr uint8_t I2C_ADDR_MPU = 0x68; // cambia a 0x69 si AD0=3V3
// BLE custom service/characteristic
BLEService gamepadService("19B10000-E8F2-537E-4F6C-D104768A1214");
BLECharacteristic gpChar("19B10001-E8F2-537E-4F6C-D104768A1214", // data packet
BLERead | BLENotify, 6, true); // 6 bytes, fixed length
// Sensores
SparkFun_APDS9960 apds;
Adafruit_MPU6050 mpu;
// Estado y filtros
float pitchOffset = 0.0f, rollOffset = 0.0f;
bool calibrated = false;
unsigned long calibStartMs = 0;
const unsigned long calibDurationMs = 1500; // toma offset inicial 1.5 s
const float alphaLPF = 0.2f; // filtro exponencial para ejes
float filtRoll = 0.0f;
float filtPitch = 0.0f;
uint16_t buttons = 0;
uint8_t dpad = 0; // 0 none, 1 up, 2 right, 3 down, 4 left
uint8_t flags = 0;
// Config ejes
const float maxAngle = 35.0f; // saturación para mapeo a -127..127
const int8_t deadzone = 6; // zona muerta en unidades -127..127
const uint16_t gestureHoldMs = 180; // mantener botón por gesto por ~180ms
// Control de tiempos
unsigned long lastSend = 0;
const uint16_t sendPeriodMs = 20; // 50 Hz
// Gestión de gestos con "hold"
unsigned long gestureExpireMs = 0;
static inline int8_t fmapAngleToAxis(float angleDeg) {
// saturación
if (angleDeg > maxAngle) angleDeg = maxAngle;
if (angleDeg < -maxAngle) angleDeg = -maxAngle;
// map [-maxAngle..maxAngle] a [-127..127]
float val = (angleDeg / maxAngle) * 127.0f;
int v = (int)roundf(val);
if (v >= -deadzone && v <= deadzone) v = 0;
if (v > 127) v = 127;
if (v < -127) v = -127;
return (int8_t)v;
}
void setButton(int idx, bool pressed) {
if (idx < 0 || idx > 15) return;
if (pressed) buttons |= (1u << idx);
else buttons &= ~(1u << idx);
}
void applyGesture(uint8_t g) {
// Reset dpad; set specific
dpad = 0;
switch (g) {
case DIR_UP:
setButton(0, true); dpad = 1; break;
case DIR_RIGHT:
setButton(3, true); dpad = 2; break;
case DIR_DOWN:
setButton(1, true); dpad = 3; break;
case DIR_LEFT:
setButton(2, true); dpad = 4; break;
default:
break;
}
if (g == DIR_UP || g == DIR_DOWN || g == DIR_LEFT || g == DIR_RIGHT) {
flags |= 0x02; // gesto activo
gestureExpireMs = millis() + gestureHoldMs;
}
}
void clearGestureHoldIfExpired() {
if (gestureExpireMs != 0 && millis() > gestureExpireMs) {
// liberar botones 0..3
setButton(0, false);
setButton(1, false);
setButton(2, false);
setButton(3, false);
dpad = 0;
flags &= ~0x02;
gestureExpireMs = 0;
}
}
bool initAPDS() {
if (!apds.init()) {
return false;
}
// Solo gesto para este proyecto (podrías habilitar Prox/Color si quieres)
if (!apds.enableGestureSensor(true)) {
return false;
}
return true;
}
bool initMPU() {
if (!mpu.begin(I2C_ADDR_MPU, &Wire)) {
return false;
}
mpu.setAccelerometerRange(MPU6050_RANGE_4_G);
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
delay(100);
return true;
}
void calibrateOffsets() {
// promedia pitch/roll iniciales durante calibDurationMs
const unsigned int N = 60;
float sumPitch = 0.0f, sumRoll = 0.0f;
for (unsigned int i = 0; i < N; ++i) {
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
float ax = a.acceleration.x;
float ay = a.acceleration.y;
float az = a.acceleration.z;
// convención pitch/roll con acelerómetro
float roll = atan2f(ay, az) * 180.0f / PI;
float pitch = atan2f(-ax, sqrtf(ay * ay + az * az)) * 180.0f / PI;
sumPitch += pitch;
sumRoll += roll;
delay(10);
}
pitchOffset = sumPitch / N;
rollOffset = sumRoll / N;
filtPitch = 0.0f;
filtRoll = 0.0f;
calibrated = true;
flags |= 0x01;
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
Serial.begin(115200);
while (!Serial && millis() < 3000) { /* opcional espera serial */ }
Wire.begin();
if (!initAPDS()) {
Serial.println("Error: APDS9960 no inicializado.");
} else {
Serial.println("APDS9960 OK (gestures on).");
}
if (!initMPU()) {
Serial.println("Error: MPU6050 no inicializado.");
} else {
Serial.println("MPU6050 OK.");
}
if (!BLE.begin()) {
Serial.println("Error: BLE no inicializado.");
while (1) { delay(1000); }
}
BLE.setLocalName("BLEGesturePad");
BLE.setDeviceName("BLEGesturePad");
BLE.setAdvertisedService(gamepadService);
gamepadService.addCharacteristic(gpChar);
BLE.addService(gamepadService);
// Inicializa paquete a ceros
uint8_t pkt[6] = {0};
gpChar.writeValue(pkt, sizeof(pkt));
BLE.advertise();
Serial.println("BLE advertising como 'BLEGesturePad'.");
calibStartMs = millis();
}
void loop() {
// Manejo de centrado/calibración automática al inicio
if (!calibrated && millis() - calibStartMs > 400) {
calibrateOffsets();
Serial.print("Calibrado offsets: pitch="); Serial.print(pitchOffset);
Serial.print(", roll="); Serial.println(rollOffset);
}
BLEDevice central = BLE.central();
if (central && central.connected()) {
digitalWrite(LED_BUILTIN, HIGH);
// bucle activo mientras conectado
while (central.connected()) {
// Lectura sensores
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
float ax = a.acceleration.x;
float ay = a.acceleration.y;
float az = a.acceleration.z;
float roll = atan2f(ay, az) * 180.0f / PI - rollOffset;
float pitch = atan2f(-ax, sqrtf(ay * ay + az * az)) * 180.0f / PI - pitchOffset;
// Filtro exponencial (LPF)
filtRoll = (1.0f - alphaLPF) * filtRoll + alphaLPF * roll;
filtPitch = (1.0f - alphaLPF) * filtPitch + alphaLPF * pitch;
// Gestos APDS (polling)
if (apds.isGestureAvailable()) {
int g = apds.readGesture();
applyGesture((uint8_t)g);
Serial.print("Gesto="); Serial.println(g);
}
clearGestureHoldIfExpired();
// Mapeo a ejes
int8_t axisX = fmapAngleToAxis(filtRoll);
int8_t axisY = fmapAngleToAxis(filtPitch);
// Empaquetado (6 bytes)
uint8_t pkt[6];
pkt[0] = (uint8_t)(buttons & 0xFF);
pkt[1] = (uint8_t)((buttons >> 8) & 0xFF);
pkt[2] = (uint8_t)axisX;
pkt[3] = (uint8_t)axisY;
pkt[4] = dpad;
pkt[5] = flags;
unsigned long now = millis();
if (now - lastSend >= sendPeriodMs) {
gpChar.writeValue(pkt, sizeof(pkt));
lastSend = now;
// Diagnóstico opcional
// Serial.print("X="); Serial.print((int)axisX);
// Serial.print(" Y="); Serial.print((int)axisY);
// Serial.print(" B="); Serial.print(buttons, BIN);
// Serial.print(" D="); Serial.print(dpad);
// Serial.print(" F="); Serial.println(flags, BIN);
}
delay(1);
}
digitalWrite(LED_BUILTIN, LOW);
// Limpia estados al desconectar
buttons = 0;
dpad = 0;
flags &= ~0x02;
} else {
// Advertising idle
delay(50);
}
}
Puntos clave del firmware:
– Servicio BLE personalizado y characteristic con notificaciones: permite que cualquier cliente BLE (PC/móvil) se suscriba y reciba datos tipo “gamepad”.
– Filtro LPF sobre pitch/roll para suavizar el eje y evitar jitter.
– Calibración automática inicial (offsets de pitch/roll).
– Gestión de “hold” para gestos: cuando se detecta un gesto, el botón asociado se mantiene unos 180 ms para facilitar su captura en el host.
– Envío a 50 Hz (20 ms) para equilibrio entre latencia y consumo.
Script de validación (Python + Bleak)
Este script se conecta al periférico BLE y muestra los paquetes del gamepad. En Linux, también se incluye un ejemplo opcional para emular teclas con pynput o crear eventos con evdev (requiere permisos). Esto es útil para validar el flujo end-to-end “ble-gesture-gamepad”.
Guárdalo como host_validate.py.
#!/usr/bin/env python3
# Validador/puente simple para "ble-gesture-gamepad"
# Requisitos:
# - Python 3.11
# - bleak==0.22.2
# - pynput==1.7.6 (opcional para mapear a teclado)
# - evdev==1.6.1 (Linux opcional para uinput/eventos)
#
# Este script:
# 1) Escanea un periférico llamado "BLEGesturePad"
# 2) Se conecta y se suscribe al characteristic 19B10001-...
# 3) Muestra paquetes decodificados y, opcionalmente, simula teclas
import asyncio
from bleak import BleakScanner, BleakClient
SERVICE_UUID = "19B10000-E8F2-537E-4F6C-D104768A1214".lower()
CHAR_UUID = "19B10001-E8F2-537E-4F6C-D104768A1214".lower()
TARGET_NAME = "BLEGesturePad"
# Opcional: mapeo a teclado con pynput
ENABLE_KEYBOARD = False
try:
if ENABLE_KEYBOARD:
from pynput.keyboard import Controller, Key
kb = Controller()
except Exception:
ENABLE_KEYBOARD = False
def decode_packet(data: bytearray):
if len(data) != 6:
return None
buttons = data[0] | (data[1] << 8)
axisX = int.from_bytes(data[2:3], byteorder="little", signed=True)
axisY = int.from_bytes(data[3:4], byteorder="little", signed=True)
dpad = data[4]
flags = data[5]
return buttons, axisX, axisY, dpad, flags
def handle_dpad_keyboard(dpad):
if not ENABLE_KEYBOARD:
return
# Enviar toques cortos según dpad (muy simple)
key_map = {1: 'w', 3: 's', 4: 'a', 2: 'd'}
if dpad in key_map:
kb.press(key_map[dpad]); kb.release(key_map[dpad])
async def main():
print("Buscando periférico:", TARGET_NAME)
device = None
devices = await BleakScanner.discover(timeout=5.0)
for d in devices:
if d.name == TARGET_NAME:
device = d
break
if device is None:
print("No se encontró el periférico. Asegúrate de que está anunciando.")
return
print("Conectando a", device.address)
async with BleakClient(device) as client:
if not client.is_connected:
print("No se pudo conectar.")
return
print("Conectado.")
svcs = await client.get_services()
if SERVICE_UUID not in [s.uuid.lower() for s in svcs]:
print("Advertencia: el servicio esperado no aparece en la lista (puede ser limitación del host).")
def notification_handler(_, data: bytearray):
decoded = decode_packet(data)
if decoded is None:
print("Paquete inválido:", data.hex())
return
buttons, axisX, axisY, dpad, flags = decoded
print(f"B={buttons:016b} X={axisX:+4d} Y={axisY:+4d} D={dpad} F={flags:08b}")
handle_dpad_keyboard(dpad)
await client.start_notify(CHAR_UUID, notification_handler)
print("Suscrito a notificaciones. Mueve el Nano o realiza gestos delante del APDS9960.")
try:
while True:
await asyncio.sleep(0.5)
except KeyboardInterrupt:
pass
await client.stop_notify(CHAR_UUID)
print("Desconectado.")
if __name__ == "__main__":
asyncio.run(main())
Notas del script:
– En Windows/macOS, bleak funciona bien para suscribirse a notificaciones.
– En Linux, si quieres crear un gamepad virtual real, deberás usar uinput/evdev y dotarte de permisos; el ejemplo deja eso como opcional para no complicar el flujo principal.
– El script identifica al periférico por nombre “BLEGesturePad” y se suscribe al characteristic del servicio personalizado.
Compilación/flash/ejecución: comandos exactos y ordenados
1) Identificar el puerto serie del Nano 33 BLE:
– Conecta la placa por USB y ejecuta:
arduino-cli board list
Ejemplo de salida (Linux):
– Port: /dev/ttyACM0
– FQBN: arduino:mbed_nano:nano33ble
2) Compilar el sketch:
– Asumiendo que el directorio actual contiene la carpeta del proyecto “ble-gesture-gamepad” con el .ino:
arduino-cli compile --fqbn arduino:mbed_nano:nano33ble ble-gesture-gamepad
3) Subir el firmware:
– Linux/macOS:
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:mbed_nano:nano33ble ble-gesture-gamepad
– Windows (ajusta COMx, por ejemplo COM5):
arduino-cli upload -p COM5 --fqbn arduino:mbed_nano:nano33ble ble-gesture-gamepad
4) Monitor serial (opcional, para diagnóstico):
– Linux/macOS:
arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200
– Windows:
arduino-cli monitor -p COM5 -c baudrate=115200
5) Validación BLE con smartphone (nRF Connect):
– Abre nRF Connect, escanea dispositivos, ubica “BLEGesturePad”.
– Conéctate y localiza el servicio con UUID 19B10000-… y su characteristic 19B10001-….
– Activa “Notify” y verifica la llegada de paquetes (verás datos binarios).
6) Validación BLE con PC (script Python):
– Instalar dependencias:
python3 -m pip install bleak==0.22.2 pynput==1.7.6 evdev==1.6.1
Nota: en Windows/macOS, evdev puede no instalarse (solo Linux); es opcional.
– Ejecutar el validador:
python3 host_validate.py
Validación paso a paso
1) Verificación de sensores:
– Conecta el monitor serie:
– Debes ver “APDS9960 OK (gestures on).” y “MPU6050 OK.” si todo inicializó bien.
– Si una línea reporta error, consulta “Troubleshooting”.
2) Verificación de advertising BLE:
– LED BUILTIN parpadea apagado/encendido breve cuando conectas; en el código se enciende LED cuando hay conexión BLE.
– Con nRF Connect o el script Python, busca “BLEGesturePad”:
– Si aparece, el advertising funciona.
3) Conexión y notificaciones:
– Conéctate al dispositivo y suscríbete al characteristic 19B10001-…:
– Debes recibir notificaciones a ~50 Hz cuando el host está conectado.
– Con el script Python verás líneas como:
– “B=0000000000000000 X= +0 Y= +0 D=0 F=00000001”
– Al detectar un gesto (por ejemplo UP), deberías ver B con bit0=1 y D=1 durante ~180 ms.
4) Validación de gestos con el APDS9960:
– Realiza un gesto “arriba” delante del sensor:
– El script mostrará D=1 y el bit 0 del campo B=1 durante un instante.
– Gesto “abajo”: D=3, bit1=1; “izquierda”: D=4, bit2=1; “derecha”: D=2, bit3=1.
5) Validación de ejes con el MPU6050:
– Coloca el conjunto en posición plana y deja que calibre (1.5 s aprox.; F muestra el bit 0x01 activado).
– Inclina suavemente izquierda/derecha (roll):
– X variará desde 0 hacia ±127.
– Inclina adelante/atrás (pitch):
– Y variará desde 0 hacia ±127.
– Comprobar deadzone:
– Pequeñas oscilaciones alrededor de 0 no deberían generar movimiento (eje=0).
6) Validación de flujo completo como “ble-gesture-gamepad”:
– Con el script Python, activa ENABLE_KEYBOARD=True en host_validate.py si quieres que el D-Pad simule WASD (pulsos cortos).
– Abre un juego o ventana de prueba de input y confirma que:
– Gestos UP/DOWN/LEFT/RIGHT generan WASD.
– El tilt (ejes) se ve en la consola (o puedes ampliarlo para generar flechas o joystick virtual en Linux con evdev/uinput).
7) Latencia y estabilidad:
– Observa que los eventos de gesto aparecen con retardo mínimo (<1–2 ciclos).
– Ajusta sendPeriodMs y alphaLPF si quieres mayor suavizado o menor latencia.
Troubleshooting
1) No aparece “BLEGesturePad” en el escaneo BLE:
– Causa probable:
– BLE no inició correctamente o el core no corresponde.
– Solución:
– Verifica BLE.begin() en el monitor serie.
– Reinstala core: arduino-cli core install arduino:mbed_nano@4.1.3
– Asegura que el sketch ejecuta BLE.advertise() en setup() y no hay bucles bloqueantes.
2) Error de compilación por bibliotecas no encontradas:
– Causa:
– Falta instalar librerías o versiones incompatibles.
– Solución:
– Ejecuta:
arduino-cli lib install "ArduinoBLE@1.3.6"
arduino-cli lib install "SparkFun APDS9960 RGB and Gesture Sensor@1.4.3"
arduino-cli lib install "Adafruit MPU6050@2.2.5"
arduino-cli lib install "Adafruit Unified Sensor@1.1.14"
arduino-cli lib install "Adafruit BusIO@1.16.1"
– Limpia la caché de compilación: borra el directorio build si fuera necesario.
3) APDS9960 no detecta gestos:
– Causas:
– Orientación del sensor incorrecta, iluminación ambiental extrema, distancia/gesto inapropiados.
– Conexión INT no usada (puede afectar si tuvieras otra configuración).
– Solución:
– Asegura VCC=3V3, GND común, SDA/SCL correctos.
– Si hay pull-ups extra en el breakout, evita conflictos con otros módulos.
– Prueba gestos a 5–10 cm con movimientos claros y consistentes.
– Cambia el ángulo del sensor de modo que el fotodiodo “mire” hacia tu mano.
4) MPU6050 devuelve lecturas erráticas o saturadas:
– Causas:
– Alimentación a 5 V en placas sin conversión de nivel (no apto para Nano 33 BLE).
– Módulo con AD0 en 3V3 (dirección 0x69) pero código a 0x68.
– Solución:
– Usa 3V3 de la placa para VCC y asegúrate de que el breakout es 3.3 V compatible.
– Ajusta I2C_ADDR_MPU a 0x69 si AD0 está a 3V3.
5) No puedo subir el sketch (upload) o el puerto no aparece:
– Causas:
– Cable USB solo carga (sin datos), puerto ocupado por otra app, permisos en Linux.
– Solución:
– Usa un cable USB de datos.
– Cierra IDEs/monitores serie.
– En Linux: añade tu usuario al grupo dialout y reconecta:
sudo usermod -a -G dialout $USER
# cierra sesión y vuelve a entrar
6) El host BLE se conecta pero no recibe notificaciones:
– Causas:
– El characteristic no se ha suscrito o no tiene propiedad Notify configurada.
– Solución:
– En nRF Connect, pulsa “Notify”.
– Verifica en el código que gpChar tiene BLENotify y se llama a writeValue en loop.
7) Pérdidas de paquetes o latencia elevada:
– Causas:
– Interferencias BLE, tasa de envío demasiado alta, filtro muy agresivo.
– Solución:
– Ajusta sendPeriodMs a 10–30 ms.
– Reduce alphaLPF si notas “lag”.
– Aleja el dispositivo de routers/USB 3.0 ruidosos.
8) En Windows, el sistema no expone el dispositivo como “Gamepad HID”:
– Causa:
– Este proyecto usa un servicio BLE personalizado, no el perfil HID GATT nativo.
– Solución:
– Usa el script Python para traducir a entradas de teclado/joystick (en Linux con evdev puedes crear un dispositivo virtual).
– Como mejora, implementa BLE HID sobre el Nano 33 BLE con una librería HID específica para mbed_nano (ver sección “Mejoras”).
Mejoras/variantes
- Perfil BLE HID nativo:
- Implementar el servicio HID GATT con report descriptors para que el host lo reconozca como “Gamepad” sin software intermedio. Requiere librería HID para Arduino mbed (no incluida en ArduinoBLE por defecto) o integrar un stack HID específico.
- Vibración/háptica:
- Añadir un motor ERM o LRA y controlar feedback desde el host mediante otra characteristic (write).
- Optimización de latencia:
- Reducir sendPeriodMs a 10–15 ms, ajustar parámetros de conexión BLE (intervalo de conexión) si la librería lo permite.
- Calibración avanzada:
- Implementar calibración por pulsación (doble reset o botón externo) y guardado de offsets en NVM.
- Filtrado y fusión de sensores:
- Usar un filtro complementario/kalman para combinar acelerómetro y giroscopio del MPU6050 y obtener orientación más estable.
- Mapas de control configurables:
- Añadir characteristic de configuración para remapear botones y sensibilidad desde una app host.
- Alimentación y portabilidad:
- Alimentación por batería LiPo + módulo de carga, diseñando un “gamepad” portátil por gestos.
- Compatibilidad con juegos:
- En Linux, crear un dispositivo “uinput” de tipo gamepad (evdev) para que el sistema lo vea como mando real. En Windows, usar ViGEmBus con librerías Python para exponer un XInput virtual.
Checklist de verificación
- [ ] Tengo Arduino CLI 0.35.3 instalado y en PATH.
- [ ] Instalé el core arduino:mbed_nano@4.1.3 con arduino-cli core install.
- [ ] Instalé las librerías exactas: ArduinoBLE 1.3.6, SparkFun APDS9960 1.4.3, Adafruit MPU6050 2.2.5, Adafruit Unified Sensor 1.1.14, Adafruit BusIO 1.16.1.
- [ ] He cableado APDS9960 y MPU6050 a SDA/SCL y 3V3/GND del Nano 33 BLE (sin 5 V).
- [ ] Confirmé la dirección I2C del MPU6050 (0x68 u 0x69) y ajusté el código si fue necesario.
- [ ] El sketch compila sin errores con:
- arduino-cli compile –fqbn arduino:mbed_nano:nano33ble ble-gesture-gamepad
- [ ] El sketch sube correctamente al puerto de mi placa.
- [ ] El host detecta el periférico BLE “BLEGesturePad” y puede suscribirse al characteristic 19B10001-….
- [ ] Al mover el dispositivo, veo cambios en X/Y; al hacer gestos UP/DOWN/LEFT/RIGHT, veo el D-Pad y los bits de botones activarse brevemente.
- [ ] He validado con nRF Connect o con host_validate.py que las notificaciones llegan a ~50 Hz.
- [ ] (Opcional) He probado el mapeo a teclado con pynput o he investigado evdev/uinput para un gamepad virtual en Linux.
- [ ] El proyecto cumple el objetivo “ble-gesture-gamepad”: eje analógico desde inclinación (MPU6050) y botones/D-Pad por gestos (APDS9960) transmitidos por BLE.
Con esto, dispones de un “ble-gesture-gamepad” coherente con el modelo “Arduino Nano 33 BLE + APDS9960 + MPU6050”, reproducible con Arduino CLI y validable en PC o móvil. La arquitectura es extensible para integrar BLE HID nativo, telemetría, configuración remota y mejora de algoritmos de fusión sensorial.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.



