Solved

Reading asynchronous serial port data in VB 2005

Posted on 2008-06-12
7
1,435 Views
Last Modified: 2013-11-26
Hello everyone,

I'm developing a driver that sends out text commands via serial port to an instrument. When that instrument is finished executing the command, or an error results, it will send back a text result. I expect this message will be less than 15 characters long.

1) I'm able to send out the commands and have the instrument respond, but am having difficulty capturing the Event and the message, as I'm new to Serial Communication and Event Handling. I've attached the code I've written.

2) What is the best way to wait for a response, with a given timeout value? Each command should be followed by a response, successful or not.

3) Is it better to open the COM port once or open and close it before/after each command?

Thanks!
Haj


Module Module1
 
    Sub Main()
        Dim strCommand As String
        Dim CtrlJ As New Char
        Dim strReset As String ' Reset command
        Dim strPlateRequest As String ' Plate request command
        Dim strPlateReturn As String ' Plate return command
        Dim strReturnValue As String ' Post-command status returned from Q-Stack
 
        ' Commands have a <LF> (Ctrl-J or ASCII (10)) before and after.
        CtrlJ = Chr(10) ' Ctrl-J = Linefeed (ASCII)
        strReset = CtrlJ & "AK5=1" & CtrlJ
        strPlateRequest = CtrlJ & "AK3=1" & CtrlJ
        strPlateReturn = CtrlJ & "AK4=1" & CtrlJ
 
        strCommand = strReset ' Subsitute any of the commands for testing purposes
        OutToCOM4(strCommand, False)
 
        strReturnValue = ""
        InFromCOM4(strReturnValue) ' Wait for status from instrument before proceeding
        MsgBox(strReturnValue)
 
    End Sub
 
    Public Sub OutToCOM4(ByVal serialData As String, _
        ByVal useLineTermination As Boolean)
        Dim com4Port As IO.Ports.SerialPort = Nothing
 
        Try
            ' ---- Access the port.
            com4Port = My.Computer.Ports.OpenSerialPort("COM4", 9600, IO.Ports.Parity.None, 8, IO.Ports.StopBits.One)
            ' COM Port, baud rate, parity, data bits, stop bits
 
            ' ---- Write the data.
            If (useLineTermination = True) Then
                com4Port.WriteLine(serialData)
            Else
                com4Port.Write(serialData)
            End If
 
            ' ---- Finished with the port.
            com4Port.Close()
 
        Catch ex As Exception
            MsgBox("Error writing data to serial port: " & _
                ex.Message)
 
        Finally
            If (com4Port IsNot Nothing) Then com4Port.Dispose()
            com4Port = Nothing
 
        End Try
    End Sub
 
    Public Sub InFromCOM4(ByVal returnData As String)
        Dim com4Port As IO.Ports.SerialPort = Nothing
 
 
        Try
            ' ---- Access the port.
            com4Port = My.Computer.Ports.OpenSerialPort("COM4", 9600, IO.Ports.Parity.None, 8, IO.Ports.StopBits.One)
            ' COM Port, baud rate, parity, data bits, stop bits
 
            ' ---- Wait for incoming data.
            '--->>>   What is the best way to wait for incoming data? <<<---
 
            ' ---- Read the data.
            '--->>>   returnData = com4Port.??? ' How do I capture the incoming data? <<<---
 
            ' ---- Finished with the port.
            com4Port.Close()
 
        Catch ex As Exception
            MsgBox("Error reading data from serial port: " & _
                ex.Message)
 
        Finally
            If (com4Port IsNot Nothing) Then com4Port.Dispose()
            com4Port = Nothing
 
        End Try
    End Sub
 
End Module

Open in new window

0
Comment
Question by:hsano6294
  • 3
  • 2
7 Comments
 
LVL 11

Expert Comment

by:AkisC
ID: 21776720
If you are using a SerialPort control (Tool box->Components) it is easy
Let's say you have named your SerialPort control mySerialPort

You capture the data within the DataReceived sub
    Private Sub DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles mySerialPort.DataReceived
        'fires when data is received in the input buffer
        Dim buffer As String = "", bufASCII As Integer = 0, aCHR As String = ""
        Do
            If mySerialPort.BytesToRead > 0 Then
                bufASCII = mySerialPort.ReadByte
                aCHR = ChrW(bufASCII)
                buffer &= aCHR
            Else
                Exit Do
            End If
        Loop
        If buffer.Length > 0 Then
            '/////com response///////
        End If
    End Sub

Open in new window

0
 

Author Comment

by:hsano6294
ID: 21861319
AkisC,

Sorry for the delay. The production system just became available to me.

1) Can your DataReceived subroutine handle ReadLine as well as ReadByte?
2) What do I put in my main routine that sent out my WriteLine string and is waiting for the response? The response may take as long as 45 seconds before the mechanical components finish moving. Do I put the main program in a loop waiting for the event? And does the serial port need to be declared with a special option to handle the event?

Thanks!
Haj
0
 
LVL 11

Accepted Solution

by:
AkisC earned 500 total points
ID: 21862579
Q:"1) Can your DataReceived subroutine handle ReadLine as well as ReadByte?"
As you see in the code use ReadByte and is converted to string aCHR = ChrW(bufASCII) 'valid for ascii chars only
I beleive it is safest to read one-byte-at-atime

Q:"2) What do I put in my main routine that sent out my WriteLine string and is waiting for the response?The response may take as long as 45 seconds before the mechanical components finish moving. Do I put the main program in a loop waiting for the event? "
You do not have to wait. When dataReceived you have it there. You can have a (hidden) label to alert your user like label1.text="waiting response...":label1.visible=true. When you receive the response.label1.visible=false

Q:"And does the serial port need to be declared with a special option to handle the event?"
No just put the sub as above. When data is recevied fires...
Drop a SerialPort control (Tool box->Components) name it mySerialPort as sub above (Handles mySerialPort.DataReceive) and then rename it from control properties to any name you want (VB2005 translates automatically all your code)

Q:"3) Is it better to open the COM port once or open and close it before/after each command?"
I paste the code I use (you will have some errors when you paste it in your code, since I use some public variables-but-I guess it is to grasp the idea)

On formLoad I open it once like
Disconnect_COMPort()
'Initialize_UI()  '////initialize my user interface -you have your own
Connect_COMPort()

Have fun coding... ;)
Private Sub Connect_COMPort()
        Try
            If Not mySerialPort.IsOpen Then
                If MyConfiguredSerialPort.Length > 0 Then
                    Dim strCOM As String() = Split(MyConfiguredSerialPort, ",")
                    PortNameCOM1 = strCOM(0)
                    Disconnect_COMPort() 'Disconnect_mySerialPort
                    'Set ComPort Rates
                    mySerialPort.PortName = PortNameCOM1
                    mySerialPort.BaudRate = CInt(strCOM(1))
                    mySerialPort.Parity = CType([Enum].Parse(GetType(Ports.Parity), strCOM(2)), Ports.Parity)
                    mySerialPort.DataBits = CInt(strCOM(3))
                    mySerialPort.Handshake = CType([Enum].Parse(GetType(Ports.Handshake), strCOM(4)), Ports.Handshake)
                    mySerialPort.DtrEnable = True
                    mySerialPort.RtsEnable = True
                    If Not mySerialPort.IsOpen Then
                        mySerialPort.ReadBufferSize = 2
                        mySerialPort.ReadTimeout = 5000
                        mySerialPort.WriteTimeout = 5000
                        mySerialPort.Encoding = ASCII
                        Try
                            mySerialPort.Open()
                            defender.Pause(0.4)
                        Catch ex1 As Exception
                            CatchThisError(FRM_HEADER & "Connect_COMPort:" & PortNameCOM1 & Chr(251) & ex1.ToString)
                            If ShowErrorMessages Then MsgBox("Sub ->Connect_COMPort" & ex1.ToString, , PortNameCOM1)
                        End Try
                    End If
                End If
            End If
            update_mySerialPort_IconsAndLabels()
        Catch ex2 As Exception
            CatchThisError(FRM_HEADER & "Connect_COMPort:" & PortNameCOM1 & Chr(251) & ex2.ToString)
            If ShowErrorMessages Then MsgBox("Sub ->Connect_COMPort " & ex2.ToString, , PortNameCOM1)
        End Try
    End Sub
    Private Sub Disconnect_COMPort()
        Try
            If Me.mySerialPort.IsOpen Then
                mySerialPort.RtsEnable = False
                mySerialPort.DtrEnable = False
                mySerialPort.Close()
                Application.DoEvents()
                Sleep(1000)
            End If
        Catch ex As Exception
            CatchThisError(FRM_HEADER & "_" & "Disconnect_COMPort" & PortNameCOM1 & Chr(251) & ex.ToString)
            If ShowErrorMessages Then MsgBox("Sub ->Disconnect_COMPort " & PortNameCOM1 & ex.ToString, , FRM_HEADER)
        End Try
    End Sub

Open in new window

0
 

Author Comment

by:hsano6294
ID: 22145839
Thanks for your help AkisC. My apologies- the system just deployed to the customer and we are in the middle of final integration and installation. I used portions of your suggestions in the final solution. There were additional issues that arose related to how the instrument communicates with its own internal components.
I have attached key portions of the code.
Thanks again, and my apologies for not posting this in a more timely manner.
    Private WithEvents _serialPort As IO.Ports.SerialPort
 
 
Friend Function Send(ByVal Data As String) As String
        ' Function will return to calling method as soon as it receives the processed return message.
        Dim returnMessage As String = String.Empty
        Try
 
            'Read the port buffer, timeout is set in New
            Dim received As String = String.Empty
            Try
                _serialPort.Write(Data)
 
                ' Read incoming reply
                Dim incomingMessage As String = String.Empty
                Dim _continue As Boolean = True
                Do
                    Thread.Sleep(30) ' Wait 30 msec before checking cmdIndex
 
                     If cmdIndex = 0 Then 'key at beginning, received processed message from DataReceived
                        returnMessage = inMessageBuffer
 
                        Return returnMessage ' Return before another incoming message disrupts things
                         _continue = False
                    End If
                Loop While (_continue)
            Catch ex As Exception
 
                'return has timed out - received will be blank
                Throw
            End Try
 
        Catch ex As Exception
            
            Throw
 
        End Try
 
        Return returnMessage
    End Function
-------------------------------
    ' Event Handler for incoming data
    Private Sub DataReceived( _
        ByVal sender As Object, _
        ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) _
        Handles _serialPort.DataReceived
 
        Dim ValidMessage As Boolean = False
        inMessage = _serialPort.ReadLine
 
        inMessageBuffer = String.Empty
        cmdIndex = -999
        ' Dim key As String = "P"
        cmdIndex = inMessage.IndexOf(cmdKey)
 
        'If cmdIndex < 0 Then ' key not found, check if valid reply is embedded
        If (inMessage.Contains("PDCOMP")) Then ' Destack complete
 
            inMessageBuffer = "PDCOMP"
            cmdIndex = 0
            ValidMessage = True
 
        ElseIf (inMessage.Contains("PMCOMP")) Then ' MoveToStation or ReRead complete
 
            inMessageBuffer = "PMCOMP"
            cmdIndex = 0
            ValidMessage = True
 
	ElseIf ' check for other valid return messages
 
        End If
 
        'End If
        If (ValidMessage) Then
 
            ClosePort() ' Call ClosePort to immediately close serial port and prevent additional traffic from interrupting post-processing.
 
        End If
 
    End Sub
-------------------------------
    Friend Sub ClosePort()
        Try
            'Close serial port
 
            If (_serialPort IsNot Nothing) AndAlso _serialPort.IsOpen Then
 
                _serialPort.Close()
            Else
                ' Port is not open
            End If
 
 
        Catch ex As Exception
 
            Throw
        End Try
    End Sub

Open in new window

0
 
LVL 11

Expert Comment

by:AkisC
ID: 22145937
Hi hsano6294
Nice you got it working!
If you face any issues let me know

0

Featured Post

3 Use Cases for Connected Systems

Our Dev teams are like yours. They’re continually cranking out code for new features/bugs fixes, testing, deploying, testing some more, responding to production monitoring events and more. It’s complex. So, we thought you’d like to see what’s working for us.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
This video shows how to quickly and easily add an email signature for all users on Exchange 2016. The resulting signature is applied on a server level by Exchange Online. The email signature template has been downloaded from: www.mail-signatures…
A short tutorial showing how to set up an email signature in Outlook on the Web (previously known as OWA). For free email signatures designs, visit https://www.mail-signatures.com/articles/signature-templates/?sts=6651 If you want to manage em…

823 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question