Monday, 6 April 2015

Programa para graficar las lecturas de Arduino / Program to chart data from Arduino

Éste es un programa realizado con Visual Basic Express 2013 (entorno de programación gratuito y que te puedes bajar de la página de Microsoft) para poder visualizar y guardar los datos de tensión leídos a través de Arduino.

This is a program made with Visual Basic Express 2013 (free tool for programmers that you can download from the Microsoft web page) that is useful to see and save tension data read through Arduino.

El programa está en su segunda versión y por supuesto, como suele ser usual en estos casos, no me hago responsable de cualquier daño que pueda hacer el programa.

Elegí Visual Basic por la facilidad y rapidez con la que se puede hacer un programa para entorno Windows con él. Está todo muy "masticado". Me hubiese gustado utilizar otro lenguaje de programación como Phyton, aunque fuese por probar, pero a día de hoy estoy más acostumbrado a Visual Basic, por no decir que no tengo ni idea de Phyton. Tal vez algún día ...

Para Arduino el programa es:

/* 
Comunicación serial para Arduino
*/
int dato;

void setup(){
   Serial.begin(9600); // La misma velocidad de conexión debe ser usada en el programa de Windows
}

void loop(){
   dato=analogRead(A0); // Ver la siguiente imagen, donde se puede ver la entrada analógica utilizada.
   Serial.println(dato); // Envía el valor leído
   delay(1000);     
   /* Importante: "delay" debe tener el mismo valor que el intervalo de muestreo
   del programa de Windows (ver variable intMuestreo) */
}

Y las pruebas se pueden hacer por ejemplo con el siguiente conexionado (R es un potenciómetro. En mi caso usé uno de 100K), en este caso con la Arduino Mega 2560:

A0 se trata de una entrada analógica que leerá una señal de entre 0 y 5 V, convirtiéndolo a un valor proporcional de entre 0 y 1023, es decir, si el A0 nos está diciendo que tiene una señal de 614, por ejemplo, nos está diciendo que tiene una señal de aproximadamente 3V (exactamente será el resultado de 614·(5/1023)).

Para Visual Basic la GUI será algo como lo que sigue (captura de una de las primeras versiones):
 
 
O también algo como lo que sigue (se corresponde con la segunda versión):
 
 
Como se puede ver tiene un selector del puerto COM, puerto que se elegirá según sea a cual se conecta Arduino, un botón para conectar y desconectar (son el mismo botón, que cambia de texto y de color según sea su función), un área donde se representará las tensiones leídas a lo largo del tiempo, un botón de "Guardar como ... " por si se quiere guardar los datos leídos en una hoja *.csv (si se desconecta y se vuelve a conectar se seguirán guardando los datos donde se dejó) y además hay una serie de datos que se muestran en tiempo real (uno de ellos notareis que está en un formato distinto, pero es para probar una función distinta a la usada en los otros campos).

Para la GUI se eligen las siguientes propiedades (es posible que algunas se elijan en el propio programa y no directamente en el editor de la GUI):

Ventana principal
Name   Form1
Size      600; 400
Text     Conexión Serial 2015 (9600 bps)

Selector del puerto de conexión (herramienta ComboBox)
Name         cbxPuertos
Botón         conectar/desconectar (herramienta Button)
Name         btnConectar
Text           Conectar

Gráfica (herramienta Chart)
Name         chGrafica

Botón Guardar como (herramienta Button)
Name         btnGuardarComo

Caja de texto de la ruta para Guardar como
Name         tbxRuta
Multiline   True

Caja de texto de la última lectura
Name         tbxActual

Caja de texto del valor mínimo
Name         tbxMinimo

Caja de texto del valor máximo
Name         tbxMaximo

Caja de texto del valor medio acumulado
Name         tbxMedia

Caja de texto del numero de lecturas correctas
Name         tbxLecturas

Caja de texto del numero de lecturas incorrectas
Name         tbxIncorrectas

Y como elementos o herramientas insertadas no visibles:

Dialogo guardar como (herramienta SaveFileDialog)
Name         dlgGuardarComo

Cronometro o timer (herramienta Timer)
Name         tmrTimer

Comunicación o puertos serial (herramienta SerialPort)
Name         spPuertos

Para Visual Basic el programa es:

REM Programa realizado con fines formativos. Sin garantías de ningún tipo
REM Autor:JAIS
REM Fecha: 06/04/2015
REM Versión: 2
REM Función: Comunicación serial con Arduino y graficado de los datos procedentes de él
REM Nombre: ComunicacionSerial2015

'Para poder acceder al sistema de archivos
Imports System
Imports System.IO
Imports System.IO.StreamWriter

'Para poder dibujar el gráfico
Imports System.Windows.Forms.DataVisualization.Charting


Public Class Form1


    Const vConexion As Integer = 9600 'En esta ocasión no daremos opción de conectar a otras velocidades que no sean la usual
    Const intMuestreo As Integer = 1000 'En milisegundos (este valor debe ser el usado también en el programa de Arduino)
    Const digRedondeo As Integer = 2 'Redondearemos todos los números o lecturas a 2 decimales
    Const factorConv As Double = 5 / 1023 'Factor de conversión entre dato de Arduino (valor interno) y tension real de entrada.
    Const tensionMax As Double = 5 'Valor de la tensión máxima de entrada que permite Arduino
    Const nMaxMedidas As Integer = 200 'REVISAR PARA QUE AL RECONECTAR SE REINICIE EL CONTADOR

    'Se definen e inicializan las variables
    Dim valorMinimo As Double = tensionMax 'Se elige el valor máximo disponible para que salga lo que salga sea menor a este valor
    Dim valorMedio As Double = 0.0 'Valor medio de todos lso valores leidos hasta el momento
    Dim valorActual As Double = 0.0 'Último valor leido
    Dim cLecturas As Integer = 0 'Contador de lecturas correctas
    Dim cIncorrectas As Integer = 0 'Contador de lecturas incorrectas

    Dim strBufferIn As String = Nothing 'Se guardara en esta variable lo que se lea del puerto serial

    Dim ruta As String = Nothing 'Ruta del archivo donde se guardan las lecturas
    Dim archivo As Stream = Nothing 'Archivo donde se guardan las lecturas
    Dim strStreamWriter As StreamWriter = Nothing


    Private Sub Form1_FormClosing1(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing

        If ruta <> Nothing Then
            strStreamWriter.Close() 'Se ejecuta al cerrar el formulario.
            archivo.Close() 'Se cierra tambien el archivo
        End If

    End Sub  


    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' Para ahorrarse poner un monton de With. Notar que se pone gbxProgreso y no Me porque
        ' realmente los controles a los que nos referimos están dentro de ese grupo
        ' Así se inicializan todas las propiedas que son iguales de los textBox del grupo Progreso.
        '------------------------------------------------------------------------------------------------------------

        For Each thisTbx As TextBox In gbxProgreso.Controls.OfType(Of TextBox)()

            With thisTbx
                .Enabled = False 'Para que no se puedan introducir datos, sólo leerlos.
                .Text = "0" 'Por ejemplo. También podia poner n/a o similares.
                .BackColor = Color.White
                .TextAlign = HorizontalAlignment.Center
                .BorderStyle = BorderStyle.None
            End With

        Next thisTbx

        'Inicializar al cargar la ventana
        '------------------------------------

        tmrTimer.Enabled = False 'Debe estar desabilitado al principio
        tbxRuta.Enabled = False 'Siempre está deshabilitado
        tbxRuta.Text = "Aún no ha seleccionado ningún archivo"

        'Se cargan los puertos disponibles y se añaden al combobox
        '-----------------------------------------------------------------------

        cbxPuertos.Items.Clear() 'Se limpia el combobox

        For Each puertoDisponible As String In My.Computer.Ports.SerialPortNames
            cbxPuertos.Items.Add(puertoDisponible)
        Next

        If cbxPuertos.Items.Count > 0 Then
            cbxPuertos.Text = cbxPuertos.Items(0) 'Elegir el primer puerto de la lista como puerto visible
            btnConectar.Enabled = True
            btnGuardarComo.Enabled = True
            btnConectar.BackColor = Color.Red
            btnConectar.ForeColor = Color.White
        Else
            MessageBox.Show("Ningun puerto encontrado.")
            btnConectar.Enabled = False
            btnGuardarComo.Enabled = False
            cbxPuertos.Items.Clear()
            cbxPuertos.Text = ("")
        End If

        'Se acondiciona la gráfica (la ventana del gráfico donde se representan los datos leídos).
        '--------------------------------------------------------------------------------------------------------

        With chGrafica

            .Legends("Legend1").Enabled = False 'Legend1 es el nombre por defecto que le da Visual a la leyenda de la gráfica. En principio sólo una si no se especifica otra cosa.
            .Series("Series1").ChartType = SeriesChartType.Line 'Series1 es el nombre por defecto que el ada Visual a la serie de datos. En principio sólo una si no se añaden más.
            With .ChartAreas("ChartArea1") 'También se puede poner ... ChartAreas(0)
                .AxisX.Interval = 2
                .AxisX.LabelStyle.Angle = 0
                .AxisX.Maximum = 10
                .AxisX.Title = "Medida nº"
                .AxisY.Title = "Tensión [V]"
                .AxisX.MajorGrid.Enabled = False
                .AxisY.MajorGrid.LineColor = Color.LightBlue
            End With

        End With

    End Sub


    Private Sub btnGuardarComo_Click(sender As Object, e As EventArgs) Handles btnGuardarComo.Click

        'Ventana de dialogo Guardar Como
        '------------------------------------------

        dlgGuardarComo.Filter = _
            "Archivo de valores separados por punto y coma (*.csv)|*.csv|Archivos de texto (*.txt)|*.txt|Todos los archivos (*.*)|*.*"
        dlgGuardarComo.ShowDialog()
        ruta = dlgGuardarComo.FileName 'Incluye toda la ruta de acceso, no solo el nombre del archivo

        If ruta <> Nothing Then
            tbxRuta.Text = ruta
            Try
                If File.Exists(ruta) Then
                    archivo = File.Open(ruta, FileMode.Truncate) 'Para que no se mezclen datos primero hay que eliminar los datos anteriores
                Else
                    archivo = File.Create(ruta)
                End If
                strStreamWriter = New StreamWriter(archivo, System.Text.Encoding.Default)
            Catch ex As Exception
                MessageBox.Show("Problemas al crear/abrir el archivo. Acceso denegado")
            End Try
        End If

    End Sub


    Private Sub btnConectar_Click(sender As Object, e As EventArgs) Handles btnConectar.Click

        'Cambia el texto del botón conectar según estemos conectados o no.

        If btnConectar.Text = "Conectar" Then
            conectar()
        Else
            desconectar()
        End If

    End Sub


    Private Sub tmrTimer_Tick(sender As Object, e As EventArgs) Handles tmrTimer.Tick

        strBufferIn = spPuertos.ReadExisting 'Guardamos en nuestra variable lo que hay en el puerto, si es que hay algo
        Static Dim valorMaximo As Double = 0.0

        'Sólo si hay una lectura positiva se ejecuta el codigo del programa
        If strBufferIn <> "" Then

            Try
                If Not IsNumeric(strBufferIn) Then
                    Throw New Exception("Lectura: " & strBufferIn)
                End If
                valorActual = CDbl(strBufferIn) * factorConv 'Lo metemos en un try por si no es posible convertir el valor que llega de arduino

                'Se convierte a voltios (máximo 5 V) el valor dado por Arduino (máximo 1023)
                tbxActual.Text = CStr(Math.Round(valorActual, digRedondeo))
                'Verificar si es el valor mínimo
                If valorActual < valorMinimo Then
                    valorMinimo = valorActual
                    tbxMinimo.Text = CStr(Math.Round(valorMinimo, digRedondeo))
                End If

                'Verificar si es el valor máximo
                If valorActual > valorMaximo Then
                    valorMaximo = valorActual
                    tbxMaximo.Text = String.Format("{0:E}", Math.Round(valorMaximo, digRedondeo))
                End If

                'Calcular el valor medio
                cLecturas += 1
                tbxLecturas.Text = cLecturas.ToString()
                If cLecturas > 1 Then
                    valorMedio = (cLecturas * valorMedio + valorActual) / (cLecturas + 1)
                Else
                    valorMedio = valorActual
                End If
                tbxMedia.Text = Math.Round(valorMedio, digRedondeo).ToString()
                If cLecturas > 10 Then
                    With chGrafica.ChartAreas(0).AxisX
                        .Minimum = .Minimum + 1
                        .Maximum = .Maximum + 1
                    End With
                End If
                chGrafica.Series("Series1").Points.AddY(valorActual)
                If ruta <> Nothing Then
                    strStreamWriter.WriteLine(tbxLecturas.Text & ";" & tbxActual.Text)
                End If
            Catch ex As Exception
                tbxActual.Text = ex.Message
                cIncorrectas += 1
                tbxIncorrectas.Text = CStr(cIncorrectas)
            End Try

            strBufferIn = ""
            spPuertos.DiscardInBuffer() 'Se limpia el buffer de entrada para que no registre más datos o los repita

            If cLecturas = nMaxMedidas Then
                desconectar()
            End If

        End If

    End Sub


    Sub desconectar()

        With btnConectar 'Se cambia el botón de conectar a estado de desconectado
            .Text = "Conectar"
            .BackColor = Color.Red
            .ForeColor = Color.White
        End With

        tmrTimer.Enabled = False
        spPuertos.Close() 'Se cierra la conexión pero no el archivo, por si hay que volver a escribir al reconectarse.

    End Sub


    Sub conectar()

        With btnConectar 'Se cambia el botón de conectar a estado de conectado
            .Text = "Desconectar"
            .BackColor = Color.LightGreen
            .ForeColor = Color.Black
        End With

        With spPuertos 'Se abre el puerto de conexion
            .PortName = cbxPuertos.Text
            .BaudRate = vConexion
            .Open()
        End With

        With tmrTimer 'Se habilita el timer
            .Enabled = True
            .Interval = intMuestreo 'En milisegundos
        End With

    End Sub


End Class


Y finalmente el resultado es: