Arduino, GPS, GSM/GPRS: Transmitting GPS Coordinates via UDP to Google Maps

This is a somewhat involved project. Using an Arduino, I read coordinates from a u-blox GPS, then use a GSM/GPRS cell radio to transmit those coordinates via UDP every 60 seconds to a Raspberry Pi running a LIGHTTPD web server. The Raspberry Pi prepares the GPS data in a form that interfaces with Google Maps and can be served up by the web server software.

The result is a ground track of the arduino’s movements:


Some parts of this project are beyond the scope of this document. When I get to those sections, I’ll try to point you to the right place to fill in the blanks. Mainly I’m going to deal with the hardware, and the customized software in this post.

This project builds on the work covered in my prior post, Arduino, GPS, and SMS: Texting GPS Coordinates. That will cover how to get all of the hardware connected and operational.

Here is a diagram of the overall equipment and data flow:



I found these references useful for this project:

Tutorial for Using TCP/UDP with the SIM900 GPRS:

The SIM900 AT Command Manual

This SIM900 Application Note

Arduino Base64 Library

Free Pascal Networking Library

Free Pascal Base64 Library

Transmit a Test UDP Packet from Arduino Over the Cellular Network

The first thing I needed to figure out was how to transmit a packet from the arduino across the cellular network to my Raspberry Pi.

Set Up Dynamic DNS

First, you need a static IP address. Like most people, I don’t have one. Instead I own several domain names and I use freeDNS to provide Dynamic DNS. My router is running the free wrt-dd router operating system. When the ISP changes my IP address, the router notices the change and posts the change to freeDNS. Then, when someone requests my domain name, it properly points to my router. Setting this up is beyond the scope of these instructions; however, the processes are well documented.

It is possible to proceed with just your external router IP address, but when your ISP changes it, your Arduino will no longer be able to locate your Raspberry Pi.

Enable Port Forwarding on Your Router

Now that the Arduino can find your firewall router, its UDP packet must be properly forwarded to the Raspberry Pi inside your network. This is done using port forwarding in the router. Every router handles this slightly differently. wrt-dd’s screen looks like this:


Let’s assume the RPI is given IP address and I want to forward UDP packets on port 9999 to the RPI. Then the fields would be set as:

  • Application: description
  • Protocol: UDP
  • Source Net: blank (allows anyone to transmit this packet)
  • Port From: 9999: the inbound UDP port
  • IP Address: the IP address of the RPI
  • Port To: 9999: the UDP port being used on the RPI – normally I have the set to be the same.

Once the forwarding rule is setup, it would be extremely handy to be able to verify it works properly. The above is the proper setting for the running application; however, I don’t have any applications running yet, but I want to verify port forwarding is working independently of my own code – that way if my code is wrong, I know it is my fault, not a network issue.

To do this, I actually set the forward rule like this:

  • Protocol: BOTH
  • Source Net: blank (allows anyone to transmit this packet)
  • Port From: 9999: the inbound UDP port
  • IP Address: the IP address of the RPI
  • Port To: 7: Echo Service.

This is going to allow the TCP packet with port 9999 to be forwarded to port 7 of the RPI. Port 7 is the echo service which is handy for initial testing like this. Once you have the forward rule set in the router, enable Echo service on the raspberry pi by using this post: Using the Echo Service on Raspberry Pi for Network Testing.

I use this Port Forward Test web app to verify I have set up port forwarding correctly. This app will already have the correct outside IP address of your router, just enter the test port, 9999 in this case. If the port can be accessed, you get a message indicating the port can be accessed.

If the port cannot be accessed, make sure all of your IP settings are correct for the Port Forwarding configuration. If you still can’t get it to work, I suggest using WireShark to verify if the packet ever even makes it to the Raspberry Pi.

At this point, you should have verified that dynamic DNS is working and that test packets are making it all the way to the RPI.

As I continued to test the Arduino GPRS, I wanted the echo service to continue running as it provide easy testing of the RPI w/o needing to write any software for it first.

Setting Up WireShark

My next step was to get WireShark running as I wanted to see how packets sent by the Arduino were going to appear as they were received by the RPI. To do this I installed WireShark to monitor inbound network traffic. Here are some tips to getting WireShark running on an RPI: Getting Wireshark to run on Raspberry Pi with VNC.

Setup of the GPRS to Allow Network Data Transmission

These are the command necessary to bring up a GPRS network connection, in the proper order. I will only briefly explain these commands. Take a look at the SIM900 manual for a full explanation.

AT+CIPMUX=0                     // single session mode
AT+CIPMODE=0                    // non-transparent mode
AT+CGATT?                       // are we connected?
AT+CSTT="APN"                   // setup APN context 
AT+CIICR                        // bring up GPRS
AT+CIFSR                        // display IP addr

The AT+CSTT is dependent upon your cellular carrier. You may need to include an user/password here as well. You will have to research your carrier to determine what needs to be used.

We are now ready to send the AT command to initialize UDP on port 9999 (since this isn’t TCP, an actual packet is not sent to initiate the connection):


You can use either your domain name or the outside IP address of your firewall (which will forward the packet to the RPI).

Transmitting Data Over the Network

Now to send a test packet. I sent ‘hello world’:

> hello world
hello world

AT+SEND=11 indicates I will be sending 11 characters. Once the > is printed, I type ‘hello world’ and as soon as the 11th character is input, I get SEND OK. The ‘hello world’ is echoed back.

The first time or two I did this I was baffled by the 2nd ‘hello world’. Uh, I sent the test to an echo server. It echoed what I typed back at me. Duh! So I knew it worked without even looking at WireShark; nevertheless, I did look:


This worked out quite well, but it seems kind of klunky if a lot of data is being sent/transferred. It turns out there are 3 different ways to transmit data with AT+CIPSEND.

There is the non-transparent, unterminated method I just used. There is also a non-transparent control-Z terminated method. To use this method you would type:

> hello world<control-Z>

With this method, you don’t need to know the length of the transmission in advance.

The final method is using transparent mode. During setup, you would use AT+CIPMODE=1 to indicate you are using transparent mode. When you send the AT+CIPSTART command, you will receive a CONNECT OK message and now everything typed is transmitted. You must type ‘+++’ to exit transmission mode and enter new AT commands.

This is almost EXACTLY like analog modems used to work, if you are familiar with them.


CONNECT                        // now everything is sent verbatim to remote IP

abcdefg                        // my input is echoed back to my by echo server
                               // typed '+++' to escape back to command mode
AT+CIPSHUT # close network connection

Of these three methods of transmission, specifying a string length is perfect for my fixed length UDP packet that will contain GPS information.

Programming the Arduino

Once I understood the basic principles of transmitting UDP messages using the GPRS card I was ready to write some Arduino Code. The basic plan was pretty simple, continuously read GPS data and once a minute send the current location, speed, and course to the Raspberry Pi in a UDP Packet.

The UDP packet would look like this:

struct udpRecT {
 double latitude;
 double longitude;
 int speed;
 int course;
 char unused; // base64_decode adds '' to end of rec

There is one issue, though. All of my data is in binary (double and int) form. Trying to convert the double floating point values into something ASCII is a bit problematic. And transmitting binary data across the cellular link is also a problem (actually in testing I found I could transmit every character value from 1-255 in transparent mode, but I could NOT transmit 0x00 – Null).

To get around this I decided to convert the record to Base64. Base64 is a special representation of a string that uses only normal printable characters so they can be transmitted across ASCII datalinks – this is the same format used to transmit emails. Details can be found here.

There is a base64 library for both Arduino and Free Pascal, so I wouldn’t even have to write code to make this work.

As with the testing above, my plan was to write the Arduino code and just watch the echo server using WireShark. Once I could properly transmit the encoded string to the RPI, then I would work on code for it.

Arduino Program Highlights

The Arduino program source is in the download at the end of this post, but here are some code highlights.

The program was written using code::blocks for arduino. It will load and compile in the Arduino Ide, but you will see some things that you wouldn’t normally see in a normal Arduino program, mainly the required function forward references.

During the initialization of the modem, any AT command failure (eg OK is not returned) results in a call to die() which will cause the Arduino to reboot after 5 seconds. It has not been unusual to have problems getting the link up and running w/o a failure so the code takes that into account.

Most of the time is spent calling doGpsIo which simply keeps the GPS data structure up to date by reading the incoming stream of GPS data. Once a minute, doUdpMsg is called to transmit the current GPS data.

Note that I actually look at the current minute on the GPS and send the update when the minute changes. I initially was using millis() to track the time. Turns out, by using the softwareSerial library interrupts blocked enough to cause millis() to be way out of whack. Generally it was 1/2 the speed it should be.

There are places in the program where I continue to use millis() (IO timeouts). It is acceptable for these to be longer than I expect, but I wanted the UDP packet to transmit on time.

The GPS data is loaded into the udpRec structure. That is then encoded using base64 and the encoded  string is transmitted via UDP.

The transmit function transmits the specified string and it watches for the echoed response and it eats that response. In powerUpSms, this behaviour is not acceptable, so gprs.print is called directly.

AT commands are generally transmitted using transmitAT. This function calls transmit to xmit the string, then uses waitfor to wait for an OK response. If OK is not returned, it will call die() and force a reboot.

transmitAT_P is a wrapper for transmitAT to allow the use of storing string in program memory to save SRAM memory.

 Programming the Raspberry Pi

The basic function of the Raspberry Pi program is to accept UDP packets containing GPS data from the Arduino. These are then formatted as necessary and written to an HTML include file that will be used by the static web page.

Once you are ready to start testing the program on the Raspberry Pi, you will want to fix up your firewall router port forwarding rule to stop forwarding the the Raspberry Pi Echo Service and start forwarding to the correct port for the software you are writing (9999 in my case).

The program is written in Free Pascal, but is simple enough you should have no trouble converting it to the language of your choice. This program is small enough I will include it here, but it is also in the download file:

program gsmGpsUdpDemo;

{$mode objfpc}{$H+}

    {$IFDEF UNIX}{$IFDEF UseCThreads}

{$R *.res}

    htmlFname                   = 'gpspoints.html';

    udpRecT                     = record
        case byte of            // this variant record is like a C union
                latitude        : single;
                longitude       : single;
                speed           : smallint;
                course          : smallint;
                b               : array[1..12] of char;
        end; // record


    buf                         : string;
    dist                        : double;
    htmlF                       : text;
    i                           : integer;
    oldLat                      : double    = 0.0;
    oldLon                      : double    = 0.0;
    s                           : string;
    udp                         : TUDPBlockSocket;
    udpRec                      : udpRecT;

  // ------------------------------
  // calculate distance between two long/lat points

  function calcDist(
        lat1                           : double;
        lon1                           : double;
        lat2                           : double;
        lon2                           : double
        )                              : double;


    calcDist := arccos(sin(degtorad(lat1))*sin(degtorad(lat2))+
    CalcDist := CalcDist * 0.621371192; // convert from km to miles
    calcDist := calcDist * 5280         // for this program, I want feet returned

  end; // calcDist


assign(htmlF, htmlFname);
if not fileExists(htmlFname) then begin

udp := TUDPBlockSocket.create;

    udp.bind('', '9999');                            // open udp port 9999
    writeln('bind failed');

while true do begin
    while (udp.WaitingData = 0) do begin

    s := udp.RecvPacket(1000);                            // 1 second timeout
    if udp.LastError = 0 then begin
        buf := decodeStringBase64(s);
        for i := 1 to length(buf) do
            udprec.b[i] := buf[i];
        dist := calcDist(oldLat, oldLon, udpRec.latitude, udpRec.longitude);
        writeln(format('%s: %10.6F %10.6F %3d MPH %3d Degrees dist: %10.4F',
            [formatdatetime('hh:nn:ss',now), udprec.latitude, udpRec.longitude,
            udpRec.speed, udpRec.course, dist]));
        oldLat := udpRec.latitude;
        oldLon := udpRec.longitude;
        if (dist > 20) then begin
                writeln(htmlF, format('new google.maps.LatLng(%10.6f,%10.6f),',
                [udprec.latitude, udprec.longitude]));
                end; //try
    end; // while


RPI Program Hightlights

This is a pretty basic program. It first verifies the output file exists and creates it if necessary.

It then opens a UDP socket and binds it to port 9999 on the local IP interface.

It then enters an infinite loop. First it watches for UDP data, and if there is none, wait .5 secs and checks again.

If data is waiting, it reads the packet and decodes the base64 text into the binary udpRec data structure.

It determines the distance between the current and previous coordinates. If they are < 20′, it goes back to waiting for another packet. I don’t want to write a bunch of tightly grouped coordinates into the google maps data file.

Finally, it formats the latitude and longitude of the location into a format used by google maps and appends them to the HTML include file.

Web Server

Once the RPI program was running, I needed to get a web server running that could serve up the Google map.

I have been using lighttpd as an RPI web server for another project that has run w/o problem for a couple of years now. One could use Apache as well.

Lighttpd is in the RPI repository so apt-get install can be used to install it. The web site is at

I believe you have to enable ssi_mod to allow server side #includes in the HTML like I am doing. Here is my lighttpd config file, if it helps:

server.modules = (
#       "mod_rewrite",

server.document-root        = "/var/www"
server.upload-dirs          = ( "/var/cache/lighttpd/uploads" )
server.errorlog             = "/var/log/lighttpd/error.log"             = "/var/run/"
server.username             = "www-data"
server.groupname            = "www-data"
server.port                 = 80

ssi.extension               = ( "html" )

index-file.names            = ( "index.php", "index.html" )
url.access-deny             = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )

Once the website is running, and assuming you want outside access to this web server, you will need to go back to your firewall router and add a new port forwarding rule to allow external HTML access to your RPI.

In my case, I wanted to access this website using

So I added a port forward rule that forwarded TCP port 81 traffic to my raspberry pi’s port 80.

Registering for Google Maps API Key

To display a google map in your own web page, you must register with google and get an API key. This is free, though (at least for me) kind of confusing. After some monkeying around I was able to get registered and get their sample code to work on my website.

Static Web Page

gps.html is the web page on the server that shows the google map.  The page itself is not too complicated. This page was modified from someones example, so some elements (like flightplan) have poor terminology for my example, but it works.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <title>GPS Tracker</title>
      html, body, #map-canvas {
        height: 90%;
                width: 90%;
        margin: 0px;
        padding: 0px
    <script type="text/javascript"
      src="<your API key>">

    <script src=""></script>

function initialize() {
  var myLatlng = new google.maps.LatLng(46.0,-116.0);
  var mapOptions = {
    zoom: 14,
    center: myLatlng
  var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

    var flightPlanCoordinates = [
<!--#include file="gpspoints.html"-->

  var flightPath = new google.maps.Polyline({
    path: flightPlanCoordinates,
    geodesic: true,
    strokeColor: '#FF0000',
    strokeOpacity: 1.0,
    strokeWeight: 2


google.maps.event.addDomListener(window, 'load', initialize);


<h1>GPS Tracker</h1>
<div id="map-canvas"></div>

You will need to insert your API key where I have <your api key>.

The line:

  var myLatlng = new google.maps.LatLng(46.0,-116.0);

Indicates where the map will be centered. You will want to pick something closer to your own location.

Also note the line:

<!--#include file="gpspoints.html"-->

This loads the file being generated by the RPI program.

At this point, if you request your page, it should pull up a map, but there won’t be any mapped coordinates until the gpspoints.html file starts getting updated.  If you run the RPI program inside of the /var/www directory and let it receive coordinates from the Arduino, it should start creating entries in the gpspoints.html file and refreshing the map should start showing line segments between the points received.

Automating the RPI program

Once I had the entire process running properly, the last step was to run the RPI program in the background. I did this by adding the following to crontab (sudo chrontab -e):

@reboot cd /var/www/ ; gsmGpsUdpDemo >~/gsmGpsUdpDemo.out

This causes the gsmGpsUdpDemo program to run on boot up, from the /var/www directory and the ‘debugging’ output is written the the file /home/root/gsmGpsUdpDemo.out.

I rebooted the RPI and verified gsmGpsUdpDemo was indeed running in the background and after driving around with the Arduino, could see my location was being tracked.

The Software

Both the Arduino C files and RPI Free Pascal files can be found here:




This entry was posted in c-arduino, c-rpi and tagged . Bookmark the permalink.

One Response to Arduino, GPS, GSM/GPRS: Transmitting GPS Coordinates via UDP to Google Maps

  1. Pingback: Arduino, GPS, GSM/GPRS: Transmitting GPS Coordinates via UDP to Google Maps | 7uptomillion

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s