Arduino Home

An Open-Source platform to create digital devices and interactive objects that sense and control physical devices.

Arduino Tutorials
Arduino Projects


This project is an entry in the Build a Smarter World MKR Fest.

The challenge is to build a practical and useful system based on an arduino  MKR 1300.

My plan is to build the MKR 1300 into a platform with a set of useful interfaces that allow it to be adapted to many applications.

The core interfaces will be:

  • a LoRa radio to enable low cost long range communications
  • a GPS receiver (with UART interface) to track the location of the system and provide an accurate time
  • a low-power SPI LCD to display graphics and text
  • an I2C Grove interface to connect any serial Grove module
  • 2 analog Grove interfaces to connect any analog Grove sensors
  • a digital Grove interface to connect any digital grove module

These interfaces and connectors will be implemented on a custom PCB that results in a clean compact system platform.

Application systems will be packaged in 3D printed cases tailored to accommodate the necessary features.

The first application, which is the subject of this project, will be a geo-locator system that can send GPS location data to a LoRa gateway.

This system will initially be used to explore the reach of the LoRa radio.

Some of the applications where I might use this geo-location capability include:

  • I generally don't have my cell phone turned on, so this may be used to show where I am or where my car is.
  • It could be used to show when I am getting close to arriving at home.
  • It can show how far away my ride is when someone is coming to pick me up after work, so I don't have to hover around the door.
  • It could be used to tell me to turn my cell phone on - take it out of airplane mode.

The range of the radio will dictate which applications can be useful or how useful they will be.

Part of this initial project will be to explore ways to maximize range of the radio.



Here is an unboxing video showing the kit supplied by element14. It also shows the design of the custom interconnect PCB designed for the platform:

The 915 MHz antennas I purchased claim to have 5 dBi gain.


Update 1 - PCB and LCD

The interconnect PCB came back from the printer in 9 days so I put some connectors on it, connected an SPI LCD and wrote a little program to check out that it works properly. It is always fun when a PCB works properly


Update 2 - GPS & LCD

This update adds GPS to the MKR platform. Right now I am just displaying latitude, longitude and altitude to test the GPS and display. The digits after the decimal point on latitude and longitude have been zeroed just for the video. In the final system both displays will show the distance between the modules, the elevation of both modules and the received signal strength of both modules. The information being transmitted bidirectionally via LoRa will be latitude, longitude, altitude, and received signal strength. This information will allow me to explore the range capabilities of LoRa and the factors that affect it.

The project is proceeding well. All add-on electronics are working under software control. Now it just a matter of getting the radios communicating and then packaging the modules prior to running some field tests.


Update 3 - Mechanical Packaging

I got the LoRa radios communicating but because the antennas cables are so delicate, I decided I better package the systems before proceeding to integrate LoRa communications and GPS data transfer. The packaging design took more effort than expected so it has been a while since my last update. This video is a quick view of the packaging and how it goes together:

Now that I can pick the devices up without stressing the cables and connectors, I can get back to working on the software.

Then I can start having fun testing system performance and local communication range.


Update 4 - LoRa Communications

I have been really struggling to get the software working - my software issues, not the development system, but it has taken far to many hours to get this far. However the LoRa geo-location system is fully operational now. Here is a video showing the two units communicating GPS and signal strength data:

I have tried walking round the neighborhood out to 783 meters and it never lost connection, even through many houses and a hill. So it does not need direct line-of-sight.

Now that the platforms are fully functional, the next step is to take one on a drive and see how far away it can still make contact. The system is all set up to display the distance between units as well as the altitude of both units and the received signal strength of both units. This way I will know how high I have to be to make contact and how far away I can connect.


Update 5 - LoRa Range Test

I took the LoRa GPS system out for a spin in my car today. Here are a few pictures:

This picture shows the LoRa GPS module mounted on the driver's side window.

Getting the design angles right was a bit tricky, partly because taking measurements at cold temperatures with bare hands is problematic.

This is what the unit looks like from inside the car.

Here is a picture at 3.4 km from my house. The signal was pretty good out to 1 km even though there was lots of houses preventing direct line-of-sight. The signal dropped out a couple of times between 1 km and 3.4 km. There is never a clear line-of-sight to my house on this route . The spot where this image was taken is slightly elevated, but also does not have a clear line-of-sight to my house. There are a bunch of trees and buildings between this location and my house.

Clearly this LoRa radio has much longer range than WiFi, but it still benefits from a direct line-of-sight transmission if one can be arranged.

I plan to try some longer range connections, but they may have to wait for spring as the local mountain lookout is closed during winter.


Update 6

I decided to do a quick comparison between LoRa RF power and cell phone RF power.

Here is the video showing LoRa transmits less RF power than a cell phone:

The LoRa RF power was higher than I expected, but still significantly lower than a cell phone.

All of these measurements are pretty near field and I don't know anything about the cell phone or meter polarizations.


Here is the Arduino code for the LoRa GeLo MKR - for those that want to know how not to write code....


 * LoRa GeLo MKR - Long Range Geo-Locator MKR 1300 
 * Reads GPS location and altitude. Calculates distance to home base from GPS coordinates. 
 * Displays distance, altitude and RSSI data on an LCD.
 * by Doug Wong   2018
#include <TinyGPS++.h>
#include <SPI.h>  
#include <LoRa.h>
#define PIN_SCE   6       //chip select pin for LCD
#define PIN_RESET 10      //reset pin for LCD
#define PIN_DC    7       //data / command select pin for LCD
#define PIN_SDIN  8       //serial data input pin for LCD (MOSI)
#define PIN_SCLK  9       //SPI clock pin for LCD
#define LCD_C     LOW     //this value select command mode on LCD DC
#define LCD_D     HIGH    //this value select data mode on LCD DC
#define LCD_X     84      //number of pixels on an LCD line
#define LCD_Y     48      //number of vertical pixels on LCD

TinyGPSPlus gps;  
const unsigned long SEND_INT = 5000;         // Sends a msg on this interval (milliSec)  
const unsigned long LISTEN_TIME = 200;       // Listens for a response for this interval (milliSecs)  
unsigned long numMsgs = 0;                   // Number of messages sent  
unsigned long lastSend = 0;                  // Time of last message sent (milliSecs)  
char latitude[14];                           // GPS latitude
char longitude[14];                          // GPS longitude
char altitudes[14];                          // GPS altitude
char gpslats[6];
char gpslongs[6];
char gpsalts[6];
char rsi[6];                     // received signal strength at home base
char LoRaIn;                     // character received via LoRa
char LoRaInStr[2];
char distl[4];
char distr[4];
char loopc[6];
char LoRaChar;
char LoRaString[1];
double gpslat;
double gpslong;
double gpsalt;
double lat1;                       // latitude of this device
double lat2 = 45.42;               // default latitude of home base
double lon1;                       // longitude of this device
double lon2 = -75.6;               // default longitude of home base
double gpsdist;                    // distance to home base
double xpos;
double ypos;
double rad =  6371000;
int gpslati;
int gpslongi;
int gpsalti;
int rssi;                          // recieved signal strength
int packetSize;
int disti;
int distj;
int loops;
double p = 0.017453292519943295;      // Pi/180

// pixel map of character set for LCD
static const byte ASCII[][5] =
 {0x00, 0x00, 0x00, 0x00, 0x00} // 20  
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c <
,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥
,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j 
,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ←
,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f →

void LcdCharacter(char character)                       // display a character on the LCD
  LcdWrite(LCD_D, 0x00);                                // display a blank space before character
  for (int index = 0; index < 5; index++)               // font uses 5 x 7 pixels
    LcdWrite(LCD_D, ASCII[character - 0x20][index]);    // display next column of pixels for this character
  LcdWrite(LCD_D, 0x00);                                // display a blank space after character

void LcdClear(void)                                     // display a blank screen
  LcdWrite(LCD_C, 0x40 );                               // command to set Y cursor position to 0
  LcdWrite(LCD_C, 0x80 );                               // command to set X cursor position to 0
  for (int index = 0; index < LCD_X * LCD_Y / 8; index++)
    LcdWrite(LCD_D, 0x00);                              // display a blank column of 8 pixels
  }                                                     // the LCD automatically steps to the next position

void LcdInitialise(void)
  pinMode(PIN_SCE, OUTPUT);                                        // set the chip select pin to be an output
 // pinMode(PIN_RESET, OUTPUT);
  pinMode(PIN_DC, OUTPUT);                                         // set the DC pin to be an output
  pinMode(PIN_SDIN, OUTPUT);                                       // set the MOSI pin to be an output
  pinMode(PIN_SCLK, OUTPUT);                                       // set the SPI clock pin to be an output
 // digitalWrite(PIN_RESET, LOW);
 // digitalWrite(PIN_RESET, HIGH);
  LcdWrite(LCD_C, 0x21 );                                         // put LCD in Extended Commands mode.
  LcdWrite(LCD_C, 0x80 );                                         // Set LCD Vop (Contrast). 0x80 - 0xFF
  LcdWrite(LCD_C, 0x04 );                                         // Set Temp coefficent. 0x04 - 0x07
  LcdWrite(LCD_C, 0x14 );                                         // LCD bias mode 1:48.  0x10 - 0x17
  LcdWrite(LCD_C, 0x20 );                                         // LCD Basic Commands
  LcdWrite(LCD_C, 0x0C );                                         // put LCD back in normal mode.

void LcdString(char *characters)                        // display a sting of characters on LCD
  while (*characters)

void LcdWrite(byte dc, byte data)                       // display a byte as 8 vertical pixels
  digitalWrite(PIN_DC, dc);                             // control the DC pin - should be data
  digitalWrite(PIN_SCE, LOW);                           // apply chip select to LCD
  shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data);         // send byte as serial bit stream
  digitalWrite(PIN_SCE, HIGH);                          // deselect LCD

void setup() {  
  Serial.begin(115200);                                 // the serial port may be used for debugging
  Serial.println("LoRa Send and Listen started...");  
  if (!LoRa.begin(915E6)) {  
    Serial.println("LoRa start failed");  
    while (1);  
  Serial1.begin(9600);                                  // this serial port is used for GPS communications

  LcdInitialise();                                      // display a splash screen
  LcdString("   ARDUNIO  ");
  LcdString("   MKR WAN  ");
  LcdString("  LORA  GPS ");
  LcdString("            ");
  LcdString("     by     ");
  LcdString("  DOUG WONG ");
  delay(1000);                                          // show the splash screen for 1 second
  LcdClear();                                           // clear the LCD

//  lat2 = 0;
//  lon2 = 0;
void loop()                         // main loop reads GPS sends via LoRa, calculates distance, reads RSSI, displays data
  while (Serial1.available() > 0)

  if (gps.location.isUpdated())
    gpslat =;     // Latitude in degrees (double)
    gpslong = gps.location.lng();    // Longitude in degrees (double)
    gpsalt = gps.altitude.meters();  // Altitude in meters (double)

    lat2 = gpslat;
    lon2 = gpslong;
    lat1 = 45.42;
    lon1 = -75.6;
//    gpsdist = 12742 * asin(sqrt(0.5 - cos((lat2 - lat1) * p)/2 + cos((lat1 * p) * cos(lat2 * p) * (1 - cos((lon2 - lon1) * p)))/2));
//    gpsdist = acos(sin(lat1)*sin(lat2) + cos(lat1*PI/180) + cos(lat1*PI/180)*cos(lat2*PI/180)*cos(lon2*PI/180 - lon1*PI/180))*6371000;
//    xpos = (lon2-lon2)*cos((lat2-lat1)/2);
// Calculate distance to home base from GPS data
    xpos = 2 * rad * tan((lon2-lon1)/2) * (90 - ((lat2-lat1)/2))/90;
    ypos = 2 * rad * tan((lat2-lat1)/2);
    gpsdist = sqrt(xpos*xpos + ypos*ypos);
    gpsalti = int(gpsalt);
    itoa(gpsalti,altitudes, 10);
    gpsalti = int((gpsalt - gpsalti) * 100);
    itoa(gpsalti, gpsalts, 10);
    disti = int(gpsdist/46.7);
    itoa(disti,distl, 10);
    distj = int((gpsdist - disti) * 1000);
    itoa(distj, distr, 10);
  // Check to see if time to send another message  
  if ((millis() - lastSend) > SEND_INT){  
    lastSend = millis();  
    rssi = LoRa.packetRssi();  
    LoRa.print("     ");
    LoRa.print("     ");
    LoRa.print("     ");

    LcdWrite(LCD_C, 0x80 );  // set X Command
    LcdWrite(LCD_C, 0x41 );  // set Y Command
    LcdString("RSI  ");
    LcdString("   ");
    LcdWrite(LCD_C, 0x80 );  // set X Command
    LcdWrite(LCD_C, 0x42 );  // set Y Command
    LcdString("ALT  ");
    LcdString("   ");
    LcdWrite(LCD_C, 0x80 );  // set X Command
    LcdWrite(LCD_C, 0x44 );  // set Y Command
    LcdString("ALTB 75.4");
    LcdWrite(LCD_C, 0x80 );  // set X Command
    LcdWrite(LCD_C, 0x45 );  // set Y Command
    LcdString("DIST ");
    LcdString("     ");
    LcdWrite(LCD_C, 0x80 );  // set X Command
    LcdWrite(LCD_C, 0x40 );  // set Y Command
    LcdString(" LoRa   GPS ");
    LcdWrite(LCD_C, 0x80 );  // set X Command
    LcdWrite(LCD_C, 0x43 );  // set Y Command
    LcdString("RSSI ");

    // Now listen for response  
    while ((millis() - lastSend) < LISTEN_TIME){  
      // try to parse packet  
      int packetSize = LoRa.parsePacket();  
      if (packetSize > 0){  
        while (LoRa.available()){
          LoRaChar = (char);
          LoRaString[0] = LoRaChar;
    LcdString("  ");  


Project Wrap Up Notes

The MKR WAN 1300 platform packs powerful performance into a compact form factor, and despite my personal problems with programming, its development environment makes it easy to build applications.

The LoRa radio has better range than WiFi and lower RF power than a cell phone. It connects well out to about 1 km regardless of buildings and obstructions in the path, and it can connect much, much further if there is a direct line-of-sight.

The project involved learning more about LoRa, designing a custom PCB, designing system enclosures and of course software to make the system perform.

I am happy with the project result, the electronics perform well, the packaging fits and works well, and I find it very interesting to drive around monitoring signal strength versus distance and altitude.

This MKR system with LCD, GPS and LoRa is a great platform that I can use to develop lots more LoRa applications.


Related links:

Winners Announcement: Build a Smarter World: Build an Arduino MKR WAN 1300 Project for an Arduino Engineering Kit!