SEÑAL ECG
MATERIA: PROCESAMIENTO DIGITAL DE SEÑALES
DOCENTE: ING. GERMAN PEREIRA MUÑOZ
ESTUDIANTE: JUAN DAVID QUISPE MAMANI
PARALELO: 8 vo ING. MECATRÓNICA
FECHA: 23 / 09 / 2025
LA PAZ – BOLIVIA
2025
Introducción
El presente informe describe el proceso de síntesis digital de una señal ECG utilizando un
microcontrolador PIC18F4550 y un conversor digital–analógico R–2R. El objetivo es
obtener una forma de onda periódica con morfología realista P–QRS–T y frecuencia de 1 Hz,
correspondiente a 60 latidos por minuto, la cual pueda ser visualizada mediante simulación
en Proteus y servir como base para aplicaciones de procesamiento digital de señales en el
área biomédica.
Se presentan los cálculos de diseño, desde la elección de la frecuencia de muestreo,
modelado de la forma de onda mediante funciones gaussianas, discretización y
normalización a 8 bits, hasta la configuración del temporizador interno para garantizar una
frecuencia de interrupción estable. Asimismo, se consideran opciones de suavizado por
interpolación y filtros FIR, así como la validación numérica de puntos clave de la señal y el
análisis de la complejidad computacional.
Marco Teórico del ECG
El electrocardiograma (ECG) es el registro gráfico de la actividad eléctrica del corazón. Cada
ciclo cardiaco genera un conjunto de ondas que representan eventos eléctricos específicos:
- Onda P: despolarización auricular.
- Complejo QRS: despolarización ventricular.
- Onda T: repolarización ventricular.
Los intervalos PR, ST y QT son parámetros clínicos relevantes para identificar arritmias,
isquemias y otras alteraciones cardiacas. Su importancia radica en que constituyen una
herramienta no invasiva, económica y ampliamente difundida en el diagnóstico médico.
Importancia en el Área Biomédica
La señal ECG es fundamental para el diagnóstico de patologías cardiacas. El uso de modelos
sintéticos permite realizar simulaciones controladas en ambientes académicos y de
investigación, evitando depender de registros clínicos reales. Esto facilita la docencia, la
experimentación con filtros digitales, compresión de datos y el diseño de sistemas de
monitoreo.
Señales Sintéticas de ECG
La generación de señales ECG artificiales permite reproducir condiciones de frecuencia
cardiaca y amplitud bajo control del usuario. Existen diferentes modelos matemáticos, como
la suma de senoidales o el uso de funciones gaussianas. En este trabajo se adoptó un modelo
gaussiano que representa adecuadamente las ondas P, QRS y T. Esto facilita la
implementación en hardware mediante microcontroladores.
Aplicación de la Electrónica Digital
La implementación se realizó con un microcontrolador PIC18F4550, el cual utiliza una LUT
(tabla de valores) y un conversor R–2R para generar la señal analógica. El temporizador
interno configura una ISR periódica que actualiza la salida en PORTD. Además, se
implementó comunicación UART a 9600 bps para modificar parámetros como frecuencia
(Hz) y amplitud (%). La simulación en Proteus junto con una interfaz gráfica en
Python/Thonny demuestra la flexibilidad del diseño.
Cálculos de Diseño
Síntesis de Señal ECG
Requisitos y decisión de diseño
• Queremos una ECG periódica de 1 Hz con morfología P–QRS–T realista.
• El DAC es un R–2R de 8 bits → salida 0..255, cuantización 1 LSB.
• Usaremos una LUT de N = 1000 puntos (1 ms por muestra) y Timer2 a 1 kHz, o bien ISR a
5 kHz con upsampling ×5 para suavizar.
• Para justificar formalmente, documentamos: muestreo, modelo de forma, discretización,
normalización y temporizador.
1) Muestreo básico
Periodo T = 1 s → f0 = 1 Hz.
Elegimos Fs = 1000 Hz para tener resolución temporal de 1 ms.
Número de puntos por ciclo: N = Fs · T = 1000 · 1 = 1000.
Tiempo discreto: t[n] = n/Fs, n = 0,1,…,999.
2) Modelo de forma – Suma de gaussianas
La ECG continua se compone de cinco gaussianas (P, Q, R, S, T) más segmentos casi planos
(PR y ST).
Ecuación: y(t) = Σ Ai·exp(−(t−μi)^2/(2σi^2)) + y_base(t).
Relación FWHM→σ: σ = FWHM / (2·√(2·ln 2)) ≈ FWHM / 2.355.
Parámetros clínicos elegidos (T = 1 s):
Onda μi [s] FWHM σi [ms] Ai (adim.) μi σi
[ms] [muestras] [muestras]
P 0.180 80 34.0 +0.12 180 34.0
Q 0.460 28 11.9 -0.08 460 11.9
R 0.500 18 7.6 +1.00 500 7.6
S 0.540 28 11.9 -0.35 540 11.9
T 0.780 160 67.9 +0.40 780 67.9
Segmento PR: rampa leve desde baseline (0) hasta 0.02 entre t=0 y Q.
Segmento ST: elevación suave de +0.03 entre S y (T−0.08).
3) Discretización (N = 1000)
Definimos x[n] = n/N ∈ [0,1). Evaluamos cada gaussiana en x[n]:
g_i[n] = Ai · exp(−(x[n] − μi)^2 / (2 σi^2)).
La señal bruta: y[n] = Σ g_i[n] + y_base[n].
Cálculo ejemplo en n = 500 (onda R):
x = 500/1000 = 0.5; para R: μR=0.50, σR≈0.00764 → exp(−(0)^2/(2σ^2))=1 →
contribución A_R=+1.00.
Para Q en n=500: Δ = 0.5−0.46=0.04; 2σ_Q^2 ≈ 2·(0.01189)^2 ≈ 0.000283;
exp(−0.04^2/0.000283) ≈ exp(−5.65) ≈ 0.0035; A_Q·g ≈ −0.08·0.0035 ≈ −2.8×10⁻⁴
(despreciable).
4) Normalización a 8 bits
1) Buscar extremos: ymin, ymax de y[n].
2) Escalar a z[n] ∈ [−1,1]: z[n] = 2·(y[n]−ymin)/(ymax−ymin) − 1.
3) Aplicar offset/ganancia: u[n] = clip( DC + 0.5·GAIN·z[n], 0, 1 ), con DC=0.52, GAIN=0.96.
4) Cuantizar 8 bits: DAC[n] = round(255·u[n]).
Con los parámetros anteriores se obtiene: ymin=-0.34922, ymax=0.99858.
El rango útil u[n] ocupa ~0.96 del total (por GAIN), con media ≈ 0.355 y pico a pico de 0.96.
En códigos: media(DAC) ≈ 90.5, pico a pico ≈ 245 cuentas.
5) Temporizador Timer2 para 1 kHz (Fosc = 4 MHz)
Fórmula: Fs_ISR = (Fosc/4) / (Presc · (PR2+1) · Post).
Elegimos Presc=1:4 y Post=1:1. Entonces:
• Fosc/4 = 4 MHz / 4 = 1 MHz.
• Denominador buscado = Fs_ISR·Presc·Post = 1000 · 4 · 1 = 4000.
• (PR2+1) = 1e6 / 4000 = 250. → PR2 = 249 (entero).
Bits: T2CKPS=01 (1:4), TOUTPS=0000 (1:1), PR2=249. La ISR dispara cada 1 ms.
6) Opción de suavizado por software (ISR 5 kHz)
Para reducir el ‘staircase’: ISR a 5 kHz + interpolación lineal ×5 + FIR 8–32 taps.
Con Fosc/4 = 1 MHz, Presc=1:4, Post=1:5:
• Denominador = 5000 · 4 · 5 = 100000.
• (PR2+1) = 1e6 / 100000 = 10 → PR2 = 9.
Así, la LUT sigue siendo de 1000 puntos (1 Hz), pero el DAC se actualiza 5 veces por muestra
base con interpolación, dando trazos más suaves.
7) Conversión digital–analógica y amplitud
Con R–2R a VDD=5 V, el escalón ideal de 1 LSB es 5/255 ≈ 19.6 mV en vacío.
La carga (R1||R2 ≈ 500 Ω) atenúa; el OPAMP en seguidor recupera nivel pero el pre–filtro
R17–C1 define el ancho de banda visible.
Para visualizar ECG (1 Hz), conviene f_c del filtro RC en 10–100 Hz. Ejemplo: R17=10 kΩ,
C1=1 µF → f_c ≈ 15.9 Hz.
8) Validación numérica puntual
n = 180 (x = 0.180): y = P:+0.1200 Q:-0.0000 R:+0.0000 S:-0.0000 T:+0.0000
BASE:+0.0078 → y_total = +0.1278
n = 460 (x = 0.460): y = P:+0.0000 Q:-0.0800 R:+0.0000 S:-0.0000 T:+0.0000
BASE:+0.0000 → y_total = -0.0800
n = 500 (x = 0.500): y = P:+0.0000 Q:-0.0003 R:+1.0000 S:-0.0012 T:+0.0001
BASE:+0.0000 → y_total = +0.9986
n = 540 (x = 0.540): y = P:+0.0000 Q:-0.0000 R:+0.0000 S:-0.3500 T:+0.0008
BASE:+0.0000 → y_total = -0.3492
n = 780 (x = 0.780): y = P:+0.0000 Q:-0.0000 R:+0.0000 S:-0.0000 T:+0.4000
BASE:+0.0000 → y_total = +0.4000
9) Complejidad y memoria
• LUT de 1000 bytes (8 bits).
• ISR 1 kHz: una lectura LUT y escritura en LATD.
• ISR 5 kHz: interpolación lineal (2 multipl. + sumas) + FIR (M sumas), con M=8..32.
• CPU a 4 MHz (1 MIPS): 5 kHz con FIR de 8–16 taps es viable.
10) Reproducción de resultados en mikroC
• Genera la LUT con los parámetros de la tabla y el algoritmo de normalización.
• Configura Timer2 según la sección 5 o 6 (1 kHz o 5 kHz).
• Escribe DAC[n] en LATD dentro de la ISR; usa RA0 como pin de prueba para medir Fs_ISR.
Constantes finales:
PR2 (1 kHz): 249.00 → usar 249; PR2 (5 kHz con Post=1:5): 9.00 → usar 9.
Amplitudes: AP=+0.12, AQ=−0.08, AR=+1.00, AS=−0.35, AT=+0.40.
Centros (muestras): μP=180, μQ=460, μR=500, μS=540, μT=780.
Anchuras (σ en muestras): σP≈33.97, σQ≈11.89, σR≈7.64, σS≈11.89, σT≈67.95.
Normalización: DC=0.52, GAIN=0.96, salida 8 bits 0..255.
Código de MikroC
#include <math.h>
/*** ====== Configuración de frecuencia visible ====== ***/
// Pon 1 para 1 Hz real (1 s por ciclo) o 10 para 10 Hz (0.1 s por ciclo).
#define ECG_HZ 10
/*** ====== Muestreo / upsampling ====== ***/
#define UPS 5 // upsampling x5 -> ISR 5 kHz (constante)
#if (ECG_HZ == 1)
#define LUT_LEN 1000 // 1000 puntos -> 1 s a 1 kHz base
#elif (ECG_HZ == 10)
#define LUT_LEN 100 // 100 puntos -> 0.1 s a 1 kHz base
#else
#error "ECG_HZ debe ser 1 o 10"
#endif
/*** ====== Parámetros de forma (gaussianas) ====== ***/
// Amplitudes relativas (antes de normalizar)
#define AMP_P ( 0.12)
#define AMP_Q (-0.08)
#define AMP_R ( 1.00)
#define AMP_S (-0.35)
#define AMP_T ( 0.40)
// Posiciones (0..1 dentro del ciclo)
#define POS_P (0.18)
#define POS_Q (0.46)
#define POS_R (0.50)
#define POS_S (0.54)
#define POS_T (0.78)
// Anchuras (s en fracción del ciclo)
#define SIG_P (0.030)
#define SIG_Q (0.012)
#define SIG_R (0.008)
#define SIG_S (0.012)
#define SIG_T (0.055)
// Niveles de línea base (segmentos casi planos)
#define BASELINE (0.00) // nivel de reposo
#define ST_ELEVATION (0.03) // ligera elevación en ST (0?no elevación)
// Escalado final a 8 bits
const float DC_OFFSET = 0.52f; // centro alrededor de mitad de escala
const float GAIN = 0.96f; // aprovecha casi todo el rango
/*** ====== Buffers / estado ====== ***/
unsigned char lut[LUT_LEN]; // LUT base (1 kHz) – ocupa 1000 ó 100 bytes
unsigned int i_base = 0; // índice LUT (0..LUT_LEN-1)
unsigned char frac = 0; // 0..UPS-1 (fase fraccional)
// FIR 8 taps (promedio móvil)
#define FIR_TAPS 8
unsigned int fir_sum = 0;
unsigned char fir_buf[FIR_TAPS];
unsigned char fir_pos = 0;
/*** ====== Helpers ====== ***/
float fminf(float a, float b){ return (a<b)?a:b; }
float fmaxf(float a, float b){ return (a>b)?a:b; }
// Gaussiana centrada en mu con sigma – usamos exp() (double) que sí soporta mikroC
static float gauss(float x, float mu, float sigma){
double d = (double)x - (double)mu;
double val = - (d*d) / (2.0 * (double)sigma * (double)sigma);
return (float)exp(val);
/*** ====== Señal ECG "médica" en 0..1 (sin escalar a 8 bits) ====== ***/
static float ecg_shape(float x){ // x en [0,1)
float y = 0.0f;
// Segmento PR casi plano antes de QRS
if (x < POS_Q) {
// leve pendiente positiva hacia Q
float alpha = (x - 0.00f) / (POS_Q - 0.00f);
if (alpha < 0.0f) alpha = 0.0f; if (alpha > 1.0f) alpha = 1.0f;
y = BASELINE * (1.0f - alpha) + (BASELINE + 0.02f) * alpha;
// Complejo QRS por gaussianas cortas y altas
y += AMP_Q * gauss(x, POS_Q, SIG_Q);
y += AMP_R * gauss(x, POS_R, SIG_R);
y += AMP_S * gauss(x, POS_S, SIG_S);
// Segmento ST (ligeramente elevado y casi plano)
if (x > POS_S && x < (POS_T - 0.08f)) {
float alpha = (x - POS_S) / ((POS_T - 0.08f) - POS_S);
if (alpha < 0.0f) alpha = 0.0f; if (alpha > 1.0f) alpha = 1.0f;
y += (BASELINE + ST_ELEVATION) * (0.6f + 0.4f*alpha);
// Onda P y T (anchas y suaves)
y += AMP_P * gauss(x, POS_P, SIG_P);
y += AMP_T * gauss(x, POS_T, SIG_T);
return y;
/*** ====== Construcción de la LUT (1 kHz base) ====== ***/
void build_lut(void){
float y, ymin = 1e9, ymax = -1e9, x;
unsigned int n;
// medir extremos
for (n = 0; n < LUT_LEN; n++){
x = (float)n / (float)LUT_LEN;
y = ecg_shape(x);
if (y < ymin) ymin = y;
if (y > ymax) ymax = y;
// normalizar a 0..255 con offset/ganancia
for (n = 0; n < LUT_LEN; n++){
x = (float)n / (float)LUT_LEN;
y = ecg_shape(x);
if (ymax > ymin) y = 2.0f * ((y - ymin) / (ymax - ymin)) - 1.0f; // -1..+1
else y = 0.0f;
y = DC_OFFSET + 0.5f * GAIN * y; // 0..1
if (y < 0.0f) y = 0.0f; if (y > 1.0f) y = 1.0f; // clip
lut[n] = (unsigned char)(y * 255.0f + 0.5f);
/*** ====== Inicialización de HW ====== ***/
void init_hw(void){
// Todo digital / comparadores OFF
ADCON1 = 0x0F;
CMCON = 0x07;
#ifdef PSPMODE_bit
PSPMODE_bit = 0; // asegura PORTD como puerto normal
#endif
// PORTD -> R-2R
TRISD = 0x00;
LATD = 0x80;
// RC0/RC1 (sumador al op-amp) en 0
TRISC.B0 = 0; LATC.B0 = 0;
TRISC.B1 = 0; LATC.B1 = 0;
// RA0 test 5 kHz
TRISA.B0 = 0; LATA.B0 = 0;
// Timer2: Fs_ISR = 5 kHz (Presc=1:4, PR2=9, Post=1:5)
T2CON = 0x00;
PR2 = 9; // (PR2+1)=10
T2CON |= 0x20; // Post 1:5 (TOUTPS=0100b)
T2CON |= 0x01; // Presc 1:4 (T2CKPS=01b)
TMR2IF_bit = 0;
TMR2IE_bit = 1;
PEIE_bit = 1;
GIE_bit = 1;
TMR2ON_bit = 1;
// FIR init
unsigned char i;
fir_sum = 0; fir_pos = 0;
for (i = 0; i < FIR_TAPS; i++) { fir_buf[i] = 128; fir_sum += 128; }
/*** ====== ISR 5 kHz (upsampling ×5 + FIR) ====== ***/
void interrupt(){
if (TMR2IF_bit){
unsigned char y0, y1, y_i, y_out;
unsigned int acc;
// test
LATA.B0 ^= 1;
// interpolación lineal entre muestras base (1 kHz)
y0 = lut[i_base];
y1 = (i_base + 1 < LUT_LEN) ? lut[i_base + 1] : lut[0];
acc = (unsigned int)y0 * (UPS - frac) + (unsigned int)y1 * frac;
y_i = (unsigned char)((acc + (UPS/2)) / UPS); // redondeo
// FIR 8 taps -> divide por 8
fir_sum -= fir_buf[fir_pos];
fir_buf[fir_pos] = y_i;
fir_sum += y_i;
fir_pos++; if (fir_pos >= FIR_TAPS) fir_pos = 0;
y_out = (unsigned char)((fir_sum >> 3) & 0xFF);
LATD = y_out;
// avanzar fase
frac++;
if (frac >= UPS) { frac = 0; i_base++; if (i_base >= LUT_LEN) i_base = 0; }
TMR2IF_bit = 0;
}
}
/*** ====== Main ====== ***/
void main(){
init_hw();
build_lut();
while(1){ /* la ISR hace todo */ }
Circuito Simulado en Proteus
Conclusiones
El modelo implementado permite reproducir una señal ECG de forma fiel y controlada,
demostrando la viabilidad de su síntesis en microcontroladores con recursos limitados. El
control a través de la interfaz gráfica ofrece la posibilidad de simular diferentes escenarios
clínicos (bradicardia, taquicardia) y facilita el análisis en entornos académicos. Este trabajo
integra conocimientos de procesamiento digital de señales, electrónica analógica y
programación, constituyendo un ejemplo práctico de aplicación biomédica.