Author Topic: RS232 timing /write / buffering inside - DataReceived Event  (Read 16912 times)

LiamC

  • Member
  • ***
  • Posts: 4
RS232 timing /write / buffering inside - DataReceived Event
« on: September 24, 2013, 05:38:10 am »
Hello to all users  :)

I’m presently coding an application in VB.NET (VS 2010 Express on the laptop) with different parts/functions. I have to tell that I’m not an expert in neither VB or .Net  But I’ve been busy on that issue for some time now. I’m trying to achieve the following:

-   Send commands (string) to a Newport controller through RS232 (which in turn, drives a 2 axis-motor to rotate an item placed on these axis). For example the command to move the axis n°1 to the position “20 degrees” will look like : “1PA20” (ie: axis1-Position Absolute-20°)

-   Once one movement of these 2 axis is finished, the controller sends back an answer to the first command string (ie: position, state of the motor, state of the movement: finished or running)

-   After verifying the state of the rotating axis (it has a “1” at the end of the return string when it has stopped), I’m running an image processing thread (backgroundworker for the image Processing and UI thread for the Serialport stuff) which acquires images from CCDs cameras and processes them.

-   The sequence detailed above happens 50 times (ie: 50 positions coordinates for the 2 axis, and the respective 50 images acquisitions after each and every position).

Now, that the context is explained, here is the problem:

For the moment I’m using ReadExisting() which maybe isn’t the best answer to my subject. From time to time, I’m having a timing issue with the Data Received Event of the Serialport class: he seems to fire too quickly and during acquisition of let’s say position n°6, the axis rotates to position n°7 (even though the thread is not the same).  As he does it faster than usual, I thought of a buffer issue with some remaining data in it.

For the process to run a bit smoothly with my actual buggy setup, I have set the “ReceivedBytesThreshold” to 34, although the length of the string is closer to 70 char total (as they also are negative positions, the length of the string sent to the controller isn’t quite the same for the 50 cases, “1PA-10” being one char longer than “1PA20”). When this parameter is set to the exact length of the string (let’s say 70), the DataReceived event doesn’t fire.

The problem only occurs when I’m in a For Loop and going through the whole moving axis+process one after the other. The serialport part doesn’t produce an awkward behavior when used as a standalone vb program: I can send a 120 char long string and the movement is perfect and the answers in return in the richtextbox are as expected. 

My questions:

1.   Is this an OutBuffer issue?
2.   Could DiscardOutBuffer + Breakstate (just after writing to the port or someplace else) bring a stable solution?
3.   Should I use something not based on the DataReceived Event? Like maybe WriteLine() + ReadLine() with a vbCr as newline and try to stop the writing as soon as the vbCr is found? How to time the checking of the returned chars in that case ? (I would still need to read the last 2 char of the controller’s answer for each step).

Jan Axelson

  • Administrator
  • Frequent Contributor
  • *****
  • Posts: 3033
    • Lakeview Research
Re: RS232 timing /write / buffering inside - DataReceived Event
« Reply #1 on: September 26, 2013, 04:44:22 pm »
Using ReadLine can ensure that the whole command is present when you read it.

ReadExisting with a threshold > 1 can mean that the reads hang while waiting to reach the threshold. You can read with a threshold of 1 and string together data from multiple reads.

Does that help/make sense?

LiamC

  • Member
  • ***
  • Posts: 4
Re: RS232 timing /write / buffering inside - DataReceived Event
« Reply #2 on: September 27, 2013, 03:25:43 am »
Hello, thanks for taking a look at my issue :)

I have had some troubles with timings, but the whole start of the problem is ReadExisting as it reads buffer/stream too often for my application.

I have a setup with 2 rotating arms and an object placed on it. This object is illuminated. We move the arms (and thus the object) in 50 positions. For every position reached (arms are STOPPED) we grab an image from a camera (hence the timing/pause issues in this context).

So, it looks like this:

definition of the serialport
open port
send command to port (string command with Serialport.Write() , the controller moving the arms needs commands like "1PA20" = move axis 1 to Absolute Position 20", etc.
Verify that the arms have reached the right position and are stopped (here it is the read() returning string from the controller (terminated by CrLf)
When and only when we know the arms have stopped, invite the user to click on next to go to the fram grabing module (image grab + processing)
When the procesing is finished, RE-ENTER the "moving arms" LOOP and go to position +1

x 50 times.

I think I would need to see if a Cr is present in the string found in DataReceivedEvent (ie: total return message is there), get that string and process it (if I find a "1" 2 chars from the end = movement has stopped, if it is a "0" : movement in progress). The thing I don't want is the arm moving (controller getting a command, or going to step +1) when I'm ACQUIRING an image ! That's the main issue.

Is a byte conversion and count ByteToRead, and then convert to string the returning message feasible ? As I need to send char/string to the controller...

How to define in a not so amateur way (mine !) the link between the 2 processes (one function on one thread and the other on another thread ?). Run the serialport part in the UI and have a background worker somewhere ?

Thanks again for sharing your knowledge on this !
« Last Edit: September 27, 2013, 03:49:35 am by LiamC »

LiamC

  • Member
  • ***
  • Posts: 4
Re: RS232 timing /write / buffering inside - DataReceived Event
« Reply #3 on: October 02, 2013, 03:21:27 am »
Using ReadLine can ensure that the whole command is present when you read it.

ReadExisting with a threshold > 1 can mean that the reads hang while waiting to reach the threshold. You can read with a threshold of 1 and string together data from multiple reads.

Does that help/make sense?

IHi,

I skipped the readexisting and turned to a Read with bytes + BytesToRead. Someone helped me and we got the following code running for one position:

Code: [Select]

Option Strict On
Public Class Form1

    Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
        '
        'port setup and open
        '
        Dim p() As String = IO.Ports.SerialPort.GetPortNames

        'settings here
        With SerialPort1
            .PortName = "COM1"
            .Encoding = System.Text.Encoding.GetEncoding(28591)
       
            '.RtsEnable = True => I think I have to enable this as it's written in the controller manual thre is a CTS-RTS handshaking, but I do not know how to use it with VB.
            .BaudRate = 19200
'etc...
        End With

        Try
            SerialPort1.Open()
            isopen.Set()

            BackgroundWorker1.WorkerReportsProgress = True
            BackgroundWorker1.WorkerSupportsCancellation = True
            BackgroundWorker1.RunWorkerAsync()

        Catch ex As Exception
            RichTextBox1.AppendText(ex.Message & Environment.NewLine)
        End Try
    End Sub


    Private Sub ClosePort_Click(sender As Object, e As EventArgs) Handles ClosePort.Click
        'close port
        isopen.WaitOne()
        SerialPort1.Close()
    End Sub

    Dim sc As Threading.Thread

    Private Sub btnSendMultiple_Click(sender As Object, e As EventArgs) Handles btnSendMultiple.Click

        btnSendMultiple.Enabled = False
        sc = New Threading.Thread(AddressOf SendCmds)
        sc.IsBackground = True
        sc.Start()

    End Sub

    Dim cmdDone As New Threading.AutoResetEvent(True)

    Private Sub SendCmds()
       

Dim cmds As New List(Of String)
        cmds.Add("my first command to the controller" & vbCr)
        cmds.Add("my second command to the controller" & vbCr)
        cmds.Add("my third command to the controller" & vbCr)
       
Try

If isopen.WaitOne(0) AndAlso SerialPort1.IsOpen Then

For Each c As String In cmds
            cmdDone.WaitOne()
            SerialPort1.Write(c)
        Next

        Me.Invoke(Sub() btnSendMultiple.Enabled = True)

End If

Catch ex As TimeoutException
            MessageBox.Show(ex.Message)

        Catch ex As InvalidOperationException
            MessageBox.Show(ex.Message)

        Catch ex As UnauthorizedAccessException
            MessageBox.Show(ex.Message)

        End Try

        isopen.Set()

    End Sub

    Dim isopen As New Threading.AutoResetEvent(False)

    Dim dataByts As New List(Of Byte)
    Dim dataLock As New Object
    Dim dataAvailable As New Threading.AutoResetEvent(False)

    Private Sub SerialPort1_DataReceived(sender As Object, _
                                         e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived

        Dim br As Integer = SerialPort1.BytesToRead '# of bytes to read

        If br > 0 Then

            Dim b(br - 1) As Byte 'create buffer to read the data

            Try
                br = SerialPort1.Read(b, 0, b.Length) 'read the bytes,  non-blocking
                If br < b.Length Then 'adjust length if required
                    Array.Resize(b, br)
                End If
                'add bytes that have been just read to the list
                Threading.Monitor.Enter(dataLock)
                dataByts.AddRange(b)
                Threading.Monitor.Exit(dataLock)
                dataAvailable.Set()
            Catch ex As Exception
                'exception handling
            End Try

        End If

    End Sub

    Const LF As Byte = 10 'controller responses end with vbCrLf

    Private Sub BackgroundWorker1_DoWork(sender As Object, _
                                         e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
        Do
            If BackgroundWorker1.CancellationPending Then Exit Do

            Dim idxLF As Integer = dataByts.IndexOf(LF)  'get the index of the LF

            If idxLF >= 0 Then 'do we have a LF?

                'If yes, get the message from controller:
                Dim s As String
                Threading.Monitor.Enter(dataLock)
                s = System.Text.Encoding.GetEncoding(28591).GetChars(dataByts.ToArray, 0, idxLF - 1) 'encode bytes to string of char
                dataByts.RemoveRange(0, idxLF + 1) 'remove the message from the buffer
                Threading.Monitor.Exit(dataLock)
                BackgroundWorker1.ReportProgress(1, s)

                cmdDone.Set() '<<< signal to the WaitHandle
            Else
                'Else, total message not 100% complete, so wait for more data: data Waithandle is in the halted state
                dataAvailable.WaitOne()
            End If

        Loop

    End Sub


    Private Sub BackgroundWorker1_ProgressChanged(sender As Object, _
                                                  e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
 
        'this code run on UI ??
        Dim s As String = DirectCast(e.UserState, String) 'get message
        RichTextBox1.AppendText(s & Environment.NewLine)

    End Sub


    Private Sub SerialPort1_ErrorReceived(sender As Object, _
                                          e As IO.Ports.SerialErrorReceivedEventArgs) Handles SerialPort1.ErrorReceived

        Debug.WriteLine("ER " & e.EventType.ToString)
    End Sub

    Private Sub SerialPort1_PinChanged(sender As Object, _
                                       e As IO.Ports.SerialPinChangedEventArgs) Handles SerialPort1.PinChanged

        Debug.WriteLine("PC " & e.EventType.ToString)
    End Sub

End Class

Now, I would like to insert a big loop so I can do the SerialPort.Write() part 50 times.
I have a dataTable with all the 50 positions the controller has to reach. I would like to append the position TO the commands I write to the controller.

To sum up, SerialPortwrite() would have to be in a "For Each row of dataTable" loop and wait at every end of every position for another thread or function to finish (the image grabbing part).

Does my explanation/needs make sense ?

Thanks for your input !

« Last Edit: October 02, 2013, 03:23:21 am by LiamC »

Jan Axelson

  • Administrator
  • Frequent Contributor
  • *****
  • Posts: 3033
    • Lakeview Research
Re: RS232 timing /write / buffering inside - DataReceived Event
« Reply #4 on: October 02, 2013, 04:33:17 pm »
That makes sense. Not sure if you have a remaining question.

LiamC

  • Member
  • ***
  • Posts: 4
Re: RS232 timing /write / buffering inside - DataReceived Event
« Reply #5 on: October 03, 2013, 08:24:04 am »
Hi,

yes, still have few questions still ;D

I have several threads:

UI thread
a backgroundworker
a thread named SC (SendCommand)

and the DataReceived triggered by Serialport.Write().

UI thread loads the Backgroundworker at startup with RunWorkerAsync. BackgroundWorker has to wait for data to proceed (IF DATA condition, and stays in the ELSE loop as long as there  is no incoming data with a dataAvailable.WaitOne()).
I click on a "SEND" button: it creates and starts the SC thread which has a WaitHandle cmdDone.WaitOne() (with AutoResetEvent(True)) just before the serialport.Write(). Writing commands to the controller (axis motion/what position ? and motion status command) triggers the Data Received Event. In Data Received there is the Read bytes (with BytesToRead) function and a signal to the first waithandle: it signals the dataAvailable handle to the backgroundworker so it can exit the ELSE part of the loop. Once the BCKGND worker resumes, it checks the returning string from the controller (converted from bytes to char just before).

At this point we have sent a command to the controller, the axis have moved and we have checked the answer of the controller to our commands/questions to be sure its motion is stopped.  I can then start another function to grab an image of the object placed on the axis with a camera.

My main question is global/generic; what to do, to have proper coding of a 50 positions loop with Serialport.Write() in the middle in order to:

1. Be sure the ritchtextbox updates itself with the returning string for every position (It is updated in the ProgressChanged of the BCKGND worker at the moment, I have to check again if it is updated correctly every time)
2. Not have a bufferoverflow on the controller's end , ie: do I need to flush/erase one of the buffers. The controllers easily hangs from my short experience

Jan Axelson

  • Administrator
  • Frequent Contributor
  • *****
  • Posts: 3033
    • Lakeview Research
Re: RS232 timing /write / buffering inside - DataReceived Event
« Reply #6 on: October 03, 2013, 01:38:49 pm »
It's hard to say from a general description what the problems are.

For example, if you have to check if the text box is updated correctly, and you know how to check it and fix it, maybe that is the way to enter the data the first time around.

Not sure how buffer overflow is related to a controller hang.

If you have a specific code example and a description of what it does, someone may be able to help.