If you are adding location tracking to an embedded project, the default pick for a reliable, low-cost gps module for raspberry pi is the GY-GPS6MV2 (u-blox NEO-6M clone). It costs around $12, operates at 3.3V logic (safe for Pi GPIO), and outputs standard NMEA-0183 sentences over UART at 9600 baud. While premium RTK modules exist for surveying, the NEO-6M hits the sweet spot for vehicle tracking, weather stations, and marine logging where 2.5-meter accuracy is sufficient.
This guide walks through the exact hardware wiring, the Raspberry Pi OS UART configuration traps that brick 90% of first-time builds, and a production-ready Python script with robust error handling.
The Verdict: Which GPS Module for Raspberry Pi Should You Buy?
Do not buy a module until you define your accuracy requirement and interface preference. Use this decision matrix to select the exact part number for your bench.
| Use Case | Module Pick | Chipset / Interface | Price Range | Verdict |
|---|---|---|---|---|
| Basic tracking, logging, geofencing | GY-GPS6MV2 | u-blox NEO-6M / UART | $10 - $15 | DEFAULT PICK. Best value for 95% of hobbyist and IoT projects. |
| Reliable fixes, indoor lock, datalogging | Adafruit Ultimate GPS V3 | MTK3339 / UART | $35 - $45 | Choose when you need a built-in coin cell for battery-backed RTC and faster cold starts. |
| Sub-meter accuracy, robotics, drones | SparkFun GPS-RTK2 | u-blox ZED-F9P / I2C+UART | $200 - $240 | Choose only if you have an RTK base station and need centimeter-level precision. |
| Quick prototyping, avoiding GPIO config | VK-162 USB Dongle | u-blox NEO-M8N / USB | $15 - $20 | Choose to bypass UART entirely. Plugs into USB, mounts as /dev/ttyACM0. |
Hardware Spec Sheet and GPIO Pin Mapping
This build targets the Raspberry Pi 4 Model B (4GB) and the Raspberry Pi 5. Both boards use the same primary UART mapping on the 40-pin header, though the underlying OS file paths differ slightly (addressed in the next section).
Time to Complete: 45 minutes.
Parts List
- Microcontroller: Raspberry Pi 4 Model B (4GB) or Raspberry Pi 5
- GPS Module: GY-GPS6MV2 (u-blox NEO-6M breakout board)
- Antenna: Active GPS ceramic patch antenna with u.FL to SMA pigtail (usually included with the module)
- Wiring: 4x Female-to-Female silicone jumper wires (26 AWG)
Pin Mapping Table
Critical Rule: UART requires crossed lines. The Pi's Transmit (TX) must connect to the GPS's Receive (RX), and vice versa.
| Raspberry Pi 40-Pin Header | GPIO / Function | GY-GPS6MV2 Pin | Wire Color (Suggested) |
|---|---|---|---|
| Pin 1 | 3.3V Power | VCC | Red |
| Pin 6 | Ground | GND | Black |
| Pin 8 | GPIO 14 (TXD) | RX | Yellow |
| Pin 10 | GPIO 15 (RXD) | TX | Green |
OS Configuration: Freeing the UART from Bluetooth
The most common reason a gps module for raspberry pi fails is that the Pi's primary hardware UART (ttyAMA0) is hardcoded to route to the onboard Bluetooth chip, leaving the GPIO pins attached to the inferior 'mini UART' (ttyS0), which lacks a stable baud rate clock. We must force the OS to route the primary PL011 UART back to the GPIO header.
- Disable the Serial Console: Run
sudo raspi-config, navigate to Interface Options > Serial Port. Select No to 'Would you like a login shell to be accessible over serial?', and Yes to 'Would you like the serial port hardware to be enabled?'. - Edit the Boot Config: Open the configuration file.
- For Pi 4 (older OS):
sudo nano /boot/config.txt - For Pi 4 (Bookworm) / Pi 5:
sudo nano /boot/firmware/config.txt
- For Pi 4 (older OS):
- Add the Overlays: Add these exact lines to the bottom of the file:
enable_uart=1 dtoverlay=disable-bt - Disable the Bluetooth Service: Prevent the hciuart service from trying to initialize the now-disabled Bluetooth chip.
sudo systemctl disable hciuart - Reboot:
sudo reboot. Your GPIO UART is now mapped to/dev/ttyAMA0.
Python NMEA Parsing Code (Target: Pi 4B / Pi 5)
This script uses the pynmea2 library to parse raw NMEA sentences. Install dependencies via terminal before running: pip3 install pyserial pynmea2.
The code includes explicit pin/port definitions and handles the two most common failure modes: serial port lockouts and malformed NMEA checksums.
import serial
import pynmea2
import time
import sys
# --- HARDWARE DEFINITIONS ---
# Target Board: Raspberry Pi 4B / Pi 5
# Port: /dev/ttyAMA0 (Primary PL011 UART, mapped via dtoverlay=disable-bt)
# Baud: 9600 (Default for u-blox NEO-6M)
GPS_PORT = '/dev/ttyAMA0'
BAUD_RATE = 9600
TIMEOUT_SEC = 5
def init_serial_port():
try:
port = serial.Serial(GPS_PORT, BAUD_RATE, timeout=TIMEOUT_SEC)
print(f'Successfully opened {GPS_PORT} at {BAUD_RATE} baud.')
return port
except serial.SerialException as e:
print(f'FATAL: Could not open serial port. Is Bluetooth disabled in config.txt?')
print(f'Error: {e}')
sys.exit(1)
def parse_gps_stream(port):
while True:
try:
# Read a line from the serial buffer
raw_line = port.readline().decode('ascii', errors='replace').strip()
# Ignore empty reads (timeout) or non-NMEA data
if not raw_line or not raw_line.startswith('$'):
continue
# Parse the NMEA sentence
msg = pynmea2.parse(raw_line)
# We only care about GGA sentences (Global Positioning System Fix Data)
if isinstance(msg, pynmea2.types.talker.GGA):
if msg.gps_qual == 0:
print('Status: Searching for satellites...')
else:
print(f'Fix Quality: {msg.gps_qual} | Lat: {msg.latitude:.6f} | Lon: {msg.longitude:.6f} | Alt: {msg.altitude}m | Sats: {msg.num_sats}')
except pynmea2.ParseError as e:
# Triggered when NMEA checksum fails (common on noisy breadboard wires)
print(f'Parse Error (discarding sentence): {e}')
continue
except serial.SerialException as e:
print(f'Serial stream interrupted: {e}')
break
except KeyboardInterrupt:
print('\nTracking stopped by user.')
break
if __name__ == '__main__':
ser = init_serial_port()
try:
parse_gps_stream(ser)
finally:
if ser.is_open:
ser.close()
print('Serial port closed cleanly.')
Debugging: "device reports readiness to read but returned no data"
If your script crashes immediately upon execution, you will likely see this exact error string:
serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected or multiple access on port?)
This is the hallmark of a port contention issue. The OS is holding the UART open in the background. Here are the first three things to check, ranked by likelihood:
- The
serial-gettyservice is still active. Even if you disabled the login shell inraspi-config, systemd might still be running a getty process on the port.
Fix: Runsudo systemctl stop serial-getty@ttyAMA0.serviceandsudo systemctl disable serial-getty@ttyAMA0.service. (ReplacettyAMA0withttyS0if you didn't disable Bluetooth). - Bluetooth was not successfully disabled. If
dtoverlay=disable-btwas added to the wrongconfig.txtfile (e.g., you edited/boot/config.txton a Pi 5 running Bookworm, which ignores it in favor of/boot/firmware/config.txt), the OS routes the port to the BT chip, causing garbage data and lockups.
Fix: Verify your edit path. Rundmesg | grep ttyto confirmttyAMA0is mapped to the PL011 hardware, not the mini UART. - TX and RX are wired straight-through instead of crossed. If Pi TX goes to GPS TX, the Pi's transmit line pulls the GPS transmit line high, causing a hardware collision that results in the kernel dropping the port state.
Fix: Swap the yellow and green jumper wires on the GPIO header (Pin 8 and Pin 10).
Extending or Simplifying Your Build
Depending on your project timeline and end-goal, you may need to pivot from the default NEO-6M UART setup.
How to Simplify (The USB Bypass)
If you are building a quick proof-of-concept and do not want to deal with config.txt overlays, buy a VK-162 USB GPS module. It contains a u-blox chipset but interfaces via USB.
- Plug it into the Pi.
- It mounts automatically as
/dev/ttyACM0. - Change
GPS_PORT = '/dev/ttyAMA0'toGPS_PORT = '/dev/ttyACM0'in the Python script above. - No UART configuration or GPIO wiring required.
How to Extend (MQTT and Home Assistant)
To turn this standalone logger into an IoT node, integrate the paho-mqtt library. Inside the isinstance(msg, pynmea2.types.talker.GGA) block, format the coordinates as a JSON payload and publish to an MQTT broker:
import json
import paho.mqtt.client as mqtt
client = mqtt.Client('Pi_GPS_Node')
client.connect('192.168.1.100', 1883, 60)
# Inside the GGA parsing block:
payload = json.dumps({'lat': msg.latitude, 'lon': msg.longitude, 'alt': msg.altitude})
client.publish('homeassistant/sensor/pi_gps/state', payload)
This allows Home Assistant to ingest the GPS data via the MQTT integration, enabling automations like turning on your driveway lights when your vehicle crosses a geofence boundary.
For deeper hardware specifications on the u-blox 6 positioning engine, refer to the official u-blox NEO-6M datasheet. For comprehensive details on Raspberry Pi peripheral routing, consult the Raspberry Pi UART configuration documentation.






