Lectura y Escritura en Puertos Serie con .Net Framework 2.0
Por Vinicio Montero
Descargar ejemplos de este artículo (113 KB).
Contenido
Introducción
Acceso a la Lista de Puertos del Sistema
Configuración, Apertura y Cierre del Puerto
Transmisión de Datos
Recepción de Datos
Código del Usuario
Conclusión
Referencias
Introducción
En este artículo se muestra la simplicidad con la cual se pueden manipular los Puertos Seriales utilizando el Framework 2.0. Aquí veremos, además, la utilización de algunas de las nuevas funciones y namespaces que proveen acceso a los recursos del sistema sin necesidad de invocar las API de Windows.
El Framework 2.0 ha mejorado sustancialmente el acceso a los recursos del computador integrando una serie de funciones capaces de hacer casi todo el trabajo por nosotros. En este documento veremos la utilización de algunos elementos, como son:
- El namespace My para acceder a la lista de puertos en el computador:
For Each strPort As String In My.Computer.Ports.SerialPortNames
tscbPorts.Items.Add(strPort)
Next
- Se utiliza además System.IO.Ports para crear un objeto que interactúe directamente con los puertos serie, y se abre el puerto directamente desde los recursos del sistema:
Private WithEvents RS232 As New System.IO.Ports.SerialPort
RS232 = My.Computer.Ports.OpenSerialPort(SelectedPort)
Acceso a la Lista de Puertos del Sistema
Algo que debemos tener en cuenta es que nuestra aplicación no siempre será utilizada por personas con conocimientos avanzados, por lo tanto es necesario dotar al usuario final de la menor cantidad de informaciones técnicas con el objetivo de reducir los márgenes de errores que puedan ser cometidos; el Framework 2.0 permite hacer esto sin necesidad de escribir grandes cantidades de líneas de código.
Una forma muy eficaz de hacer esto es que tu aplicación presente solamente la lista de puertos disponibles en el sistema. En vez de permitirle al usuario escribir el nombre del puerto, se ofrece todos los puertos disponibles en una lista desplegable; de esta manera el usuario final nunca tendría que intentar acceder a los recursos del sistema para obtener esta información; de esta manera contribuimos además a minimizar la posibilidad de errores o excepciones que puedan surgir por una configuración incorrecta.
El siguiente código utiliza la instrucción For Each para obtener la lista de puertos disponibles desde la colección My.Computer.Ports.SerialPortNames; estos datos son obtenidos en formato string y luego son colocados en una lista desplegable:
For Each strPort As String In My.Computer.Ports.SerialPortNames tscbPorts.Items.Add(strPort) Next If tscbPorts.Items.Count > 0 Then tscbPorts.SelectedIndex = 0 Else 'No hay puertos en el sistema End If
El código anterior puebla la lista desplegable tscbPorts con los nombres de los puertos, luego determina si se encontró algún puerto y, si esto es así, selecciona por defecto el primero de ellos.
Configuración, Apertura y Cierre del Puerto
En la programación tradicional, utilizando las API de Windows o librerías de terceros que finalmente invocan esas API, siempre hemos tenido que escribir algunas líneas de código tales como BaudRate, Stopbit, Databit, Parity, etc., para configurar el puerto antes de abrirlo. Por supuesto, eso también es posible en el Framework 2.0; todas estas propiedades pueden ser establecidas. Sin embargo, en este artículo pretendo mostrar las nuevas funcionalidades que permiten iniciar todas estas propiedades en una sola línea de código.
Debido a que ya tenemos una lista desplegable -tscbPorts, la cual contiene los nombres de los puertos- y suponiendo que el usuario haya hecho su elección, accedemos a la propiedad SelectedItem para obtener el nombre del puerto seleccionado; luego de abrir el puerto, deshabilitamos la lista para evitar que el usuario seleccione otro puerto e intente abrir un segundo puerto (por supuesto que tú puedes establecer los controles que desees para este fin sin embargo, como método de simplificación, utilizo el camino más corto).
Debido a que la manipulación de puertos es muy volátil, es necesario tomar control sobre las excepciones con el objeto de evitar el mal funcionamiento de nuestra aplicación y a la vez informar al usuario de las causas y fallas en la apertura del puerto. Para esto, encierro el código en un bloque Try , Catch , End Try y utilizo las excepciones más comunes -System.IO.IOException y System.UnauthorizedAccessException- para obtener un reporte detallado del error, ya sea porque el puerto esté abierto o no exista, y finalmente utilizo System.Exception para atrapar cualquier otra excepción que pueda surgir:
Dim SelectedPort As String = tscbPorts.SelectedItem Try RS232 = My.Computer.Ports.OpenSerialPort(SelectedPort, 9600, IO.Ports.Parity.None, 8, IO.Ports.StopBits.One) txtConsole.AppendText(RS232.PortName & " abierto a las " &_ Now.ToString & vbCrLf) tscbPorts.Enabled = False Catch ex As System.IO.IOException txtConsole.AppendText("Error abriendo el puerto: " & _ vbCrLf & ex.Message & vbCrLf) Catch ex As System.UnauthorizedAccessException txtConsole.AppendText("El pueto ya esta abierto: " & vbCrLf & _ ex.Message & vbCrLf) Catch ex As System.Exception txtConsole.AppendText("Error general accediendo al puerto:" & _ vbCrLf & ex.Message & vbCrLf) End Try
Como se puede observar, en la tercera línea el puerto es abierto e inicializado con los parámetros de configuración ya preestablecidos dentro del código. Para llevar a cabo una codificación más robusta y flexible al usuario, te recomiendo utilizar un archivo .XML para guardar esta configuración.
Transmisión de Datos
Con un objeto System.IO.Ports.SerialPort podemos escribir casi todo tipo de datos hacia un puerto serie; entre éstos, podemos enviar desde un simple caracter hasta un buffer de carácteres y/o bytes; sin embargo, para este ejemplo utilizamos la función WriteLine(), la cual envía una cadena de carácteres y a la vez un terminador de línea encontrado en la propiedad NewLine.
Para este ejemplo utilizaremos una caja de texto llamada tstxtSend. Antes de enviar datos a un puerto serie te recomiendo verificar que el tipo de dato a enviar concuerde con la función que vamos a utilizar; en este ejemplo sólo se verifica si se está intentando enviar una cadena vacía o no, evitando así resultados no deseados.
If tstxtSend.Text.Length > 0 AndAlso RS232.IsOpen Then RS232.NewLine = vbCrLf RS232.WriteLine(tstxtSend.Text.Trim) txtConsole.AppendText("Cadena enviada: " & _tstxtSend.Text.Trim & vbCrLf) Else txtConsole.AppendText("Nada ha sido enviado" & vbCrLf) End If
Recepción de Datos
La recepción de datos es bastante sencilla o bien puede ser un poco más compleja cuando se requiere interactividad o cuando la recepción de datos puede darse de manera espontánea y no programada. En este artículo veremos ambas formas; dividiremos este tema en dos partes para mostrar lo simple o compleja que puede ser.
Método simple: hacemos referencia a la lectura de datos programada, o sea que se ejecuta el método de lectura del objeto justo en el momento en que deseamos recibir los datos. Por ejemplo:
StrRead = objeto.Read IntRead = objeto.ReadByte IntRead = objeto.ReadChar StrRead = objeto.ReadExisting StrLineRead = objeto.ReadLine
Puedes utilizar cualquiera de estos métodos de acuerdo a tus necesidades o al tipo de dato que deseas recibir; como puedes notar, en las 3 primeras letras se indica el tipo de datos a leer. Generalmente este método aplica a ocasiones en que enviamos datos a un dispositivo y luego esperamos una respuesta. Por ejemplo:
RS232.WriteLine(tstxtSend) 'Se Envia el dato. RS232.ReadExisting() 'Lee la respuesta.
Método Complejo: A menudo estamos interactuando con dispositivos que pueden no requerir una trasmisión de datos en ambos sentidos o que pueden genera información de una manera espontánea. Es entonces donde entra en juego el evento DataReceived, el cual se dispara en el mismo momento en que se detecta la llegada de datos al puerto previamente abierto. Este evento se dispara en un hilo o Thread separado; puedes manipular los datos en ese hilo de modo separado, como por ejemplo enviarlos a una base de datos, pero muchas veces se necesita pasar esta información al hilo principal para desplegar estos datos en algún objeto del formulario; necesitaremos utilizar un delegado para invocar un método dentro del hilo principal que contiene la Interfaz de Usuario.
Delegate Sub WriteDataDelegate(ByVal str As String)
En el siguiente código se muestra el procedimiento utilizado por el evento:
Private Sub RS232_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) _ Handles RS232.DataReceived Dim strData As String = RS232.ReadExisting Dim WriteInvoke As New _ WriteDataDelegate(AddressOf Me.WriteData) Me.Invoke(WriteInvoke, strData) End Sub
Si observamos el código, encontraremos que al dispararse el evento DataReceived lo primero que hacemos es obtener los datos provenientes del disposivo conectado al puerto ya abierto, en este ejemplo se utiliza ReadExisting. Una vez obtenidos los datos provenientes del puerto, procedemos a instanciar el procedimiento WriteData a través del delegado WriteDataDelegate. Finalmente invocamos el procedimiento previamente instanciado pasando los datos capturados desde el puerto serie. Dentro del procedimiento WriteData colocamos el siguiente código:
Private Sub WriteData(ByVal str As String) txtConsole.AppendText(str) End Sub
La razon de que el evento se dispare en hilo separado ofrece sus ventajas y desventajas de acuerdo a la cantidad de información a recibir y del procesamiento de esta información. Debido a que el Framework utiliza ciertos recursos del sistema para manejar los hilos, sería conveniente no utilizar el evento DataReceived a menos que la situación lo amerite.
Sin embargo, este mismo evento nos da una ventaja enorme cuando, por ejemplo, se recibe una cadena de texto que debe ser analizada, procesada y finalmente almacenada en una base de datos, porque todo este proceso es realizado en hilos separados de la interfaz de usuario. Recomiendo investigar sobre System.Threading, Delegate e Invoke().
Código del Usuario
A continuación te dejo el código necesario para esta aplicación; nota que las definiciones de los controles del formulario no están en este código; sin embargo, al final te dejo la lista de controles utilizados.
Public Class Console Private WithEvents RS232 As New System.IO.Ports.SerialPort Private _strData As String = "" Private ReadOnly Property DataRecivida() Get DataRecivida = _strData _strData = "" End Get End Property Private Sub Console_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load For Each strPort As String In My.Computer.Ports.SerialPortNames tscbPorts.Items.Add(strPort) Next If tscbPorts.Items.Count > 0 Then tscbPorts.SelectedIndex = 0 Else 'No hay puertos en el sistema End If End Sub Private Sub ToolStripButton1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripButton1.Click Dim SelectedPort As String = tscbPorts.SelectedItem Try RS232 = My.Computer.Ports.OpenSerialPort(SelectedPort, 9600, IO.Ports.Parity.None, 8, IO.Ports.StopBits.One) txtConsole.AppendText(RS232.PortName & " abierto a las " & Now.ToString & vbCrLf) tscbPorts.Enabled = False Timer1.Start() Catch ex As System.IO.IOException txtConsole.AppendText("Error abriendo el puerto: " & vbCrLf & ex.Message & vbCrLf) Catch ex As System.UnauthorizedAccessException txtConsole.AppendText("El pueto ya esta abierto: " & vbCrLf & ex.Message & vbCrLf) Catch ex As System.Exception txtConsole.AppendText("Error general accediendo al puerto:" & vbCrLf & ex.Message & vbCrLf) End Try End Sub Private Sub ToolStripButton2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripButton2.Click RS232.Close() Timer1.Stop() txtConsole.AppendText(RS232.PortName & " cerrado a las " & Now.ToString & vbCrLf) tscbPorts.Enabled = True End Sub Private Sub ToolStripButton4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripButton4.Click If RS232.IsOpen Then RS232.Close() End If Me.Dispose() End Sub Private Sub RS232_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles RS232.DataReceived While _strData.Length > 0 End While _strData = RS232.ReadLine End Sub Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick If _strData.Length > 0 Then txtConsole.AppendText(DataRecivida & vbCrLf) End If End Sub Private Sub ToolStripButton3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripButton3.Click If tstxtSend.Text.Length > 0 AndAlso RS232.IsOpen Then RS232.NewLine = vbCrLf RS232.WriteLine(tstxtSend.Text.Trim) txtConsole.AppendText("Cadena enviada: " & tstxtSend.Text.Trim & vbCrLf) Else txtConsole.AppendText("Nada ha sido enviado" & vbCrLf) End If End Sub End Class Controles utilizados en el formulario del formulario ToolStrip1 = New System.Windows.Forms.ToolStrip ToolStripButton1 = New System.Windows.Forms.ToolStripButton ToolStripButton2 = New System.Windows.Forms.ToolStripButton ToolStripButton3 = New System.Windows.Forms.ToolStripButton ToolStripButton4 = New System.Windows.Forms.ToolStripButton tscbPorts = New System.Windows.Forms.ToolStripComboBox tstxtSend = New System.Windows.Forms.ToolStripTextBox txtConsole = New System.Windows.Forms.TextBox Timer1 = New System.Windows.Forms.Timer(Me.components)
Conclusión
El .Net Framework 2.0 está dotado de nuevas clases y un namespace que hacen realmente posible la interacción con los puertos serie del computador de una manera eficiente, fácil y sobre todo dentro del ambiente de código manejado sin necesidad de recurrir a librerías y objetos de terceros.
Referencias
La información que se muestra en este artículo fue encontrada en la librería en línea de MSDN y en la ayuda de Visual Studio 2005 Edición Express. El código fue desarrollado en Visual Basic Express Beta 2.
Vinicio Montero trabaja en Santo Domingo, Dominicana, como responsable del departamento de servicios de una importante empresa de Ventas y Servicios Informáticos en Antillas Menores y el Caribe; y ha trabajado en diferentes corporaciones tanto publicas como privadas, siendo responsable de la administracion de sistemas de negocios integrados. . Posee experiencia programando con Cobol RM87, Foxpro para DOS y Windows, Visual Foxpro y Visual Studio .net; además de extensos conocimientos de infraestructura de redes y plataformas de sistemas operativos. Cuenta con tres certificaciones Microsoft Bussines Solutions, así como otras aplicaciones de negocios. Ha obtenido 3 Estrellas del programa Desarrollador 5 Estrellas de Microsoft MSDN. |