To use an Arduino with a GPS module like the u-blox NEO-6M, you connect the module's TX and RX pins to the Arduino's digital pins via SoftwareSerial, power it with 5V or 3.3V (depending on the breakout board's onboard regulator), and parse the raw NMEA-0183 sentences using the TinyGPS++ library. The most common pitfall is attempting to get a satellite lock indoors; a cold start requires a clear view of the sky and up to 15 minutes to download the almanac.
Project Overview & Parts List
This build focuses on the most widely available, cost-effective GPS setup for hobbyists. We are using the TinyGPS++ library by Mikal Hart, which is vastly superior to the legacy TinyGPS library because it handles complex NMEA sentences (like GSA and GSV) and provides built-in data validation.
Estimated Time: 30 minutes (excluding outdoor cold-start wait time)
Target Board Variant: Arduino Uno R3 (ATmega328P) or Arduino Uno R4 Minima (Renesas RA4M1)
| Component | Exact Variant / Spec | Typical Price (2026) |
|---|---|---|
| Microcontroller | Arduino Uno R3 or R4 Minima | $25.00 - $30.00 |
| GPS Module | u-blox NEO-6M Breakout with EEPROM & Ceramic Patch Antenna | $8.00 - $14.00 |
| Wiring | Dupont Male-to-Female Jumper Wires (4 required) | $3.00 |
| Software Library | TinyGPS++ (v1.0.3 or newer via Arduino Library Manager) | Free (Open Source) |
Pin Mapping & Wiring the NEO-6M
Most NEO-6M breakout boards sold on Amazon or AliExpress include an onboard 3.3V LDO voltage regulator and a 3.3V logic level shifter (or simple resistor divider) on the RX line. This means you can safely power them from the Arduino's 5V pin and connect the TX/RX directly to 5V digital pins. Always verify your specific board's silkscreen; if it says '3.3V ONLY' with no regulator, you must power it from the 3.3V pin and use a logic level converter for the Arduino's TX line.
| NEO-6M Breakout Pin | Arduino Uno Pin | Function / Notes |
|---|---|---|
| VCC | 5V | Powers the onboard LDO and LED. Draws ~45mA during acquisition. |
| GND | GND | Common ground reference. Mandatory for serial comms. |
| TX | Pin 3 | GPS Transmit -> Arduino SoftwareSerial RX. |
| RX | Pin 4 | GPS Receive -> Arduino SoftwareSerial TX. (Used for sending UBX config commands). |
Complete Arduino GPS Code (TinyGPS++)
The following code is fully compilable and includes error handling for stale data and invalid fixes. It initializes a SoftwareSerial port at 9600 baud (the factory default for the NEO-6M) and parses the incoming NMEA streams. If the GPS loses lock, the code explicitly flags the data as invalid rather than printing the last known ghost coordinates.
#include <SoftwareSerial.h>
#include <TinyGPSPlus.h>
// Pin definitions for SoftwareSerial
const int RXPin = 3;
const int TXPin = 4;
const uint32_t GPSBaud = 9600; // NEO-6M default baud rate
// The TinyGPSPlus object
TinyGPSPlus gps;
// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);
void setup() {
// Initialize hardware serial for debugging
Serial.begin(115200);
while (!Serial) {
; // Wait for serial port to connect (needed for native USB boards like Leonardo, harmless on Uno)
}
// Initialize software serial for GPS
ss.begin(GPSBaud);
Serial.println(F("Arduino NEO-6M GPS Module Initialized"));
Serial.println(F("Waiting for satellite lock... (Go outdoors!)"));
Serial.println(F("-------------------------------------------"));
}
void loop() {
// Parse data as it becomes available
while (ss.available() > 0) {
if (gps.encode(ss.read())) {
displayGpsData();
}
}
// Error handling: Check if no data has been received for 5 seconds
if (millis() > 5000 && gps.charsProcessed() < 10) {
Serial.println(F("ERROR: No GPS data received. Check wiring and baud rate."));
delay(2000);
}
}
void displayGpsData() {
// Check if location data is valid and not stale (age < 2000ms)
if (gps.location.isValid() && gps.location.age() < 2000) {
Serial.print(F("Latitude: "));
Serial.println(gps.location.lat(), 6);
Serial.print(F("Longitude: "));
Serial.println(gps.location.lng(), 6);
Serial.print(F("Altitude: "));
Serial.print(gps.altitude.meters());
Serial.println(F(" m"));
Serial.print(F("Satellites: "));
Serial.println(gps.satellites.value());
Serial.println(F("-------------------------------------------"));
} else {
Serial.println(F("Status: Searching for satellites... No valid fix."));
}
}
Debugging: First Three Things to Check When It Fails
GPS modules are notorious for confusing beginners because the hardware is usually fine, but the environmental or configuration variables are wrong. If your Serial Monitor isn't showing valid coordinates, check these three things in order.
1. Serial Monitor Shows 'Status: Searching...' or '******' (No Satellite Lock)
The Cause: You are testing indoors, under heavy tree cover, or near tall buildings. The NEO-6M requires a line-of-sight to at least 4 satellites to calculate a 3D fix. A 'cold start' (first time powered on, or after being moved hundreds of miles) requires downloading the almanac from the satellites, which can take up to 15 minutes.
The Fix: Take the entire rig outside. Ensure the ceramic patch antenna is facing straight up at the sky. Do not put it in a metal enclosure; RF signals cannot penetrate metal. Wait at least 5 to 10 minutes without moving it.
2. Serial Monitor Shows Garbled Text like 'Ã' or Random Question Marks
The Cause: Baud rate mismatch. The u-blox NEO-6M defaults to 9600 baud. If your SoftwareSerial is initialized at 115200, or if your Serial Monitor (hardware serial) is set to 9600 instead of 115200, the bytes will be misinterpreted.
The Fix: Verify ss.begin(9600); in the code matches the GPS hardware. Verify the dropdown in the bottom right corner of the Arduino IDE Serial Monitor is set to 115200 baud (which matches the Serial.begin(115200); debug line).
3. Compilation Error: 'fatal error: TinyGPSPlus.h: No such file or directory'
The Cause: The library is either not installed, or the include statement is misspelled. Many tutorials reference the ancient TinyGPS.h library, which uses different syntax and lacks modern validation.
The Fix: Open the Arduino IDE, go to Sketch -> Include Library -> Manage Libraries. Search for 'TinyGPSPlus' by Mikal Hart and install it. Ensure your code uses #include <TinyGPSPlus.h> (note the capitalization and the 'Plus').
Extending and Simplifying the Build
Once you have a stable lock, you will likely want to move beyond printing to the Serial Monitor. Here is how to adapt the build for real-world applications.
How to Simplify (Hardware UART): SoftwareSerial is CPU-intensive and can drop bytes at baud rates above 9600, especially if you have other interrupts running (like reading encoders or driving servos). To simplify and stabilize the data stream, upgrade to an Arduino Mega 2560 or an ESP32. These boards feature multiple hardware UARTs. You would wire the GPS to Serial1 (Pins 18/19 on the Mega) and change the code to use Serial1.begin(9600), freeing the main CPU from bit-banging the serial protocol.
How to Extend (Data Logging & Displays): To make a standalone tracker, add a MicroSD card breakout board using the SPI bus (Pins 10-13 on the Uno) and log the NMEA sentences directly to a .CSV file. Alternatively, add a 0.96-inch I2C OLED display (Pins A4/A5) to show latitude, longitude, and speed in real-time. If you need to maintain accurate time when the GPS loses lock (like in a tunnel), add a DS3231 Real Time Clock (RTC) module and sync it to the GPS time whenever a valid fix is achieved.
FAQ: Using Arduino with GPS Modules
How to use Arduino with a GPS module to get just latitude and longitude?
If you only need latitude and longitude and want to save memory, you can strip down the TinyGPS++ code provided above. Remove the altitude and satellite print statements. Ensure you keep the gps.location.isValid() check. If you are severely constrained on flash memory (e.g., using an ATtiny85), consider using the TinyGPS (legacy) library or parsing the raw $GPRMC NMEA string manually using strtok(), though this is highly prone to edge-case parsing errors.
Why is my Arduino GPS module blinking but not returning coordinates?
The blinking LED on the NEO-6M breakout board (usually labeled 'PPS' or '1PPS') indicates that the module is powered and outputting data sentences, not necessarily that it has a satellite lock. On some boards, the LED blinks at 1Hz when a lock is achieved, and stays solid or blinks erratically when searching. However, the most common reason for 'blinking but no coordinates' is that the module is indoors. The ceramic patch antenna is highly directional and requires an unobstructed view of the sky hemisphere to resolve the trilateration math required for a fix.
Can I use Arduino with a GPS module indoors or underground?
No, standard NEO-6M modules with passive ceramic patch antennas will not work indoors, underground, or in dense urban canyons. GPS L1 band signals (1.57542 GHz) are extremely weak by the time they reach Earth (around -130 dBm) and cannot penetrate concrete, metal roofs, or earth. If your project requires indoor positioning, you must abandon GPS and look into UWB (Ultra-Wideband) modules like the Decawave DW1000, WiFi RSSI fingerprinting, or BLE beacons.
How do I change the default 9600 baud rate on a NEO-6M GPS module?
You can change the baud rate by sending a specific UBX binary configuration packet from the Arduino's TX pin to the GPS module's RX pin. However, unless your breakout board has an onboard EEPROM (like the ones with a small 8-pin chip near the antenna connector), the NEO-6M will revert to 9600 baud every time it loses power. If your board has the EEPROM, you can use the Arduino SoftwareSerial library to send the UBX-CFG-PRT command at 9600 baud to change it to 115200, followed by the UBX-CFG-CFG command to save it to non-volatile memory. For 99% of hobbyist projects, leaving it at 9600 baud is the most reliable path.






