Archivo de la categoría: Serial Ports

Serial Ports

Problemas en la recepción de datos

Considerando interesante la pregunta y con el permiso de Cream, me gustaría compartir esta pequeña cuestión en tanto a la metodología  en la recepción de datos usando el IO.Ports.

La pregunta:
“no siempre recibo bien los datos o no los capturo por el puerto serie”.. el código que estoy usando actualmente es el siguiente :

puertoSerie.write(«R»)                                              ‘enviamos carácter
dato=Asc(puertoSerie.ReadExisting.Chars(1))          ‘leemos y cogemos valor de x

Una de las características de las comunicaciones serie en uno de sus modos, es que son asíncronas… lo que significa que los ‘Bytes’ se serializan y transmiten sin que el receptor conozca el momento preciso. Es decir el bit de ‘start’ indica al receptor el envío de un ‘byte’ pero en ningún caso y por ausencia de protocolo conocemos de antemano la cantidad de ‘bytes’ que vamos a recibir asi como el intervalo de tiempo que va a transcurrir en la transmision  entre byte’s.

Como tu bien dices y expresas en tus razonamientos, estas ejecutando la lectura directamente sin esperar al evento de ‘Data Received’,  es presumible que en la rapidez de proceso del PC nos dispare la excepción de   “index out of range exception”  pues estas intentando procesar la posición del segundo carácter que evidentemente aun no has recibido y por lo tanto es inexistente.

El mandato ReadExisting, lee los bytes recibidos que actualmente permanecen en el buffer, en tu caso y en ocasiones, dependiendo del flujo de ejecución, en el buffer debes tener por leer, 0, quizas 1 o cuando te funciona bien2 bytes.

La práctica correcta es formalizar el intercambio de tramas delimitándolas con un carácter de inicio y otro de fin… por ejemplo la mayoría implementan el CR o Chr(13) como fin de trama, algunos fabricantes optan por utilizar como fin de trama secuencias tales como 2 bytes de FCS + Un Carácter identificativo + LF + CR, en fin como siempre hay para todos los gustos. Al menos con este método podrás estar seguro de que la transmisión a finalizado correctamente.

Deberías implementar el disparo de recepción y procesar los datos una vez se ha completado la recepción.  En todo caso otra opción podría ser algo parecido a:

‘String de recepción utilizado como buffer
Private PortSerie_Recepcion As String = «»    
‘Anadir el manipulador de recepción en la sub New, Load…   
AddHandler Serie.DataReceived, AddressOf Rx
 
Sub
Rx(ByVal sender As Object, _
       ByVal e As serialDataReceivedEventArgs)       
   Try

    ‘Añadir la recepción actual al buffer            
    PortSerie_Recepcion += Serie.ReadExisting
    If PortSerie_Recepcion.Contains(Chr(13)) Then                
        ….PROCESAR LA INFORMACION
        PortSerie_Recepcion = «»           
    End If       
   Catch ex As Exception
    ‘En caso de excepción       
  End Try   
 End Sub

Si la longitud de tus tramas es de 2 bytes… una idea muy básica puede ser controlar el número de caracteres que están en el buffer pendientes de ser leídos y antes de procesarlos con: PuertoSerie.BytesToRead

         Prohibido definitivamente
       
        If PuertoSerie.BytesToRead = 2 Then
            dato = Asc(PuertoSerie.ReadExisting.Chars(1))
       
End If

        ‘ Definitivamente NO ES RECOMENDABLE!
       
        If PuertoSerie.BytesToRead > 2 Then
            dato = Asc(PuertoSerie.ReadExisting.Chars(1))
       
End If

El problema utilizando el primer caso es que nunca se procesara el dato si no se reciben exactamente dos caracteres, por ende y peor aún si BytesToRead es > 2 nunca procesara los bytes recibidos, poniendo en apuros a nuestra aplicación cuando él se desborde el buffer de recepción. En segundo caso y en determinadas situaciones de intercambios muy rápidos o con interferencias podria perder la secuencia lecturas de pares de bytes y procesar inadecuadamente por desplazamiento de posiciones… como mal menor necesitarías usar PuertoSerie.BytesToRead > 2 conjuntamente con PuertoSerie.Read(Dato(),0,2), aunque insisto lo mejor es incluir un fin de trama en cada transmisión desde el firmware del dispositivo.

        ‘ una propuesta… Quizas

       

        Dim Trama() As Char

        If PuertoSerie.BytesToRead > 2 Then

            PuertoSerie.Read(Trama, 0, 2)

            dato = Asc(Trama(1))

        End If

Saludos,
Pep Lluis,

 

 

 

Enumerar los puertos COM en un ‘combo’ listos para ser seleccionados.

En algunas ocasiones he leído alguna respuesta en los foros contestando la forma en que podemos enumerar nuestros puertos “COM” y dejarlos listos para ser seleccionados por el usuario… sin entrar en valorar complejas respuestas con ‘For Each’ ‘Add’ y etc, os dejo un código ejemplo de cómo… pero en una sola línea!!

Me.ComboBox1.Items.AddRange(My.Computer.Ports.SerialPortNames.ToArray)

… luego en ‘ComboBox1.SelectedIndexChanged’
PuertoSerie.Close()
PuertoSerie.PortName = ComboBox1.SelectedItem
PuertoSerie.Open()

Saludos,
Pep Lluis,

FCS Frame Check Secuence – CRC Cyclic Redundancy Check algorithm

En la mayoría de antiguos protocolos de comunicaciones “sobre todo Serie”, disponemos de algunos mecanismos para la detección y corrección de errores en el intercambio de tramas. En concreto hablando del puerto serie, a primer nivel disponemos del control de paridad con el que conseguimos descartar algunos de los errores de transmisión a nivel de byte, sin embargo muchos protocolos implementan un control a nivel de trama, en el que se realiza una suma de verificación de todos los bytes transmitidos. Esto es conocido como FCS o CRC que no deja de ser una referencia a un ‘checksum’ o verificación redundante. El resultado de la misma es añadido al final de la trama y enviado con el resto de datos. Una vez recibido por el terminal o dispositivo se efectuara la misma operación calculando de nuevo la FCS y comprobando que coincide con la transmitida… con ello conseguiremos tener un elevado porcentaje de confianza en tanto la integridad de los datos recibidos.

El cálculo puede presentarse más o menos complejo dependiendo de la implementación de esta verificación según el protocolo, aunque la mayoría de ellos se conforman con aplicar un simple ‘XOR’… veamos un ejemplo común:

   
    ‘ Calculo de la FCS de una trama
    ‘ Esta funcion devolvera el valor hexadecimal
    ‘ en forma de dos caracters ASCII

    Private Function Fcs(ByVal Trama As String) As String
        Dim MiFcs As Integer = 0
        For Each c In Trama
            MiFcs = Asc(c) Xor MiFcs
        Next
        Return Hex(MiFcs).ToString
    End Function

Para los amantes de bool… Recordemos la tabla para el ‘Or’ Exclusivo : MiFcs = Asc(c) Xor MiFcs

p

q

p ≠ q

Falso

Falso

Falso

Falso

Verdadero

Verdadero

Verdadero

Falso

Verdadero

Verdadero

Verdadero

Falso

Entonces solo nos faltara alinear los valores binarios del resultado anterior; efectuar el ‘Xor’ y continuar efectuando la misma operación hasta agotar el ultimo byte. Demostrar el tema de acarreos y resultados con 16/32 bits en hexadecimal lo dejamos para otro dia J o bién podeis continuar leyendo en : http://www.ietf.org/rfc/rfc2615.txt?number=2615 Por ahora me conformo pensando que :  Hex(MiFcs).ToString, soluciona mi problema. 

Resultado_Anterior

0

1

0

1

0

1

0

1

Byte_Actual

0

0

1

1

1

0

0

1

Resultado_Actual

0

1

1

0

1

1

0

0

Ya no os podéis quejar que con el avance de los lenguajes nos alejamos de los ‘Bites’.

Saludos,
Pep Lluis,

PD. EN VB6 Tenia este aspecto

‘ ** Calculo de la Fcs

‘ Al mas puro VB6

Private Function Fcs$(Var)

Dim p As Integer, q As Integer

 For p = 1 To Len(Var)

   q = q Xor Asc(Mid$(Var, p, 1))

 Next p

Fcs$ = Right$(«0» + Hex$(q), 2)

End Function

 

Acceder al Espacio System.IO.Ports en C++

Algunos de vosotros en el foro estáis solicitando si es posible utilizar el System.IO.Ports con C++, esperando que esto sea un punto de entrada en un ejemplo súper simple!! De solo enviar y recibir una trama.

System IO Port & C++

Podéis descargaros el archivo adjunto, no esta probado en C++ express, pero supongo que no tendréis ningún problema en ejecutarlo.

Por cierto si reubicáis el proyecto (lo colocáis en un path diferente al original), cuando intentéis recompilarlo os generara un error : C1093 – detallando que no puede encontrar «stdafx.h». El concepto es que el proyecto genera y apunta determinadas referencias en tiempo de compilación, entonces la solución es  hacer un “build>Clean Solution” desde “Generar>Proyecto>Limpiar”. Luego simplemente “F5” J

Espero vuestras valoraciones.
Pep Lluis,

** El proyecto esta realizado con la vesion 9 de Visual Studio. (2008)
A peticion vuestra os dejo el codigo, para utilizarlo con Visual Studio 2005. Solo teneis que crear nuevo proyecto ‘winforms’ arrastrando y soltando un ‘ListBox’ un ‘Boton’ y un SerialPort.

namespace SystemIOPorts_EN_C {

 

      using namespace System;

      using namespace System::ComponentModel;

      using namespace System::Collections;

      using namespace System::Windows::Forms;

      using namespace System::Data;

      using namespace System::Drawing;

 

      /// <summary>

      /// Resumen de Form1

      /// Ejemplo de utilizacion del System.IO.Ports desde C++

      /// (C) Pep Lluis 2008

      ///

      ///

      /// Para realizar las pruebas es necesario insertar un

      /// conector de 9 pins en el puerto serie utilizado

      /// cortocircuitando (puenteando) los pines 2/3

      ///

      /// </summary>

      public ref class Form1 : public System::Windows::Forms::Form

      {

      public:

 

            Form1(void)

            {

                  InitializeComponent();

                  //

                  // Abrir el puerto de comunicaciones

                  // inhibir la verificacion de error de llamadas entre hilos

                  //

                  this->serialPort1->Open();

                  CheckForIllegalCrossThreadCalls = false;

            }

 

      protected:

            /// <summary>

            /// Limpiar los recursos que se estén utilizando.

            /// </summary>

            ~Form1()

            {

                  if (components)

                  {

                        delete components;

                  }

            }

      private: System::IO::Ports::SerialPort^  serialPort1;

      private: System::Windows::Forms::ListBox^  listBox1;

      private: System::Windows::Forms::Button^  button1;

      protected:

      private: System::ComponentModel::IContainer^  components;

 

      private:

            /// <summary>

            /// Variable del diseñador requerida.

            /// </summary>

 

 

#pragma region Windows Form Designer generated code

            /// <summary>

            /// Método necesario para admitir el Diseñador. No se puede modificar

            /// el contenido del método con el editor de código.

            /// </summary>

            void InitializeComponent(void)

            {

                  this->components = (gcnew System::ComponentModel::Container());

                  this->serialPort1 = (gcnew System::IO::Ports::SerialPort(this->components));

                  this->listBox1 = (gcnew System::Windows::Forms::ListBox());

                  this->button1 = (gcnew System::Windows::Forms::Button());

                  this->SuspendLayout();

                  //

                  // serialPort1

                  //

                  this->serialPort1->BaudRate = 115200;

                  this->serialPort1->DataReceived += gcnew System::IO::Ports::SerialDataReceivedEventHandler(this, &Form1::serialPort1_DataReceived);

                  //

                  // listBox1

                  //

                  this->listBox1->FormattingEnabled = true;

                  this->listBox1->Location = System::Drawing::Point(13, 13);

                  this->listBox1->Name = L«listBox1»;

                  this->listBox1->Size = System::Drawing::Size(259, 95);

                  this->listBox1->TabIndex = 0;

                  //

                  // button1

                  //

                  this->button1->Location = System::Drawing::Point(197, 114);

                  this->button1->Name = L«button1»;

                  this->button1->Size = System::Drawing::Size(75, 23);

                  this->button1->TabIndex = 1;

                  this->button1->Text = L«Enviar»;

                  this->button1->UseVisualStyleBackColor = true;

                  this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click);

                  //

                  // Form1

                  //

                  this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);

                  this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;

                  this->ClientSize = System::Drawing::Size(284, 147);

                  this->Controls->Add(this->button1);

                  this->Controls->Add(this->listBox1);

                  this->Name = L«Form1»;

                  this->Text = L«C++ & System.IO.Ports»;

                  this->ResumeLayout(false);

 

            }

#pragma endregion

 

            /// Enviar trama usando System.IO.Ports

      private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {

                         this->serialPort1->Write(L«Hola Gente»);

                   }

 

          /// Leer los caracteres recibidos y añadirlos al ListBox

      private: System::Void serialPort1_DataReceived(System::Object^  sender, System::IO::Ports::SerialDataReceivedEventArgs^  e) {

                         this->listBox1->Items->Add(serialPort1->ReadExisting());

                   }

 

      };

     

}

 

Una alternativa al SerialPort.ReadLine

Atendiendo algunas dudas sobre como optimizar la recepción de datos utilizando ‘ReadLine’, y a falta de un ejemplo completo os facilito una alternativa.

La idea es llamar a la funcion de recepcion siempre que se reciben datos y disparar el evento de trama completa cuando se cumple la condicion de carácter final.

Imports
System.IO.Ports 
Public Class PortsSerie       
 ‘
   
 ‘Declarar el evento disparo fin de recepcion
   
 Public Event RxFin(ByVal Trama As String)
   
 ‘   
 ‘String de recepcion utilizado como buffer   
 Private PortSerie_Recepcion As String = «»
   
 ‘Anadir el manipulador de recepcion en la sub New, Load…
   
 AddHandler
PortSerie.DataReceived, AddressOf Rx
 
 Protected
Sub Rx(ByVal sender As Object, _
                  ByVal e As serialDataReceivedEventArgs)
       
   Try
    
‘Añadir la recepcion actual al buffer           
     
PortSerie_Recepcion += PortSerie.ReadExisting           
    
If PortSerie_Recepcion.Contains(Chr(13)) Then
               
        
RaiseEvent RxFin(PortSerie_Recepcion)               
        
PortSerie_Recepcion = «»
           
    
End If       
  
Catch ex As Exception
    
‘En caso de excepción        
 
End Try
   
 End Sub
End Class


Espero que esto os sirva de idea, esperando dejaros un ejemplo para descargar tan pronto como me sea posible.
Saludos,
Pep Lluis,

Puertos Serie : Alta Velocidad?

Hace poco recibi una consulta sobre la velocidad de los puertos de comunicaciones. Os dejo un breve, esperando que os ayude en la compresion de lo que hay detras de un conector de 9 o 25 pins.


La mayoría de equipos incorporan en su ‘ChipSet’ UARTS compatibles con 16450.. 16550… y últimamente 16650 (Herederas de la mítica 8250 de National), la velocidad de transmisión y recepción viene fijada por las divisiones efectuadas al cuarzo y ajustadas en un registro interno de la misma, llamado BRG. Las UART actuales pueden llegar a velocidades de hasta 1,5M Baud..


–>> (Texto ejemplo de una hoja de características)


The UART includes a programmable baud rate generator that is capable of dividing the timing reference clock input by divisors of 1 to (216 -1), and producing a 16X clock for driving the internal transmitter logic.


Provisions are also included to use this 16X clock to drive the receiver logic. The UART has complete MODEM-control capability, and a processor-interrupt system. Interrupts can be programmed to the user’s requirements, minimizing the computing required to handle the communications link.



  • Fully programmable serial-interface characteristics:. 5-, 6-, 7-, or 8-bit characters. Even, odd or no-parity bit generation and detection. 1-, 11/2-or 2-stop bit generation . Baud generation ( DC to 1.5M baud )

Programmable Baud-Rate Generator
Each UART has its own Baud-Rate Generator (BRG) with a prescaler for the transmitter and receiver. The prescalar is controlled by a software bit in the MCR register. The MCR register bit 7 sets the prescalar to divide the on-board clock (14.7456 MHz) by 1 or 4. The output of the prescalar clocks the BRG. The BRG further divides the clock to a programmable divisor between 1 and (216-1) to obtain a 16X or 8X sampling clock of the serial data rate. The sampling clock is used by the transmitter for data bit shifting and receiver for data sampling.


 <<–
Tabla de configuracion de una UART segun el valor divisor.
































































[MCR Bit7=1 ] 


[MCR Bit7=0]   


[Divisor for 16X] 


[DLM (Hex)] 


[DLL (Hex)] 


600


2400


180


1


80


1200


4800


0C0


0


C0


2400


9600


060


0


60


4800


19.2k


030


0


30


9600


38.4k


018


0


18


19.2k


76.8k


00C


0


0C


38.4k


153.6k


006


0


06


57.6k


230.4k


004


0


04


115.2k


460.8k


002


0


02


Para los que utilizais la estructura DCB requerida para el acceso de puertos a traves del ‘kernel32.dll’ y aunque mi especialidad no es C++, es de  suponer que el limite correspondiente a la enumeración de velocidades ‘CBR_256000’ corresponde al hexadecimal que se envía al registro divisor de la UART para que este trabaje a la velocidad seleccionada una vez ejecutamos la orden de abrir el puerto. Aunque no se si es posible trabajar a velocidades superiores direccionando directamente los registro ‘MCR’ y ‘DL’ de las UARTS (complicado) se trataría de averiguar si añadiendo el valor de velocidad a la enumeración de la estructura DCB y su correspondencia con ‘MCR’ y ‘DL’, harian funcionar a la UART a una velocidad mayor a los 115200Bauds.


De todas formas hablando de velocidades, creo recordar que en anteriores versiones de Windows (95 o anteriores) el sistema no podía trabajar a velocidades altas (estaba limitado), pues por la naturaleza de la UART cada vez que recibe un carácter lanza una interrupción al procesador y ello podría provocar que el rendimiento general se resintiera, en la actualidad desconozco si existe alguna restricción a nivel de sistema operativo, cabe destacar que en .NET el System.IO.Ports.SerialPort puede trabajar hasta 115200 y creo no admite ninguna velocidad por encima de esta, dando como incorrecta cualquier valor que no pertenezca a la primera columna de la tabla, aunque reconozco no haber experimentado demasiado en este campo, porque realmente tampoco he sufrido esa necesidad.


P.C. (por cierto)… una manera de engañar al sistema seria abrir el port a 115200 y por la trastienda ajustar el Baud Rate MCR Bit7 a Cero!, pero creo que necesitariamos algun que otro Guru! 


Saludos,
Pep Lluis,

Puertos Serie?… Hasta cuando?

Repondiendo a las inquietudes de los que despues de 20 años contiuan viendo los conectores de 9 pins en nuestros equipos… comentaros que aunque parezca paradójico, la mayoría de Micros utilizados en la fabricación de dispositivos continúan disponiendo de una interfaz serie, aunque poco a poco se esta substituyendo por USB, algunos fabricantes están optando por integrar las comunicaciones a través de ethernet ya bien por RJ45 o WIFI.


Es difícil prever el panorama que nos espera, pues a pesar de todo, los chips continuaran incorporando interfaces como RS232, LIN, CAN, SPI, USB, RF, Infrarrojos o el nuevo ZigBee, mientras estos dispositivos utilicen comunicaciones en serie, los equipos informáticos que vayan a comunicarse con ellos necesitaran disponer de ese tipo de conexión.


NOTA: Los receptores GPS, intercambian la información a través de puertos y comunicación serie. Incluso cuando los integramos en un PDA el Buetooth extremo por extremo se convierte en un RS232.


Una de las virtudes de la comunicación serie es que solo implementa los tres primeros niveles del modelo OSI, ello lo hace tremendamente simple y sencillo de entender e implementar, de ahí que la mayoría de diseñadores continúen utilizándolo para tareas u aplicaciones básicas.


Tal y como tu apuntas los puertos serie irán desapareciendo paulatinamente de la parte posterior de nuestros equipos (la mayoría de portátiles ya no los incorporan), sin embargo existen un montón de accesorios USB-RS232 que conservaran esa herencia durante algunos años mas… o al menos eso creo!


Estare encantado de seguir con esta conversacion con quien se anime a continuarla!
Saludos,
Pep Lluis,

Compatibilidad de aplicaciones que utilizan MSCOMM32.OCX en Windows Vista

Muchas de nuestras antiguas aplicaciones utilizaban el conocido control ‘mscomm32.ocx’. Atendiendo a los nuevos escenarios en ocasiones nos vemos obligados a utilizar estas aplicaciones con el nuevo sistema operativo. No existe ningún problema y estas aplicaciones funcionan perfectamente siempre y cuando utilicen los puertos serie nativos del ordenador (o sea los que vienen incluidos o incorporados a través de placas con las UART de toda la vida).


El problema ocurre cuando necesitamos utilizar alguno de los conversores USB/Serie que existen en el mercado, he descubierto que algunos de ellos tienen problemas dependiendo del driver del fabricante, pues dicho conversor trabajara adecuadamente si utilizamos el espacio de nombres System.IO.SerialPort.


Habitualmente recibiremos el siguiente error :
error ‘8020’ en tiempo de ejecucion – Error al leer el dispositivo.


Entendiendo entonces que es un problema de adaptación entre el Driver del fabricante del conversor USB/Serie el control mscomm32.ocx y Vista, en estos momentos estoy colaborando con un fabricante para ver que posibilidades tenemos de resolver el problema en este escenario.


Estaré encantado de conversar con todos los interesados en saber más de todo esto, o en espera de resolver alguna situación similar.


Saludos,
Pep Lluis,


 

Robot Dispensador de MSDN Video

Después de mucho pensármelo “por mi condición de catalán” , me he decidido! J Y haciendo un gran alarde de generosidad, con la ayuda de Paco Marín (DotNetMania) hemos puesto el primer ejemplar de la colección de Cuadernos técnicos a disposición del publico en General, esperando tenga una buena acogida. También aunque con el matiz de que fue desarrollado en la versión Beta 1 de Visual Studio 2005, podéis descargaros el código para poder juguetear. Me gustaría mucho recibir vuestras opiniones y sugerencias.

Saludos,
Pep Lluis,

Ahi van los Links :
http://www.dotnetmania.com/libros/CTdnm01/pdf/ctdnm01.pdf

http://www.dotnetmania.com/libros.aspx

DeCom1ACom3 – Comunicaciones entre puertos serie

Respondiendo a la petición de nuestro compañero Ferran de Lleida :
___________ Foro Visual Basic MSDN Spanish.


«he de desarrollar un programa en Visual Basic .net en que se comunique el programa con un robot y el robot le irá pasando datos al programa.Pues bien, mientras no tengo el robot.. me han dado un cable que se conecta al puerto serie Com1 y termina en un USB (y el pc lo reconoce como Com3) El caso es que me han comentado… pasa datos y a ver si los recibe Com3. Bueno, llevo 1 semana liado y no hay manera, los «experimentos» que he hecho no han dado resultado. He intentado que mientras se escriba en un textbox y va al Com1, se lea en otro textbox como si fuera Com3.
____________


Podéis descargaros el ejemplo pulsando sobre el link de ‘attachments’… estaré encantado de responder a cualquier pregunta relacionada. Os invito a participar.
Pep Lluis,