GPS using the Netduino
I recently blogged about using the Arduino board as a GPS data logger. I have been using this to collect geo-location data for the Truck Tracker application. In this post, I will explore using the Netduino for that purpose.
Meet the Netduino
Here is a photo of the Netduino board.
There are more technical specs for the Netduino board on the Netduino site. The two features that stand out to me are:
- Most of the Arduino shields are compatible with the Netduino. This means that many of the Arduino projects can be ported to the Netduino. In this post, we will look at porting the GPS shield used in the Arduino project to work with the Netduino.
- The programming environment of the Netduino is the .NET Micro Framework (.NET MF). This allows your favorite flavor of Visual Studio to be used as your IDE.
Here is a photo of the GPS shield mounted to the Netduino.
Connections
If you built the GPS shield following the instructions on the ladyada site, then the following are the pin out connections that the Netduino will use to communicate with the GPS shield:
- TX (Digital I/O Pin 0) – This is the transmit pin, data that comes from the GPS module with location data.
- RX (Digital I/O Pin 1) – This is the receive pin, data that goes to the GPS module to configure it.
- PWR (Digital I/O Pin 2) – This pin is connected to a transistor that controls power to the GPS. When this pin is set to LOW the GPS module turns on and when the pin is set to HIGH the GPS turns off.
Once the unit has power, the TX/RX pins are a serial port that is used to communicate with the GPS unit. Once power is applied, GPS data will begin being streamed from the GPS unit.
Basic Programming
The most basic Netduino code to connect and read data from the GPS serial port is shown below:
using System; using System.IO.Ports; using System.Threading; using Microsoft.SPOT; using Microsoft.SPOT.Hardware; using SecretLabs.NETMF.Hardware; using SecretLabs.NETMF.Hardware.Netduino; namespace GpsLogger { public class Program { public static void Main() { // write your code here SerialPort serialPort = new SerialPort("COM1", 4800, Parity.None, 8, StopBits.One); serialPort.Open(); // pin D2 off = gps module turned on OutputPort powerPin = new OutputPort(Pins.GPIO_PIN_D2, false); while (true) { int bytesToRead = serialPort.BytesToRead; if (bytesToRead > 0) { // get the waiting data byte[] buffer = new byte[bytesToRead]; serialPort.Read(buffer, 0, buffer.Length); // print out our received data Debug.Print(new String(System.Text.Encoding.UTF8.GetChars(buffer))); } Thread.Sleep(100); // wait a bit so we get a few bytes at a time... } } } }
This code is taken from the one of the posts on the very active Netduino community forum. The first thing you will notice is a few new C# using statements. There are a few from the Microsoft.SPOT namespace. SPOT stands for “smart personal object technology” and was developed by Microsoft to personalize household electronics and other everyday devices. Microsoft has extended this namespace to include a range of essential hardware features that programmers can use when developing embedded device firmware. There are also a couple of using statements with a Secretlabs.NETMF.Hardware namespace. Secret Labs is the company that is developing the Netduino hardware and manages the developer community.
The first two lines of the code begins serial communications with the GPS. The Netduino firmware is setup so that “COM1” uses digital pins 0 / 1 for serial TX / RX. The next line of code applies power to the GPS unit. Finally the never ending while loop, reads and prints data to the debugger. Below is a screen shot of the debugger in action.
Did I mention the debugger? You can debug your Netduino embedded project just like you are used to in Visual Studio. Amazingly, break points, watches and live evaluations all work. Great job to who ever figured that out!
In the bottom right, you can see the debug output showing the serial data coming from the GPS unit.
A Refined Example
The above code is great to get you started reading the GPS data. There is a bit more work to be done to get a reusable GPS shield library. Here are the goals of this GPS library:
- Encapsulate the GPS code into a few logical classes in a form that is reusable.
- Take advantage of the Netduino’s support of events by creating an event that other parts of the application can subscribe. The event will be raised when ever new data is available.
Here is the new entry point that is using the library:
public static void Main() { SerialPort serialPort = new SerialPort("COM1", 4800, Parity.None, 8, StopBits.One); OutputPort powerPin = new OutputPort(Pins.GPIO_PIN_D2, false); Reader gpsShield = new Reader(serialPort, 100, 1.0); gpsShield.GpsData += GpsShield_GpsData; gpsShield.Start(); while (true) { // Do other processing here. // // Can stop the gps processing by calling... // gpsShield.Stop(); // // Restart by calling... // gpsShield.Start(); // Debug.Print("Main..."); Thread.Sleep(10000); } } private static void GpsShield_GpsData(GpsPoint gpsPoint) { Debug.Print("time: " + gpsPoint.Timestamp + "tLat/Lng: " + gpsPoint.Latitude + "/" + gpsPoint.Longitude); }
This code starts nearly the same by creating a ‘SerialPort’ and ‘OutputPort’ objects for serial communication and power. It uses constructor injection to create a GPS ‘Reader’ class with access to the ‘SerialPort’ object. The constructor takes three parameters: an instance of ‘SerialPort’, a timeout in milliseconds, and the minimum distance (in miles) between GPS data events. The timeout is used as a bit of a throttle for reading the serial port. The distance parameter allows the hardware take GPS observation points at a higher rate (one per second) and only raise data available events if the distance between the points is larger than this minimum. This allows data to be saved only when the new points are significantly apart thus allowing the SD card memory to be used more efficiently.
Next the “GpsData” event is subscribed to by the main application and an event handler is assigned. Currently, the data is only written to the debug console. This will eventually be the routine used to persist the data to the SD memory card. Notice the data being passed to the event is of type “GpsPoint”.
The ability to persist data to SD cards is currently being baked into the Netduino firmware. There is an update expected soon.
public class GpsPoint { public DateTime Timestamp { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public double SpeedInKnots { get; set; } public double BearingInDegrees { get; set; } }
Instances of “GpsPoint” contain the location information parsed from the GPS serial port.
The reader class is shown below:
public class Reader { private readonly object _lock = new object(); private readonly SerialPort _serialPort; private readonly int _timeOut; private readonly double _minDistanceBetweenPoints; private bool _isStarted; private Thread _processor; public delegate void LineProcessor(string line); public delegate void GpsDataProcessor(GpsPoint gpsPoint); public event LineProcessor RawLine; public event GpsDataProcessor GpsData; public bool IsStarted { get { return _isStarted; } } public Reader(SerialPort serialPort) : this(serialPort, 100, 0.0) { } public Reader(SerialPort serialPort, int timeOutBetweenReadsInMilliseconds, double minDistanceInMilesBetweenPoints) { _serialPort = serialPort; _timeOut = timeOutBetweenReadsInMilliseconds; _minDistanceBetweenPoints = minDistanceInMilesBetweenPoints; } public bool Start() { lock (_lock) { if(_isStarted) { return false; } _isStarted = true; _processor = new Thread(ThreadProc); _processor.Start(); } return true; } public bool Stop() { lock (_lock) { if(!_isStarted) { return false; } _isStarted = false; if(!_processor.Join(5000)) { _processor.Abort(); } return true; } } private void ThreadProc() { Debug.Print("GPS thread started..."); if(!_serialPort.IsOpen) { _serialPort.Open(); } while (_isStarted) { int bytesToRead = _serialPort.BytesToRead; if (bytesToRead > 0) { byte[] buffer = new byte[bytesToRead]; _serialPort.Read(buffer, 0, buffer.Length); try { string temp = new string(System.Text.Encoding.UTF8.GetChars(buffer)); ProcessBytes(temp); } catch (Exception ex) { // only process lines we can parse. Debug.Print(ex.ToString()); } } Thread.Sleep(_timeOut); } Debug.Print("GPS thread stopped..."); } private string _data = string.Empty; private GpsPoint _lastPoint; private void ProcessBytes(string temp) { while (temp.IndexOf('n') != -1) { string[] parts = temp.Split('n'); _data += parts[0]; _data = _data.Trim(); if (_data != string.Empty) { if (_data.IndexOf("$GPRMC") == 0) { if(GpsData!=null) { GpsPoint gpsPoint = GprmcParser.Parse(_data); if (gpsPoint != null) { bool isOk = true; if(_lastPoint!=null) { double distance = GeoDistanceCalculator.DistanceInMiles(gpsPoint.Latitude, gpsPoint.Longitude, _lastPoint.Latitude, _lastPoint.Longitude); double distInFeet = distance*5280; Debug.Print("distance = " + distance + " mi (" + distInFeet + " feet)"); if(distance<_minDistanceBetweenPoints) { // Too close to the last point....don't raise the event isOk = false; } } _lastPoint = gpsPoint; // Raise the event if(isOk) { GpsData(gpsPoint); } } } } if (RawLine != null) { RawLine(_data); } } temp = parts[1]; _data = string.Empty; } _data += temp; } }
The public facing features include the CTOR, ‘Start’, and ‘Stop’. We previously discussed the CTOR parameters. The ‘Start’ method spins up a thread to read and process the GPS serial port. The ‘Stop’ method signals the thread to end and waits for it to join the thread requesting the stop.
I am not as sure of how the threading is working on the Netduino. This code seems to work on my computer, but may have issues that I am not aware of. The additional thread could easily be removed from the above code. I was thinking the main thread may have other processing (think a responsive UI) and pushing the GPS to its own thread may be necessary.
The ‘ThreadProc’ method is the main processing routine running in the separate thread. It is a loop that continues until the ‘_isStarted’ variable is set to ‘false’ by the ‘Stop’ method. The guts of the while loop reads and processes data from the GPS serial port.
The ‘ProcessBytes’ method process the serial port data. The GPS unit provides a stream of ‘GPS sentences’ that have a specified format. This method puts together the incoming bytes and processes completed lines (or sentences) once they are formed. The code above looks for only the “GPRMC” sentence. If one is found it is parsed using the following parser:
public class GprmcParser { // Parse the GPRMC line // public static GpsPoint Parse(string line) { // $GPRMC,040302.663,A,3939.7,N,10506.6,W,0.27,358.86,200804,,*1A if(!IsCheckSumGood(line)) { return null; } try { string[] parts = line.Split(','); if (parts.Length != 12) { return null; } if (parts[2] != "A") { return null; } string date = parts[9]; // UTC Date DDMMYY if (date.Length != 6) { return null; } int year = 2000 + int.Parse(date.Substring(4, 2)); int month = int.Parse(date.Substring(2, 2)); int day = int.Parse(date.Substring(0, 2)); string time = parts[1]; // HHMMSS.XXX if (time.Length != 10) { return null; } int hour = int.Parse(time.Substring(0, 2)); int minute = int.Parse(time.Substring(2, 2)); int second = int.Parse(time.Substring(4, 2)); int milliseconds = int.Parse(time.Substring(7, 3)); DateTime utcTime = new DateTime(year, month, day, hour, minute, second, milliseconds); string lat = parts[3]; // HHMM.MMMM if (lat.Length != 9) { return null; } double latHours = double.Parse(lat.Substring(0, 2)); double latMins = double.Parse(lat.Substring(2)); double latitude = latHours + latMins / 60.0; if (parts[4] == "S") // N or S { latitude = -latitude; } string lng = parts[5]; // HHHMM.M if (lng.Length != 10) { return null; } double lngHours = double.Parse(lng.Substring(0, 3)); double lngMins = double.Parse(lng.Substring(3)); double longitude = lngHours + lngMins / 60.0; if (parts[6] == "W") { longitude = -longitude; } double speed = double.Parse(parts[7]); double bearing = double.Parse(parts[8]); // Should probably validate check sum GpsPoint gpsPoint = new GpsPoint { BearingInDegrees = bearing, Latitude = latitude, Longitude = longitude, SpeedInKnots = speed, Timestamp = utcTime }; return gpsPoint; } catch (Exception) { // One of our parses failed...ignore. } return null; } private static bool IsCheckSumGood(string sentence) { int index1 = sentence.IndexOf("$"); int index2 = sentence.LastIndexOf("*"); if (index1 != 0 || index2 != sentence.Length - 3 ) { return false; } string checkSumString = sentence.Substring(index2 + 1, 2); int checkSum1 = Convert.ToInt32(checkSumString, 16); string valToCheck = sentence.Substring(index1 + 1, index2 - 1); char c = valToCheck[0]; for(int i = 1;i<valToCheck.Length;i++) { c ^= valToCheck[i]; } return checkSum1 == c; } }
There are many available GPS sentence parsers out there. The above parser extracts the encoded information from the sentence and returns a ‘GpsPoint’ object (or null if there was a parsing error).
The processing code then calculates the distance between the new point and the previous point. This is done using the Haversine formula implemented by the following class:
public static class GeoDistanceCalculator { private const double _earthRadiusInMiles = 3956.0; private const double _earthRadiusInKilometers = 6367.0; public static double DistanceInMiles(double lat1, double lng1, double lat2, double lng2) { return Distance(lat1, lng1, lat2, lng2, _earthRadiusInMiles); } public static double DistanceInKilometers(double lat1, double lng1, double lat2, double lng2) { return Distance(lat1, lng1, lat2, lng2, _earthRadiusInKilometers); } private static double Distance(double lat1, double lng1, double lat2, double lng2, double radius) { // Implements the Haversine formulat http://en.wikipedia.org/wiki/Haversine_formula // var lat = MathMF.ToRadians(lat2 - lat1); var lng = MathMF.ToRadians(lng2 - lng1); var sinLat = MathMF.Sin(0.5*lat); var sinLng = MathMF.Sin(0.5*lng); var cosLat1 = MathMF.Cos(MathMF.ToRadians(lat1)); var cosLat2 = MathMF.Cos(MathMF.ToRadians(lat2)); var h1 = sinLat*sinLat + cosLat1*cosLat2*sinLng*sinLng; var h2 = MathMF.Sqrt(h1); var h3 = 2 * MathMF.Asin(MathMF.Min(1, h2)); return radius * h3; } }
This code relies heavily on a math library (MathMF namespace) developed by Elze Kool. I slightly modified this library to use the System.Math functions where ever they were available. The changes that I made are shown below:
public static readonly double PI = System.Math.PI; public static readonly double E = System.Math.E; public static double Pow(double x, double y) { return System.Math.Pow(x, y); } public static double Sqrt(double x) { return System.Math.Pow(x, 0.5); }
Not all the .NET framework is available in the .NET micro framework. One example is the ‘System.Math’ namespace. As the .NET micro framework grows up, I suspect that some of these namespaces will become available. Until then, we must rely on implementation made by the community.
If the distance is larger than the specified (in the CTOR) minimum, then the ‘GpsData’ event is raised. In addition a ‘RawLine’ event is available for every GPS sentence to be available.
Summary
Using the Netduino with the GPS shield was very straight-forward. I especially enjoy that I can use Visual Studio as my IDE. When the Netduino folks post an updated firmware that includes SD card features, I will continue this development and begin saving the data. I encourage anyone with a bit of curiosity to go get your self a Netduino and begin experimenting.
Hey Bob, some great work.
Just working on a similar project on the FEZ platform and I just noticed the MathMF.ToRadians function isn’t listed in the MathFM class. Did you create a special class for this?
Thanks!
Winston.
Hi Winston,
Thanks for the encouragement. I created a static ToRadians method in the MathMF class.
Hey Bob,
Could u explain in details what do you mean by “created a static ToRadians method in the MathMF class” above. If possible, perhaps you show an example here?
Thanks!
Good day!This was a really fabulous blog!
I come from milan, I was fortunate to find your subject in digg
Also I obtain a lot in your theme really thanks very much i will come every day
my project is GPS data loger . pic take to nmea cade and write sd card.I use PİC 18FXX microconroller but ı can not write C code 🙁 do you share your project shematic .
Hi bob, is possible to use this shield in FEZ PandaII ?? It has arduino shield pin compatibility… iwant to use it for a networking proyect about gps devices…. but im a noob in NETMF but i got experience in .Net and c#
Thanks !
I believe so (I haven’t tried). The shield was originally designed to work on the Arduino. I believe the FEZ Panda has the same pin outs as the Netduino. That is something you should check. As far as I can tell, no reason why the GPS shield would not work on the FEZ Panda.
Bob
Hi I have followed instructions of Adafruit site and your blog, unfortunately I keep getting a GPMRC record with a ‘V’ in the third field i.e. inValid record. The GPS receiver is displaying a solid red LED any ideas ?
Thanks
I had something funky like that also. Forgive my hand waving suggestion. I don’t have the device in front of me. I believe the issue for me was that pin D2 could not power on the GPS by going low. I had a faint red light on the GPS unit. So what I did was take the wire that was plugged into pin D2, crossed it over the gps device and connected it directly to one of the ground pins (‘Gnd’) on the Netduino. Then the GPS unit worked. Hope this works for you.
Bob
Hello friend, I saw your message and I am having the same problem, how you resolved?
Cristiano – Brazil
Hey! I’ve got a question, I need to know if I can work with a touch screen connected to my Netduino (besides the GPS). I want to do an application to see in that screen a GPS system quite like the ones we use in cars. Thank you! (thunkx@gmail.com)
Hi Jonathan,
I don’t have an exact answer. The GPS unit does not currently use all the i/o pins on the board. There are a few left over. Is that enough to drive a touch screen? If not, you may have to select a different main board.
Bob
Hi Bob, first of all congratulations about your project code and share all this. My name is Dan and i’m from brazil and new to Netduino. I’m recently bought a GPS Shield and also a Cellular shield for a project that i want to biuld (GPS tracker send GPS info through SMS) but i’m facing a problem on using both shields connected to netduino. Do you think that this is possible? Both shield use the same UART from netduino and probably i will have to change this in one of the shields. I really dont have a clue on how to do that… What do you think of the project idea? Thanks and congrats again !
Hi Dan,
Thanks for the kind words. I love the idea of using a Cellular shield in combination with the GPS unit. I had intended on doing something similar and just do not have the time to pursue. Originally, I wanted to use a wireless shield to post the GPS data to a website. Very similar conceptually. It seems like it should be possible since the Netduino has 2 UARTs (digital pins 1/2 and 3/4). I would recommend putting a question on the Netduino community forum (http://forums.netduino.com/). They are extremely responsive and a great help. They will be able to provide more definitive answers.
Hope this helps.
Bob
Thanks a lot for the answear and sorry about the late response. Finally received all the hardware and now i will do some work on that. I did what you mention (about Netduino Forum) and i will have to do some electronic studies on that issue. Thanks again!
first of all thx for ur efforts and hope u r fine too 🙂
well iam using netduino plus and visual c# express 2010 and the code isnt working on it 🙁
do u have any ideas ?
thx again
Hi Bob,
Great tutorial, just what I was looking for!
I have followed the 1st example, whilst I am receiving NMEA statements I have noticed they do not include any location data just time/date stamps. The GPS receiver does not ever seem to lock when connected to the Netduino. Looking at the screenshot above (image_6.png) you seem to have had the same problem – do you remember how you fixed this?
Many thanks,
Rich.
There were a power issue on Netduino plus 2.
Lot of noise on the onboard DC-DC converter.
Hi Bob, and thanks! Your code was a HUGE help. I used a crius neo-6 gps v3 instead of the board, so I moved the power to the 5v jumper and commented the lines pertaining to powering the GPS. It worked like a charm.
I did have some difficulties, however. The link to the MathMF class by Elze Kool is now dead. I pulled it up with web.archive.org that allowed me to download the exMath.cs file. It did not contain a definition for ToRadians, so I found an example from a java class and popped it in the MathMF class (renamed exMath.cs). That code is below, and not guaranteed to be correct. It will get it running, but I’m no math guy. Perhaps someone can help me out with this.
public static double ToRadians(double x)
{
return x * (Math.PI / 180);
}