Let's Make Robots!

VB programming help wanted! Plotting serial data.

Hi guys, I`m attempting my own low speed arduino oscilloscope but have hit a brick wall with my mediocre programming skills. The arduino program and input stage works fine, it`s the PC software thats killing me.

I`ve created a program that connects to the right port at the right speed, I can read the values from the serial port, and I have a panel which I can draw pixels on. When I click the button to start sampling I want to plot these points in pseudo-real-time on the panel but the problem is that you can`t draw on the panel from the function that reads the serial data. It`s hard to explain the problem and I don`t entirely understand it either but I think it comes down to ownership and that a function can`t mess with stuff that doesn`t belong to it.

This page explains it somewhat but I still don`t get it. Does anyone have any experience with VB or this kind of thing?


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

(I'm gonna assume you are using VB.Net and .Net Framework 2.0 under Visual Studio 2005)

What you are running into here is a threading issue. It can be quite complicated to understand in detail, but can be summarised quite simply.

In order for you VB program to do multiple things at once, it runs in multiple "threads". This means that the program can (for example) update the data displayed on a form and listen for more data on the serial port both at the same time.

When data is received on the serial port, an event gets raised and your application can access and process that data. The trouble you are experiencing is that this event gets raised on a different thread from that which draws your form / buttons / graphs etc on the screen - the GUI thread. And only the GUI thread can change GUI items ("you can`t draw on the panel from the function that reads the serial data").

 The solution is quite simple - you need to Invoke the code you wish to run from the Serial thread, so it gets run in the GUI thread. Firstly put all your code to draw on the panel in a method similar to the one below, then call that method from where you receive your serial data.


  Private Delegate Sub DisplayMessage_Delegate(ByVal Message As String)
 Private Sub DisplayMessage(ByVal Message As String)
 If TextBox1.InvokeRequired() Then 'If we arent running in the GUI thread, then call this method again but on the GUI thread
 TextBox1.Invoke(New DisplayMessage_Delegate(AddressOf DisplayMessage), New Object() {Message})
 TextBox1.Text = Message
End If

End Sub



Thanks a lot for your post MrDan. It gave me some good research starting points :)

I think I get the threading and delegates stuff now. I used yours and some other similar code from several places and now have a line tracking across the screen in relation to the serial data, sent in byte form from the arduino.

I have a 100ms delay between serial writes on the arduino side and no read delay VB and it works great. Only thing is when I try lowering the arduinos delay to 10ms the VB program can`t keep up and the data lags far behind. With the raw values being printed to the debug console, apart from the lag it also looks like the last digit of every number gets chopped too.

Here`s some of the code. Can you see anywhere it could be sped up?

Thread to continuously update until ended 

Private Sub DrawGraph()
            Dim y As Integer = m_Y
                If SerialPort.BytesToRead > 0 Then
                    m_Y = SerialPort.ReadByte()
                    ' Plot the new value.
                    PlotPoint(y, m_Y)
                    y = m_Y

                End If
                'Delay a bit before calculating the value.
                ' Dim stop_time As Date = Now.AddMilliseconds(10)
                'Do While Now < stop_time


        Catch ex As Exception
            AddStatus("[Thread] " & ex.Message)
        End Try
    End Sub

Method to move bitmap and draw new line

    Private Sub PlotPoint(ByVal lastDataPoint As Integer, ByVal newDataPoint As Integer)
        If picGraph.InvokeRequired() Then 'If we arent running in the GUI thread, then call this method again but on the GUI thread
            picGraph.Invoke(New PlotPoint_Delegate(AddressOf PlotPoint), New Object() {lastDataPoint, newDataPoint})
            Dim wid As Integer = picGraph.ClientSize.Width
            Dim hgt As Integer = picGraph.ClientSize.Height
            Dim bm As New Bitmap(wid, hgt)
            Dim gr As Graphics = Graphics.FromImage(bm)

            ' Move the old data one pixel to the left.
            gr.DrawImage(picGraph.Image, -1, 0)

            ' Erase the right edge and draw guide lines.
            gr.DrawLine(Pens.Black, wid - 1, 0, wid - 1, hgt - 1)

            gr.DrawLine(majorAxis, wid - 2, hgt - 1, wid - 1, hgt - 1)

            For i As Integer = 0 To hgt Step GRID_STEP
                gr.DrawLine(minorAxis, wid - 2, hgt - i, wid - 1, hgt - i)
            Next i

            ' Plot a new pixel.
            gr.DrawLine(wfPen, wid - 2, lastDataPoint, wid - 1, newDataPoint)

            ' Display the result.
            picGraph.Image = bm


        End If

    End Sub

Double posted.