locked
Serial Port - Auto detecting connected device

    Question

  • Folks, I am new to the VB.net and building applications world! I have been attempting to the following for quite a while and am in great need for help!

    Objective: Scan available serial ports on the computer or application and detect the serial device connected to the appropriate port. I try to open each port and attempt a serial "hand shake" with my device before I attempt to enter the state to acquire further data for processing.

    The below is a class, I had written.

    Imports System.IO.Ports.SerialPort

    Public Class HSerialPort

        Private WithEvents RS232Port As System.IO.Ports.SerialPort
        Private WithEvents RxTimerOut As System.Timers.Timer

    #Region "Constants"

        Private Const H_MODE_CHECK As String = "SMS"
        Private Const H_MODE_OK As String = "SMA"
        Private Const H_MODE_DATALOGGING As String = "SM2DL"

    #End Region

    #Region "Private Variables"

        Private AvailablePorts() As String
        Private NoAvailablePorts() As String
        Private PortToOpen As String

        Private PortRxMsg As String
        Private PortTxMsg As String
        Private HSerialState As String = H_MODE_CHECK

    #End Region

    #Region "Constructors"

        Sub New()

            RS232Port = New System.IO.Ports.SerialPort ' Create serial object
            RxTimerOut = New System.Timers.Timer

            ' Time out 1 second after transmit
            RxTimerOut.Interval = 1000

            InitializePortSettings() ' Initialize basic port settings
            AvailablePorts = System.IO.Ports.SerialPort.GetPortNames()
            'PortToOpen = FindPort()

            'RS232Port.PortName() = "COM1"
            'RS232Port.Open()
            'RS232Port.Write("SMS" & vbCr)

        End Sub

    #End Region

    #Region "Properties"

        '    Private Property SMPort() As String
        '        Get
        '            Return PortToOpen
        '        End Get
        '        Set(ByVal value As String)
        '            PortToOpen = value
        '        End Set
        '    End Property

        '    Private Property SMPortNames() As String()
        '        Get
        '            Return AvailablePorts
        '        End Get
        '        Set(ByVal value As String())
        '            AvailablePorts = value
        '        End Set
        '    End Property

    #End Region

        Private Sub InitializePortSettings()

            '8N1
            RS232Port.DataBits() = 8
            RS232Port.Parity() = IO.Ports.Parity.None
            RS232Port.StopBits() = IO.Ports.StopBits.One
            '115200 bps
            RS232Port.BaudRate() = 115200
            'Terminate with "carriage return"
            RS232Port.NewLine() = vbCr

            Return
        End Sub

        Private Function FindPort() As String

            Dim port As String = String.Empty
            Dim portStatus As Boolean = False

            For Each port In AvailablePorts
                RS232Port.PortName = port
                If RS232Port.IsOpen = True Then
                    RS232Port.Close()      
                End If

                portStatus = TestSMConnection()
                If portStatus = True Then
                    Return port
                Else
                    port = Nothing
                    RS232Port.Close()
                End If
            Next
            Return port

        End Function
        Dim _continue As Boolean = True
        Private Function TestSMConnection() As Boolean

            Dim connectionStatus As Boolean = False

            'RS232Port.WriteTimeout = 300
            'RS232Port.ReadTimeout = 300000

            Try
                RS232Port.Open()
                RS232Port.WriteLine(H_MODE_CHECK & vbCr)

                RxTimerOut.Start()

                While _continue
                End While

                If HSerialState = H_MODE_OK Then
                    connectionStatus = True
                    RS232Port.WriteLine(H_MODE_DATALOGGING & vbCr)
                End If

            Catch ex As Exception
                Return connectionStatus
            End Try

            Return connectionStatus

        End Function

        Public Sub Connect()
            AvailablePorts = System.IO.Ports.SerialPort.GetPortNames()
            PortToOpen = FindPort()
            If PortToOpen = Nothing Then
                MsgBox("Could not detect device on any port! Please check connection!")
            End If
        End Sub

        Public Sub Disconnect()
            RS232Port.Close()
            RS232Port.Dispose()
        End Sub

        Private Sub RS232Port_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles RS232Port.DataReceived

            PortRxMsg = RS232Port.ReadLine()
            Debug.WriteLine(PortRxMsg)

            If (PortRxMsg = H_MODE_OK) Then
                HSerialState = H_MODE_OK
                Debug.WriteLine(HSerialState)
                _continue = False
                RxTimerOut.Stop()
                Return
            End If

            If (PortRxMsg = H_MODE_DATALOGGING) Then
                ' buffer data
                '
            End If

        End Sub

        Private Sub RxTimerOut_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles RxTimerOut.Elapsed
            If _continue = True Then
                _continue = False
            End If
            RxTimerOut.Stop()
        End Sub
    End Class

    Tuesday, August 05, 2008 5:37 AM

Answers

  • 1) You can do it in two ways - the simple, but nasty one, and the more complicated one by means of the DataReceived event. My philosophy was that a port running at 115200 bit/s probably will be able to respond within a few milliseconds. Therefore it doesn't matter to block the UI thread while running through the poll sequence one time at startup and it is very simple to program. You can of course also do it by the book and poll from a background thread and then send the result to the UI thread. In this way, you will not block the UI thread. This method should be used if the devices do not respond almost immediately.

     

    When you have found the active ports, you should use the DataReceived event for the communication if the device continue to send data by itself. If you have to poll the data, you can choose between the nasty method where you do everything from the UI thread, or poll from a background thread (when a timer runs out) and receive the result by means of the DataReceived event. It all depends on the response time and the number of bytes to transfer.

     

    2) Yes. With poll I mean sending the command, which courses the device to send a reply. It is called polling when your devices do not send anything until they receive a command to do so.

     

    3) If you just call RS232Port.Readline in a try loop, you will either receive the response to the poll or a timeout exception.

     

    Since you have only one instance of serialport - RS232Port, you can only talk to one port at a time. If you have more USB-Serial converters, you need to change the port name like COM1, COM2.

     

    Since you have put your communication in its own class, you may experience problems with the DataReceived event, which may not fire. I am not sure what happens and why, but some people have solved the problems by making a new instance of the SerialDataReceivedEventHandler delegate - see chapter "Standard delegates" of my serial port totorial: http://www.innovatic.dk/knowledg/SerialCOM/SerialCOM.htm

     

    Note that if you disconnect a device from a USB-serial converter, an unhandled exception will be thrown. This is an old known bug, which Microsoft has never been able to fix although they have tried (they destroyed SerialPort in .Net 3.5 completely in their effords, but didn't fix the bug).

    Tuesday, August 05, 2008 7:21 PM
  • I am not quite sure what your problem is, but if I understand you right it is about transfering the buffer to the UI thread for further processing (your question about whether it is safe to clear the buffer after you fire the event).

     

    To answer the clear question first. To fire or raise an event is nothing but a subroutine call made by means of a delegate (the event delegate) instead of a direct call. This means that all event handlers for that event will be executed before the RaiseEvent call returns. The only difference is that the subroutines to call (event handlers) are specified in the invocation list of the event delegate. You can read much more about this in my tutorial. So yes, it is safe to clear the buffer immediately after you fire the ProgressReport event.

     

    "Once I am executing the ProgressReport event on the UI thread, how do I proceed to complete Part 2?"

    You are wrong here. All method calls are done on the thread from which the call is made. Since an event is nothing but some subrotine calls, all event handlers are executed on the thread that raises the event - in this case the background worker - not the UI thread.

     

    As you can see, events are not flags, which can be read by other programs as many thinks (raising an event is not like raising a flag). To utilize the event driven nature of .Net you need to use the message queue of the UI thread. This is described in details in the tutorial and I will only explain it in short here. The data package you have received from the serial port must be transferred to the UI thread in a message, which can then be processed by methods on the UI thread. You post that message by means of BeginInvoke (also described in details the totorial).

     

    Read my signature. What I would do in your case was to forget the background worker and then just receive one line from your device in the event handler for the DataReceived event. If each line is terminated with a fixed character like CR or LF, a simple ReadLine call may do it. Then I would send that line to the UI thread by means of BeginInvoke. An example of this is shown in the tutorial. The method on the UI thread can then have a cyclic buffer where it saves the last 256 lines, generate an average value and show this on your graph. Of course the event handler for the DataReceived event can also collect 256 lines to a complete report and post this in a message. You don't need to use a background worker because the DataReceived event is executed on a thread pool thread (not the UI thread). Therefore the event handler for the DataReceived event do not need to return before the full package - line or report - is collected or a timeout occur.

     

    For very high speed applications (a very high number of updates per second), you can also utilize that CreateGraphics is one of the four thread safe methods (see totorial) so that you can draw the graph directly from the event handler for the DataReceived event.

     

    There is a reason why my totorial has grown to the size it has. It is my true belief that if you don't understand how the basic functions of .Net works like events, delegates etc., you will not be able to program much, but unfortunately most beginners don't take the time to read and understand all the important background material in the tutorial.

    Friday, August 08, 2008 8:16 AM

All replies

  • What is your exact problem?

     

    It seems to me that you do this rather complicated. A simple loop like this ought to do it:

     

    Dim WithEvents RS232Port As New SerialPort

    RS232Port.BaudRate = 115200     ' Set the default settings

    RS232Port.ReadTimeout = 1000    ' 1 sec. read timeout

     

    For Each COMName As string In My.Computer.Ports.SerialPortNames

        If Rs232Port.IsOpen then

            RS232Port.Close()

            Application.DoEvents()  ' Give port time to close down

            Sleep(200)

        End If

        RS232Port.PortName = COMName

        Try

            RS232Port.Open()        ' Initialize the driver

        Catch ex As exception

            MsgBox(ex.Message)

        End Try

        ' Send your poll

        ' Wait for reply or timeout

        ' Record the status

    Next

     

    Remember to close the port when you terminate your application or after the poll. Also remember that each instance of SerialPort can only support one COM port at a time.

    Tuesday, August 05, 2008 8:20 AM
  • Carsten,

    Thank you very much for your response, I will give it a shot and get back to you. I have a few questions for you, thank you very much for your guidance and time!

    1. Using your model, can I still use my DataReceived Event?
    2. When you say Send you poll? - meaning my sequence of handshaking commands, right?
    3. How would I wait for a reply? just by setting a ReadTimeOut  or polling the serial port with RS232.Readline()?

    Further details regarding my problem:

    I try to ping the connected device with a SMS "Mahince Status".  Once it responds with SMA "Machine Active", I request the device to enter a SM2DL - "Machine Data Logging", where the machine will continue to send data to the PC.

    The problem, I seemed to have with my "complicated" code ... when I would test the application with multiple (2) "USB - Serial" devices connected to the computer and either one to my hardware device for the auto port detection and handshaking, it would not work. It always seemed like, it would be able to transmit, but my DataReceived Event would not fire.

    It often works, if I had only 1 USB-Serial connected on the PC. I dont' think it is a USB enumeration or such issue. I believe it has something to do with my code.


    Thanks again! I was really starting to drown.
    Tuesday, August 05, 2008 5:36 PM
  • 1) You can do it in two ways - the simple, but nasty one, and the more complicated one by means of the DataReceived event. My philosophy was that a port running at 115200 bit/s probably will be able to respond within a few milliseconds. Therefore it doesn't matter to block the UI thread while running through the poll sequence one time at startup and it is very simple to program. You can of course also do it by the book and poll from a background thread and then send the result to the UI thread. In this way, you will not block the UI thread. This method should be used if the devices do not respond almost immediately.

     

    When you have found the active ports, you should use the DataReceived event for the communication if the device continue to send data by itself. If you have to poll the data, you can choose between the nasty method where you do everything from the UI thread, or poll from a background thread (when a timer runs out) and receive the result by means of the DataReceived event. It all depends on the response time and the number of bytes to transfer.

     

    2) Yes. With poll I mean sending the command, which courses the device to send a reply. It is called polling when your devices do not send anything until they receive a command to do so.

     

    3) If you just call RS232Port.Readline in a try loop, you will either receive the response to the poll or a timeout exception.

     

    Since you have only one instance of serialport - RS232Port, you can only talk to one port at a time. If you have more USB-Serial converters, you need to change the port name like COM1, COM2.

     

    Since you have put your communication in its own class, you may experience problems with the DataReceived event, which may not fire. I am not sure what happens and why, but some people have solved the problems by making a new instance of the SerialDataReceivedEventHandler delegate - see chapter "Standard delegates" of my serial port totorial: http://www.innovatic.dk/knowledg/SerialCOM/SerialCOM.htm

     

    Note that if you disconnect a device from a USB-serial converter, an unhandled exception will be thrown. This is an old known bug, which Microsoft has never been able to fix although they have tried (they destroyed SerialPort in .Net 3.5 completely in their effords, but didn't fix the bug).

    Tuesday, August 05, 2008 7:21 PM
  • Carsten,

    I need some further advice on a topic I believe you may have mastered, based on a long long forum I read of your a few weeks ago.

    The application, I am trying to build involves the following parts:

    1. Read data from serial port at 115200 bps: using your prior advice, I was able to do so on a BackGroundWorker thread. I fire a ProgressReport event every time I have acquired 256 packets or messages.
    2. Process/Parse/Sort packets in the buffer received from the BackGroundWorker. I am not sure on what to do from here on!
    3. Once I have collected and populated the necessary objects, I need to generate a graph
    4. Finally be able to generate reports
    I am mostly confused on what to do from Part 2 on ..

    I will receive new buffer packets of 256 rows from the serial everytime ProgressReport is fired.

    Is it safe to clear my buffer immediately after I fire the ProgressReport event?
    Once I am executing the ProgressReport event on the UI thread, how do I proceed to complete Part 2?

    I was thinking about using another BackGroundWorker, but I feel like I may be hacking my design and desire to do it more professionally and effectively.

    Here is a brief sample of what my data may look like:

    A1864186590519998791500602160135  (Record Set 1 - message type 1)
    A186418659052002000147910010505480392041004971421005411080379009703230282027102511296 (RS 1- type 2)
    A2864186590519998791500602160135 (Record Set 2 - message type 1)
    A186418659052002100156484158106150736056701870723025401670430017912640053024501010218 (RS 1- type 2)
    A286418659052002000147910010505480392041004971421005411080379009703230282027102511296 (RS 2- type 2)
    A286418659052002100156484158106150736056701870723025401670430017912640053024501010218 (RS 2- type 2)


    Please advice
    Friday, August 08, 2008 6:29 AM
  • I am not quite sure what your problem is, but if I understand you right it is about transfering the buffer to the UI thread for further processing (your question about whether it is safe to clear the buffer after you fire the event).

     

    To answer the clear question first. To fire or raise an event is nothing but a subroutine call made by means of a delegate (the event delegate) instead of a direct call. This means that all event handlers for that event will be executed before the RaiseEvent call returns. The only difference is that the subroutines to call (event handlers) are specified in the invocation list of the event delegate. You can read much more about this in my tutorial. So yes, it is safe to clear the buffer immediately after you fire the ProgressReport event.

     

    "Once I am executing the ProgressReport event on the UI thread, how do I proceed to complete Part 2?"

    You are wrong here. All method calls are done on the thread from which the call is made. Since an event is nothing but some subrotine calls, all event handlers are executed on the thread that raises the event - in this case the background worker - not the UI thread.

     

    As you can see, events are not flags, which can be read by other programs as many thinks (raising an event is not like raising a flag). To utilize the event driven nature of .Net you need to use the message queue of the UI thread. This is described in details in the tutorial and I will only explain it in short here. The data package you have received from the serial port must be transferred to the UI thread in a message, which can then be processed by methods on the UI thread. You post that message by means of BeginInvoke (also described in details the totorial).

     

    Read my signature. What I would do in your case was to forget the background worker and then just receive one line from your device in the event handler for the DataReceived event. If each line is terminated with a fixed character like CR or LF, a simple ReadLine call may do it. Then I would send that line to the UI thread by means of BeginInvoke. An example of this is shown in the tutorial. The method on the UI thread can then have a cyclic buffer where it saves the last 256 lines, generate an average value and show this on your graph. Of course the event handler for the DataReceived event can also collect 256 lines to a complete report and post this in a message. You don't need to use a background worker because the DataReceived event is executed on a thread pool thread (not the UI thread). Therefore the event handler for the DataReceived event do not need to return before the full package - line or report - is collected or a timeout occur.

     

    For very high speed applications (a very high number of updates per second), you can also utilize that CreateGraphics is one of the four thread safe methods (see totorial) so that you can draw the graph directly from the event handler for the DataReceived event.

     

    There is a reason why my totorial has grown to the size it has. It is my true belief that if you don't understand how the basic functions of .Net works like events, delegates etc., you will not be able to program much, but unfortunately most beginners don't take the time to read and understand all the important background material in the tutorial.

    Friday, August 08, 2008 8:16 AM
  • Carsten:

    The ProgressChanged event is on the UI thread. You can pass whatever you wish in the UserState object and use it freely on the UI thread.

    Friday, August 08, 2008 9:39 AM
  • John

     

    Sorry that I may have misunderstood it, but engrforever mention ProgressReport - not ProgressChanged, so I thought that it was his own event. .Net has no ProgressReport event.

    Friday, August 08, 2008 9:55 AM
  • Oops .. i did mean .."ProgressChanged" event of the BackGroundWorker class.
    Friday, August 08, 2008 12:44 PM