How To Build a GPS Tracker From Scratch Using Arduino

Written by duanebester | Published 2020/08/02
Tech Story Tags: iot | arduino | imu | bluetooth | sensor | hardware | firmware | gps

TLDR Arduino Nano 33 BLE Sense with an SD card and GPS module for a side project that logs GPS and IMU data. Unbeknownst to me at the time, these boards aren’t footprint-compatible, so I combined them with a breadboard. It took a little while to get all the libraries installed and everything connected appropriately. The biggest challenge was the GPS module. The tricky part here was making sure to remove the DATALOG.CSV file every time we start up.via the TL;DR App

Combining 3 Arduino boards to create a GPS tracker & data logger.
I had an idea to combine the new Arduino Nano 33 BLE Sense with an SD card and GPS module for a side project that logs GPS and IMU data.
I decided to purchase the Nano 33, the MKR Mem Shield, and the MKR GPS Shield and connect them all together. Unbeknownst to me at the time, these boards aren’t footprint-compatible, so I combined them with a breadboard 😐.
It took a little while to get all the libraries installed and everything connected appropriately. The biggest challenge was the GPS module. It really helps to start debugging the GPS’ example code outside; where satellite signal is available 😉.

Prototype Code

The full Arduino sketch for the above connected system is here. But, might as well have a quick overview of the code. Throwing the imports and definitions below:
#include <SparkFun_Ublox_Arduino_Library.h>
#include <Arduino_LSM9DS1.h>
#include <Arduino_APDS9960.h>
#include <SPI.h>
#include <SD.h>

// SD Vars
File dataFile;
int chipSelect = 4;

// GPS Vars
SFE_UBLOX_GPS myGPS; // Connected via UART
long latitude = 0;
long longitude = 0;
long speed = 0;
byte satellites = 0;
int timeout = 50;
long lastGPSTime = 0;

// IMU Vars
float ax, ay, az;
float gx, gy, gz;

// Misc
bool DEBUG = true;
int counter = 0;

#define LEDR        (22u)
#define LEDG        (23u)
#define LEDB        (24u)
Next, we need to do all the GPS and SD card setup. The tricky part here was making sure to remove the DATALOG.csv file every time we start up.
void setup()
{
  // Setup User Terminal
  Serial.begin(115200); // UART to PC/Mac
  while(!Serial);
  Serial1.begin(9600);  // UART to GPS
  while(!Serial1);

  // Initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(LEDR, OUTPUT);
  pinMode(LEDB, OUTPUT);
  pinMode(LEDG, OUTPUT);

  digitalWrite(LEDR, HIGH); // LOW triggered LED....
  digitalWrite(LEDG, HIGH);
  digitalWrite(LEDB, HIGH);

  // Setup GPS
  if (!myGPS.begin(Serial1)) {
    Serial.println(F("GPS not detected!"));
    while (1);
  }
  Serial.println("GPS Started!");

  // Setup SD Card
  if (!SD.begin(chipSelect)) {
    Serial.println("SD Card failed or not present!");
    while (1);
  }
  
  // Remove Existing DATALOG.CSV file
  if(SD.exists("DATALOG.CSV")) {
    SD.remove("DATALOG.CSV");
    dataFile = SD.open("DATALOG.CSV", FILE_WRITE);
    dataFile.close();
  }
  
  delay(500); // Make sure existing DATALOG.CSV file is gone
  
  // Create new CSV file with appropriate headers
  dataFile = SD.open("DATALOG.CSV", FILE_WRITE);
  dataFile.println("Count,AccX,AccY,AccZ,GyrX,GyrY,GyrZ,Lat,Long,Speed");
  
  // Done with SD Card Init
  Serial.println("SD Card Initialized!");

  // Setup Gesture Sensor
  if (!APDS.begin()) {
    Serial.println("Error initializing gesture sensor!");
  }
  APDS.setGestureSensitivity(85);
  Serial.println("Gesture sensor initialized!");

  // Setup IMU
  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while(1);
  }
  Serial.println("IMU initialized!");
}
Now we can log all of the data to the SD Card. Note that we could use the Gesture sensor to stop and start recording (in the main loop).
void loop()
{
  if (millis() - lastGPSTime > 500) {
    lastGPSTime = millis(); // Update the timer
    
    latitude = myGPS.getLatitude(timeout);
    longitude = myGPS.getLongitude(timeout);
    speed = myGPS.getGroundSpeed(timeout);
    satellites = myGPS.getSIV(timeout);
    
    if(DEBUG) {
      Serial.print(F("Lat: "));
      Serial.print(latitude);
      Serial.print(F(" Long: "));
      Serial.print(longitude);
      Serial.print(F(" (degrees * 10^-7)"));
      Serial.print(F(" Speed: "));
      Serial.print(speed);
      Serial.print(F(" (mm/s)"));
      Serial.print(F(" satellites: "));
      Serial.println(satellites);
    }
  }

  if (IMU.accelerationAvailable()) {
    IMU.readAcceleration(ax, ay, az);
    if(DEBUG) {
      Serial.print(F("Accel x: "));
      Serial.print(ax);
      Serial.print(F(" y: "));
      Serial.print(ay);
      Serial.print(F(" z: "));
      Serial.println(az);
    }
  }

  if (IMU.gyroscopeAvailable()) {
    IMU.readGyroscope(gx, gy, gz);
    if(DEBUG) {
      Serial.print(F("Gyro x: "));
      Serial.print(gx);
      Serial.print(F(" y: "));
      Serial.print(gy);
      Serial.print(F(" z: "));
      Serial.println(gz);
    }
  }

  if (APDS.gestureAvailable()) {
    // A gesture was detected, read and print to serial monitor
    switch (APDS.readGesture()) {
      case GESTURE_UP:
        Serial.println("Detected UP gesture");
        break;
      case GESTURE_DOWN:
        Serial.println("Detected DOWN gesture");
        break;
      case GESTURE_LEFT:
        Serial.println("Detected LEFT gesture");
        break;
      case GESTURE_RIGHT:
        Serial.println("Detected RIGHT gesture");
        break;
      default:
        // ignore
        break;
    }
  }

  if (dataFile) {
    counter += 1;
    String dataString = "";
    dataString += String(counter) + ",";
    dataString += String(ax) + ",";
    dataString += String(ay) + ",";
    dataString += String(az) + ",";
    dataString += String(gx) + ",";
    dataString += String(gy) + ",";
    dataString += String(gz) + ",";
    dataString += String(latitude) + ",";
    dataString += String(longitude) + ",";
    dataString += String(speed);
    dataFile.println(dataString);
  }
  
  delay(150); // Don't pound too hard
}
So this is all fine and good. Prototype concept has been vetted! Time to build a custom PCB with the same functionality.

PCB Design

PCB design was definitely an experience, with a decent learning curve. I used easyeda to build my custom PCB. My first step was obtaining the schematics from the Nano 33, the MKR Mem Shield and the MKR GPS Shield. These were easy enough to find in the tech links and via Github. As they are Eagle schematic files, I downloaded the Eagle trial version to analyze in depth.
I basically copied the whole Nano 33 BLE schematic as is, including matching up the component names and numbers. I then added the GPS module (top center) and had to think about how to program via J-Link (top right):
Hoping for the best, I went ahead and sent off the design to PCBWay to be built and assembled. It was a decently smooth process, with some questions about a part name and orientation.
After a couple of weeks I got the plain PCB boards in the mail:
And a few more weeks later (6 weeks) I got the fully assembled PCB boards back!

Flashing the Arduino bootloader

I bought the J-Link EDU Mini cable to be able to program the custom board. I also installed all the J-Link drivers, etc. A similar tutorial can be found here.
You can find the Arduino 
bootloader.bin
 binary file here:
/Users/<username>/Library/Arduino15/packages/arduino/hardware/mbed/1.1.4/bootloaders/nano33ble/bootloader.bin
I ended up flashing the bootloader via nRF Connect. Fingers crossed everything would work🤞.
And it did! An Arduino Nano would pop up and connect to my MacBook when I plugged in the USB cable.
Turns out I had made a few mistakes in my reverse engineering, though.
The Nano 33 schematic has a Do Not Populate (DNP) for a pull up resistor on the RESET pin. I should have added test points for this resistor as well. I ended up hand-soldering a 4.7K.
This stopped my board from auto-resetting every second (could also adjust the UICR to disable the RESET pin).The schematic shows a 1M Ohm resistor connecting the USB shield to ground. But the Eagle file shows 330 Ohm — should be 330 Ohms.
There is tons of debate and trade-offs here.I messed up a wire with the gesture sensor, so that didn’t work. However it still measured RGB colors.
I ended up fixing these issues. To make this device portable, I added a battery connection and LiPo charging, as well as load sharing. I also added a 5V and 3.3V power rail to power external devices.
Having learned from more research, I decided to add more decoupling capacitors and various connectors (including GPIO).
I sent the new design with a more compact PCB layout to PCBWay! Currently waiting on those boards to get back 😎.

Written by duanebester | Hardware/Firmware/Software Engineer
Published by HackerNoon on 2020/08/02