Component · Sensor

NEO-6M GPS Module

A NEO-6M gives an outdoor robot absolute position over a serial port. How to read NMEA, why HDOP matters more than the accuracy figure, and what it cannot do.

What it is

A GNSS receiver: a chip that listens for the timing signals broadcast by a constellation of satellites and solves for its own position from the differences between their arrival times. The NEO-6M is the module most hobby robots meet first — a u-blox receiver, a ceramic patch antenna, and a serial port.

It is the only sensor on a robot that reports an absolute position. Everything else on this site — encoders, an IMU, a reflectance array — measures a change or a local feature. GPS is what stops odometry drift from being unbounded, and it is why an outdoor rover can be given a destination a kilometre away.

How it works

You do not talk to it. It talks to you, at 9600 baud, in NMEA 0183 sentences — one comma-separated line per message, several times a second.

Two of them carry everything a robot needs:

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,...   fix quality, sats, HDOP
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,...      validity, position, speed, course

Read the fix, then gate it. The status field in RMC is A for active or V for void, and the fix quality in GGA is 0 when there is no solution — in both cases the sentence still carries plausible-looking coordinates. The HDOP field is the one that catches the subtler failure: it describes satellite geometry, and a bad geometry yields a confident answer tens of metres out.

Latitude arrives as degrees-and-decimal-minutes (4807.038 is 48° 07.038′), which is not degrees. Convert once, then convert again into metres relative to a fixed origin — the arithmetic is here — and everything downstream works in units a controller understands.

When to use it

Use one whenever the robot works outdoors over distances longer than odometry survives: a waypoint rover, a lawnmower, a survey platform, an agricultural robot. Roughly, past thirty metres of driving, dead reckoning alone has accumulated more error than a GPS fix carries.

Do not reach for it indoors, at all, ever. And do not reach for it for precision: 2.5 m is a long way for a robot that is 30 cm wide. GPS tells you which part of the field you are in; a wheel encoder tells you where you are within a metre of that. Use both, fused.

The upgrade path, in order of value: a higher update rate first (5 or 10 Hz removes most of the staleness), then a multi-constellation module (GPS plus GLONASS or Galileo improves geometry and therefore HDOP), and only then RTK — which reaches centimetres and needs a base station, a data link, and a different budget.

Common gotchas

  • Cold start is minutes, not seconds. With no almanac the receiver has to download one from the satellites at 50 bits per second. A robot that appears dead on first power-up outdoors is usually just waiting.
  • It has no heading. Position only. Pair it with a magnetometer or a gyro; course over ground is meaningless below about 1 m/s.
  • 5 V on RX kills 3.3 V modules. Check whether your breakout level-shifts. Module TX into a 5 V board is fine.
  • The antenna needs sky, not a view. Metal above it — including your own chassis plate — costs you satellites. Mount it on top, flat, facing up.
  • Never follow raw fixes. Dead reckon between them and correct on each one; following the fixes directly costs an order of magnitude in tracking error and four times the steering.
  • Six decimal places is 11 cm — and more than a float holds. Convert to metres relative to a mission origin rather than doing arithmetic on raw degrees.

Pinout and wiring

Pin Name What it does
1 VCC 3.3–5 V. Check whether your breakout regulates — many NEO-6M boards do
2 RX Module receives configuration. 3.3 V logic on a bare module
3 TX Module transmits NMEA. Safe into a 5 V input
4 GND Ground
5 PPS One pulse per second, aligned to GPS time. Ignored by most projects
GPS Arduino Uno Note
VCC 5 V If your board has a regulator; 3.3 V if it does not
GND GND
TX Pin 4 (SoftwareSerial RX) Module talks, board listens
RX Pin 3 via divider Only needed if you configure the module

You can leave RX unconnected entirely. A GPS module in its default state streams NMEA at 9600 baud without being asked, and for a robot that reads position and nothing else, that is all you need. Connect RX only when you want to raise the update rate or silence unused sentences.

Reading it without a library

#include <SoftwareSerial.h>

SoftwareSerial gps(4, 3);          // RX from module TX, TX to module RX

char line[100];
uint8_t len = 0;

void setup() {
  Serial.begin(115200);
  gps.begin(9600);
}

void loop() {
  while (gps.available()) {
    char c = gps.read();
    if (c == '\n') {
      line[len] = '\0';
      if (strncmp(line, "$GPRMC", 6) == 0) handleRmc(line);
      len = 0;
    } else if (len < sizeof(line) - 1) {
      line[len++] = c;
    }
  }
}

void handleRmc(char *s) {
  // $GPRMC,time,status,lat,N/S,lon,E/W,speed,course,date,...
  char *field[13] = {nullptr};
  uint8_t n = 0;
  for (char *tok = strtok(s, ","); tok && n < 13; tok = strtok(nullptr, ",")) field[n++] = tok;

  if (n < 7 || field[2][0] != 'A') return;      // 'V' = void. Coordinates are still present
                                                 // and still meaningless. Gate on this.
  Serial.print(toDegrees(field[3], field[4][0]), 6);
  Serial.print(' ');
  Serial.println(toDegrees(field[5], field[6][0]), 6);
}

// NMEA gives ddmm.mmmm — degrees and DECIMAL MINUTES, not decimal degrees.
double toDegrees(const char *ddmm, char hemisphere) {
  double raw = atof(ddmm);
  int deg = (int)(raw / 100);
  double minutes = raw - deg * 100;
  double result = deg + minutes / 60.0;
  return (hemisphere == 'S' || hemisphere == 'W') ? -result : result;
}

The two lines that matter most are the ones people skip.

field[2][0] != 'A' is the validity gate. A receiver with no fix still emits perfectly well-formed RMC sentences containing coordinates — usually the last known position, or zeros. Code that does not check the status flag will confidently navigate to the middle of the Atlantic, which is where 0°N 0°E is.

toDegrees exists because 4807.038 is not 4807.038 degrees, and it is not 48.07038 degrees either. It is 48° 07.038′, which is 48.1173°. Treating NMEA coordinates as decimal degrees is the most common GPS bug there is, and the error is large enough to be obvious — which is the only good thing about it.

For real projects, use TinyGPS++. It handles every sentence type, checksums, and the edge cases. The manual version above is worth running once so you know what it is doing.

Accuracy: what the numbers actually mean

Figure Typical NEO-6M What it means
Horizontal accuracy ~2.5 m CEP Half of all fixes fall within 2.5 m. Half do not
Update rate 1 Hz default, 5 Hz configurable At 1 m/s, a 1 Hz fix is up to a metre stale before you read it
Cold start ~27 s, minutes with no almanac Nothing is wrong; it is downloading at 50 bits per second
Hot start ~1 s With backup power keeping the almanac alive
Velocity accuracy ~0.1 m/s Genuinely good — better than the position

CEP is a median, not a bound. Half your fixes are worse than 2.5 m, and the tail is long near buildings. For a robot 30 cm wide, “somewhere within a few metres” is not a position — it is a neighbourhood.

HDOP is the field to watch. It describes satellite geometry rather than signal quality, and it is the thing that produces a confident answer that is tens of metres wrong:

HDOP Quality Use it?
< 1 Ideal Yes
1–2 Excellent Yes
2–5 Good Yes, with caution
5–10 Moderate Only as a slow correction
> 10 Poor Reject the fix

Gate on HDOP as well as on the validity flag. A fix with HDOP of 12 is not a fix; it is a guess with a decimal point.

Static drift, and why it matters

Leave a GPS module perfectly still for ten minutes and log every fix. You will get a cloud several metres across. Nothing moved.

This is the single most important thing to see for yourself before writing any navigation code, because it explains why following raw fixes is hopeless. A controller fed that cloud chases it, and the robot weaves continuously while making little progress. Dead reckon with encoders between fixes and use each fix only to correct the slow drift, and the same controller tracks cleanly — the waypoint navigation tutorial has the numbers.

Configuring the module

Raising the update rate is the single most valuable change, and it is worth doing before anything else. u-blox modules take binary UBX commands:

// UBX-CFG-RATE: 200 ms measurement period -> 5 Hz
const uint8_t setRate5Hz[] = {
  0xB5, 0x62, 0x06, 0x08, 0x06, 0x00,
  0xC8, 0x00,          // 200 ms
  0x01, 0x00,          // one measurement per solution
  0x01, 0x00,          // align to GPS time
  0xDE, 0x6A           // checksum
};

void setup() {
  gps.begin(9600);
  delay(100);
  gps.write(setRate5Hz, sizeof(setRate5Hz));
}

At 5 Hz, 9600 baud becomes the bottleneck — the full NMEA sentence set does not fit in 200 ms. Either raise the baud rate to 38400, or disable the sentences you do not use (GSV in particular is verbose and reports satellite details no robot needs).

Note that on a NEO-6M the configuration does not persist without battery backup on the module. Send it at every startup.

Troubleshooting

Symptom Likely cause Fix
No data at all TX/RX not crossed, or wrong baud Module TX → board RX; try 9600 first
Data arrives, no fix ever Indoors GPS needs sky. A window is usually not enough
First fix takes minutes Cold start with no almanac Normal. Wait outdoors with a clear view
Position is 0, 0 Using a void fix Gate on the RMC status flag A
Position is wildly wrong Treating ddmm.mmmm as degrees Convert: deg + minutes/60
Position jumps several metres Normal receiver noise Expected. Fuse with odometry; never follow raw fixes
Fix is confident but far out Poor satellite geometry Reject fixes with HDOP above ~5
Fewer satellites than expected Antenna obstructed Mount on top, facing up, clear of the chassis plate
Module got hot and died 5 V into a 3.3 V RX pin Check whether your board level-shifts before wiring
Works, then stops at 5 Hz 9600 baud cannot carry the sentences Raise the baud rate or disable GSV

Choosing a module

Module Constellations Rate Notes
NEO-6M GPS 1–5 Hz The cheap default. Ageing but everywhere
NEO-M8N GPS + GLONASS + Galileo up to 10 Hz Better geometry, better HDOP — the sensible upgrade
NEO-M9N Multi-band capable up to 25 Hz Noticeably better in urban settings
ZED-F9P (RTK) Multi-band 20 Hz Centimetre accuracy, with a base station and a data link

The upgrade path is worth taking in order. A higher update rate removes most of the staleness problem and costs nothing. A multi-constellation receiver sees more satellites, which improves geometry and therefore HDOP — this is a bigger real-world improvement than the accuracy specification suggests, especially near buildings. RTK reaches centimetres and is transformative for a robot that has to follow a line in a field, but it needs a base station, a correction data link, and roughly ten times the budget.

One thing no GPS module gives you at any price: heading while stationary. Course over ground is derived from successive positions, so below about 1 m/s it is noise, and at zero speed it is meaningless. A robot that needs to know which way it is facing needs a magnetometer or a gyro regardless of how good its GPS is.

Explore the graph

Used in these builds

Projects, learning paths, and simulators that include the NEO-6M GPS Module.

Questions

NEO-6M GPS Module FAQ

How accurate is a NEO-6M really?

About 2.5 metres half the time, with a clear view of the sky and good satellite geometry — which is the number on the datasheet and an optimistic one for a robot driving beside a building or under trees. The important part is that it degrades into confident wrong answers rather than obvious failures, so gate every fix on the reported fix quality and on HDOP rather than trusting the position because it parsed.

What is HDOP and why does it matter more than the accuracy number?

Horizontal dilution of precision describes the geometry of the satellites currently in view. Satellites spread across the sky give a well-conditioned solution; satellites bunched together give a badly conditioned one, and the receiver still reports a position with full confidence. A HDOP under 2 is good and above about 2.5 is worth rejecting. It is the single most useful field in the GGA sentence and almost nobody reads it.

Can a GPS module tell my robot which way it is pointing?

No. A fix is a position, and a stationary robot facing north gives exactly the same fix as one facing south. Course over ground is computed from consecutive positions, so it only means anything while the robot is genuinely moving — below roughly 1 metre per second it is mostly noise. Outdoor rovers pair GPS with a magnetometer for absolute heading, a gyro for short-term heading, or both.

Why does my robot weave when following GPS waypoints?

Because a 1 Hz fix is both stale and noisy, and a path follower fed raw fixes lunges at each new one. Measured on the same course with the same controller, a 1 Hz fix at metre-scale noise turns 0.09 m of RMS cross-track error into 1.25 m and multiplies steering effort by four and a half. The fix is to dead reckon between fixes and use each new fix to correct that estimate rather than replace it.

Does it work indoors?

Essentially never, and that is not a fault. The signal from a GPS satellite arrives weaker than the thermal noise floor and needs an unobstructed sky; a roof stops it. Modules on a windowsill sometimes acquire and report large errors from multipath. Test outdoors, in the open, and budget a minute for a cold start before assuming anything is broken.

5 V or 3.3 V?

Most breakout boards carry a regulator and accept 5 V on VCC, but the module's own logic is 3.3 V. Feeding 5 V into RX on a board without level shifting is the usual way these die. Check your specific board, and if in doubt drive RX through a divider — the module's TX into a 5 V microcontroller is fine, since 3.3 V clears the logic-high threshold on an AVR.

Further reading

References