The most reliable baseline GPS tracking device with Arduino in 2026 pairs the Arduino Nano Every with a u-blox NEO-M8N breakout. By leveraging the Nano Every’s secondary hardware UART (Serial1), you eliminate the dropped NMEA sentences and checksum failures that plague older SoftwareSerial implementations on the classic ATmega328P Nano. This guide gives you the exact parts, pinout, and compilable code to get a fix, log the data, and debug the inevitable wiring faults.

Project Spec Sheet
Difficulty: Intermediate (Requires basic UART debugging and Li-ion power management)
Time to Build: 90 minutes
Target Board: Arduino Nano Every (ATmega4809, 5V logic with 3.3V I/O tolerance on RX/TX)
Total BOM Cost: ~$42 USD

The GPS Module Decision Matrix

Do not buy the $6 "NEO-6M" modules found on generic marketplaces. They are almost universally counterfeit, lack proper RF shielding, and fail to lock indoors or in urban canyons. Use this decision path to select your receiver:

ModuleAccuracyUrban Canyon PerformanceApprox. CostVerdict
Generic NEO-6M Clone5m - 15mPoor (frequent dropouts)$6 - $9Avoid. High failure rate.
u-blox NEO-M8N2.5m (CEP)Excellent (72-ch, multi-GNSS)$22 - $28Best all-rounder.
u-blox ZED-F9P (RTK)0.01m + RTKExceptional$180+Overkill for basic tracking.

Decision Path:
• If you need centimeter-level RTK accuracy for rover/tractor guidance → Buy the ZED-F9P.
• If you are building a high-altitude balloon (above 50km) → Buy the SAM-M8Q (high-altitude mode enabled).
• If you are building a standard vehicle, pet, or asset tracker → Default Pick: SparkFun GPS Breakout - NEO-M8N (Part #GPS-15193).

Hardware Spec Sheet & Pin Mapping

The Arduino Nano Every runs at 5V, but its RX/TX pins are connected to the ATmega4809's USART1, which tolerates 3.3V logic from the GPS module without a level shifter. We are using hardware serial to guarantee zero buffer overruns at 9600 baud.

Bill of Materials (BOM)

  • Microcontroller: Arduino Nano Every (with headers soldered) - $11.50
  • GPS Module: SparkFun NEO-M8N Breakout (Qwiic/UART) - $24.95
  • Antenna: Active ceramic patch antenna with SMA connector (usually included with breakout) - $5.00
  • Power: Adafruit PowerBoost 1000C + 18650 Li-ion cell (3.7V) - $20.00
  • Wiring: 24 AWG silicone stranded wire, heat shrink

Pin Mapping Table

NEO-M8N Breakout PinArduino Nano Every PinNotes
TX (Transmit)D0 (RX / Serial1)GPS sends data to MCU. 3.3V logic is safe for Nano Every RX.
RX (Receive)D1 (TX / Serial1)MCU sends UBX commands to GPS. 5V to 3.3V; M8N RX is 5V tolerant.
VIN5V PinUse the Nano's 5V rail; the breakout's onboard LDO drops it to 3.3V.
GNDGNDCommon ground is mandatory for UART reference.
Callout Tip: The Backup Battery
The NEO-M8N breakout includes a small MS621FE rechargeable lithium cell. This powers the real-time clock (RTC) and retains the satellite ephemeris data when main power is cut. If your Time-To-First-Fix (TTFF) takes 15+ minutes every time you power on, this battery is likely dead or missing, forcing a cold start every time.

Step-by-Step Wiring & Assembly

  1. Prepare the Power Rail: Solder the Adafruit PowerBoost 1000C 5V and GND outputs to the Nano Every's 5V and GND pins. Do not use the VIN pin on the Nano for the PowerBoost; bypassing the onboard regulator prevents thermal throttling.
  2. Connect the UART Lines: Wire the GPS TX to Nano Every D0 (RX), and GPS RX to Nano Every D1 (TX). Never cross TX to TX.
  3. Attach the Antenna: Screw the active SMA antenna onto the breakout. Ensure the copper ground plane of the antenna is facing the sky. Active antennas require 3.3V power from the module, which the breakout supplies automatically.
  4. Verify Voltages: Before plugging in the USB, use a multimeter to check the voltage between the GPS breakout's 3V3 out pin and GND. It must read between 3.25V and 3.35V. If it reads 5V, the onboard LDO is blown.

Complete Arduino Tracking Code (Nano Every + M8N)

This code targets the Arduino Nano Every. It uses the industry-standard SparkFun u-blox GNSS Arduino Library. Install this via the Arduino Library Manager before compiling.

#include <SparkFun_u-blox_GNSS_Arduino_Library.h>

// Pin Definitions for Arduino Nano Every Hardware Serial1
#define GPS_RX_PIN 0
#define GPS_TX_PIN 1
#define GPS_BAUD   9600

// Instantiate the u-blox object
SFE_UBLOX_GNSS myGNSS;

// Tracking state variables
float latitude = 0.0;
float longitude = 0.0;
float altitude = 0.0;
uint8_t satellites = 0;

void setup() {
  // Initialize USB Serial for debugging
  Serial.begin(115200);
  while (!Serial) { delay(10); } // Wait for serial port to connect
  
  Serial.println(F("Arduino GPS Tracker - Nano Every + NEO-M8N"));
  
  // Initialize Hardware Serial1 for GPS communication
  Serial1.begin(GPS_BAUD);
  
  // Attempt to connect to the u-blox module with error handling
  if (myGNSS.begin(Serial1) == false) {
    Serial.println(F("ERROR: u-blox GNSS not detected at 9600 baud on Serial1."));
    Serial.println(F("Check TX/RX wiring, ensure VIN is 5V, and verify module is genuine."));
    // Halt execution to prevent silent failures in the field
    while (1) { 
      // Blink built-in LED rapidly to indicate hardware fault
      digitalWrite(LED_BUILTIN, HIGH); delay(100);
      digitalWrite(LED_BUILTIN, LOW);  delay(100);
    }
  }
  
  Serial.println(F("GNSS module connected successfully."));
  
  // Configure module for optimal tracking
  myGNSS.setNavigationFrequency(1); // 1 Hz update rate (use 5Hz or 10Hz for fast-moving drones)
  myGNSS.saveConfigSelective(VAL_CFG_SUBSEC_NAVCONF); // Save to flash/EEPROM
}

void loop() {
  // Check if new NMEA/UBX data is available
  if (myGNSS.getPVT()) {
    
    // Extract data with explicit casting to prevent integer truncation
    latitude  = (float)myGNSS.getLatitude()  / 10000000.0;
    longitude = (float)myGNSS.getLongitude() / 10000000.0;
    altitude  = (float)myGNSS.getAltitudeMSL() / 1000.0; // Convert mm to meters
    satellites = myGNSS.getSIV(); // Satellites in View
    
    // Only print if we have a valid 3D fix (Fix Type 3)
    if (myGNSS.getFixType() == 3) {
      Serial.print(F("FIX | Lat: ")); Serial.print(latitude, 6);
      Serial.print(F(" Lon: ")); Serial.print(longitude, 6);
      Serial.print(F(" Alt: ")); Serial.print(altitude, 1);
      Serial.print(F("m | Sats: ")); Serial.println(satellites);
    } else {
      Serial.print(F("SEARCHING... Sats: ")); Serial.println(satellites);
    }
  }
  
  delay(250); // Pace the loop to avoid flooding the I2C/UART buffer
}

Debugging: The First Three Things to Check When It Fails

GPS modules are notorious for silent failures. If your serial monitor isn't outputting clean coordinates, follow this ranked troubleshooting path.

1. Symptom: Serial Monitor prints "ERROR: u-blox GNSS not detected..."

Exact Error String: ERROR: u-blox GNSS not detected at 9600 baud on Serial1.

  • Cause A (Most Likely): TX/RX lines are swapped. The GPS TX must go to the Arduino RX (D0). Swap them and reset.
  • Cause B: The module is stuck in I2C mode. Some breakouts default to I2C (DDC) on boot. Send a UBX-CFG-PRT command via I2C to force UART, or use the SparkFun library's I2C fallback initialization: myGNSS.begin(Wire).
  • Cause C: Counterfeit module. Fake chips do not respond to the proprietary UBX protocol handshake that the SparkFun library initiates. Measure the current draw; a real M8N draws ~45mA during acquisition. Fakes often draw <15mA.

2. Symptom: Gibberish Characters or "Checksum Failed" in Raw NMEA

Exact Error String: $GPGGA,...*XX [CHECKSUM FAIL] or random ASCII garbage.

  • Cause A: Baud rate mismatch. The NEO-M8N defaults to 9600 baud. If your code initializes Serial1.begin(115200), you will read gibberish. Ensure both match.
  • Cause B: If you are using an older Arduino Uno with SoftwareSerial, it cannot reliably read 9600 baud while simultaneously printing to the hardware serial monitor. Fix: Switch to the Nano Every's hardware Serial1 as shown in the code above.

3. Symptom: Module Connects, but "SEARCHING..." Never Changes to "FIX"

Observation: Sats count stays at 0 or 1, even after 20 minutes.

  • Cause A: You are testing indoors. GPS L1 signals (1575.42 MHz) are completely blocked by standard roof trusses and HVAC ducting. Take the device to a window or outdoors for the first cold start.
  • Cause B: Passive vs. Active Antenna mismatch. If your breakout board does not supply 3.3V to the antenna SMA center pin, and you are using an active antenna, the LNA (Low Noise Amplifier) in the antenna is unpowered. Check the breakout's jumper pads to ensure VCC_RF is bridged.

Extending or Simplifying the Build

Once you have a stable local serial output, you need to decide how to handle the data in the field.

How to Simplify: Offline SD Card Logging

If you don't need real-time tracking, drop the cellular uplink. Add an Adafruit MicroSD Breakout wired to the Nano Every's SPI pins (D11, D12, D13, D10 for CS). In the loop(), append the latitude and longitude floats to a CSV file every 5 seconds. This drops power consumption to ~65mA total, giving you roughly 40 hours of tracking on a single 18650 cell.

How to Extend: Live Cellular Uplink (MQTT)

To view the tracker on a map in real-time, you must add a cellular modem. The SIM7600G-H 4G HAT is the current standard for global coverage. Warning: The SIM7600 requires 2A peak current bursts during tower handshakes. The Nano Every's onboard regulator cannot supply this. You must power the SIM7600 directly from a dedicated 4A buck converter tied to your 18650 battery, sharing only the GND and UART TX/RX lines with the Nano Every. Use the u-blox M8N documentation to configure the GPS for 1Hz updates, format the payload as JSON, and push it via MQTT over the SIM7600's TCP/IP stack.