Reading Position with a NEO-6M

Draft. This tutorial has not been checked against the physical product yet. Confirm wiring, pin assignments and voltage limits against the product documentation before building it.
DifficultyAdvanced
CategoryCommunication
Supported boardsArduino Uno R3 and compatible ATmega328P boards
Time needed60 minutes
PrerequisitesBluetooth Serial with an HC-05
Tested boardNot yet verified
Tested onNot yet verified
Arduino IDE versionNot yet verified
LibraryNot yet verified

What it does

A GPS receiver listens for signals from satellites twenty thousand kilometres up, works out how long each took to arrive, and from that calculates where it is. It then reports position, time, speed and heading as lines of text on a serial port.

From your side it could not be simpler: four wires, and a stream of readable sentences at 9600 baud. The complications are not electrical. They are about where you are standing and how long you are prepared to wait.

Before you begin

Required parts and tools

  • An Arduino Uno or compatible board.
  • One NEO-6M GPS module. This is Siratek product 2613.
  • Two resistors, 1 kilohm and 2 kilohms, for the module’s RX pin.
  • Four jumper wires, and somewhere outdoors to test.

What is on the board

The Siratek part is the red board carrying a NEO-6M-0-001 module, with:

  • A ceramic patch antenna on a short lead.
  • A backup battery, the small round cell, which is what makes a warm start possible.
  • An EEPROM, which stores settings across power cycles.
  • A u.FL connector for an external antenna, if the supplied one is not enough.
  • A four-pin header: VCC, GND, TX, RX.

You must go outside

This is the thing to accept before anything else.

A GPS receiver needs a clear view of the sky. Indoors, it will usually never get a fix: not near a window, not on a bench by an open door. The signal arriving from the satellites is extraordinarily weak, and a roof is enough to stop it.

So the first test happens outside, in the open, away from tall buildings. There is no sketch that works around this.

Cold start takes minutes

Even outdoors, the first fix on a new module takes thirty seconds to several minutes. It has to find satellites it has no prior knowledge of, and then download orbital data from them, which is transmitted slowly.

The backup battery is what shortens this afterwards. With it charged, the module remembers where it was and what the satellites were doing, and a later start takes seconds. A flat or missing battery means a full cold start every single time, which feels exactly like a broken module.

So: leave it powered outside for ten minutes on the first run. The blinking LED tells you when it has a fix, and it does not blink until it does.

The LED is the first diagnostic

The module’s LED is more useful than any sketch:

  • Not blinking: no fix. Either still searching, or it cannot see the sky.
  • Blinking, about once a second: it has a fix.

If the LED never blinks after ten minutes outdoors with a clear view, the problem is the antenna or the module, not your code.

The RX pin needs the same care as the HC-05

TX from the module is about 3.3 V, which an Uno reads as HIGH without any trouble.

The module’s RX is a 3.3 V input. You only need it if you intend to send configuration commands, and most projects never do. But if you wire it, use a 1 k and 2 k divider, exactly as in Bluetooth Serial with an HC-05.

If you are only reading position, you can leave RX disconnected entirely, which removes the question.

What comes out: NMEA sentences

The module sends plain text, several lines a second, in a standard called NMEA:

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A

You do not have to parse these yourself, and should not. But knowing what they are helps:

  • $GPGGA carries the fix: position, altitude, number of satellites, fix quality.
  • $GPRMC carries the minimum recommended set: position, speed, heading, date and time.
  • The A in that second sentence means the data is valid. A V means it is not.

The sentences flow before there is a fix. They simply have empty position fields. So output appearing does not mean it is working, which catches people who stop looking once text arrives.

Note also that the time is correct long before the position is, because the time comes from the satellite signal itself. A module reporting a sensible UTC time but no position is working properly and still searching.

Pin reference

Module pin Connect to Purpose Notes
VCC Uno 5V Power The board regulates it
GND Uno GND Ground
TX Uno D4 Module out, Arduino in Direct; 3.3 V reads fine
RX Uno D3 through a divider, or not at all Module in Only for configuration
A NEO-6M GPS module wired to an Uno with its TX direct and its RX through a divider, and the satellite view that a roof blocks
Four wires, and a clear view of the sky. Indoors it will usually never get a fix.

Wiring steps

  1. Unplug the USB cable.
  2. GND to GND, VCC to 5V.
  3. Module TX straight to D4.
  4. Leave RX unconnected, unless you plan to configure the module. If you do, feed it from D3 through a 1 k series resistor with a 2 k to ground.
  5. Plug the USB cable back in, and take the whole thing outside.

Software and libraries

Install TinyGPSPlus by Mikal Hart through Library Manager. SoftwareSerial comes with the IDE.

Minimal working example

See the raw sentences first. It is the fastest way to know whether the module is alive.

// Siratek: show exactly what the module is saying.

#include <SoftwareSerial.h>

SoftwareSerial gps(4, 3);     // Arduino RX, Arduino TX

void setup() {
  Serial.begin(9600);
  gps.begin(9600);
  Serial.println("Siratek: raw NMEA follows. Take it outside.");
}

void loop() {
  while (gps.available()) {
    Serial.write(gps.read());
  }
}

Three outcomes, each meaning something different:

  • Nothing at all: wiring. Check TX goes to the Arduino’s receive pin, and check power.
  • Rubbish characters: baud rate. These modules default to 9600, but a previously configured one may not be.
  • Readable sentences with empty position fields: it is working and has no fix yet. Go outside and wait.

Position, parsed

// Siratek: position, time and satellite count.

#include <SoftwareSerial.h>
#include <TinyGPS++.h>

SoftwareSerial gpsSerial(4, 3);
TinyGPSPlus gps;

unsigned long lastReport = 0;

void setup() {
  Serial.begin(9600);
  gpsSerial.begin(9600);
  Serial.println("Siratek: waiting for a fix. Outdoors, this takes minutes the first time.");
}

void loop() {
  // Feed every arriving character to the parser. This must not be starved.
  while (gpsSerial.available()) {
    gps.encode(gpsSerial.read());
  }

  if (millis() - lastReport < 2000) {
    return;
  }
  lastReport = millis();

  Serial.print("satellites: ");
  Serial.print(gps.satellites.isValid() ? gps.satellites.value() : 0);

  if (gps.location.isValid()) {
    Serial.print("   lat ");
    Serial.print(gps.location.lat(), 6);
    Serial.print("   lon ");
    Serial.print(gps.location.lng(), 6);
    Serial.print("   age ");
    Serial.print(gps.location.age());
    Serial.println(" ms");
  } else {
    Serial.println("   no fix yet");
  }

  // A useful sanity check: if characters arrive but nothing parses, the baud is wrong.
  if (millis() > 10000 && gps.charsProcessed() < 10) {
    Serial.println("  no usable data: check wiring and baud rate");
  }
}

Three details carry weight.

Six decimal places. Latitude and longitude need them: four decimal places is about eleven metres, six is about eleven centimetres. Printing two makes a perfectly good fix look useless.

location.age() is how long since that position was updated. A valid-looking position with an age of two minutes is stale, which matters on anything moving. Checking isValid() alone is not enough.

charsProcessed() distinguishes “no data arriving” from “data arriving that I cannot parse”. Those have completely different causes and look identical without it.

Why SoftwareSerial is the weak point here

The module sends several sentences a second, continuously, whether you read them or not.

SoftwareSerial receives by timing bits in software, with interrupts disabled while a character arrives. If your loop is busy doing something else when a character turns up, it is lost — there is no buffer in the hardware to catch it. Lost characters corrupt sentences, and corrupted sentences are discarded, so the fix appears intermittent.

Three ways out, in order of effort:

  • Keep the loop quick. Call gps.encode() constantly and do everything else between fixes.
  • Reduce the output. The module can be told to send only $GPRMC, which cuts the traffic considerably. That is what the RX pin is for.
  • Use a board with real hardware serial. A Mega has four ports, and an ESP32 has three. On those, GPS becomes undramatic.

This is the clearest case in the library for Uno, Nano, Mega, Pro Mini or ESP32: the part works on an Uno, and it works much better on a board with a spare hardware port.

Time, which you get for free

A GPS receiver is an extremely accurate clock, and the time arrives before the position does.

if (gps.time.isValid() && gps.date.isValid()) {
  Serial.printf("%04d-%02d-%02d %02d:%02d:%02d UTC\n",
                gps.date.year(), gps.date.month(), gps.date.day(),
                gps.time.hour(), gps.time.minute(), gps.time.second());
}

It is UTC, not local time. Ghana is on UTC, so no adjustment is needed here; elsewhere you must apply an offset yourself, because the module does not know where it is until it has a fix, and does not know about daylight saving at all.

For a datalogger that already has a GPS, this is a better time source than a DS3231 and costs nothing extra.

Expected result

Indoors: readable sentences, empty position fields, LED not blinking. That is correct behaviour, not a fault.

Outdoors with a clear view: the LED starts blinking within a few minutes on the first run, the satellite count climbs past four, and a position appears. Subsequent starts take seconds while the backup battery holds.

Position should be stable to a few metres while stationary. It will wander slightly, which is normal; a GPS fix is not a fixed number.

Troubleshooting

Symptom Likely cause Check or fix
No characters at all TX on the wrong pin, or no power Module TX to the Arduino’s receive pin
Rubbish characters Baud rate Default is 9600; a configured module may differ
Sentences arrive, no position No fix yet Go outside; wait; watch the LED
Never gets a fix outdoors Antenna not connected, or faulty Check the antenna lead
First fix takes for ever, every time Backup battery flat Leave it powered to charge
Fix comes and goes Characters lost by SoftwareSerial Keep the loop quick; reduce output
charsProcessed() stays near zero Data arriving but unparseable Baud rate
Position looks wrong by hundreds of metres Too few decimal places printed Print six
Position valid but stale Not checking age() Check it on anything moving
Time is hours out It is UTC Apply your own offset
Works on a Mega, poorly on an Uno SoftwareSerial under load Use a hardware port

Next steps

  • Easier first: Bluetooth Serial with an HC-05, for SoftwareSerial without the timing pressure.
  • Related: Logging Data to an SD Card, the obvious destination for a track.
  • Next project: a journey logger writing position and time to a card, with the GPS supplying both.

Sources and attribution

All text, wiring guidance and code in this tutorial are original Siratek work.

  • u-blox NEO-6 documentation for the default baud rate, the cold and warm start behaviour and the backup battery’s role.
  • The NMEA sentence structure is a published standard; the two example sentences here are illustrative and carry no real position.
  • TinyGPSPlus documentation for the calls used.
  • The NEO-6M-0-001 module marking, the backup cell, the EEPROM and the u.FL antenna connector were read from the Siratek product photographs for item 2613.

Fix times and accuracy depend on sky view, surroundings and satellite geometry, and are given as typical values. No third-party tutorial text, diagrams, photographs or code has been reproduced here.

Product used in this tutorial: NEO-6M v2 GPS module
Shop
Sidebar
0 Wishlist
0 Cart