To build a reliable Raspberry Pi GPS tracker, you need a Raspberry Pi 4 Model B, a u-blox NEO-6M module, and the pynmea2 Python library communicating over the hardware UART (/dev/ttyS0). Expect to spend about $65 on parts and 45 minutes on assembly. This guide bypasses the generic tutorials that ignore UART configuration and gives you the exact pinout, production-ready Python code, and the specific debugging steps for when the serial port inevitably throws a timeout error.

Hardware Spec Sheet & Parts List

The following parts list uses specific variants tested for 3.3V logic compatibility and reliable cold-start satellite acquisition. Do not use 5V-only GPS modules without a logic level shifter, or you will fry the Pi's GPIO pins.

Component Exact Variant / Model Est. Price (2026) Technical Notes
Microcontroller Raspberry Pi 4 Model B (4GB) $55.00 Target board for this guide. Pi 5 requires different UART mapping.
GPS Module u-blox NEO-6M with EEPROM $12.00 Must include the onboard AT24C32 EEPROM to save configuration.
Antenna Active Ceramic Patch (25x25mm) (Included) Requires 3.3V power from the module. Do not use passive antennas.
Wiring Female-to-Female Jumper Wires $3.00 Use 20cm length to keep signal noise low.
Storage 32GB MicroSD (Class 10 / A1) $8.00 A1 rating ensures reliable random I/O for logging.

Pin Mapping & Wiring Steps

The Raspberry Pi 4 has two UARTs: the PL011 (/dev/ttyAMA0) and the mini UART (/dev/ttyS0). By default, the PL011 is routed to the Bluetooth chip. We will use the mini UART on GPIO 14 and 15 for the GPS module.

GPIO Pin Mapping Table

NEO-6M Pin Raspberry Pi 4 Pin GPIO BCM Number Function
VCC Pin 1 N/A 3.3V Power (Do NOT use 5V Pin 2)
GND Pin 6 N/A Ground
TXD Pin 10 GPIO 15 (RXD) GPS Transmit to Pi Receive
RXD Pin 8 GPIO 14 (TXD) GPS Receive to Pi Transmit
⚠️ Critical UART Configuration Step:
Before writing any code, you must enable the serial hardware and disable the serial console.
1. Run sudo raspi-config > Interface Options > Serial Port. Select No for login shell, and Yes for serial hardware.
2. Alternatively, edit /boot/firmware/config.txt (on modern Pi OS Bookworm/Bullseye) and add enable_uart=1 at the bottom. Reboot the Pi.

Python GPS Tracking Code

This code targets the Raspberry Pi 4 Model B running Raspberry Pi OS (64-bit). It uses pyserial to read the raw NMEA stream and pynmea2 to parse the GGA (fix data) sentences. Install the dependencies first: pip install pyserial pynmea2.

import serial
import pynmea2
import time
import sys

# --- PIN & PORT DEFINITIONS ---
# Raspberry Pi 4 mini UART mapped via enable_uart=1
SERIAL_PORT = '/dev/ttyS0'
BAUD_RATE = 9600
TIMEOUT = 1.0

def init_serial():
    try:
        ser = serial.Serial(
            port=SERIAL_PORT,
            baudrate=BAUD_RATE,
            timeout=TIMEOUT,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS
        )
        print(f"Successfully opened {SERIAL_PORT} at {BAUD_RATE} baud.")
        return ser
    except serial.SerialException as e:
        print(f"FATAL: Could not open serial port. Error: {e}")
        sys.exit(1)

def parse_gps_data(ser):
    try:
        while True:
            raw_line = ser.readline()
            if not raw_line:
                continue # Timeout reached, no data
            
            # Decode bytes to string, ignore invalid characters
            line = raw_line.decode('ascii', errors='ignore').strip()
            
            # We only care about GGA sentences (Global Positioning System Fix Data)
            if line.startswith('$GPGGA') or line.startswith('$GNGGA'):
                try:
                    msg = pynmea2.parse(line)
                    if msg.gps_qual > 0:
                        print(f"[FIX] Lat: {msg.latitude} {msg.lat_dir}, "
                              f"Lon: {msg.longitude} {msg.lon_dir}, "
                              f"Alt: {msg.altitude}m, Sats: {msg.num_sats}")
                    else:
                        print("[SEARCHING] Waiting for satellite fix...")
                except pynmea2.ParseError as e:
                    print(f"Parse Error: {e} | Raw: {line}")
                    
    except KeyboardInterrupt:
        print("\nTracking stopped by user.")
    except serial.SerialException as e:
        print(f"\nSerial connection lost: {e}")
    finally:
        if ser.is_open:
            ser.close()
            print("Serial port closed.")

if __name__ == '__main__':
    serial_conn = init_serial()
    parse_gps_data(serial_conn)

Debugging: 'Serial Timeout' and 'No Fix' Errors

GPS modules are notorious for failing silently or throwing cryptic serial errors. Here are the exact error strings you will encounter and how to fix them.

Error 1: serial.serialutil.SerialException: [Errno 13] Permission denied: '/dev/ttyS0'

Ranked Causes:

  1. User Permissions: Your current user is not in the dialout group. Fix: Run sudo usermod -a -G dialout $USER and reboot.
  2. Serial Console Conflict: The OS is still using the port for the login shell. Fix: Check sudo systemctl status serial-getty@ttyS0.service. If active, disable it via raspi-config.
  3. Wrong Port: You are using /dev/ttyAMA0 instead of /dev/ttyS0. On the Pi 4, ttyAMA0 is tied to Bluetooth unless explicitly disabled in /boot/firmware/config.txt using dtoverlay=disable-bt.

Error 2: pynmea2.ParseError: could not parse '$GPGGA,...' or No Output

Ranked Causes:

  1. No Satellite Fix (Cold Start): The NEO-6M takes 1 to 15 minutes to achieve a first fix from a cold start. The module LED will blink while searching and turn solid once locked. You must be outdoors or near an open window.
  2. TX/RX Swapped: The most common wiring mistake. TX on the GPS must go to RX on the Pi (GPIO 15). If swapped, the Pi hears nothing.
  3. Baud Rate Mismatch: The module was previously configured to 115200 baud and saved to its EEPROM. Fix: Change BAUD_RATE = 115200 in the Python script, or use the u-center software to factory reset the module.
💡 The First Three Things to Check When It Fails:
  1. Run cat /dev/ttyS0 in the terminal. If you see raw NMEA text scrolling, your wiring and UART config are perfect; the issue is in your Python code.
  2. Verify enable_uart=1 is present in /boot/firmware/config.txt and the Pi has been rebooted.
  3. Check the GPS module LED. If it's off, you have a power issue (check VCC/GND). If it's blinking, it's searching (go outside).

Extending and Simplifying the Build

Depending on your end goal, you might want to strip this project down or scale it up for remote telemetry.

How to Simplify (The USB Route):
If you don't want to deal with GPIO UART configuration, buy a VK-162 USB GPS Module (~$18). It plugs directly into a USB port and mounts as /dev/ttyACM0. You only need to change the SERIAL_PORT variable in the Python code. This is the best route for dashboard integrations like OpenPlotter or Navit.

How to Extend (Remote Telemetry):
A standalone Pi GPS tracker is useless if you can't retrieve the data. To make it a true vehicle tracker:

  • Add LTE: Stack a Waveshare SIM7600X 4G HAT on top of the Pi. Use the pppd daemon to establish a cellular data connection and push the parsed JSON coordinates to an MQTT broker (like Adafruit IO or AWS IoT Core).
  • Add Local Logging: Import sqlite3 in the Python script and write every valid GGA fix to a local database. This ensures you don't lose track data when driving through cellular dead zones.
  • Power Management: The Pi 4 draws ~3W idle. For vehicle tracking, integrate a Waveshare UPS HAT with a 18650 Li-ion cell and an ignition-sense circuit to gracefully shut down the Pi when the car turns off, preventing battery drain.

Raspberry Pi GPS Tracker FAQ

How accurate is a Raspberry Pi GPS tracker?

Using the standard NEO-6M module with an active patch antenna, you can expect a Circular Error Probable (CEP) of 2.5 meters under open sky. This is perfectly adequate for vehicle tracking, geocaching, and speed logging. If you need centimeter-level accuracy for surveying or robotics, you must upgrade to an RTK-capable module like the u-blox ZED-F9P, which requires a base station or NTRIP caster for corrections.

Can I use a Raspberry Pi Zero 2 W for a GPS tracker?

Yes, and it is actually a better choice for battery-powered projects. The Pi Zero 2 W draws roughly 1.2W compared to the Pi 4's 3W+. The wiring and Python code are identical, but be aware that the Pi Zero 2 W has only one hardware UART (/dev/ttyAMA0), which is not shared with Bluetooth in the same way the Pi 4's is. You will map your GPS to /dev/ttyAMA0 instead of ttyS0.

Why does my NEO-6M GPS module blink but show no coordinates?

The blinking LED indicates the module is powered and actively searching for satellites, but has not yet achieved a 'lock'. This is known as a cold start. The module must download the almanac and ephemeris data from the satellites, which requires a clear, unobstructed view of the sky. Indoor testing will almost always fail. Take the setup outside, leave it stationary for 5 to 15 minutes, and wait for the LED to turn solid.

Do I need an active or passive GPS antenna for the NEO-6M?

You need an active antenna. The NEO-6M breakout boards are designed to supply 3.3V through the antenna coaxial cable to power the Low Noise Amplifier (LNA) inside the active ceramic patch. If you connect a passive antenna to a board expecting an active one, the signal-to-noise ratio will be too low to achieve a satellite fix. Always ensure your antenna has the active LNA circuit built into the base.