Mike Trethowan
Several years ago, 2000ish I developed a Tower Clock Controller that captured the WWVB signal from Fort Collins, Colorado and converted it to the format used by the Tower Clock Drive Boards manufactured by the company that I work for. For that project I used a device that was available for Basic Stamp projects. Later on when those devices became unavailable I redesigned the project for a GPS module.
During the redevelopment I tried several different modules from different manufacturers, so this left me with a lot of unused GPS modules in my prototype collection bin. This project will use the Pharos GPS-360, a USB connected GPS that came with a navigation software popular on PC Laptops before the advent of Smart Phones. I am sure some of you still have your USB GPS module somewhere. The GPS outputs NMEA data on a port designated as a Serial Com Port by Windows. The baud rate is 4800, (as are all NMEA messages.)
The project will use ManagementObjectSearcher to find and set the com port for the GPS. A Handler will be created to allow us to have a SerialDataReceived Event. Also, Delegates are used for cross thread access of Labels. There are several NMEA messages, but we will only focus on two for this Quick Tip, “$GPGSV” and $GPRMC.” From these two we will get the Date, UTC Time, position data (Longitude/Latitude) reported by the GPS and the Satellites In View data. These two NMEA sentences provide enough information to understand the other NMEA message types.
Your form will have a SerialPort, six Labels and a Panel. The SerialPort name will be spGPS, the baud will be 4800. The Panel size will be 14,14 and the BackColor will be Red. Name the Labels lblMessage, lblZDA, lblLong, lblLat, lblGSV and lblUTC. Place the objects where they line up vertically, (you can use the vertical alignment feature of VS2013 to do this easier.)
If you have read my previous tutorials you will know how I setup the code and regions in the main form. You will need three references, for SystemManagement you may have to add it to the list of References in the Project Properties tab, (hint: you will have to right click the project folder and select Properties, not the Solution in the Solution Explorer.)
Imports System.IO.Ports
Imports System.Management
Imports System.ComponentModel
For the Dims, rxMessage is the string that holds the data collected from the GPS. DataType() is the string array that the message data types will get parsed to. VidPid is the Vendor ID and the Product ID of this particular GPS. If you use a different model you can get this information from the Device Manager in the Windows Control Panel. If you are using Windows 8 and above, you can search for it using the keywords “Device Manager”.
#Region " Dims "
Dim rxMessage As String
Dim DataType() As String
Const VidPid As String = "VID_067B&PID_AAA0"
#End Region
The program needs to find the GPS. This can be done at startup without any input needed from the user in the Form_Load routine (which is why there are no Buttons in this project.) When the GPS is located the routine automatically assigns the com port and opens it. We exit the For statement once the GPS is located as we no longer need the Searcher slowing things up. The value for foundGPS is tested against -1 as Indexes can be assigned 0 to x.
#Region " Main Routines "
Private Sub FindGPS()
Dim searcher As New ManagementObjectSearcher("root\WMI", "SELECT * FROM MSSerial_PortName")
For Each queryObj As ManagementObject In searcher.Get()
Dim foundGPS As Integer = queryObj("InstanceName").IndexOf(VidPid)
If foundGPS > -1 Then
lblMessage.Text = "GPS Found At " & queryObj("PortName")
spGPS.PortName = queryObj("PortName")
System.Threading.Thread.Sleep(100)
spGPS.Open()
Exit For
End If
Next
End Sub
#End Region
Also add the routines that the program will be accessing on loading and closing.
#Region " Main Routines "
Private Sub StartUp()
lblMessage.Text = ""
findGPS()
AddHandler spGPS.DataReceived, AddressOf GetMessage
End Sub
Public Function FindGPS() As Boolean
Dim answer As Boolean = False
Dim searcher As New ManagementObjectSearcher("root\WMI", "SELECT * FROM MSSerial_PortName")
For Each queryObj As ManagementObject In searcher.Get()
Dim foundGPS As Integer = queryObj("InstanceName").IndexOf(VidPid)
If foundGPS > -1 Then
lblMessage.Text = "GPS Found At " & queryObj("PortName")
answer = True
spGPS.PortName = queryObj("PortName")
System.Threading.Thread.Sleep(100)
spGPS.Open()
Exit For
End If
Next
Return answer
End Function
Private Sub WrapUp()
spGPS.Close()
spGPS.Dispose()
End Sub
#End Region
To get all of this going at run time there has to be an event for the loading of the form and to wrap up when the program is closed there has to be a Closing event with calls to the routines we want to run. While VS2013 will automatically create the Load event when you double click the form, it doesn’t create the Closing event.
#Region " Form Loading & Closing "
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
StartUp()
End Sub
Private Sub Form1_Closing(sender As Object, e As EventArgs) Handles Me.Closing
WrapUp()
End Sub
#End Region
The Delegate allows cross thread activity so that the GetMessage() routine can access Labels without creating exceptions at run time. There are two parts to the Delegate, the Delegate itself and the subroutine that handles the Label text. Also, if you don’t need a MessageBox, send the Exceptions to a Label. Include the Routine’s name so that you know where the Exception took place. This is a much cleaner way of presenting messages. This also has another benefit; If you use a message box, those message boxes can quickly multiply if they are within an event that continues to run after the exception is thrown.
#Region " Delegates "
Private Delegate Sub MessageDelagate(ByVal Lbl As Label, ByVal txt As String)
Private Sub DisplayMessage(ByVal Lbl As Label, ByVal txt As String)
Try
If Lbl.InvokeRequired Then
Lbl.Invoke(New MessageDelagate(AddressOf DisplayMessage), New Object() {Lbl, txt})
Else
Lbl.Text = txt
End If
Catch ex As Exception
lblMessage.Text = "Error In DisplayMessage() " & ex.Message
End Try
End Sub
#End Region
Lets create the serial event routine. The message string gets set to nothing so we can test it for valid data later on. We use ReadLine as the NMEA data is sent in sentences which we will have to parse later on in the routine TranslateNMEA(). As GetMessage() is an Event there is no need for Timers.
Private Sub GetMessage(sender As Object, e As SerialDataReceivedEventArgs)
Try
rxMessage = ""
If spGPS.IsOpen And spGPS.BytesToRead Then
rxMessage = spGPS.ReadLine
TranslateNMEA(rxMessage)
pnlRx.BackColor = Color.Green
Else
pnlRx.BackColor = Color.Red
spGPS.Open()
End If
Catch ex As Exception
DisplayMessage(lblMessage, "Error In <code class="vbnet plain">GetMessage() " &</code> ex.Message)
End Try
End Sub
TranslateNMEA() is where we evaluate the message that was just received. There are several NMEA messages, but we will only focus on three for this Quick Tip. A Select Case statement is used to evaluate the message header and the appropriate sub routine is called.
#Region " NMEA Routines "
Public Function TranslateNMEA(ByVal msg As String) As Boolean
DataType = msg.Split(","c)
Select Case DataType(0)
Case "$GPGSV" : GSV()
Case "$GPRMC" : RMC()
End Select
End Function
#End Region
The last two pieces of the puzzle. Place these in the NMEA Routines region. Here you can see how the Delegates are used. If these didn’t exist the program would throw exceptions every time the Labels were accessed by GetMessage(). Even though GetMessage() does not directly access the Labels, the routines called through GetMessage() are part of the same thread.
Private Sub GSV()
DisplayMessage(lblGSV, "Satellites In View: " & DataType(3))
End Sub
Private Sub RMC()
Dim dateIs As String = DataType(9)
Dim utcIs As String = DataType(1)
utcIs = utcIs.Insert(2, ":")
utcIs = utcIs.Insert(5, ":")
dateIs = dateIs.Insert(2, "/")
dateIs = dateIs.Insert(5, "/")
DisplayMessage(lblLat, "Latitude: " & DataType(3) & " " & DataType(4))
DisplayMessage(lblLong, "Longitude: " & DataType(5) & " " & DataType(6))
DisplayMessage(lblZDA, "Date: " & dateIs)
DisplayMessage(lblUTC, "Time UTC: " & utcIs)
End Sub
While this is not a fancy program, it does demonstrate the logic.
To learn more about the NMEA standard, go to http://www.gpsinformation.org/dale/nmea.htm
Disclaimer: The code in this tutorial is not intended as a final product or by no means the only way this project can be coded. The presented code is for educational purposes only and no warranty is granted or implied. I/we are not responsible for any damages as a result of using this code. Use at your own risk.
VB.NET Quick Tip – Reading A USB GPS
Posted: 21st November 2015 by Mike Trent in Computer, Electronics, General Comments, Hack, Physics, Programming, Quick Tip, Quick Tips, Science, Technology, VB.NetTags: Blog, Com, GPS, Hack, microcontroller devices, NMEA, Port, Programming, Science, Serial, time, Tutorial, understanding, USB, Vb.Net, vs2013
Mike Trethowan
Several years ago, 2000ish I developed a Tower Clock Controller that captured the WWVB signal from Fort Collins, Colorado and converted it to the format used by the Tower Clock Drive Boards manufactured by the company that I work for. For that project I used a device that was available for Basic Stamp projects. Later on when those devices became unavailable I redesigned the project for a GPS module.
During the redevelopment I tried several different modules from different manufacturers, so this left me with a lot of unused GPS modules in my prototype collection bin. This project will use the Pharos GPS-360, a USB connected GPS that came with a navigation software popular on PC Laptops before the advent of Smart Phones. I am sure some of you still have your USB GPS module somewhere. The GPS outputs NMEA data on a port designated as a Serial Com Port by Windows. The baud rate is 4800, (as are all NMEA messages.)
The project will use ManagementObjectSearcher to find and set the com port for the GPS. A Handler will be created to allow us to have a SerialDataReceived Event. Also, Delegates are used for cross thread access of Labels. There are several NMEA messages, but we will only focus on two for this Quick Tip, “$GPGSV” and $GPRMC.” From these two we will get the Date, UTC Time, position data (Longitude/Latitude) reported by the GPS and the Satellites In View data. These two NMEA sentences provide enough information to understand the other NMEA message types.
Your form will have a SerialPort, six Labels and a Panel. The SerialPort name will be spGPS, the baud will be 4800. The Panel size will be 14,14 and the BackColor will be Red. Name the Labels lblMessage, lblZDA, lblLong, lblLat, lblGSV and lblUTC. Place the objects where they line up vertically, (you can use the vertical alignment feature of VS2013 to do this easier.)
If you have read my previous tutorials you will know how I setup the code and regions in the main form. You will need three references, for SystemManagement you may have to add it to the list of References in the Project Properties tab, (hint: you will have to right click the project folder and select Properties, not the Solution in the Solution Explorer.)
For the Dims, rxMessage is the string that holds the data collected from the GPS. DataType() is the string array that the message data types will get parsed to. VidPid is the Vendor ID and the Product ID of this particular GPS. If you use a different model you can get this information from the Device Manager in the Windows Control Panel. If you are using Windows 8 and above, you can search for it using the keywords “Device Manager”.
The program needs to find the GPS. This can be done at startup without any input needed from the user in the Form_Load routine (which is why there are no Buttons in this project.) When the GPS is located the routine automatically assigns the com port and opens it. We exit the For statement once the GPS is located as we no longer need the Searcher slowing things up. The value for foundGPS is tested against -1 as Indexes can be assigned 0 to x.
Also add the routines that the program will be accessing on loading and closing.
To get all of this going at run time there has to be an event for the loading of the form and to wrap up when the program is closed there has to be a Closing event with calls to the routines we want to run. While VS2013 will automatically create the Load event when you double click the form, it doesn’t create the Closing event.
The Delegate allows cross thread activity so that the GetMessage() routine can access Labels without creating exceptions at run time. There are two parts to the Delegate, the Delegate itself and the subroutine that handles the Label text. Also, if you don’t need a MessageBox, send the Exceptions to a Label. Include the Routine’s name so that you know where the Exception took place. This is a much cleaner way of presenting messages. This also has another benefit; If you use a message box, those message boxes can quickly multiply if they are within an event that continues to run after the exception is thrown.
Lets create the serial event routine. The message string gets set to nothing so we can test it for valid data later on. We use ReadLine as the NMEA data is sent in sentences which we will have to parse later on in the routine TranslateNMEA(). As GetMessage() is an Event there is no need for Timers.
TranslateNMEA() is where we evaluate the message that was just received. There are several NMEA messages, but we will only focus on three for this Quick Tip. A Select Case statement is used to evaluate the message header and the appropriate sub routine is called.
The last two pieces of the puzzle. Place these in the NMEA Routines region. Here you can see how the Delegates are used. If these didn’t exist the program would throw exceptions every time the Labels were accessed by GetMessage(). Even though GetMessage() does not directly access the Labels, the routines called through GetMessage() are part of the same thread.
While this is not a fancy program, it does demonstrate the logic.
To learn more about the NMEA standard, go to http://www.gpsinformation.org/dale/nmea.htm
Disclaimer: The code in this tutorial is not intended as a final product or by no means the only way this project can be coded. The presented code is for educational purposes only and no warranty is granted or implied. I/we are not responsible for any damages as a result of using this code. Use at your own risk.