r36 - 16 Jun 2006 - 14:45:14 - PabloHoffmanYou are here: pablohoffman.com >  Oscusb Web  >  OscusbDocumentacion > DocCap06Software

Capítulo 6. Software

Introducción

Este capítulo trata sobre la implementación del software, que es la aplicación utilizada en el PC para controlar el osciloscopio. Incluye información sobre el diseño, selección de las herramientas, métodos de programación empleados, estructura del código e funcionamiento del software.

Selección de las herramientas

La primera decisión a la hora de desarrollar la aplicación para controlar el osciloscopio fue la selección de las herramientas a utilizar, en particular:

  • el lenguaje de programación a usar
  • el toolkit gráfico (librería para el manejo de ventanas y creación de la interfaz gráfica)
  • las librerías a utilizar para controlar diversos aspectos de la aplicación
  • el entorno de desarrollo a utilizar para crear la aplicación gráfica

Lenguaje de programación

Para tomar la decisión del lenguaje a utilizar para la elaboración del sofate del software gráfico que se instalará en el PC para el control del osciloscopio desarrollado se han tenido en cuenta los siguientes requisitos:

Los siguientes fueron los requisitos tenidos en cuenta:

  1. Debe tener interfaz gráfica
  2. Debe ser multi-plataforma (Windows, Linux, Mac OS X)
  3. Debe ser de rápido desarrollo
  4. Debe poder comunicarse con puertos virtuales de Windows (para hablar con el driver RS232-USB de CDC de Microchip) de forma sencilla
  5. Debe ser un lenguaje gratuito que no requiera de licencias para compilar o distribuir el código compilado

El lenguaje que seleccionas para programar la aplicación fue python, ya que es un lenguaje robusto y extremadamente portable (multi-plataforma, requisito 2) puesto que existen interpretadores para todas las plataformas, dos de las cuales (Linux y Mac OS X) ya lo traen incluido dentro del propio sistema operativo. Además, es un lenguaje interpretado lo cual elimina los tediosos ciclos de edición-compilación-enlazado-ejecución y permite un desarrollo rápido y ágil (requisito 3). Por eso es comúnmente utilizado en el prototipado de aplicaciones, y lo hace ideal para nuestro caso.

Además python cuenta con una extensión llamada pySerial que permite el acceso transparente a puertos series (virtuales o reales) de Windows y, a la vez, a puertos series en linux, lo cual nos permite desentendernos del problema de la comunicación (requisito 4).

Otras aplicaciones como Java, si bien son portables, exigen más tiempo de diseño e implementación (pues el lenguaje es más estricto) y requieren tener instalada una máquina virtual Java (JRE o JDK) en la máquina del cliente donde se vaya a correr la aplicación. Al hacerlo en python se puede evitar esa restricción compilando la aplicación para que corra nativamente en cada arquitectura y sistema operativo.

La plataforma .Net de Microsoft no es portable para sistemas operativos fuera de windows y además su licencia tiene algunas restricciones respecto al código generado con ella, por lo cual fue descartada inmediatamente.

Toolkit gráfico

En cuanto al toolkit gráfico a decisión no fue tan sencilla.

Las bindings más populares disponibles para python son:

  • tkInter - para trabajar con el toolkit tk
  • GTK+ - utilizando los bindings PyGTK
  • qt - utilizando los bindings PyQt
  • wxWidgets - utilizando los bindings wxPython

tkinter

Tk es el toolkit nativo de python para trabajar con interfaces gráficas, pero la razón de esto es que era el mejorcito que existía en el momento que python fue diseñado, y de eso hace mucho tiempo. Tk tiene dos grandes desventajas: requiere mucho código para implementar cosas simples y ademas no es muy presentable. Por estas razones, tkinter fue descartado inmediatamente.

GTK+

A través de los bindings PyGTK se puede utilizar el popular toolkit GTK+ de la Free Software Foundation. Sin embargo lo descartamos por las siguientes razones:

  • GTK no es muy lindo estéticamente en Windows
  • GTK no tiene implementación para Mac OS X
  • GTK no tiene un buen entorno rápido de desarrollo (RAD) para el diseño de ventanas

qt

Qt es muy portable (Windows, Linux, Mac) y estéticamente muy prolijo. Sin embargo, lo descartamos puesto que su licencia es ligeramente restrictiva para aplicaciones Windows (no así para Linux/Mac) y además la versión disponible actualmente (8 Feb 2006) de PyQt (los bindings de qt para python) no soporta la última versión de qt (4.x) sino que solo soporta hasta la 3.x, y además no soporta python 2.4 (hasta 2.3).

wxWidgets

wxPython usa el toolkit wxWidgets que es bastante prolijo y muy portable (Windows, Linux y Mac). Además tiene las siguientes ventajas:

  • cuenta un muy buen diseñador rápido de ventanas (llamado Boa Constructor) que es gratuito y libre
  • tiene una excelente documentación
  • tiene una gran popularidad entre los toolkit GUI para python, lo cual significa que tiene un gran soporte comunitario (foros, etc)

Por estas razones es que decidimos adoptar wxPython como toolkit gráfico para la aplicación del osciloscopio.

Librerías

Otras librerías a utilizar son las siguientes:

pySerial

La librería pySerial permite comunicarse con los puertos de la PC de forma transparente, sencilla y (lo que es más importante) independiente de la plataforma.

pyWin32

La librería pyWin32 son bindings para poder acceder a la API de win32 desde python. La razón por la cual la usamos es que el pySerial la necesita para acceder a los puertos serie en Windows.

NumPy

La librería NumPy provee funciones para calculo de transformada discreta de Fourier (FFT) la cual es usada para mostrar el espectro de la señal en el software.

Entorno de desarrollo

El entorno de desarrollo utilizado para crear la aplicación gráfica fue el Boa Constructor. Entre sus características se encuentra el desarrollo rápido (drag and drop) de ventanas y sus componentes (botones, barras de estado, etc), como así también la posibilidad de depurar el programa gráfico utilizando breakpoints y las herramientas típicas de depuración.

Un dato curioso es que, además de ser un entorno de desarrollo para wxPython, el Boa Constructor también está escrito en python utilizando wxPython.

Compilador

Para la plataforma Windows en particular se utilizó el compilador py2exe para compilar la aplicación a un archivo ejecutable (EXE) que no dependiera de ninguna otra librería externa brindándole así al osciloscopio una gran portabilidad y facilitando su distribución.

Para los sistemas operativos Linux y Mac esto no es necesario puesto que dichos sistemas operativos ya vienen con python integrado y puede correr cualquier programa escrito en ese lenguaje, sin necesidad de compilarlo previamente. Esto incluso es una gran ventaja puesto que, al no tener que ser compilado, la aplicación es independiente de la arquitectura. De esta forma, tenemos una aplicación que es, a la vez, multi-plataforma e independiente de la arquitectura. Todo gracias a python.

Resumen

Este es un resumen del lenguaje, librerías y herramientas utilizadas para desarrollar el software:

Lenguaje python
Librerías pySerial
NumPy
pyWin32 (solo windows)
Entorno de desarrollo Boa Constructor
Compilador py2exe (solo Windows)
Tabla 6.1 Herramientas de desarrollo del software

Estructura del software

Clases

Dado que python es un lenguaje preferentemente orientado a objetos, el software se compone de varias clases, las cuales a su vez contienen métodos que son las funciones encargadas de implementar las diferentes funcionalidades. A continuación se presenta un resumen de las mismas. Las clases que componen el software son las siguientes:

Osc

La clase Osc es la encargada de la comunicación con el osciloscopio. Implementa el protocolo de comunicación (como se encuentra especificado en el capítulo 7) proveyendo primitivas para el envío y recepción de comandos al osciloscopio.

El constructor de la clase acepta como parámetro el puerto serie donde se encuentra el osciloscopio.

Las funciones de esta clase son:

  • Clase Osc
    • connect() intenta conectarse al osciloscopio. Devuelve TRUE en caso de lograrlo, o FALSE en caso contrario.
    • getVersion() devuelve la versión de firmware del osciloscopio
    • getPort() devuelve el puerto por el cual se encuentra conectado el osciloscopio
    • getData() devuelve los datos obtenidos por el último comando de datos (ej. AQHI, AQME, AQLO)
    • getSample() se bloquea hasta recibir un byte del osciloscopio y, una vez recibido, lo devuelve. Utilizado para capturas de baja velocidad en tiempo real
    • stop() envía un comando STOP al osciloscopio y limpia el buffer de entrada
    • acquire(type, chan, vdv1, vdv2, dual, chop) solicita una captura de datos al osciloscopio. Una vez finalizada la misma retorna TRUE (si la captura fue exitosa) o FALSE si hubo algún error. Para obtener los datos capturas usar la función getData(). Los parámetros que acepta esta función son los siguientes:
      • type - el tipo de captura a realizar (AQHI, AQME, AQLO)
      • chan - el canal a utilizar (1 ó 2)
      • vdv1 - el divisor vertical a usar en el canal 1 (0, 1, 2 ó 3)
      • vdv2 - el divisor vertical a usar en el canal 2 (0, 1, 2 ó 3)
      • dual - habilita (1) o deshabilita (0) el modo dual
      • chop - habilita (1) o deshabilita (0) el modo chop
    • debug() escribe en un archivo el texto pasado como parámetro. Utilizado para dejar un registro de la comunicación con el osciloscopio con fines depurativos.
    • send(cmd, param, retry) envía un comando al osciloscopio. Los parámetros que acepta son:
      • cmd - el comando a enviar (string de 4 caracteres, obligatorio)
      • param - el parámetro del comando (número entero, opcional)
      • retry - indica si debe reintentar enviar el comando en caso de error (por defecto es TRUE).
    • disconnect() se desconecta del osciloscopio

Frame1

La clase Frame1 es la encargada de desplegar la ventana del osciloscopio en pantallas, así como también de llevar a cabo todas las funciones de interacción con el usuario.

Las funciones de esta clase son las siguientes:

  • cleantrigger() aplica el trigger por software descartando las muestras irrelevantes del principio
  • triggerlevelup() rutina que implementa el algoritmo de trigger por software. Es utilizada por la función cleantrigger.
  • updatevalues() actualiza los parámetros numéricos de la señal (voltaje medio, voltaje pico-pico y voltaje eficaz)
  • updatefft() actualiza los análisis espectrales de las señales
  • updatedivs() actualiza los valores seleccionables de divisiones de voltaje para la vista actual (en modo dual separado dichos valores se duplican)
  • updatetriggers() actualiza los valores seleccionable de los triggers de voltaje
  • updatezeros() actualiza el nivel de los ceros en el display del osciloscopio. Dicho nivel depende del modo dual y de si se está utilizando etapa de entrada
  • savetofile() guarda las muestras recibidas en el archivo especificado en la interfaz gráfica
  • acqstart() inicia el proceso de captura de datos. Es ejecutada al activar el botón de captura.
  • acqstop() detiene el proceso de captura. Es ejecutada al desactivar el botón de captura.
  • connectosc() intenta conectarse al osciloscopio barriendo todos los puertos disponibles en la PC. Devuelve TRUE si pudo conectarse, o FALSE en caso contrario.
  • doconnect() se conecta al osciloscopio utilizando la función connectosc() y actualiza los controles de la aplicación según la conexión allá sido exitosa o no.

Además de las funciones ya mencionadas esta clase tiene otras rutinas especiales que son disparadas por eventos de la aplicación, desde la pulsación de un botón hasta la llegada de los datos del osciloscopio. En particular, las más importantes son las siguiente:

  • OnOscDisplayPaint() es disparada cuando el display necesita actualizarse, ya sea porque la ventana se movió o porque llegaron nuevas muestras del osciloscopio
  • OnAqcuireData() es disparada cuando llegan nuevos datos del osciloscopio. Se encarga de actualizar todos los displays y valores a través de las funciones update*

AcquireThread

La clase AcquireThread es la encargada de llevar a cabo la captura de datos. Corre en forma paralela a la clase Frame1 y se comunica con ésta a enviándole mensaje, los cuales también son clases (AcquireEvent).

Esta clase tiene una única función (run()) en donde se realiza la comunicación con el osciloscopio (a través de la clase Osc) y se ejecuta toda la lógica necesaria para procesara una nueva secuencia de muestras del osciloscopio.

AcquireEvent

La clase AcquireEvent es el tipo de objeto utilizado para el transporte de las muestras del osciloscopio entre la clase AcquireThread y la clase Frame1. Por consiguiente, carece de funciones.

Archivos

La estructura del código fuente es bastante simple y consta de los siguientes archivos:

  • oscapp.py - este es el código que dispara toda la aplicación. Es el que llama a la ventana (ver clase Frame1) para controlar el programa
  • oscframe.py - aquí está el código que define el comportamiento, la apariencia y el funcionamiento de la ventana de la aplicación. En este archivo se encuentran implementadas las clases: Frame1, AcquireThread y AcquireEvent
  • osc.py - aquí se encuentra el código de comunicación con el osciloscopio. En este archivo está implementada la clase Osc
  • setup.py - este archivo no es código del progama, sino que son las directivas utilizadas para compilar el programa en Windows utilizando el compilador py2exe

Funcionamiento del software

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.

Control de la interfaz gráfica

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 (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).

Conexión con el osciloscopio

La comunicación con el osciloscopio se realiza a través de la clase provista por el driver (oscctrl.py) al dispararse ciertos eventos.

Actualmente la aplicación sigue el siguiente mecanismo para conectarse con el osciloscopio:

  1. Intenta conectarse al primer puerto serie disponible en el PC (COM1 en caso de Windows, /dev/ttyACM0 en caso de linux)
  2. Si logra conectarse envía un comando VERS (ver Capítulo 7 - protocolo de comunicación), de lo contrario prueba con el siguiente puerto disponible que encuentra
  3. Si obtiene respuesta al comando VERS, entonces considera que la comunicación con osciloscopio se ha realizado exitosamente y despliega la versión de firmware del mismo (la cual fue obtenida en la respuesta del comando VERS).

Este 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:

  1. la solución es trivial consiste simplemente en colocar (en la aplicación) un selector del puerto serie a utilizar para el osciloscopio.
  2. la solución prolija consiste en desarrollar un driver USB personalizado para el osciloscopio que no involucre puertos serie virtuales de por medio.

El único otro momento donde el programa se comunica con el osciloscopio es al pulsar el botón Capturar en el cual envía un comando de captura, precedido por los comandos de configuración de los parámetros de captura (HDIV, DUAL, etc).

Desplegado de muestras en pantalla

La graficación de las muestras recibidas del osciloscopio es realizada a través del objeto wxDC de la librería wxWidgets el cual (a diferencia de usar el API de win32, por ejemplo) lo hace independiente de la plataforma.

A continuación se muestra un esbozo reducido de la rutina de graficación:

    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 esperando los datos del osciloscopio hasta que estos llegan, para finalmente los graficarlos. El problema es que, como el tiempo de transferir los datos del osciloscopio al PC es mucho mayor comparado con el resto de los tiempos, el programa está continuamente esperando datos del osciloscopio y mientras esto sucede la ventana no responde lo cual resulta en una efecto extremadamente molesto para la usuario y deja la aplicación inusable.

Para solucionar este problema se utilizó la tecnología de hilos en el cual, al presionar el botón de captura la aplicación dispara una especie de sub-programa (llamado 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 suceso al programa principal a través de un evento, con el cual también le transfiere los datos. La aplicación principal, al recibir el evento con los datos, lo único que tiene que hacer es extraer los datos y graficarlos, lo cual es un proceso muy rápido y por lo tanto la ventana no queda congelada.

Como ya se mencionó anteriormente, python es un lenguaje fuertemente orientado a objetos. 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 siguiente diagrama se muestra la interacción que ocurre entras las diferentes clases del software, para llevar a cabo la adquisición de datos del osciloscopio.

oscusb-sw-captura.gif
Fig 6.1 Interacción entre las clases del software

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.

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. Solo 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.

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, AcquireEvent(data))
        else:
            wx.PostEvent(self._notify_window, AcquireEvent(None))

De particular interés son las lineas 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

Notar 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.

Manual de uso

El manual de uso del software se encuentra en el capítulo 9.

Código fuente

El código fuente del software se encuentra disponible para bajar en la siguiente página:

http://pablohoffman.com/oscusb/software/

Referencias

toggleopenShow attachmentstogglecloseHide attachments
Topic attachments
I Attachment Action Size Date Who Comment
gifgif oscusb-sw-captura.gif manage 32.3 K 12 Jun 2006 - 04:34 PabloHoffman Interacción entre las clases del software
zipzip oscusb-sw-captura.zip manage 368.8 K 12 Jun 2006 - 04:55 PabloHoffman Interacción entre las clases del software - fuentes visio
Edit | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r36 < r35 < r34 < r33 < r32 | More topic actions
Oscusb.DocCap06Software moved from Oscusb.DocCap07Software on 27 May 2006 - 21:13 by PabloHoffman - put it back

Osciloscopio USB


Esta Web
Cronograma
  • DONE planificación
  • DONE análisis
  • DONE presentación oral
  • DONE diseño
  • DONE implementación
  • DONE documentación
  • DONE imprevistos
  • DONE defensa
  • MOVED TO... correcciones
 
Powered by pablohoffman.com
This site is powered by the TWiki collaboration platformCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding pablohoffman.com? Send feedback