Arduino and Visual Basic Part 2: Receiving Data From the Arduino

This post continues from Arduino and Visual Basic Part 1: Receiving Data From the Arduino

In the previous post we received a stream of data from the Arduino and displayed it inside a Visual Basic text box. This is all well and good but we did not know what the data was. We simply received it and displayed it. The next step is to send data that has some kind of meaning and display it in an appropriate field. This could be a temperature, a wind speed, a switch state or anything else. In the following example I am using a 1 wire temperature probe (it’s actually got 2 wires…), a potentiometer and a button switch.

Commands or Codes

To identify which values are which we will use a simple code attached to the actual value:
B for button switch
P for potentiometer
T for temperature

So we know when we have a complete data segment or code we will use start and end markers, “<” and “>”. The Visual Basic app will ignore anything not inside the markers.

The button switch only has 2 states HIGH or LOW / pressed or not pressed so a single character can be used for the value. In this example H for HIGH and L for LOW. The full codes are:
  <BH>, button switch HIGH or pressed
  <BL>, button swtich LOW or not pressed

The potentiometer gives a value between 0 and 1023. To make things a little easier the example uses ascii characters for the value (“1000″ instead of 1000) and a fixed length of 4 characters; “0000” to “1023”. Of course this means the value has to be converted to a string before sending.
  <Pnnnn>, P for potentiometer and nnnn = the value of the potentiometer
  <P0000>, potentiometer value of zero
  <P1023>, potentiometer value of 1023

In line with keeping things simple, an integer is used to store the value of the temperature probe (you would normally use a float). This means we can treat it the same as the potentiometer and use a string of 4 characters for the value; “0000” to “1023”
  <Tnnnn>, T for temperature and nnnn = the value of the temperature probe
  <T0504>, temperature probe value of 504
  <T0400>, temperature probe value of 400

 

The Arduino Set Up

Set up the Arduino with the following:
– The temperature probe is connected to A0.
– The potentiometer is on A1.
– The button switch is connected to D4

Arduino and Visual Basic Part 2 - Arduino Set Up 001 1600

Arduino and Visual Basic Part 2 - Arduino Set Up 002 1600

Circuit Diagram

Arduino and Visual Basic Part 2 - Receiving Data From the Arduino - CircuitDiagram

Arduino Sketch

The Arduino sketch polls the values of the pins and then, if the value has changed, creates the code and then sends the code out over the serial connection.

/*
*
* Sketch Arduino to Visual Basic 002 - Receiving Data From the Arduino
*
* Read pin state / value and send to a host computer over a serial connection.
* This is a one way communication; Arduino to a host computer. No data is received from the computer.
*
* Pins
* A0 - a 2 wire temperature probe (sending raw data only. Not the actual temperature)
* A1 - a potentiometer
* D4 - button switch
* The above can be changed to something else
*
*
* It should noted that no data is sent until something changes
* The sketch can be expanded so that an initial value is sent
*
*/
 
// When DEGUG is TRUE send an newline to the serial monitor
const boolean DEBUG = true;
 
const byte tempPin = A0;
const byte potPin = A1;
const byte buttonSwitchPin = 4;
 
unsigned int oldTempVal = 0;
unsigned int newTempVal = 0;
unsigned int oldPotVal = 0;
unsigned int newPotVal = 0;
boolean oldButtonSwitchState = false;
boolean newButtonSwitchState = false;
 
// used to hold an ascii representation of a number
// [10] allows for 9 digits but in this example I am only using 4 digits
char numberString[10];
 
 
void setup()  
{
  // set the button switch pin to input
  pinMode(buttonSwitchPin, INPUT); 
 
  // open serial communication
  Serial.begin(9600);
  Serial.println("Adruino is ready");
  Serial.println(" ");
}
 
 
void loop()  
{
 
    // read the pins
    newTempVal = analogRead(tempPin); 
    newPotVal = analogRead(potPin); 
    newButtonSwitchState = digitalRead(buttonSwitchPin);
 
    if (newTempVal != oldTempVal)
    {
       oldTempVal = newTempVal;
       formatNumber( newTempVal, 4);
       Serial.print("<T");
       Serial.print(numberString);
       Serial.print(">");
       if (DEBUG) { Serial.println("");  }
    }
 
 
    //The pot I am using jitters +/-1 so I only using changes of 2 or more.  
    if ( abs(newPotVal-oldPotVal) > 1)
    {
       oldPotVal = newPotVal;
       formatNumber( newPotVal, 4);
       Serial.print("<P");
       Serial.print(numberString);
       Serial.print(">");
       if (DEBUG) { Serial.println("");  }      
    }    
 
 
    if (newButtonSwitchState != oldButtonSwitchState)
    {
       oldButtonSwitchState = newButtonSwitchState;
       if (oldButtonSwitchState == true)
       {
          Serial.print("<BH>");
       }
       else
       {
          Serial.print("<BL>");
       }
 
       if (DEBUG) { Serial.println("");  }
 
    }   
 
    delay (100);
 
}
 
 
 
void formatNumber( unsigned int number, byte digits)
{
    // formats a number in to a string and copies it to the global char array numberString
    // pads the start of the string with '0' characters
    //
    // number = the integer to convert to a string
    // digits = the number of digits to use. 
 
    char tempString[10] = "\0"; 
    strcpy(numberString, tempString);
 
    // convert an integer into a acsii string
    itoa (number, tempString, 10);
 
    // create a string of '0' characters to pad the number    
    byte numZeros = digits - strlen(tempString) ;
    if (numZeros > 0)
    {
       for (int i=1; i <= numZeros; i++)    { strcat(numberString,"0");  }
    }
 
    strcat(numberString,tempString); 
 
}

Load the sketch and open the serial monitor. Since you should get something like the following:
Arduino and Visual Basic Part 2 - serial monitor 001

Press the button switch and you should get:
Arduino and Visual Basic Part 2 - serial monitor 002

Turn the potentiometer and you should see something similar to:
Arduino and Visual Basic Part 2 - serial monitor 003

Now that the Arduino side is working the Visual Basic app is next.

We can use the Visual Basic app from Arduino and Visual Basic Part 1: Receiving Data From the Arduino as the starting point but we need to make some changes. Unlike the previous example, the data is now inside start and end markers and we we need to determine what data we are receiving.

 

Visual Basic App

The program is fairly simple:
It waits for the user to select a COM port and click the connect button.
It then makes a connection the Arduino.
Checks for incoming serial data
Parses the data
Updates the form.

Here is a screen shot of the Visual Basic App in action.
Arduino and Visual Basic Part 2 - VBscreen_001

Visual Basic Program

Here is the main form
Arduino and Visual Basic Part 2 - VBscreen_002
The form is the same one used in the previous example. New labels have been added to show the temperature, the potentiometer value and the switch state. I have also included labels to show the timer interval and the number of commands processed.
Arduino and Visual Basic Part 2 - VBscreen_003

I have left the text box and use it to display the current command. You can also use the text box for debugging.

The code

'
' Arduino and Visual Basic Part 2: Receiving Data From the Arduino
' An example of receiving data from an Arduino using start and end markers
'

Imports System
Imports System.IO.Ports


Public Class Form1

    Dim comPORT As String
    Dim receivedData As String = ""
    Dim commandCount As Integer = 0


    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Timer1.Enabled = False
        TimerSpeed_lbl.Text = "Timer speed: " & Timer1.Interval

        comPORT = ""
        For Each sp As String In My.Computer.Ports.SerialPortNames
            comPort_ComboBox.Items.Add(sp)
        Next
    End Sub


    Private Sub comPort_ComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles comPort_ComboBox.SelectedIndexChanged
        If (comPort_ComboBox.SelectedItem <> "") Then
            comPORT = comPort_ComboBox.SelectedItem
        End If
    End Sub


    Private Sub connect_BTN_Click(sender As Object, e As EventArgs) Handles connect_BTN.Click
        If (connect_BTN.Text = "Connect") Then
            If (comPORT <> "") Then
                SerialPort1.Close()
                SerialPort1.PortName = comPORT
                SerialPort1.BaudRate = 9600
                SerialPort1.DataBits = 8
                SerialPort1.Parity = Parity.None
                SerialPort1.StopBits = StopBits.One
                SerialPort1.Handshake = Handshake.None
                SerialPort1.Encoding = System.Text.Encoding.Default 'very important!
                SerialPort1.ReadTimeout = 10000

                SerialPort1.Open()
                connect_BTN.Text = "Dis-connect"
                comPort_ComboBox.Enabled = False
                Timer1.Enabled = True
                Timer_LBL.Text = "Timer: ON"
            Else
                MsgBox("Select a COM port first")
            End If
        Else
            Timer1.Enabled = False
            SerialPort1.Close()
            connect_BTN.Text = "Connect"
            comPort_ComboBox.Enabled = True

            Timer_LBL.Text = "Timer: OFF"
            comPort_ComboBox.Text = String.Empty
            RichTextBox1.Text = ""

        End If

    End Sub


    ' clear the text box
    Private Sub clear_BTN_Click(sender As Object, e As EventArgs) Handles clear_BTN.Click
        RichTextBox1.Text = ""
    End Sub


    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        'stop the timer (stops this function being called while it is still working
        Timer1.Enabled = False
        Timer_LBL.Text = "Timer: OFF"


        ' get any new data and add the the global variable receivedData
        receivedData = ReceiveSerialData()

        'If receivedData contains a "<" and a ">" then we have data
        If ((receivedData.Contains("<") And receivedData.Contains(">"))) Then
            parseData()
        End If

        ' restart the timer
        Timer1.Enabled = True
        Timer_LBL.Text = "Timer: ON"
    End Sub


    Function ReceiveSerialData() As String
        ' returns new data from the serial connection
        Dim Incoming As String
        Try
            Incoming = SerialPort1.ReadExisting()
            If Incoming Is Nothing Then
                Return "nothing" & vbCrLf
            Else
                Return Incoming
            End If
        Catch ex As TimeoutException
            Return "Error: Serial Port read timed out."
        End Try

    End Function


    Function parseData()

        ' uses the global variable receivedData
        Dim pos1 As Integer
        Dim pos2 As Integer
        Dim length As Integer
        Dim newCommand As String
        Dim done As Boolean = False

        While (Not done)

            pos1 = receivedData.IndexOf("<") + 1
            pos2 = receivedData.IndexOf(">") + 1

            'occasionally we may not get complete data and the end marker will be in front of the start marker
            ' for exampe "55><"
            ' if pos2 < pos1 then remove the first part of the string from receivedData
            If (pos2 < pos1) Then
                receivedData = Microsoft.VisualBasic.Mid(receivedData, pos2 + 1)
                pos1 = receivedData.IndexOf("<") + 1
                pos2 = receivedData.IndexOf(">") + 1
            End If

            If (pos1 = 0 Or pos2 = 0) Then
                ' we do not have both start and end markers and we are done
                done = True

            Else
                ' we have both start and end markers

                length = pos2 - pos1 + 1
                If (length > 0) Then
                    'remove the start and end markers from the command
                    newCommand = Mid(receivedData, pos1 + 1, length - 2)

                    ' show the command in the text box
                    RichTextBox1.AppendText("Command = " & newCommand & vbCrLf)

                    'remove the command from receivedData
                    receivedData = Mid(receivedData, pos2 + 1)


                    ' B for button switch
                    If (newCommand(0) = "B") Then
                        If (newCommand(1) = "L") Then
                            buttonSwitchValue_lbl.Text = "LOW"
                        ElseIf (newCommand(1) = "H") Then
                            buttonSwitchValue_lbl.Text = "HIGH"
                        End If
                    End If ' (newCommand(0) = "B")


                    ' P for potentiometer
                    If (newCommand.Substring(0, 1) = "P") Then
                        potentiometerValue_lbl.Text = newCommand.Substring(1, 4)
                    End If '(newCommand.Substring(0, 1) = "P")


                    'T for temperature
                    If (newCommand.Substring(0, 1) = "T") Then
                        temperatureValue_lbl.Text = newCommand.Substring(1, 4)
                    End If '(newCommand.Substring(0, 1) = "P")

                    commandCount = commandCount + 1
                    commandCountVal_lbl.Text = commandCount

                End If ' (length > 0) 

            End If '(pos1 = 0 Or pos2 = 0)

        End While

    End Function
End Class

 

Code run through

on start up, the Form1_Load function is called. This updates the timer interal label, and populates the combobox with the available COM ports.

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Timer1.Enabled = False
    TimerSpeed_lbl.Text = "Timer speed: " & Timer1.Interval

    comPORT = ""
    For Each sp As String In My.Computer.Ports.SerialPortNames
        comPort_ComboBox.Items.Add(sp)
    Next
End Sub

The program then waits for the user to select a COM port and hit the Connect button. If the Connect button is clicked without a COM port selected an error message is displayed. If a COM port has been selected then the serial port attributes are set and the program tries to open the port. If successful the Connect button text is change to “Dis-connect” and the timer started. The timer is used to check for incoming data. The timer is set to 100ms. This means there are 10 checks a second to see if there is any new serial data.

If there is an existing connection (the Connect button text is not equal to “Connect”) then the serial port is closed.

Private Sub comPort_ComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles comPort_ComboBox.SelectedIndexChanged
    If (comPort_ComboBox.SelectedItem <> "") Then
        comPORT = comPort_ComboBox.SelectedItem
    End If
End Sub


Private Sub connect_BTN_Click(sender As Object, e As EventArgs) Handles connect_BTN.Click
    If (connect_BTN.Text = "Connect") Then
        If (comPORT <> "") Then
            SerialPort1.Close()
            SerialPort1.PortName = comPORT
            SerialPort1.BaudRate = 9600
            SerialPort1.DataBits = 8
            SerialPort1.Parity = Parity.None
            SerialPort1.StopBits = StopBits.One
            SerialPort1.Handshake = Handshake.None
            SerialPort1.Encoding = System.Text.Encoding.Default 'very important!
            SerialPort1.ReadTimeout = 10000

            SerialPort1.Open()
            connect_BTN.Text = "Dis-connect"
            comPort_ComboBox.Enabled = False
            Timer1.Enabled = True
            Timer_LBL.Text = "Timer: ON"
        Else
            MsgBox("Select a COM port first")
        End If
    Else
        Timer1.Enabled = False
        SerialPort1.Close()
        connect_BTN.Text = "Connect"
        comPort_ComboBox.Enabled = True

        Timer_LBL.Text = "Timer: OFF"
        comPort_ComboBox.Text = String.Empty
        RichTextBox1.Text = ""
    End If

End Sub

When the timer function is called, any new serial data is added to a buffer in the form of a global variable called receivedData. Adding the new data to a buffer like this ensures we get full commands. Only checking the latest received data means we are likely to miss commands. The received data could be fragmented like this; “34<“, “T011″, “1><BH><P0245><BL><T01″.

If the buffer contains a start marker (“<“) and also an end marker(“>”) then we have a complete command (not 100% true!) and the function parseData() is called. Because receivedData is a global variable we do not need to pass it to the parseData() function.

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

    'stop the timer (stops this function being called while it is still working
    Timer1.Enabled = False
    Timer_LBL.Text = "Timer: OFF"


    ' get any new data and add the the global variable receivedData
    receivedData = ReceiveSerialData()

    'If receivedData contains a "<" and a ">" then we have data
    If ((receivedData.Contains("<") And receivedData.Contains(">"))) Then
        parseData()
    End If

    ' restart the timer
    Timer1.Enabled = True
    Timer_LBL.Text = "Timer: ON"
End Sub

 
All the heavy work is done in the parseData() function.

Because the received data buffer may have more than one command the function loops through the received data removing the first complete command from the buffer on each loop. The positions of the start and end markers are used to determine if we still have a complete command.

pos1 = receivedData.IndexOf("<") + 1
pos2 = receivedData.IndexOf(">") + 1

If (pos1 = 0 Or pos2 = 0) Then
' we do not have both start and end markers and we are done
done = True

Using the positions of the first start and end markers the first command can be copied to the variable newCommand.

length = pos2 - pos1 + 1
If (length > 0) Then
'remove the start and end markers from the command
   newCommand = Mid(receivedData, pos1 + 1, length - 2)

    ' show the command in the text box
    RichTextBox1.AppendText("Command = " & newCommand & vbCrLf)

    'remove the command from receivedData
     receivedData = Mid(receivedData, pos2 + 1)

Once we have the command we can check which one it is and update the form.

' B for button switch
If (newCommand(0) = "B") Then
    If (newCommand(1) = "L") Then
        buttonSwitchValue_lbl.Text = "LOW"
    ElseIf (newCommand(1) = "H") Then
        buttonSwitchValue_lbl.Text = "HIGH"
    End If
End If ' (newCommand(0) = "B")


' P for potentiometer
If (newCommand.Substring(0, 1) = "P") Then
    potentiometerValue_lbl.Text = newCommand.Substring(1, 4)
End If '(newCommand.Substring(0, 1) = "P")


'T for temperature
If (newCommand.Substring(0, 1) = "T") Then
    temperatureValue_lbl.Text = newCommand.Substring(1, 4)
End If '(newCommand.Substring(0, 1) = "P")

commandCount = commandCount + 1
commandCountVal_lbl.Text = commandCount

 
 

Downloads



 
 

4 thoughts on “Arduino and Visual Basic Part 2: Receiving Data From the Arduino

  1. Thank you for this article- I still did not read it but it looks good- I will try it pretty soon- again thanks for you effort

  2. Pingback: Arduino and Visual Basic Part 1: Receiving Data From the Arduino | Martyn Currey

  3. I ran the program and sometimes it works good and sometimes it gets stuck.
    The VB says (after it compiles the program) that there might be a null exeption when running the program since the parse() function may not return a value for all cases. Is there a cure for that?
    Appreciate an answer to my E- mail
    Thanks a lot

    • Hi Dan,

      I haven’t used this for a while but I never had an issue in the past and I use the same code on my dropController project which runs without a problem.

      The parseData() does not return a value. It looks for the start and end markers and then takes the data they contain. If it finds data it checks what the data is.

      Can you post a more specific error and possible show the program line in the code.

Leave a Reply

Your email address will not be published. Required fields are marked *


− three = 0

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>