GPS Modules for Maker Projects: NEO-6M/NEO-M8N Wiring, NMEA Parsing, and Logging with ESP32
A GPS module is one of the cheapest ways to add absolute position, precise timekeeping, and speed data to a project, and it shows up constantly once you start looking: asset trackers, weather balloons, drone telemetry, geotagged data loggers, and any project that wants to know where and when something happened without relying on WiFi or cell coverage. The u-blox NEO-6M and NEO-M8N modules are the de facto standard for hobbyist GPS — cheap, well documented, and simple UART devices — but getting clean fixes and parsed data out of one trips up a lot of first-time users. This guide covers wiring, the NMEA sentence format you'll actually be parsing, and a working ESP32 example.
NEO-6M vs NEO-M8N: Which One to Buy
ModuleGNSS SystemsCold Start Fix TimeUpdate RateNotes NEO-6MGPS only~27s typicalUp to 5HzCheapest, widely cloned, fine for most hobby projects NEO-M8NGPS + GLONASS + Galileo + BeiDou (configurable)~26s typical, faster reacquisitionUp to 10Hz (fewer constellations) / 18Hz maxBetter accuracy and faster fix in weak-signal areas (urban canyons, under tree cover)For anything moving quickly (drones, vehicles) or used in a location with poor sky visibility, the M8N's multi-constellation support is worth the small price difference. For a stationary data logger or weather balloon with clear sky view, the NEO-6M is perfectly adequate and is what most "GPS module" listings on Amazon and AliExpress actually ship.
Wiring
Both modules communicate over UART (serial) at 3.3V logic and typically break out four pins: VCC, GND, TX, and RX. Most breakout boards accept 3.3V–5V on VCC (check your specific board — some have an onboard regulator, some don't) but the TX/RX logic level is 3.3V, so if you're wiring to a 5V Arduino Uno you generally need a level shifter or voltage divider on the module's RX line; ESP32 boards run natively at 3.3V logic so this isn't a concern there.
GPS Module PinESP32 Pin (example)Notes VCC3.3VCheck board's actual input voltage range first GNDGNDCommon ground required TXRX2 (GPIO16, or any free UART-capable pin)Module's TX goes to your microcontroller's RX RXTX2 (GPIO17, or any free UART-capable pin)Only needed if you're sending configuration commands to the moduleESP32 boards have multiple hardware UARTs (typically UART0, used for USB/serial debug, plus UART1 and UART2 free for peripherals), so wiring GPS to a second hardware UART lets you keep USB serial free for debug output — use Serial2 in Arduino code and avoid SoftwareSerial, which struggles to keep up reliably above 9600 baud on fast microcontrollers. Most NEO-6M/M8N modules default to 9600 baud, which is comfortably within reach for either hardware or software serial, but hardware UART is still the more reliable choice if you have a pin free.
Antenna and Getting a Fix
GPS modules need a clear view of open sky to get a fix — indoors, near a window at best, is usually the practical limit for a first test, and even then expect it to take longer than outdoors. Most breakout boards ship with a small ceramic patch antenna soldered directly to the board or a passive external antenna on a short cable; for anything mounted inside an enclosure (a 3D printed case, a metal project box), an external active antenna with a U.FL or SMA connector routed outside the enclosure will get a fix far more reliably than a patch antenna buried inside a case. First fix from cold start ("cold start" meaning no prior almanac/ephemeris data) commonly takes 30 seconds to a couple of minutes; subsequent fixes after a short power-off are much faster since the module retains almanac data if it has a backup capacitor or coin cell.
NMEA Sentences: What You're Actually Parsing
GPS modules output plain ASCII text over UART in the NMEA 0183 format — a stream of comma-separated sentences, each starting with a sentence identifier. The two you'll use in almost every project:
SentenceContainsTypical Use GGAFix quality, latitude/longitude, altitude, satellite count, HDOPPrimary sentence for position logging RMCLatitude/longitude, speed over ground, course, date, time, fix statusPreferred when you need speed/heading or a combined date+time GSVSatellites in view and their signal strengthDiagnosing weak-signal or no-fix problems GSAFix type (2D/3D), PDOP/HDOP/VDOP dilution of precisionJudging fix quality/accuracyExample raw GGA sentence: $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 — fields are UTC time, latitude, N/S, longitude, E/W, fix quality (0 = no fix, 1 = GPS fix, 2 = DGPS fix), satellite count, HDOP, altitude, and a checksum. Coordinates are in a raw "ddmm.mmmm" format (degrees and decimal minutes), not decimal degrees, so they need conversion before you can plot them directly on most mapping libraries.
Parsing with TinyGPS++ (Arduino/ESP32)
Writing an NMEA parser from scratch is a solved problem — use the TinyGPS++ library, which handles sentence parsing, checksum validation, and coordinate conversion for you:
#include <TinyGPSPlus.h> HardwareSerial gpsSerial(2); // UART2 TinyGPSPlus gps; void setup() { Serial.begin(115200); gpsSerial.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17 } void loop() { while (gpsSerial.available() > 0) { gps.encode(gpsSerial.read()); } if (gps.location.isUpdated()) { Serial.print("Lat: "); Serial.println(gps.location.lat(), 6); Serial.print("Lng: "); Serial.println(gps.location.lng(), 6); Serial.print("Satellites: "); Serial.println(gps.satellites.value()); Serial.print("HDOP: "); Serial.println(gps.hdop.hdop()); } }TinyGPS++ exposes ready-to-use fields for latitude/longitude in decimal degrees, altitude, speed, course, satellite count, HDOP, and a full date/time object — no manual sentence parsing required. Check gps.location.isValid() before trusting a reading, and watch gps.satellites.value() and gps.hdop.hdop() during testing: fewer than 4 satellites means no fix at all, and a high HDOP value (above roughly 5) indicates a fix that's technically valid but not very accurate.
Common Problems
SymptomLikely CauseFix No data at all from the moduleWrong baud rate, TX/RX swapped, wrong logic levelConfirm 9600 baud default, double check TX-to-RX (not TX-to-TX), verify 3.3V logic compatibility Data arrives but garbledSoftwareSerial dropping bytes, wrong baud rateSwitch to a hardware UART; verify baud rate matches the module's configured rate Never gets a fixNo sky view, antenna issue, module needs almanac download timeTest outdoors with clear sky first; wait a few minutes for cold start; check antenna connection Fix is inaccurate / jumps aroundMultipath from nearby buildings/trees, low satellite countCheck HDOP value; move to more open sky; average multiple readings if the application allows itPower Considerations for Battery Projects
GPS modules draw meaningfully more current while acquiring a fix (searching for satellites) than once locked, and cold-start acquisition can take long enough to matter for a battery-powered logger. If the project also uses ESP32 deep sleep between readings, budget extra wake time for the GPS to reacquire a fix after each sleep cycle rather than assuming an instant fix on wake — some modules support a backup power pin (VBAT/V_BCKP) wired to a small coin cell or supercapacitor specifically to preserve almanac data through a sleep cycle and dramatically shorten reacquisition time, which is worth wiring up for any project that sleeps between GPS reads.
Related Guides
- I2C vs SPI vs UART: How to Choose and Use Serial Communication Protocols
- Build a Standalone ESP32 GPS Equipment Tracker with LoRa
- How to Program Addressable LED Strips: WS2812B Patterns, Effects, and Power Design
- How to Use Sensors with Arduino and ESP32: Temperature, Distance, Load, Current, and Hall Effect
- How to Control Motors with Arduino and ESP32: Stepper, DC, and Servo Drivers
- ESP32: Setting Up for Arduino IDE
- Using Flipper Zero as a USB-to-UART Serial Bridge
- Arduino vs ESP32: Which Should You Use? A Practical Comparison