Paso 24: Simulador de ECG - Sketch de Arduino
// ***************************************************************************************************SIMULADOR DE ECG
//
Objetivo: simular la señal de ECG del ritmo sinusal normal (puntas de 3 RA, LA, RL)
//
Fondo:
//
Electrocardiograma normal (ECG o EKG si eres alemán), tres conforman el
Triángulo de Einthoven. Dos conductores están pegadas al lado derecho e izquierdo del tórax anterior
el corazón (RA = brazo derecho, LA = brazo izquierdo) y un plomo es pegado a la parte inferior del tórax, por lo general
en la cadera derecha (RL = pierna derecha).
//
Es importante saber que estas señales de ECG son milivoltios de amplitud. Esto puede lograrse por
alimentar el convertidor a través de un divisor de voltaje para llegar a los niveles habituales.
//
//
La señal de ECG:
//
Encontré una conveniente forma de onda de ECG desde internet. Aquí es cómo convertí un cuadro de mi
pantalla del monitor a una variedad de lenguaje C de los valores de A/D, cada 1.00 espaciados aparte mseg.
//
Captura de pantalla A. de forma de onda usando la pantalla gratis programa MWSNAP
http://www.mirekw.com/winfreeware/MWSnap.html
//
B. digitalizar la onda jpeg utilizando el programa de digitalización ENGAUGE
http://digitizer.sourceforge.net/
//
//
C: me escribió un programa de Python para convertir las muestras algo irregulares de ENGAUGE
en una matriz de valores espaciados 1.0 milisegundos aparte usando interpolación lineal.
Luego he creado un archivo de texto donde estos puntos eran parte de una variedad de lenguaje C
construir; es decir, los puntos de datos son C inicializadores.
//
D: corte y pegar el texto del archivo la matriz de datos de C con los inicializadores en la
Sketch de Arduino por debajo.
//
//
Recursos de Arduino:
//
Viruta digital salida # 9 - Seleccione el puerto SPI de display de 7 segmentos (baja para seleccionar)
Seleccione digital salida # 10 - chip convertidor (bajo para seleccionar)
Datos digitales de la salida # 11 - SDI (interfaz de SPI) con el convertidor D/A
Reloj digital de la salida # 13 - SCK para el convertidor (interfaz de SPI)
//
Análogo Input # 0 - limpiador pasador central de pote de ohmios de 5 k (ajuste de la frecuencia cardíaca)
//
He seguido la configuración del Timer2 señalados por Sebastian Wallin
http://popdevelop.com/2010/04/Mastering-Timer-Interrupts-on-the-Arduino/
//
Configurar la interfaz SPI según las instrucciones excelentes del australiano John Boxall,
que maravilloso sitio web tiene muchos excelentes tutoriales de Arduino:
http://tronixstuff.wordpress.com/
//
Programador: James P Lynch
lynch007
//
// ***************************************************************************************************
#include "SPI.h" / / compatible con la interfaz SPI al convertidor y pantalla de 7 segmentos
#include < Wire.h > / / necesidad de la biblioteca de alambre
varios constantes utilizadas por el generador de forma de onda
#define INIT 0
#define inactivo 1
#define QRS 2
#define cuatro 4
#define tres 3
#define 2
#define uno 1
// *******************************************************************************************
forma de onda ecg y_data [543] - digitalizado, muestreada en 1.0 msec
//
Se escala de forma de onda para un convertidor de D/a de 12 bits (0.. 4096)
//
Un golpe/minuto 60 ECG requiere esta forma de onda (543 muestras) y muestras de 457
del primer y_data [0] valor de 939.
//
// *********************************************************************************************
const y_data corto [] = {}
939, 940, 941, 942, 944, 945, 946, 947, 951, 956,
962 967, 973, 978, 983, 989, 994, 1000, 1005, 1015,
1024, 1034, 1043, 1053, 1062, 1075, 1087, 1100, 1112, 1121,
1126 1131 1136, 1141, 1146, 1151, 1156, 1164, 1172, 1179,
1187 1194, 1202, 1209, 1216, 1222, 1229, 1235, 1241, 1248,
1254 1260 1264, 1268, 1271, 1275, 1279, 1283, 1287, 1286,
1284 1281, 1279, 1276, 1274, 1271, 1268, 1266, 1263, 1261,
1258, 1256, 1253, 1251, 1246, 1242, 1237, 1232, 1227, 1222,
1218, 1215, 1211, 1207, 1203, 1199, 1195, 1191, 1184, 1178,
1171 1165 1159, 1152, 1146, 1141, 1136, 1130, 1125, 1120,
1115, 1110, 1103, 1096, 1088, 1080, 1073, 1065, 1057, 1049,
1040 1030, 1021, 1012, 1004, 995 987, 982, 978, 974,
970, 966, 963, 959, 955, 952, 949, 945, 942, 939,
938, 939, 940, 941, 943, 944, 945, 946, 946, 946,
946 946 946, 946, 946, 947, 950, 952, 954, 956,
958 960, 962, 964, 965, 965, 965, 965, 965, 965,
963 960, 957, 954, 951, 947, 944, 941, 938, 932,
926 920, 913, 907, 901, 894, 885, 865, 820, 733,
606, 555, 507, 632, 697, 752, 807, 896, 977, 1023,
1069, 1127, 1237, 1347, 1457, 2085, 2246, 2474, 2549, 2595,
2641, 2695, 3083, 3135, 3187, 3217, 3315, 3403, 3492, 3581,
3804, 3847, 3890, 3798, 3443, 3453, 3297, 3053, 2819, 2810,
2225, 2258, 1892, 1734, 1625, 998, 903, 355, 376, 203,
30, 33, 61, 90, 119, 160, 238, 275, 292, 309,
325, 343, 371, 399, 429, 484, 542, 602, 652, 703,
758, 802, 838, 856, 875, 895, 917, 938, 967, 1016,
1035, 1041, 1047, 1054, 1060, 1066, 1066, 1064, 1061, 1058,
1056 1053, 1051, 1048, 1046, 1043, 1041, 1038, 1035, 1033,
1030 1028 1025, 1022, 1019, 1017, 1014, 1011, 1008, 1006,
1003, 1001, 999, 998, 996, 994, 993, 991, 990, 988,
986 985, 983, 981, 978, 976, 973, 971, 968, 966,
963 963 963, 963, 963, 963, 963, 963, 963, 963,
963 963 963, 963, 963, 963, 963, 963, 963, 963,
964, 965, 966, 967, 968, 969, 970, 971, 972, 974,
976, 978, 980, 983, 985, 987, 989, 991, 993, 995,
997, 999, 1002, 1006 1011, 1015, 1019, 1023, 1028, 1032,
1036, 1040, 1045, 1050, 1055, 1059, 1064, 1069, 1076, 1082,
1088, 1095, 1101, 1107, 1114, 1120, 1126, 1132, 1141, 1149,
1158, 1166, 1173, 1178, 1183, 1188, 1193, 1198, 1203, 1208,
1214, 1221, 1227, 1233, 1240, 1246, 1250, 1254, 1259, 1263,
1269 1278 1286, 1294, 1303, 1309, 1315, 1322, 1328, 1334,
1341, 1343, 1345, 1347, 1349, 1351, 1353, 1355, 1357, 1359,
1359 1359 1359, 1359, 1358, 1356, 1354, 1352, 1350, 1347,
1345, 1343, 1341, 1339, 1336, 1334, 1332, 1329, 1327, 1324,
1322, 1320, 1317, 1315, 1312, 1307, 1301, 1294, 1288, 1281,
1275 1270, 1265, 1260, 1256, 1251, 1246, 1240, 1233, 1227,
1221 1214, 1208, 1201, 1194, 1186, 1178, 1170, 1162, 1154,
1148, 1144, 1140, 1136, 1131, 1127, 1123, 1118, 1114, 1107,
1099 1090, 1082, 1074, 1069, 1064, 1058, 1053, 1048, 1043,
1038 1034, 1029, 1025, 1021, 1017, 1013, 1009, 1005, 1001,
997, 994, 990, 991, 992, 994, 996, 997, 999, 998,
997, 996, 995, 994, 993, 991, 990, 989, 989, 989,
989 989 989, 989, 988, 986, 984, 983, 981, 980,
982, 984, 986, 988, 990, 993, 995, 997, 999, 1002,
1005, 1008, 1012};
variables globales utilizadas por el programa
unsigned int NumSamples = sizeof(y_data) / 2; número de elementos de [] de y_data arriba
unsigned int QRSCount = 0; corriente cuenta de MS período QRS
unsigned int IdleCount = 0; cuenta corriente ociosa MS período
unsigned IdlePeriod largo = 0; período de ralentí se ajusta Pot para establecer la frecuencia cardíaca
unsigned int estado = Inicio; Estados son INIT, QRS y marcha lenta
unsigned int DisplayCount = 0; recuentos de 50 mseg a actualizar el display de 7 segmentos
unsigned int tcnt2; TIMER2 recargar valor, disponible a nivel mundial
Float BeatsPerMinute; representación de punto flotante de la frecuencia cardiaca
unsigned int Bpm; versión de entero de la frecuencia cardiaca (por 10)
unsigned int BpmLow; ritmo cardíaco más bajo permitido (x10)
unsigned int BpmHigh; ritmo cardíaco más alto permitido (x10)
int valor; marcador de posición para entrada analógica 0
sin firmar largo BpmValues [32] = {0, 0, 0, 0, 0, 0, 0, 0, / / tiene 32 lecturas de analógico pot último
0, 0, 0, 0, 0, 0, 0, 0, / / para uso en la filtración hacia fuera de la pantalla de la inquietud
0, 0, 0, 0, 0, 0, 0, 0, / / para uso en la filtración hacia fuera de la pantalla de la inquietud
0, 0, 0, 0, 0, 0, 0, 0}; para uso en la filtración hacia fuera de la pantalla de la inquietud
unsigned BpmAverage largo = 0; utilizado en un filtro de promedio simple
unsigned char índice = 0; utilizado en un filtro de promedio simple
unsigned int DisplayValue = 0; Beats por minuto filtrado enviado Mostrar
void setup() {}
Configurar los puertos de salida (1 indicador de intrerrupt mseg y soporte SPI D/A)
pinMode (9, salida); pantalla de 7 segmentos chip select (baja para seleccionar el chip)
pinMode (10, salida); Chip convertidor seleccione (baja para seleccionar el chip)
pinMode (11, salida); Datos de SDI
pinMode (13, salida); Reloj SCK
estado inicial de la interfaz de SPI
SPI.begin(); despierta el bus SPI.
SPI.setDataMode(0); modo: CPHA = 0, datos capturados en flanco ascendente del reloj (bajo a alto)
SPI.setClockDivider(SPI_CLOCK_DIV64); reloj / 64
SPI.setBitOrder(MSBFIRST); bit 7 relojes en primer lugar
establecer el rango de ritmo cardíaco permitido
BpmLow = 300 (30 bpm x 10)
BpmHigh = (60.0 / (NumSamples * 0.001)) * 10 = (60.0 /.543) * 10 = 1104 (110.49 x 10)
BpmLow = 300;
BpmHigh = (60.0 / ((float) NumSamples * 0.001)) * 10;
Primero deshabilita la interrupción de desbordamiento del temporizador mientras estamos configurando
TIMSK2 & = ~ (1 << TOIE2);
Configurar timer2 en modo normal (puro contando, no PWM etc..)
TCCR2A & = ~ ((1 << WGM21) | (1 << WGM20));
TCCR2B & = ~ (1 << WGM22);
Seleccione reloj: el reloj interno de I/O
ASSR & = ~ (1 << AS2);
Desactivar comparar habilitar interrupción de partido A (sólo quiere desbordamiento)
TIMSK2 & = ~ (1 << OCIE2A);
Ahora configure el prescaler a reloj de la CPU dividida por 128
TCCR2B | = (1 << CS22) | (1 << CS20); Set de brocas
TCCR2B & = ~ (1 << CS21); Poco claro
Tenemos que calcular un valor apropiado para cargar el contador de tiempo.
A continuación carga el valor 131 en el registro de contador 2 del temporizador
La matemática detrás de esto es:
(Frecuencia CPU) / (valor divisor) = 125000 Hz = 8us.
(período) / 8us = 125.
Max(Uint8) + 1-125 = 131;
//
Guardar valor a nivel mundial para posteriormente volver a cargar en ISR /
tcnt2 = 131;
Finalmente la carga final activar el temporizador
TCNT2 = tcnt2;
TIMSK2 | = (1 << TOIE2);
}
void loop() {}
leer de la olla de la frecuencia cardíaca (entrada analógica 0)
Valor = analogRead(0);
mapa de la gama de entrada analógica 0 (0.. 1023) para el rango de Bpm (300.. 1104)
BPM = mapa (valor, 0, 1023, BpmLow, BpmHigh);
Para atenuar la inquietud o rebote en el dígito menos significativo de la pantalla,
un filtro de media móvil (32 valores) lo que alisar hacia fuera.
BpmValues [índice ++] = Bpm; Añada la última muestra a matriz de ocho elementos
Si (índice == 32) {/ / manejar alrededor
Índice = 0;
}
BpmAverage = 0;
para (int i = 0; i < 32; i ++) {/ / suma de todos los valores de la matriz
BpmAverage += BpmValues [i];
}
BpmAverage >> = 5; Dividir por 32 a media
ahora actualizar la pantalla de 4 dígitos - formato: XXX. X
puesto que la actualización es una transferencia de múltiples byte, deshabilitar interrupciones hasta que se hace
noInterrupts();
DisplayValue = BpmAverage;
Interrupts();
teniendo en cuenta el valor del pote (beats por minuto) leído en, calcular IdlePeriod (MS)
Este valor es utilizado por la rutina de servicio de interrupción Timer2 1.0 msec
BeatsPerMinute = (float) Bpm / 10.0;
noInterrupts();
IdlePeriod = (unsigned int) ((float) 60000.0 / BeatsPerMinute)-(float) NumSamples;
Interrupts();
Delay(20);
}
// ********************************************************************************
Rutina de servicio de interrupción TIMER2
//
Interrumpir la rutina de servicio (ISR) por desbordamiento del Timer2 en 1.000 mseg.
//
//
La función de interrupción Timer2 se utiliza para enviar el punto de forma de onda de 16 bits
el convertidor D/A de MCP4921 de Microchip con la interfaz SPI.
//
La función de interrupción Timer2 se utiliza también para enviar el pulso actual
como leer el potenciómetro cada 50 interrupciones Timer2 para el display de 7 segmentos.
//
El bote se lee y se calcula la frecuencia cardiaca en el circuito de fondo.
Mediante la ejecución de ambos periféricos SPI en el nivel de interrupción, "serializar" y evitar
corrupción por una transmisión de SPI se interrumpa por el otro.
//
Un estado machime se implementa para lograr esto. Sus Estados son:
//
INIT - básicamente borra los contadores y establece el estado del QRS.
//
QRS - salidas de los datos de forma de onda de ECG siguiente punto cada 1.0 msec
hay 543 de estos puntos de datos complejos de QRS.
//
Marcha lenta - período variable después de la parte QRS.
D/A sostiene primer valor del ECG (939) para todo el período de inactividad.
Período de inactividad varía para permitir el ajuste de la frecuencia cardíaca básica;
un valor de cero milisegundos durante el período de inactividad da 110,4 latidos por min
durante el período de inactividad máximo de 457 mseg da 30.0 bpm.
//
Tenga en cuenta que el período de inactividad se calcula en el fondo principal
lazo leyendo una olla y convertir su gama a uno adecuado
para el período de fondo. La rutina de interrupción lee esto
valor para determinar cuándo parar el período inactivo.
//
La transmisión del siguiente punto de datos para el convertidor vía SPI toma
unos 63 microsegundos (que incluye dos transmisiones de byte SPI).
//
La transmisión de las cifras de frecuencia cardiaca para el display de 7 segmentos de Sparkfun
tiene cerca de 350 usec (sólo es transmitida cada 50 interrupciones Timer2)
//
// ********************************************************************************
{ISR(TIMER2_OVF_vect)}
Cargar el contador de tiempo
TCNT2 = tcnt2;
máquina de estado
Switch (estado) {}
caso INIT:
cero el QRS y contadores inactivos
QRSCount = 0;
IdleCount = 0;
DisplayCount = 0;
establecer estado siguiente QRS
Estado = QRS;
rotura;
caso de QRS:
la siguiente muestra de la onda QRS en el convertidor de salida
DTOA_Send(y_data[QRSCount]);
contador muestra de avance y verificación final
QRSCount ++;
Si (QRSCount > = NumSamples) {}
Inicio período de inactividad y salida primera muestra a DTOA
QRSCount = 0;
DTOA_Send(y_data[0]);
Estado = inactivo;
}
rotura;
caso inactivo:
convertidor D/A mantendrá el valor anterior por escrito, todos tenemos
hacer es determinar cuánto debería ser el período de inactividad.
avanzar el contador de ralentí y compruebe el final
IdleCount ++;
la IdlePeriod se calcula en el bucle principal (de un bote)
Si (IdleCount > = IdlePeriod) {}
IdleCount = 0;
Estado = QRS;
}
rotura;
por defecto:
rotura;
}
salida para el segmento de 7 mostrar cada 50 milisegundos
DisplayCount ++;
Si (DisplayCount > = 50) {}
DisplayCount = 0;
Display7Seg_Send(DisplayValue);
}
}
// ***************************************************************************************************
void DTOA_Send(unsigned short)
//
Propósito: enviar valor de D/a de 12 bits al convertidor D/A de MCP4921 Microchip (0.. 4096)
//
//
Entrada: DtoAValue - 12-bit D/A valor (0.. 4096)
//
//
El DtoAValue se antepone con la A B, BUF, GA y SHDN pedacitos antes de la transmisión.
//
ESCRIBA EL COMANDO
// |-----------|-----------|-----------|-------------|--------------------------------------------------------------------------------|
// | A/B | BUF | GA | SHDN | D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 |
// | | | | | |
|setting: |setting: |setting: | Ajuste: | DtoAValue (12 bits) |
// | 0 | 0 | 1 | 1 | |
// | DAC-A |unbuffer | 1 x |power-on|] (salida 0.. 4096 como voltios 0.. 5 voltios) |
// |-----------|------------|----------|-------------|--------------------------------------------------------------------------------|
// 15 14 13 12 11 0
// To D/A <======================================================================================
//
Nota: WriteCommand se registra hacia fuera con poco 15 primero!
//
//
Devoluciones: no
//
//
Recursos de I/O: Pin Digital 9 = chip select (baja para seleccionar el chip)
Pin digital 13 = reloj SPI
Pin digital 11 = datos SPI
//
Nota: poniendo a tierra el LDAC * perno de gancho el hardware, los datos SPI serán estar registrados en el
Cierres del convertidor cuando la selección de chip se levanta en la final de la transferencia.
//
Esta rutina toma usec 63 con una Adafruit Menta
// ***************************************************************************************************
void DTOA_Send (unsigned DtoAValue corto) {}
byte datos = 0;
seleccionar el chip D/A (bajo)
digitalWrite (10, 0); chip seleccionar bajo
Envíe el 0011xxxx primer byte alto
Datos = highByte(DtoAValue);
Datos = 0b00001111 y datos;
Datos = 0b00110000 | Datos;
SPI.transfer(Data);
envía el byte bajo siguiente xxxxxxxx
Datos = lowByte(DtoAValue);
SPI.transfer(Data);
todo hecho, deseleccionar el chip (esto actualiza el D/A con el nuevo valor)
digitalWrite (10, 1); Seleccione alta viruta
}
// ***************************************************************************************************
void Display7Seg_Send(char *)
//
Propósito: enviar 4 dígitos a serie SparkFun 7 segmentos Display (requiere 4 escribe SPI)
//
Entrada: valor - versión unsigned int de BeatsPerMinute
//
Devoluciones: no
//
Recursos de I/O: Pin Digital 10 = chip select (baja para seleccionar el chip)
Pin digital 13 = reloj SPI
Pin digital 11 = datos SPI
//
Nota: esta rutina toma 350 usec usando un Adafruit Menta
// ***************************************************************************************************
void Display7Seg_Send (unsigned int sincopal) {}
digit1 de uint8_t, digit2, digit3, digit4;
unsigned int valor;
convertir a cuatro dígitos (conjunto de los ceros a los espacios en blanco; 0x78 es el carácter espacio en blanco)
valor = ritmo cardíaco;
digit1 = valor / 1000;
valor = digit1 * 1000;
Si (digit1 == 0) digit1 = 0x78;
digit2 = valor / 100;
valor = digit2 * 100;
Si ((digit1 == 0x78) & & (digit2 == 0)) digit2 = 0x78;
digit3 = valor / 10;
valor = digit3 * 10;
Si ((digit1 == 0x78) & & (digit2 == 0x78) & & (digit3 == 0)) digit3 = 0x78;
digit4 = valor;
digitalWrite (9, bajo); Seleccione el display 7-seg de Sparkfun
SPI.transfer(0x76); Restablecer pantalla
SPI.transfer(0x7A); comando de brillo
SPI.transfer(0x00); 0 = brillante, 255 = dim
SPI.transfer(digit1); Miles dígitos
SPI.transfer(digit2); Cientos dígitos
SPI.transfer(digit3); Dígito de decenas
SPI.transfer(digit4); Los dígitos
SPI.transfer(0x77); comando set decimales
SPI.transfer(0x04); encender dec pt entre los dígitos 3 y 4
digitalWrite (9, alto); versión display 7-seg de Sparkfun
}