Results 1 to 15 of 15

Thread: Serial Port

Threaded View

  1. #1

    Thread Starter
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,897

    Serial Port

    If you have a question about this please post the question here
    http://www.vbforums.com/forumdisplay.php?f=25 not in this tread. Thanks!

    Much of the difficulty I see here, and in other forums, surrounding the use of Windows serial port is centered around an assumption.

    That assumption is that the serial port receives data in chunks that are conveniently packaged in the format we want. If you are talking about terminal stuff that is true, they are called lines and it is easy. But when that is not the case panic sets in.

    The second thing I see is people fighting the DataReceived Event Handler. My advice is don’t. Treat it like a spoiled child that is never going to learn and just give it whatever it wants, whenever it wants.

    Lastly, a byte can contain 256 values. The standard encoder for the serial port is for 7-bit data. If you have 8 bit data you need to change the encoding. I use
    System.Text.Encoding.GetEncoding("Windows-1252") always and have not had any problems.

    I wanted fast code, and to write the code in such a way that it could be used for any application.

    I have successfully sent, received, processed, and updated the UI at close to 1Mbps (USB Serial Converter) using the techniques I will describe. My test bed is a USB SerialConverter to breakout box with Pins 2 & 3 hot wired.



    **DataReceived Event Handler**

    Mine is short, sweet, simple. I read the number of bytes available (.BytesToRead) into a buffer of bytes of the correct size. I store that buffer of bytes into a queue.

    Depending on how the UI is being driven I then raise an event, start a delegate, or do nothing if it is timer or usr interaction based.

    That is it! Any time it wants to fire, it can.



    **So how does the data get to the user…**

    Ask yourself what are the possible scenarios of how you might want data? You might want:

    -one byte
    -a number of bytes
    -a character
    -a string
    -a string delimited by a character (like lines)
    -etc.

    And what if you want 2 bytes, then 4 characters, then a byte, and then a variable length line?

    At this point my DataReceived Event Handler has placed any data received into a queue. Here is an example. I have a GPS receiver that sends NMEA sentences (lines ending with CRLF). This is an example of one of the sentences:

    $GPGSV,3,1,11,10,75,053,29,29,52,311,32,24,50,298,30,02,39,073,30*77

    The serial ports DataReceived Event Handler might (usually does on my PC) fire several times with chunks of that data. The queue might look like this:


    Queue entry – data
    1 $
    2 GPGSV,3,1,11,10
    3 ,75,053,29,29,52,311,
    4 32,24,50,298,30,02,39,073,30*77

    The approach I took, with my limited knowledge of the programming tool at the time, was to write several routines that pieced the data back together. The common routine for all of those created a user buffer by removing entries from the queue and piecing them back together. The other routines then access the user buffer when they need data, and in the case of string based functions, converts it. If there is not enough data to satisfy the request the routines return empty strings or zero length byte arrays.

    Lets say I used a Timer control to read, process the sentences, and update the UI from my GPS receiver. In my Timer routine I have a function call to my version of readline. The first few times it is called it might return empty strings because the entire sentence has not been received. Eventually all of the data is in and I get the entire sentence back, because it is actually all there.

    At time 1
    Queue entry – data
    1 $
    2 GPGSV,3,1,11,10

    User buffer – empty


    At time 2
    Queue entry – data
    1 ,75,053,29,29,52,311,
    2 32,24,50,298,30,02,39,073,30*77

    User buffer – $ GPGSV,3,1,11,10


    At time 3
    Queue empty


    User buffer – $ GPGSV,3,1,11,10,75,053,29,29,52,311, 32,24,50,298,30,02,39,073,30*77


    The only imperative is that when you have a device that sends data all the time is that you process it in a timely manner. If not, as you can imagine, you run out of memory.

    During my testing of the USB to SerialPort converter I was surprised to find that it was capable of speeds near 1Mbps and I made the mistake of not doing that. I was using a user interface driven (click a button, see the data) routine that read all of the data and displayed the last chunk, when I received a high priority interrupt from Honey. You know the one, take the trash out, change the light bulb, etc. I left the test bed running and I had received an out of memory exception while servicing the Honey interrupt.


    What prompted me to write this was something I read in another thread. The poster had a problem because he said the device he was interfacing with only provided a 1 ms. gap in the data as its protocol. As it turned out the protocol was a delimiter, followed by a length byte, followed by the data. Then the problem was that that wasn’t enough to know that you actually had good data. The posters point was this:

    message_delimiter - 1 byte
    4 - length - 1 byte
    (4 data bytes) - 4 bytes

    But if you look at it this way, a **valid** message is always bounded by the message_delimiter:

    message_delimiter - 1 byte
    4 - length - 1 byte
    (4 data bytes) - 4 bytes
    message_delimiter - 1 byte

    Then you can safely process the data.


    Many of you are better at using your programming tool of choice. The code I wrote for serial ports was written several years ago when I was just learning VB .Net.(I cringe when I look at some of it today). But I am willing to bet that it will out perform most of the C(pick a flavor) code I have seen posted on many forums. One day I plan on re-visiting the code.

    I hope that this helps.





    The attached has several examples that use the loopback feature of a modem to do simulations. If you have a modem you should just need to open the port "COMx" and click go(a serial port with a loopback plug should work also). There is a debug feature, but it should only be used sparingly and for short periods of time. The examples show how to do reads using a timer, delegate, or loop.

    There is one other example that reads NMEA sentences and checks that the checksum is correct.

    There are routines to read bytes, strings and terminated strings.


    Enjoy.
    Link to this post http://www.vbforums.com/showpost.php...31&postcount=1

    VB 2008 Version
    NuSerial.zip



    updated - 3 Jan 2009
    changed sample program to remove confusion over receiving bytes backwards, at least i hope i did.

    updated - 9 Jan 2009
    corrected error in zip file

    updated - 29 Jan 2009
    new version

    EDIT: 31 March 2023

    A simple version of how to do SerialPorts. The test code requires a button, a textbox, three labels and a richtextbox.

    Two background tasks run. One accumulates the data looking for a message terminated by a carriage return. When found the message is queued for the message processing task.

    Code:
    Public Class Form1
    #Region "CODE TO TEST SERIAL PORT IMPLEMENTATION"
    
        '>>>>>>>>>>>>>>>>>>>>>>>>  
        Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
            'start the worker tasks <<<<<<<<<<<<!!!!!
            'this could be in the load event also
            MessageProcTask = Task.Run(Sub() ProcMessages())
            ReadSerialPortTask = Task.Run(Sub() ReadSerialPort())
    
            SerialPort1.PortName = "COM1"
            'OverRun errors occur at speeds > 38400 when using the loop back plug
            SerialPort1.BaudRate = 38400 '110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200 
            SerialPort1.Open()
            SerialPort1.DtrEnable = True
            SendStuff()
        End Sub
    
        Private Sub SendStuff()
            ' this is ran on a PC with a hardware serial port
            '  this port has a loop back plug which means what is sent is received on the same PC
            Static t As Task
            If t Is Nothing OrElse t.Status = TaskStatus.RanToCompletion Then
                Me.BeginInvoke(Sub()
                                   RichTextBox1.Clear()
                                   Label2.Text = _DataReceivedEvents.ToString("n0")
                                   Label1.Text = _messagesRcvd.ToString("n0")
                               End Sub)
                t = Task.Run(Sub()
                                 Dim mess As String
                                 Dim mTX As Integer
                                 Threading.Thread.Sleep(100)
                                 Const sendCT As Integer = 100
                                 For mTX = 1 To sendCT
                                     mess = String.Format("{1}{0}FOO {1}{0}{1,4} Lorem ipsum dolor sit amet, consectetur adipiscing...{0}",
                                                            ControlChars.Tab, mTX)
                                     SerialPort1.Write(mess)
                                     SerialPort1.Write(ControlChars.CrLf)
                                 Next
    
                                 'test empty messages
                                 SerialPort1.Write(ControlChars.Cr)
                                 SerialPort1.Write(ControlChars.Cr)
    
                                 Threading.Thread.Sleep(sendCT \ 100 * 1000 + 500) 'give a chance for all messages to be processed
    
                                 Me.BeginInvoke(Sub()
                                                    Label2.Text = _DataReceivedEvents.ToString("n0")
                                                    Label1.Text = _messagesRcvd.ToString("n0")
                                                    Label3.Text = _ErrorReceivedEvents.ToString("n0")
                                                    _DataReceivedEvents = 0
                                                    _messagesRcvd = 0
                                                    _ErrorReceivedEvents = 0
                                                End Sub)
                             End Sub)
            End If
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            SendStuff()
        End Sub
    #End Region
    
    #Region "SERIAL PORT IMPLEMENTATION"
        Private WithEvents SerialPort1 As New IO.Ports.SerialPort
        Private _ErrorReceivedEvents As Long = 0L
        Private Sub SerialPort1_ErrorReceived(sender As Object,
                                              e As IO.Ports.SerialErrorReceivedEventArgs) Handles SerialPort1.ErrorReceived
            Threading.Interlocked.Increment(_ErrorReceivedEvents)
            Dim err As Long = Threading.Interlocked.Read(_ErrorReceivedEvents)
            Debug.Write(err.ToString("n0"))
            Debug.Write("  ERR  ")
            Debug.WriteLine(e.EventType.ToString)
            ' Stop
        End Sub
    
        Private _DataReceivedEvents As Integer = 0 'typically a lot more _DataReceivedEvents than of _messagesRcvd
        Private ReadWait As New Threading.AutoResetEvent(False)
        Private Sub SerialPort1_DataReceived(sender As Object,
                                             e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
            'this is the entire DataReceived event handler
            Threading.Interlocked.Increment(_DataReceivedEvents)
            ReadWait.Set() 'cause ReadSerialPort to run
        End Sub
    
        Private _messagesRcvd As Integer = 0
        Private ReadSerialPortTask As Task
        Private Sub ReadSerialPort()
            '
            'this is run as a Background task
            '
            Dim data As New System.Text.StringBuilder(1024 * 1024) 'accumulate SerialPort data here
            Dim mess As New System.Text.StringBuilder(1024 * 4) 'individual message
            Dim foundMess As Boolean = False
            Do
                ReadWait.WaitOne() 'wait for SerialPort1.DataReceived
    
                Do While SerialPort1.BytesToRead > 0 'accumulate all available bytes
                    data.Append(SerialPort1.ReadExisting)
                Loop
                Do 'process message, if one available
                    '  protocol
                    '   in this case looking for a string terminated by CR
                    mess.Length = 0 'reset message
                    foundMess = False ' and not found
                    Dim idx As Integer = 0
                    For idx = 0 To data.Length - 1
                        'look for CR??????
                        If data(idx) = ControlChars.Cr Then '<<<<<<<<<<<<<<<<<<<<<<<
                            'note that the CR will NOT be in mess
                            foundMess = True 'message found 
                            Threading.Interlocked.Increment(_messagesRcvd)
                            Exit For
                        ElseIf data(idx) = ControlChars.Lf Then '<<<<<<<<<<<<<<<<<<<<<<<
                            Continue For 'skip LF
                        End If
                        mess.Append(data(idx))
                    Next
                    If foundMess Then
                        data.Remove(0, idx + 1) 'remove found message from data
                        _messageQ.Enqueue(mess.ToString)
                        MessageWait.Set() ' method ProcMessages will process messages
                    End If
                    'look for more messages in data?
                Loop While foundMess AndAlso data.Length > 0 'found one, more data present 
            Loop
        End Sub
    
        Private MessageProcTask As Task
        Private _messagesProcd As Integer = 0
        Private MessageWait As New Threading.AutoResetEvent(False)
        Private _messageQ As Concurrent.ConcurrentQueue(Of String)
        Private Sub ProcMessages()
            _messageQ = New Concurrent.ConcurrentQueue(Of String)
            Do
                MessageWait.WaitOne()
                Do While _messageQ.Count > 0
                    Dim mess As String
                    If _messageQ.TryDequeue(mess) Then
                        Dim parts() As String
                        parts = mess.Split(ControlChars.Tab)
                        '  interact with the UI
                        If parts.Length < 3 Then Continue Do
                        Me.BeginInvoke(Sub()
                                           Label1.Text = parts(0)
                                           If parts.Length >= 3 Then TextBox1.Text = parts(2)
                                           RichTextBox1.AppendText(parts(1))
                                           RichTextBox1.AppendText(ControlChars.Cr)
                                           RichTextBox1.ScrollToCaret()
                                       End Sub)
                    Else
                        Threading.Thread.Sleep(0)
                    End If
                Loop
            Loop
        End Sub
    #End Region
    End Class
    Last edited by dbasnett; Mar 31st, 2023 at 12:29 PM.
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width