Generador de señales de prueba

Los generadores de funciones son parte del equipo de pruebas para el diseño en electrónica, pero hasta los modelos más sencillos pueden representar una inversión fuerte. Así que en esta entrada les presento una forma de hacer un convertidor digital – analógico (DAC) para los PIC, y los principios para usarlo en un generador de señales de prueba – cuadradas, triangulares, sinusoidales y electrocardiogramas (ECG).

En la figura siguiente se muestra la carátula de un generador de funciones, observen que:

  • Produce señales sinusoidales, triangulares y cuadradas.
  • La magnitud de las señales en la salida se ajusta fuertemente con un botón y gradualmente con un potenciómetro.
  • La frecuencia de las señales se ajusta por intervalos con 7 botones y gradualmente con un potenciómetro.
  • Hay otros controles que permiten alterar simetrías, producir barridos, desplazar el nivel medio de la señal, etc.
Carátula de un generador de funciones que incorpora sólo componentes analógicos.

Qué se puede hacer fácilmente con el material en esta entrada:

  • Producir las 3 formas de onda o señales que hace un generador.
  • Producir señales de prueba que NO suelen hacer los generadores de funciones, en este caso un electrocardiograma.
  • Obtener una frecuencia en particular (quienes han usado estos equipos y les gustaría que la carátula dijera, por ejemplo, 630 y no 632.27 o 598.64 saben de qué hablo).
  • Obtener un generador de señales de baja frecuencia, mejor a los que se basan exclusivamente en electrónica analógica.

Qué requerirá más trabajo:

  • Hacer una interfaz para el usuario.
  • Agregar electrónica analógica para ajustar las características de la señal de salida – amplitud, desplazamiento (DC OFFSET), impedancia, etc.
  • Agregar filtros para eliminar ruido de alta frecuencia.
  • Posiblemente quieras agregar una memoria para almacenar las señales especiales como el ECG.
  • Dependiendo de tus intereses y conocimientos de instrumentación, posiblemente un largo, largo etcétera.

Ya entrando en materia: el PIC16F753 es el único microcontrolador de los PIC16, con núcleo original, que cuenta con convertidor digital analógico; hay más de 90 procesadores con núcleo mejorado que cuentan con DAC de 5, 8 ó 10 bits; pero, según mi experiencia, en los comercios de la Ciudad de México sólo se consiguen los PIC16F1823, 1827 y 1938, todos con DAC de 5 bits. La alternativa de comprar el convertidor por separado, casi siempre lo lleva a uno a un circuito de la serie DAC0800; y ya que estos dispositivos entregan el valor convertido en forma de corriente, suele ser necesario usar además un amplificador operacional para obtener un voltaje analógico. Es por esto que les propongo experimentar sólo con el amplificador operacional a partir del circuito siguiente:

Conecté las entradas de este circuito al puerto D de un PIC16F887. Usé resistencias con tolerancias del 5 % (obtendrán mejores resultados usando resistencias al 1 %). Aunque en el diagrama sólo se observa una resistencia por terminal o bit, en realidad usé los arreglos siguientes: RD7, 2 resistencias en paralelo de 1 kΩ; RD5, 2 resistencias en serie de 1 kΩ; RD4 3.9 kΩ en serie con 100 Ω; RD3 4.7 + 3.3 kΩ; RD2 15 + 1 kΩ; RD1 22 + 10 kΩ; y RD0 51 +13 kΩ.

En pocas palabras:

  • Un uno (5 V) en una terminal del puerto inyecta una corriente al circuito propuesto. La magnitud de dicha corriente depende de la resistencia que la terminal tiene conectada.
  • Un cero en una terminal del puerto no inyecta ni acepta corriente.
  • Las corrientes que se inyectan al circuito se suman al pasar todas a través de Rr, y esto produce el voltaje de salida.
  • En cada rama de entrada y en la única rama de salida, las corrientes y tensiones se pueden determinar por ley de Ohm.

Las características del amplificador operacional que hacen funcionar el circuito como lo acabo de describir son:

  • La diferencia de potencial entre las entradas se mantiene muy cercana a cero, por lo que la tensión de referencia aplicada en la entrada +, “se observa” o “se refleja” en la entrada –.
  • Prácticamente no circula corriente hacia el operacional a través de sus entradas, es decir, estos amplificadores tienen una impedancia de entrada muy alta.
  • Cuando los voltajes son mayores en la entrada – que en la entrada +, se producen salidas negativas.

Para que puedan corroborar sus cálculos y entendimiento del circuito, al aplicar un uno a la vez se obtendría:

  • 1000 0000. Corriente a través de R7 y Rr 10 mA, voltaje de salida – 2.2 V.
  • 0100 0000. Corriente a través de R6 y Rr 5 mA, voltaje de salida – 1.1 V.
  • 0010 0000. Corriente a través de R5 y Rr 2.5 mA, voltaje de salida – 0.55 V.
  • 0001 0000. Corriente a través de R4 y Rr 1.25 mA, voltaje de salida – 0.275 V.
  • 0000 1000. Corriente a través de R3 y Rr 0.625 mA, voltaje de salida – 0.1375 V.
  • 0000 0100. Corriente a través de R2 y Rr 0.3125 mA, voltaje de salida – 0.06875 V.
  • 0000 0010. Corriente a través de R1 y Rr 0.15625 mA, voltaje de salida – 0.034375 V.
  • 0000 0001. Corriente a través de R0 y Rr 0.078125 mA, voltaje de salida – 0.0171875 V.

Diseñé el circuito que se muestra en el siguiente diagrama para producir las señales cuadrada, triangular y sinusoidal. Las terminales 13 y 14 del microcontrolador están desconectadas, porque para estos ejemplos usé el oscilador interno; en un instrumento real, será mejor usar un oscilador a cristal, y mejor aún si éste está compensado ante cambios de la tensión de alimentación y temperatura.

Como su nombre lo indica cuadrada.asm es el código para el PIC16F887 que produce una señal cuadrada. Básicamente el programa entrega alternadamente un 0 y un 255 (0xFF) al puerto D. El tiempo que cada valor permanece en el puerto, se establece a partir del temporizador 0 – un contador que se desborda cada 256 eventos, un factor de escala para el oscilador de 1:256 y un oscilador de 4 MHz, llevan a un periodo de oscilación de aproximadamente 131 ms (ciclo de instrucción * 256 * 256 * 2 = 4 / 4000000 *256 *256 *2).

        #INCLUDE    "p16f887.inc"
 
        __CONFIG    _CONFIG1,023E4
        __CONFIG    _CONFIG2,03FFF
 
        ORG     0
        BSF     STATUS,RP0  ; Cambia al banco 1.
        CLRF    TRISD       ; Puerto D como salida.
        MOVLW   0D7         ; Escala 1:256 para el
        MOVWF   OPTION_REG  ; temporizador 0.
        BCF     STATUS,RP0  ; Regresa al banco 0.
        MOVLW   0FF         ; Para poner a 1 el puerto.
CICLO
        BTFSS   INTCON,T0IF ; Espera a que se desborde
        GOTO    CICLO       ; el temporizador 0.
        BCF     INTCON,T0IF
        CLRF    PORTD       ; Señal a 0.
UNO
        BTFSS   INTCON,T0IF
        GOTO    UNO
        BCF     INTCON,T0IF ; Espera nuevamente a que
        MOVWF   PORTD       ; se desborde TMR0.
        BCF     INTCON,T0IF
        MOVWF   PORTD       ; Señal a 1.
        GOTO    CICLO       ; Repite indefinidamente.
        END
Pantalla de osciloscopio mostrando la salida SAi (señal analógica invertida) cuando se ejecuta cuadrada.asm. Observen que: cada cuadro horizontal equivale a 25 ms, cada cuadro vertical equivale a 2 V; se dio la orden al osciloscopio de dibujar invertida la señal del canal 1.

El código para producir la señal triangular se muestra a continuación. Observen que durante el intervalo que la señal cuadrada mantiene un sólo valor en el puerto, triangular.asm debe aplicar 256 valores diferentes, por eso cambié el factor de escala para TMR0 de 1:256 a 1:1.

        #INCLUDE    "p16f887.inc"

        __CONFIG    _CONFIG1,023E4
        __CONFIG    _CONFIG2,03FFF

        UDATA   020
VALOR   RES     01	

        ORG     0
        BSF     STATUS,RP0  ; Cambia al banco 1.
        CLRF    TRISD       ; Puerto D como salida.
        MOVLW   0DF         ; Escala 1:1 para el
        MOVWF   OPTION_REG  ; temporizador 0.
        BCF     STATUS,RP0  ; Regresa al banco 0.
        CLRF    VALOR
SUBE                        ; Valor de la señal subiendo.
        BTFSS   INTCON,T0IF ; Espera a que se desborde
        GOTO    SUBE        ; el temporizador 0.
        BCF     INTCON,T0IF
        MOVF    VALOR,W     ; Entrega el dato al puerto.
        MOVWF   PORTD
        INCF    VALOR,F     ; Si corresponde,
        BTFSS   STATUS,Z    ; incrementa valor.
        GOTO    SUBE
        MOVLW   0FE
        MOVWF   VALOR
BAJA                        ; Valor de la señal bajando.
        BTFSS   INTCON,T0IF ; Espera a que se desborde
        GOTO    BAJA        ; el temporizador 0.
        BCF     INTCON,T0IF
        MOVF    VALOR,W     ; Entrega el dato al puerto.
        MOVWF   PORTD
        DECFSZ  VALOR,F     ; Decrece el valor.
        GOTO    BAJA
        GOTO    SUBE        ; Repite indefinidamente.
        END
Salida del DAC cuando el PIC ejecuta triangular.asm.
Acercamiento a los valores medios de la triangular. La discontinuidad que se observa se debe a que usé resistencias con tolerancia del 5 %.

Para producir la función sinusoidal use una tabla, es decir, usé una PC para hacer los cálculos y después almacené los resultados (200 valores) en la memoria de programa del PIC16F887. Aquí pueden descargar el código, que parcialmente, se muestra en seguida:

        #INCLUDE    "p16f887.inc"

        __CONFIG    _CONFIG1,023E4
        __CONFIG    _CONFIG2,03FFF

        UDATA   020
CONT    RES     01

        ORG     0
        BSF     STATUS,RP0  ; Cambia al banco 1.
        CLRF    TRISD       ; Puerto D como salida.
        MOVLW   0DF         ; Escala 1:1 para el
        MOVWF   OPTION_REG  ; temporizador 0.
        BCF     STATUS,RP0  ; Regresa al banco 0.
        MOVLW   01          ; Se prepara el uso de la
        MOVWF   PCLATH      ; tabla.
        CLRF    CONT        ; Inicia el contador.
CICLO
        BTFSS   INTCON,T0IF ; Espera desborde de TMR0.
        GOTO    CICLO
        MOVF    CONT,W      ; Obtiene el valor que debe
        CALL    TABLA       ; entregar al DAC.
        MOVWF   PORTD
        BCF     INTCON,T0IF
        INCF    CONT,F      ; Incrementa o reinicia el
        MOVLW   .200        ; contador.
        XORWF   CONT,W
        BTFSS   STATUS,Z
        GOTO    CICLO
        CLRF    CONT
        GOTO    CICLO       ; Repite indefinidamente.

        ORG     0100        ; Tabla con valores
TABLA                       ; sinusoidales que empieza
        ADDWF   PCL,F       ; en la dirección 0x100
        RETLW   .127        ; de la memoria de programa.
        RETLW   .131        ; Contiene 200 elementos
        RETLW   .135        ; previamente calculados
        RETLW   .139        ; en la PC.
        RETLW   .119
        RETLW   .123
        END
Señal producida con sinusoidal.asm.
Contenido en frecuencia de la señal anterior. Observen que predominan la componente de corriente directa (0 Hz) y otra cercana a los 20 Hz (en la figura anterior se observa que el osciloscopio determinó 19.6328 Hz).

Para que puedan comparar, en las dos figuras siguientes les muestro una señal producida con un generador de funciones (que sólo usa componentes analógicos) y su contenido de frecuencias.

Hasta este momento he cambiado arbitrariamente, y casi sin cuidado, el periodo de las señales. Esta situación deberá cambiar de acuerdo a la aplicación que le demos al convertidor. Para ejemplificar, generaremos una derivación de ECG a partir de un archivo almacenado en la PC.

Estableceremos la conexión con la PC a través de un convertidor USB-RS323, yo usé el qué está marcado aquí con el número 4.

Obtendremos el archivo de la MIT-BIH Arrhythmia Database (1) disponible a través de PhysioNet. Esa base de datos fue creada para hacer trabajos relacionados con arritmias, pero para nuestros fines, lo más relevante es que cada registro:

  • Dura aproximadamente 30 minutos.
  • Fue digitalizado a 360 muestras por segundo, 11 bits por muestra.
  • Cuenta con dos canales.
  • Cada grupo de 3 bytes contiene 2 muestras, una de cada canal o derivación. El primer byte contiene los 8 bits menos significativos de la muestra 1; el tercer byte contiene los 8 bits menos significativos de la muestra 2; el segundo byte contiene la parte más significativa de ambas muestras. Observen la figura siguiente.

Establecer correctamente el intervalo entre muestras implicó cambiar la base de tiempo del microcontrolador – el intervalo de muestreo (1/360 ≈ 0.002777778) es un número racional que no tiene una representación decimal exacta, y que tampoco se puede obtener de forma exacta a partir del conteo de intervalos de 1 us (base de tiempo al usar un oscilador de 4 MHz). Usé un cristal de 1.8432 MHz como se ve en la figura siguiente:

Recuerda que TMR0 puede contar la cantidad (entera) que se le programe hasta 256 eventos, y que cuenta con un pre-escalador, observa que (4*8*160)/1843200 = 1 / 360. Contar 160 eventos, factor de escala de 8, el oscilador principal se divide entre 4.

Llamé ecg.asm al programa para el PIC16F887, y su contenido es:

        #INCLUDE    "p16f887.inc"

        __CONFIG    _CONFIG1,023E1
        __CONFIG    _CONFIG2,03FFF

        UDATA   020
DATO    RES     01

        ORG     0
        GOTO    INI

        ORG     04          ; Se ejecuta cada vez que
        BCF     INTCON,T0IF ; se desborda TMR0.
        NOP                 ; Recarga TMR0 para lograr
        MOVLW   .97         ; el intervalo
        MOVWF   TMR0        ; correspondiente a 360 Hz.
        MOVF    DATO,W      ; Entrega el último dato
        MOVWF   PORTD       ; recibido al puerto D.
        MOVLW   't'         ; Envía byte de
        MOVWF   TXREG       ; sincronización.
RX	
        BTFSS   PIR1,RCIF   ; Espera el próximo dato
        GOTO    RX          ; y lo almacena.
        MOVF    RCREG,W
        MOVWF   DATO
        RETFIE

INI
        CLRF    PORTB
        BSF     STATUS,RP0
        CLRF    TRISD       ; Puerto D como salida.
        MOVLW   0D2         ; Escala 1:8 para TMR0.
        MOVWF   OPTION_REG
        MOVLW   026         ; Comunicación serie
        MOVWF   TXSTA       ; a 57600 bps.
        MOVLW   .1
        MOVWF   SPBRG
        BCF     STATUS,RP0
        MOVLW   090
        MOVWF   RCSTA
PC_LISTA    
        BTFSS   PIR1,RCIF   ; Espera orden de la PC
        GOTO    PC_LISTA    ; para iniciar.
        MOVF    RCREG,W
        BSF     INTCON,T0IE ; Habilita interrupción del
        BSF     INTCON,GIE  ; temporizador 0.
; Después de la configuración
; es la rutina de interrupción quien realiza la tarea.
CICLO       
        GOTO    CICLO       ; Espera interrupciones.
        END

GenECG.c es el programa para la PC, a continuación puedes ver su contenido, así como las instrucciones para compilarlo y ejecutarlo.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>

void main(int nargs, char *args[])
{
 int n, puerto, recibidos, dato;
 struct termios atributos;
 unsigned char aviso, grupo[3];
 FILE *archivo;
 
 if (nargs!=3)
 {
  printf("Error en el número de argumentos\n");
  exit(1);
 }
 puerto=open(args[1],O_RDWR|O_NOCTTY); // Abre el puerto
 archivo=fopen(args[2],"rb"); // Abre el archivo
 if (puerto<0 || archivo==NULL)
 {
  printf("Falla al abrir el puerto o el archivo\n");
  if (puerto<0) fclose(archivo);
  else close(puerto);
  exit(1);
 }
 printf("Usando el puerto: %s\n",args[1]);
 printf("Obteniendo datos de: %s\n",args[2]);
 printf("La ejecución durará 30 min aproximadamente, ");
 printf("pero se puede interrumpir con ctrl + c\n");
 // Lee configuración del puerto
 tcgetattr(puerto,&atributos);
 // Elimina los caracteres de control
 atributos.c_lflag&=(~ICANON);
 atributos.c_lflag&=(~ECHO); // Elimina el eco
 // Habilita lectura e ignora lineas de control, 
 // 8 bits a 57600 bps
 atributos.c_cflag=B57600|CS8|CREAD|CLOCAL;
 // Regresa inmediatamente de la operación 
 // de lectura con lo que encuentre
 atributos.c_cc[VMIN]=0; atributos.c_cc[VTIME]=0;
 // Permitir la transmisión y recepción
 // de los códigos del 0 al 255
 atributos.c_iflag&=~ICRNL;
 atributos.c_iflag&=~IXOFF;
 atributos.c_iflag&=~IXON;
 atributos.c_lflag&=~ISIG;
 atributos.c_oflag&=~OPOST;
 // Establece la nueva configuración
 tcsetattr(puerto,TCSANOW,&atributos);
 // Envía aviso de que la PC puede iniciar
 dato='i';
 write(puerto,&dato,1);
 // Procesa todo el archivo
 n=0;
 do
 {
  recibidos=read(puerto,&aviso,1);
  if (recibidos!=0)
  {
   // Obtiene 3 bytes del archivo
   fread(grupo,1,3,archivo);
   // Obtiene el valor del dato en el canal uno
   dato=grupo[1]&0xF;
   dato=(dato<<8)+grupo[0];
   // Escala el dato a 8 bits
   dato>>=3;
   // Saca el dato por el puerto
   write(puerto,&dato,1);
   n++;
  }
 } while(n<650000); // Repite hasta el final del archivo
 close(puerto); // Cierra el puerto
 fclose(archivo); // Cierra el archivo
}
gcc GenECG.c -o GenECG
./GenECG /dev/ttyUSB0 107.dat

Para obtener la siguiente figura:

  • Usé el registro 107, archivo 107.dat.
  • Conecté el circuito a la PC de donde obtuve 5 V (a través del módulo USB-RS232).
  • Conecté las fuentes de -12 y +12 V.
  • Ejecuté GenECG.
Pongan especial atención en el segundo latido (supongo que gracias a la TV todos reconocemos un latido en un ECG sin explicación alguna).

Para poder comparar obtuve la figura siguiente con la herramienta PhysioBank ATM, observen el último latido en el trazo superior (derivación MLII).

Cuando entren a PhysioBank ATM para ver esto, tendrán que seleccionar la base de datos, luego el registro, y finalmente avanzar el segmento que se muestra.

Espero que el material les resulte interesante, suficiente para que lo usen como base para algún proyecto, y nos compartan sus comentarios.

 

(1) Goldberger AL, Amaral LAN, Glass L, Hausdorff JM, Ivanov PCh, Mark RG, Mietus JE, Moody GB, Peng C-K, Stanley HE. PhysioBank, PhysioToolkit, and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals. Circulation 101(23):e215-e220 [Circulation Electronic Pages; http://circ.ahajournals.org/content/101/23/e215.full]; 2000 (June 13).

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *