To use a Raspberry Pi with a GPS module, you must connect the module's TX and RX pins to the Pi's hardware UART (BCM 14 and 15), disable the serial login console in raspi-config, and parse the incoming NMEA 0183 data stream using Python's pynmea2 library. While the concept is straightforward, hardware UART on Linux single-board computers is notorious for permission errors, baud rate mismatches, and buffer timeouts.
This guide targets the Raspberry Pi 4 Model B (4GB) running Raspberry Pi OS (64-bit, Bookworm). We will use a 3.3V logic-level u-blox NEO-M8N UART breakout, which provides multi-constellation tracking without the need for logic-level shifters.
Hardware Specifications & Module Selection
Before wiring anything, you need to know what your GPS module actually outputs. The market is flooded with cheap NEO-6M clones that struggle with indoor lock times, while high-end RTK modules are overkill for basic logging. Here is how the common hobbyist modules compare in 2026:
| Chipset / Module | Constellations | Cold Start Time | CEP Accuracy | Logic Level | Typical Price |
|---|---|---|---|---|---|
| u-blox NEO-6M (Generic) | GPS (L1) | ~27 seconds | 2.5m | 3.3V / 5V | $8 - $12 |
| u-blox NEO-M8N | GPS, GLONASS, Galileo | ~26 seconds | 2.0m | 3.3V | $18 - $25 |
| MTK3339 (Adafruit Ultimate) | GPS, GLONASS | ~28 seconds | 2.5m | 3.3V | $35 - $40 |
| u-blox ZED-F9P (RTK) | Multi-band L1/L2 | ~25 seconds | 0.01m (with RTK) | 3.3V | $180 - $250 |
Difficulty: Intermediate (Requires Linux CLI and basic Python)
Estimated Time: 45 minutes (hardware) + 30 minutes (software/debugging)
Required Parts List
- Raspberry Pi 4 Model B (2GB, 4GB, or 8GB) with active cooling
- u-blox NEO-M8N UART Breakout Board (ensure it is the 3.3V variant)
- Active GPS Antenna with SMA connector (usually included with the breakout)
- Female-to-Female jumper wires (22 AWG silicone recommended for flexibility)
- MicroSD card (32GB minimum) flashed with Raspberry Pi OS (64-bit)
Wiring the u-blox NEO-M8N to the Pi 4
The Raspberry Pi 4 exposes its primary hardware UART on the 40-pin GPIO header. By default, this is mapped to /dev/serial0. The most critical rule of UART wiring is that transmit (TX) must connect to receive (RX), and vice versa. Never connect TX to TX.
| Pi 4 GPIO (BCM) | Pi 4 Physical Pin | Function | NEO-M8N Pin | Wire Color (Suggested) |
|---|---|---|---|---|
| BCM 14 (TXD) | Pin 8 | Pi Transmit | RX / RXD | Yellow |
| BCM 15 (RXD) | Pin 10 | Pi Receive | TX / TXD | Orange |
| 3.3V Power | Pin 1 or 17 | VCC (3.3V) | VCC / 3V3 | Red |
| Ground | Pin 6, 9, 14, etc. | GND | GND | Black |
Step-by-Step Physical Connection
- Power down: Completely shut down the Pi and disconnect the USB-C power supply.
- Attach antenna: Screw the active GPS antenna onto the SMA connector on the NEO-M8N breakout. Do this before applying power to avoid damaging the RF front-end.
- Connect power and ground: Route 3.3V and GND from the Pi header to the breakout.
- Cross the data lines: Connect Pi Pin 8 (TX) to GPS RX, and Pi Pin 10 (RX) to GPS TX.
- Verify: Double-check the TX/RX cross. A swapped pair won't damage the hardware, but it will yield zero data.
Software Setup & Python NMEA Parsing
Out of the box, the Raspberry Pi routes kernel boot logs and a login shell to the hardware UART. This will corrupt your GPS data stream. You must disable the serial console while keeping the serial hardware enabled. For deeper background on Pi UART mapping, refer to the official Raspberry Pi UART configuration documentation.
1. Configure the Serial Port
Open a terminal and run:
sudo raspi-config
Navigate to Interface Options > Serial Port.
- When asked "Would you like a login shell to be accessible over serial?", select No.
- When asked "Would you like the serial port hardware to be enabled?", select Yes.
Reboot the Pi.
2. Install Python Dependencies
We use pyserial for port access and pynmea2 to parse the NMEA 0183 sentences. See the pynmea2 documentation for advanced sentence types.
sudo apt update
sudo apt install python3-pip python3-venv -y
python3 -m venv gps_env
source gps_env/bin/activate
pip install pyserial pynmea2
3. Complete Python Parsing Script
This script handles the most common failure modes: port permission errors, mid-sentence buffer reads, and timeout empty-reads.
import serial
import pynmea2
import time
import sys
# Target: Raspberry Pi 4 Model B (Hardware UART)
SERIAL_PORT = '/dev/serial0'
BAUD_RATE = 9600 # Default for u-blox and MTK modules
def main():
try:
# timeout=1 prevents readline() from blocking forever if the GPS stops sending
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
except serial.SerialException as e:
print(f"Fatal Serial Error: {e}")
sys.exit(1)
print(f"Listening on {SERIAL_PORT} at {BAUD_RATE} baud...")
try:
while True:
# Read a line from the serial buffer
raw_line = ser.readline()
# Handle timeout (empty byte string)
if not raw_line:
continue
# Decode bytes to string, ignoring malformed characters
try:
line = raw_line.decode('ascii', errors='replace').strip()
except UnicodeDecodeError:
continue
# pynmea2 requires sentences to start with '$'
if not line.startswith('$'):
continue
try:
msg = pynmea2.parse(line)
# We only care about GGA (Global Positioning System Fix Data)
if isinstance(msg, pynmea2.types.talker.GGA):
if msg.latitude != 0.0 and msg.longitude != 0.0:
print(f"Fix: {msg.latitude:0.6f}, {msg.longitude:0.6f} | "
f"Alt: {msg.altitude}m | Sats: {msg.num_sats}")
else:
print("Waiting for GPS fix...")
except pynmea2.ParseError as e:
# Catch bad checksums or truncated sentences
print(f"Parse error: {e}")
except KeyboardInterrupt:
print("\nExiting gracefully.")
finally:
ser.close()
if __name__ == '__main__':
main()
Debugging Common GPS & UART Failures
When working with hardware UART on Linux, things rarely work perfectly on the first boot. Here are the exact error strings you will encounter and how to fix them.
The "First Three Things" to Check
Before diving into complex Linux device tree overlays, verify these three physical and configuration basics:
- raspi-config state: Did you actually disable the serial console? Run
cat /boot/firmware/cmdline.txt. If you seeconsole=serial0,115200, the console is still hijacking the port. - TX/RX Swap: Did you connect Pi TX to GPS TX? They must be crossed. Swap the yellow and orange wires.
- Baud Rate Mismatch: Is your code set to 115200 while the module defaults to 9600? Check the module datasheet. u-blox and Adafruit MTK modules default to 9600 baud.
Ranked Error Causes & Fixes
Error 1: serial.serialutil.SerialException: [Errno 13] could not open port /dev/serial0: [Errno 13] Permission denied: '/dev/serial0'
- Cause A (Most Likely): Your user account is not in the
dialoutgroup. Fix:sudo usermod -a -G dialout $USER, then log out and log back in. - Cause B: The serial console is still enabled in
cmdline.txtand holding the port lock. Fix: Re-runraspi-configand disable it.
Error 2: serial.serialutil.SerialException: [Errno 2] could not open port /dev/ttyAMA0: [Errno 2] No such file or directory: '/dev/ttyAMA0'
- Cause: You hardcoded
/dev/ttyAMA0in your Python script, but on the Pi 4, the primary UART is aliased to/dev/serial0. The Pi 5 routesttyAMA0to the Bluetooth chip by default. Fix: Always use the/dev/serial0symlink in your code.
Error 3: pynmea2.ParseError: could not parse: b'$GPGGA,......*XX' or checksum does not match
- Cause A: Baud rate mismatch. Reading 9600 baud data at 115200 baud results in garbage characters that fail the NMEA checksum. Fix: Set
BAUD_RATE = 9600. - Cause B: Buffer overrun. The Pi's UART buffer is filling up faster than Python is reading it, causing sentence truncation. Fix: Ensure your loop doesn't have heavy blocking operations (like writing to a slow SD card) inside the read loop.
Extending and Simplifying the Build
Depending on your end goal, you might want to strip this project down to its bare essentials or scale it up into a precision timing server.
How to Simplify: The USB GPS Dongle Route
If you are building a mobile tracker (like a dashcam or marine logger) and don't want to deal with GPIO wiring, raspi-config, or voltage dividers, buy a VK-162 or VK-172 USB GPS module ($15-$20). These plug directly into a USB-A port and register as a standard /dev/ttyACM0 or /dev/ttyUSB0 serial device. You bypass the hardware UART configuration entirely, and the Python code above works with zero modifications other than changing the SERIAL_PORT variable.
How to Extend: Sub-Millisecond NTP Timekeeping
Standard NMEA sentences only provide time resolution down to the hundredth of a second, and serial latency adds unpredictable jitter. If you want to turn your Pi into a Stratum-1 NTP server for your local network, you need the PPS (Pulse Per Second) pin found on higher-end breakouts like the Adafruit Ultimate GPS.
- Wire the GPS PPS pin to GPIO 4 (Physical Pin 7) on the Pi.
- Add
dtoverlay=pps-gpio,gpiopin=4to your/boot/firmware/config.txt. - Install
pps-toolsand configurechronyorntpdto read from/dev/pps0.
This combination locks the Pi's clock to the atomic clocks aboard the GPS satellites, yielding timing accuracy within 1 to 5 microseconds—far beyond what the NMEA serial stream alone can provide. For more on precision timing, the Adafruit GPS on Pi tutorial provides an excellent walkthrough of the PPS overlay setup.






