Visualizador LED de Latidos en Tiempo Real con ECG
Componentes y suministros
| Componente | Cantidad |
|---|---|
| Dispositivo uECG | 1 |
| Arduino Nano R3 | 1 |
| Anillo Adafruit NeoPixel: WS2812 5050 RGB LED | 1 |
| Batería Li-Ion 1000mAh | 1 |
Herramientas y máquinas necesarias
- Soldador (genérico)
Sobre el proyecto
Durante años quise hacer algo con LEDs que parpadeara al ritmo de mis latidos (y no solo cuando estoy perfectamente quieto, saltándome un latido aquí y allá). Resultó sorprendentemente difícil, lo intenté y fallé durante años. ¡Pero ya no!
En realidad, todo el trabajo pesado lo hace uECG, un pequeño dispositivo ECG vestible de código abierto que tiene un pin de salida compatible con Arduino (ese pin va high/low con cada latido). Procesar estos estados de pin es mucho más fácil que procesar la señal ECG, y he intentado sacarle el máximo partido.
Actualización: puede que quieras ver la 2ª iteración de este proyecto, que recibe datos por enlace de radio.
1. Esquemático
Como solo trabajamos con señales digitales, es muy simple. Pero como dispositivo vestible sería mucho más fiable (y pequeño) si la mayoría de conexiones van soldadas; para una prueba rápida no es necesario, pero si vas a usarlo durante actividad intensa, recomiendo encarecidamente soldar.

- Pin DI del anillo LED → pin D11 (configurable en código)
- Pin DRV del dispositivo uECG → pin D3 (configurable)
- Batería + → Arduino 5V y anillo LED 5V
- Batería - → Arduino GND, anillo GND y uECG GND
He usado batería LiPo directamente como entrada 5V; no es error, si la conectas a Vin no funcionará de forma fiable (el regulador de Vin introduce caída de tensión y no nos la podemos permitir). Arduino es estable mientras la tensión de entrada no baje de 3.4 voltios. LiPo empieza a 4.2V cargada y llega a 3.4V solo cuando queda menos del 15% de carga. Así que con cualquier batería >200 mAh tienes tiempo de operación decente. Además, la batería debe conectarse vía algún conector, ¡porque querrás desconectarla para cargarla!
2. Código
El programa funciona así: lee constantemente pin D3 y cuando detecta cambio, guarda el tiempo en un array de 20 elementos. La diferencia entre el primer y último elemento, dividida por 20, es el tiempo medio por latido (ms). Dividiendo 60000 ms (1 min) por ese número obtenemos BPM. Puedes ajustar el número de elementos del array. Menos elementos = respuesta más rápida pero resultados menos estables (cualquier problema en detección causa gran salto en BPM calculado). Más elementos = datos más estables, pero respuesta más lenta cuando BPM cambia rápido.
Luego el BPM se mapea a color (azul→verde→amarillo→rosa→rojo cuando BPM sube) y a número de LEDs: 80 BPM = 8 segmentos encendidos, 110 = 11, etc. (escala ajustable en código).
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 11
#define NUMPIXELS 16
int in_pin = 3;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
pixels.begin();
pinMode(in_pin, INPUT);
digitalWrite(in_pin, 1); // Pull-up crítico, uECG no tiene pull-up interno
}
#define BEAT_HIST 20
long beats[BEAT_HIST];
void push_beat(long ms) {
for(int x = 0; x < BEAT_HIST-1; x++) beats[x] = beats[x+1];
beats[BEAT_HIST-1] = ms;
}
int get_bpm() {
long dt = beats[BEAT_HIST-1] - beats[0];
long bpm = BEAT_HIST * 60000 / dt;
return bpm;
}
long last_pix_upd = 0;
int prev_in_state = 0;
void loop() {
long ms = millis();
int in_state = digitalRead(in_pin); // 1 = sin latido, 0 = latido
if(in_state == 1 && prev_in_state == 0) {
push_beat(ms);
}
prev_in_state = in_state;
if(ms - last_pix_upd > 10) {
int r, g, b;
last_pix_upd = ms;
int bpm = get_bpm();
int max_bright = 120;
float dd = 20;
float t1 = 90, t2, t3, t4;
t2 = t1 + dd; t3 = t2 + dd; t4 = t3 + dd;
if(bpm < t1){ r = 0; g = 0; b = max_bright; }
else if(bpm < t2) { r = 0; g = max_bright * (bpm-t1)/dd; b = max_bright - g; }
else if(bpm < t3) { r = max_bright * (bpm-t2)/dd; g = max_bright - r; b = r/4; }
else if(bpm < t4) { r = max_bright; g = 0; b = max_bright/2 - max_bright * (bpm-t3)/(2*dd); }
else { r = max_bright; g = 0; b = 0; }
if(in_state) { r *= 0.25; g *= 0.25; b *= 0.25; }
int on_pixels = (bpm+5)/10;
for(int i=0;i
3. Montaje como vestible
Es conveniente colocar el Arduino dentro del anillo; encaja casi perfecto. La batería cabe al lado. No olvides que uECG va en el pecho, así que necesitas un cable con conectores: primero colocas el sensor, luego te pones la camisa con los demás componentes, y entonces enchufas el conector. De lo contrario es muy incómodo ponérselo, ¡creeme, lo intenté!))



Básicamente eso es todo; si todo está bien, dentro de 30 segundos tras conectar todo empezará a parpadear indicando BPM.
4. Pruebas de campo
Lo probé caminando y corriendo; durante la carrera, la batería rebota sobre el sensor ECG distorsionando lecturas. Al moverlo, el cable que une uECG con Arduino es demasiado corto y tira del sensor ECG a cada paso, distorsionando otra vez. En general, obtuve latidos fiables solo caminando y quieto, no corriendo; pero creo que lo mejoraré. El sensor en sí, con otra camiseta, mostraba BPM correctamente durante la carrera (comprobado vía su app).
También resulta que LEDs en el pecho pueden verse chulos pero son prácticamente inútiles. Es muy incómodo mirar hacia abajo para ver tu pulso. Creo que en la próxima iteración haré una pulsera de muñeca que indique los latidos.
P.D. Si te interesa el proyecto uECG, mira su página en hackaday, hay muchos detalles técnicos, diseños PCB, discusión y logs del proyecto.
Código
- uECG_pixel_ring.ino
uECG_pixel_ring.ino (Arduino)
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 11
#define NUMPIXELS 16
int in_pin = 3;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
pixels.begin();
pinMode(in_pin, INPUT);
digitalWrite(in_pin, 1);
}
#define BEAT_HIST 20
long beats[BEAT_HIST];
void push_beat(long ms) {
for(int x = 0; x < BEAT_HIST-1; x++) beats[x] = beats[x+1];
beats[BEAT_HIST-1] = ms;
}
int get_bpm() {
long dt = beats[BEAT_HIST-1] - beats[0];
long bpm = BEAT_HIST * 60000 / dt;
return bpm;
}
long last_pix_upd = 0;
int prev_in_state = 0;
void loop() {
long ms = millis();
int in_state = digitalRead(in_pin);
if(in_state == 1 && prev_in_state == 0) push_beat(ms);
prev_in_state = in_state;
if(ms - last_pix_upd > 10) {
int r, g, b;
last_pix_upd = ms;
int bpm = get_bpm();
int max_bright = 120;
float dd = 20;
float t1 = 90, t2, t3, t4;
t2 = t1 + dd; t3 = t2 + dd; t4 = t3 + dd;
if(bpm < t1){ r = 0; g = 0; b = max_bright; }
else if(bpm < t2) { r = 0; g = max_bright * (bpm-t1)/dd; b = max_bright - g; }
else if(bpm < t3) { r = max_bright * (bpm-t2)/dd; g = max_bright - r; b = r/4; }
else if(bpm < t4) { r = max_bright; g = 0; b = max_bright/2 - max_bright * (bpm-t3)/(2*dd); }
else { r = max_bright; g = 0; b = 0; }
if(in_state) { r *= 0.25; g *= 0.25; b *= 0.25; }
int on_pixels = (bpm+5)/10;
for(int i=0;i
Esquemáticos

Proceso de manufactura
- Desfibrilador externo: Historia, diseño y fabricación
- El rodillo de cocina: de los orígenes etruscos a la artesanía moderna
- Corazón artificial: Tecnología, historia y perspectivas futuras
- Válvulas cardíacas artificiales: Tipos, historia e innovaciones futuras
- La Evolución y la Artesanía de los Bolos de Bolera Modernos
- Controle un LED con Sensor PIR de Movimiento en Raspberry Pi
- Monitoriza la temperatura de tu casa con un panel Raspberry Pi
- Sistema de Monitorización Remota de Frecuencia Cardíaca con Alertas AWS IoT
- Articulación de Nudillo Explicada: Diseño, Aplicaciones y Características Clave
- Monitoreo de Latidos Cardíacos sin Contacto vía Altavoces Inteligentes: Un Sistema Revolucionario
- Tatuaje Electrónico de Grafeno Ofrece Monitorización Cardíaca Continua y Precisa por Períodos Prolongados