MONTAJE
Osciloscopio por USB de 40MHz Séptima Parte:
Estructura del Software Conclusión En esta sección estamos brindando la expli cación paso a paso del funcionamiento e implementación de un osciloscopio de 40MHz para utilizarlo en una computadora, a través de su puerto USB, estamos en condi ciones de comenzar a describir el software empleado. Aclaramos que estamos realizan do la descripción completa de este equipo desde hace varias ediciones y que ya publi camos los temas referentes al hardware y firmeware del equipo. A continuación expli caremos “cómo funciona el software”.
Por: Pablo Hoffman y Martín Szmulewicz http://www.pablohoffman.com Introducción Una programa gráfico en wxPython consta de una Aplicación y varios Frames, que pertenecen a ella. Estos frames son justamente las diferentes ventanas de la aplicación. Como en nuestro caso el software tiene una sola ventana, el mismo tiene un solo Frame. Al disparar la aplicación (oscusb.py), ésta abre el frame por defecto (oscframe.py) que es la ventana que se ve cuando se ejecuta el programa. El comunicación con el osciloscopio se realiza a través del driver que se encuentra implementado en el archivo oscctrl.py como una clase de python y es utilizado desde oscframe.py para enviar comando y recibir datos.
(botones, etiquetas, cuadro para ingresar texto, etc) en coordenadas específicas. Las coordenadas pueden darse en pixels o en proporciones, lo cual permite que la ventana puede mantener su aspecto al ser maximizada o cambiada de tamaño. A los diferentes controles (por ejemplo, botones) se les define un comportamiento a través de eventos que son disparados cuando se realiza una acción sobre ellos (por ejemplo, pulsar un botón). Al ser disparados, dichos eventos llaman a una función especificada predefinida al crear el Frame. Asimismo, otros controles son de salida (por ejemplo, el display del osciloscopio) los cuales pueden ser modificados arbitrariamente desde el código aplicación a través de métodos que éstos proveen (por ejemplo, dibujar una línea, cambiar el color de fondo, etc).
Control de la Interfaz Gráfica Conexión con el osciloscopio La creación de la ventana se realiza (al igual que en cualquier aplicación gráfica) creando un cuadro (o Frame) el cual controla el funcionamiento de la ventana de la aplicación gráfica. A dicho cuadro se le asignan controles
Saber Electrónica 76
La comunicación con el osciloscopio se realiza a través de la clase provista por el driver (oscctrl.py) al dispararse ciertos eventos.
Estructura del Software de un Osciloscopio de 40MHz Actualmente la aplicación sigue el siguiente mecanismo para conectarse con el osciloscopio: Intenta conectarse al primer puerto serie disponible en el PC (COM1 en caso de Windows, /dev/ttyACM0 en caso de linux). Si logra conectarse envía un comando VERS (ya lo veremos en detalle cuando desarrollemos el tema: “pro tocolos de comunicación”), de lo contrario prueba con el siguiente puerto disponible que encuentra. Si obtiene respuesta al comando VERS, entonces considera que la comunicación con osciloscopio se ha realizado exitosamente y despliega la versión de firmwa re del mismo (la cual fue obtenida en la respuesta del comando VERS). Este mecanismo de barrido fue por un razón de comodidad ya que, debido a que osciloscopio genera dinámicamente un puerto serie virtual cada vez que se conecta, éste no siempre era el mismo, aún cuando se conectase el osciloscopio en el mismo puerto USB. Por lo tanto, el barrido nos ahorró el tiempo de estar buscando y configurando el puerto virtual del osciloscopio. Sin embargo, a pesar de contar con dichas ventajas, reconocemos que el mecanismo de barrido debe ser sustituido en el producto final puesto que, al enviar información a todos los puertos, puede interferir con el funcionamiento de algún dispositivo conectado al PC por puerto serie. Para este problema existen dos soluciones, una trivial y una prolija:
size = dc.GetSize() dc.SetPen(wx.GREEN_PEN) top = min(len(data), size.x) lastx, lasty = 0, 0 vdiv = vdivs[self.vdivch.GetSelection()] for i in range(0, top): val = (data[i] - 128) / 255 / vdiv x=i y = int(size.y/2*val) if x > 0: dc.DrawLine(lastx, lasty, x, y) lastx = x lasty = y dc.SetPen(wx.NullPen) Se puede observar que se utilizan las funciones SetPen y DrawLine del objeto wxWC que permiten seleccionan el color del trazo y dibujar una línea entre dos puntos, respectivamente.
Comunicación con el Osciloscopio
En particular, resulta de especial importancia comentar sobre el uso de hilos para la comunicación con el osciloscopio, ya que de lo contrario la aplicación funcionaría muy lenta y poco responsiva. Esto es porque, al activar la captura el programa está continuamente enviando comandos de captura al osciloscopio y recibiendo sus datos. Entonces, si esto se hace en primer plano (que es la forma trivial de hacerla) el programa queda colgado La solución es trivial y consiste simplemente en colo - esperando los datos del osciloscopio hasta que éstos llecar (en la aplicación) un selector del puerto serie a utili - gan, para finalmente los graficarlos. El problema es que, zar para el osciloscopio. como el tiempo de transferir los datos del osciloscopio a La solución prolija consiste en desarrollar un driver la PC es mucho mayor comparado con el resto de los USB personalizado para el osciloscopio que no involucre tiempos, el programa está continuamente esperando puertos serie virtuales de por medio. datos del osciloscopio y mientras esto sucede la ventana no responde, lo cual resulta un efecto extremadamente El único otro momento donde el programa se comu- molesto para el usuario y deja la aplicación inusable. nica con el osciloscopio es al pulsar el botón Capturar en Para solucionar este problema se utilizó la tecnología el cual envía un comando de captura, precedido por los de hilos en el cual, al presionar el botón de captura la comandos de configuración de los parámetros de captu- aplicación dispara una especie de sub-programa (llamara (HDIV, DUAL, etc). do hilo) que corre paralelamente a la aplicación y se encarga de comunicarse con el osciloscopio para adquirir los datos y, una vez que los obtiene, notifica del suceDesplegado de Muestras en Pantalla so al programa principal a través de un evento, con el cual también le transfiere los datos. La graficación de las muestras recibidas del oscilosLa aplicación principal, al recibir el evento con los copio es realizada a través del objeto wxDC de la librería datos, lo único que tiene que hacer es extraer los datos y wxWidgets, el cual (a diferencia de usar el API de win32, graficarlos, lo cual es un proceso muy rápido y por lo por ejemplo) lo hace independiente de la plataforma. tanto la ventana no queda congelada. A continuación se muestra un esbozo reducido de la Como ya se mencionó anteriormente, python es un rutina de graficación: lenguaje fuertemente orientado a objetos.
Saber Electrónica 77
Montaje Figura 1
Por lo cual, en el escenario de captura antes descripto, cada rol es cumplido por una clase. Ellas son: El funcionamiento del programa principal está a cargo de la clase Frame1. El sub-programa que se corre en un hilo corre es la clase AcquireThread . El evento que genera el hilo (AquireThread) y lo envía al programa principal (Frame1) es la clase AcquireEvent Finalmente, la clase encargada de comunicarse con el osciloscopio es Osc. En el diagrama de la figura 1 se muestra la interacción que ocurre entras las diferentes clases del software, para llevar a cabo la adquisición de datos del osciloscopio. Los números rojos indican (de forma ordenada) todos los pasos ejecutados por cada una de las clases, para obtener una secuencia de muestras del osciloscopio y desplegarlas en pantalla.
Saber Electrónica 78
Esto ocurre cuando el usuario activa el botón Capturar de la interfaz y continúa corriendo ininterrumpidamente hasta que el botón es desactivado. Por lo tanto, mientras el botón Capturar esté activado, los pasos del 1 al 8 se ejecutan cíclicamente. Como puede verse también en el diagrama, la clase Frame1 (que es la controla el funcionamiento de la interfaz) sigue interactuando con el usuario independientemente del resto de las clases. Sólo interrumpe brevemente el control de la interfaz cuando llega el evento AquireThread, del cual extrae los datos y los grafica en la pantalla. Si el botón Capturar sigue activado, inmediatamente dispara un nuevo hilo de captura (AquireThread) y regresa a su tarea de controlar la interfaz. Como el proceso de atender el evento y desplegar los datos ocurre muy rápido, el usuario no se percata de la demora y obtiene la impresión de que la interfaz nunca se cuelga, que es justamente el objetivo de usar los hilos.
Estructura del Software de un Osciloscopio de 40MHz AcquireEvent(data)) else:
Hilo de Captura Todo el funcionamiento mencionado en la sección anterior se realiza utilizando la clase Thread de python y extendiéndola. Esto es lo que hace la clase AcquireThread, que hereda de la clase Thread. El código de la clase AcquireThread es muy simple y se muestra continuación: class AcquireThread(Thread): def __init__(self, notify_window): Thread.__init__(self) self._notify_window = notify_window def run(self): win = self._notify_window osc = win.osc hd = win.hdivch.GetSelection() size = 512 if win.osc.acquire(count=size): chardata = win.osc.getData() data = [] for c in chardata: data.append(ord(c)) wx.PostEvent(self._notify_window,
wx.PostEvent(self._notify_window, AcquireEvent(None)) De particular interés son las líneas wx.PostEvent donde se dispara el evento una vez terminada la captura de datos. En caso de haber algún error (segunda línea de wx.PostEvent) el hilo envía None en lugar de los datos lo cual notifica al programa principal que hubo un error al capturar los datos. Para la notificación y comunicación de datos entre el hilo de captura y la aplicación se utiliza la clase wx.pyEvent de wxPython, extendiéndola para que permita enviar los datos dentro de si misma. El código de dicha clase se presenta a continuación: class AcquireEvent(wx.PyEvent): def __init__(self, data): wx.PyEvent.__init__(self) self.SetEventType(EVT_RESULT_ID) self.data = data Note que aquí la única extensión que se le hizo a la clase base (wx.PyEvent) fue la de agregarle un campo de datos (data) el cual es usado para transportar los datos del osciloscopio. ✪
Saber Electrónica 79