Reading Position with a NEO-6M
| Difficulty | Advanced |
|---|---|
| Category | Communication |
| Supported boards | Arduino Uno R3 and compatible ATmega328P boards |
| Time needed | 60 minutes |
| Prerequisites | Bluetooth Serial with an HC-05 |
| Tested board | Not yet verified |
| Tested on | Not yet verified |
| Arduino IDE version | Not yet verified |
| Library | Not 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
RXpin. - 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:
$GPGGAcarries the fix: position, altitude, number of satellites, fix quality.$GPRMCcarries the minimum recommended set: position, speed, heading, date and time.- The
Ain that second sentence means the data is valid. AVmeans 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 |
Wiring steps
- Unplug the USB cable.
GNDtoGND,VCCto5V.- Module
TXstraight toD4. - Leave
RXunconnected, unless you plan to configure the module. If you do, feed it fromD3through a 1 k series resistor with a 2 k to ground. - 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
TXgoes 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 theRXpin 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
SoftwareSerialwithout 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-001module 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.

