Hardware / GPS / GNSS receivers
VK2828U7G5LF GPS Module
A practical reference for the V.KEL VK2828U7G5LF GPS/GNSS module: its real six-pin interface, the u-blox receiver hidden under the shield, the differences between published documentation and hardware found in the wild, and a known-good way to connect it to an ESP32.
What makes this module confusing?
The VK2828U7G5LF is widely sold as a compact GPS/GNSS receiver with an integrated ceramic antenna, six-pin interface and u-blox silicon. The trouble is that the documentation, marketplace listings and individual physical boards do not always describe exactly the same hardware.
In particular, boards sold under this part number have been encountered with different u-blox
receiver generations. The published V.KEL documentation describes a module based around the
UBX-M8030-KT, while the board examined
here is marked with UBX-G7020-KT.
That does not, by itself, prove that the board is counterfeit. V.KEL appears to have used the same or closely related module design across more than one receiver implementation, and u-blox documents the G7020 and M8030 as pin-compatible devices.
The important hardware discrepancy
The V.KEL datasheet describes built-in SQI flash for storing parameters. The particular modules examined for this page did not show the expected external storage device: under the shield, the visible major components were the u-blox IC and a component marked HH1704.
Treat this as a property of the examined hardware, not as a universal statement about every
VK2828U7G5LF ever sold. If you need to know exactly what is on your board, remove the RF shield
carefully and inspect the PCB, or query the receiver firmware with UBX-MON-VER.
My script output VS expectation
The following is the output from running the script below on my hardware
--- MON-VER diagnostic poll ---
Sending UBX-MON-VER poll...
UBX packet: B5 62 0A 04 64 00 31 2E 30 30 20 28 35 39
38 34 32 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 30 30 30 37 30 30 30 30 00 00 50 52 4F 54
56 45 52 20 31 34 2E 30 30 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 47 50 53 3B 53 42 41 53 3B 47
4C 4F 3B 51 5A 53 53 00 00 00 00 00 00 00 00 00 00 00
00 00 71 B2
Checksum: calculated 71 B2, received 71 B2 -> VALID
========== UBX-MON-VER ==========
Software Version : 1.00 (59842)
Hardware Version : 00070000
Extensions:
PROTVER 14.00
GPS;SBAS;GLO;QZSS
=================================
u-blox's release note explicitly says that 1.00 (59842) is the ROM CORE for UBX-G7020-KT.
So the output of running my script against my V.KEL module is more or less exactly what is expected.
UBX-MON-VER.
VK2828U7G5LF pinout
The published six-pin interface is EN, GND, RX, TX, VCC, PPS. One particularly confusing detail is that some boards print the PPS position as B rather than P. Other examples and the V.KEL documentation identify this position as PPS. The electrical function should therefore be verified rather than inferred from the silkscreen letter alone.
| Module pin | Function | ESP32 | Direction | Wire | Why |
|---|---|---|---|---|---|
| V | 3.3–5 V power | 3.3 V | Power | Red | Conservative common supply |
| G | Ground | GND | — | Black | Common reference |
| R | Module RX | GPIO27 | ESP32 → GPS | Green | Normal output-capable GPIO |
| T | Module TX | GPIO34 | GPS → ESP32 | Blue | Input-only GPIO is ideal here |
| P / B | PPS | GPIO35 | GPS → ESP32 | White | Input-only GPIO is ideal for pulse input |
| E | Enable | GPIO32 | ESP32 → GPS | Yellow | Software power/enable control |
Wire colors are not a standard property of the VK2828U7G5LF. The colors above are the colors of the cable used for this particular setup and are included to make that wiring reproducible.
Why these ESP32 GPIOs?
GPIO34 and GPIO35 for incoming signals
ESP32 GPIO34 and GPIO35 are input-only. That is exactly what is needed for GPS TX and PPS, so using them preserves more flexible GPIOs for peripherals that actually need outputs.
GPIO27 and GPIO32 for outputs
GPS RX and EN need outputs from the ESP32. GPIO27 and GPIO32 provide ordinary output-capable pins without consuming the preferred I²C/SPI resources in this design.
GPIO16/17 are deliberately left alone because they can be associated with flash/PSRAM resources on ESP32 module variants. An ESP32-WROOM does not have PSRAM, but keeping those pins free makes a future move to an ESP32-WROVER-style module less painful.
GPIO12 is also avoided, as are the ESP32 flash pins 6–11 and the normal I²C/SPI pins. The resulting wiring leaves GPIO21/22 for I²C, GPIO5/18/19/23 for VSPI, and GPIO36/39 for ADC1 battery measurement.
In short: incoming GPS signals go to input-only GPIOs, while the two signals that the ESP32 must drive use normal output-capable pins. This preserves the more useful peripheral pins for the rest of the system.
What the module is supposed to be
The V.KEL VK2828U7G5LF datasheet describes a compact GPS/GNSS module with a 25 × 25 × 4 mm antenna, a KDS 0.5 ppm TCXO, built-in LNA, RTC components, optional SQI flash, and a UART/TTL interface. The published module dimensions are approximately 28 × 28 × 8.6 mm.
These are module-level figures and should not be confused with every specification of the underlying u-blox silicon. The exact receiver fitted to a particular board matters.
UBX-G7020-KT vs. UBX-M8030-KT
This is the most useful distinction when identifying a VK2828U7G5LF. The V.KEL documentation associated with this module names the UBX-M8030-KT, but the examined board carries the marking UBX-G7020-KT. u-blox explicitly documents the UBX-G7020 as pin-compatible with the UBX-M8030 family, which explains how a closely related PCB design can accommodate either receiver.
| Feature | UBX-G7020 | UBX-M8030 |
|---|---|---|
| Generation | u-blox 7 | u-blox M8 |
| GNSS | GPS/QZSS, GLONASS; Galileo support described by product documentation | GPS/QZSS, GLONASS, Galileo and BeiDou |
| Concurrent GNSS | Product-dependent / older architecture | Up to 3 GNSS systems concurrently |
| Package | KT/KA: 5 × 5 mm QFN40 | KT: 5 × 5 mm QFN40 |
| Interface family | UART, USB, SPI, DDC/I²C | UART, USB, SPI, DDC/I²C |
| Migration | Documented as pin-compatible with M8030 | Backward-compatible migration path documented by u-blox |
The u-blox G7020 family is now an older, end-of-life product family. For a new design, u-blox points customers toward newer generations. That does not make an existing VK2828U7G5LF useless; it simply means this module is best treated as an inexpensive legacy GNSS receiver rather than a current u-blox design.
Do not trust the chip marking alone: identify the firmware
If the RF shield is inconvenient to remove, the receiver itself can tell you a great deal. u-blox receivers
expose UBX-MON-VER, the monitor/version message. It reports software version, hardware version
and extension strings, which can include firmware version, protocol version, module identification and
GNSS capability information.
UBX-MON-VER poll
B5 62 0A 04 00 00 0E 34
- B5 62
- UBX synchronization header
- 0A 04
- MON class, VER message ID
- 00 00
- Zero-length poll payload
- 0E 34
- UBX checksum
On M8-family receivers, the response can distinguish ROM firmware from flash firmware and can expose
strings such as FWVER, PROTVER, MOD and GNSS capability identifiers.
This makes MON-VER a much better way of identifying the actual receiver firmware than relying on a seller's
product title.
ESP32 diagnostic sketch
The following sketch is intentionally a diagnostic tool rather than a full GPS library. It powers the
module through EN, opens UART2 at the documented 9600 baud default, waits for startup NMEA, sends
UBX-MON-VER, validates the UBX checksum, decodes the response and then continues displaying
ordinary GPS traffic.
#include <Arduino.h>
// ------------------------------------------------------------
// VK2828U7G5LF <-> ESP32 pin assignment
// ------------------------------------------------------------
static constexpr int GPS_TX_PIN = 34; // GPS TX -> ESP32 RX
static constexpr int GPS_RX_PIN = 27; // ESP32 TX -> GPS
static constexpr int GPS_PPS_PIN = 35; // GPS PPS -> ESP32 input
static constexpr int GPS_EN_PIN = 32; // ESP32 -> GPS EN
static constexpr uint32_t GPS_BAUD = 9600;
HardwareSerial GPS(2);
// UBX-MON-VER poll
static const uint8_t MON_VER[] = {
0xB5, 0x62,
0x0A, 0x04,
0x00, 0x00,
0x0E, 0x34
};
// ------------------------------------------------------------
// UBX checksum
// ------------------------------------------------------------
void ubxChecksum(const uint8_t *data,
size_t length,
uint8_t &ckA,
uint8_t &ckB)
{
ckA = 0;
ckB = 0;
for (size_t i = 0; i < length; ++i)
{
ckA += data[i];
ckB += ckA;
}
}
// ------------------------------------------------------------
void printHex(const uint8_t *data, size_t length)
{
for (size_t i = 0; i < length; ++i)
{
if (data[i] < 0x10)
Serial.print('0');
Serial.print(data[i], HEX);
Serial.print(' ');
}
Serial.println();
}
// ------------------------------------------------------------
// MON-VER payload consists of:
// 30 bytes: software version
// 10 bytes: hardware version
// remaining: 30-byte extension strings
// ------------------------------------------------------------
void printFixedString(const uint8_t *data, size_t length)
{
for (size_t i = 0; i < length; ++i)
{
if (data[i] == 0)
break;
char c = static_cast<char>(data[i]);
if (c >= 32 && c <= 126)
Serial.print(c);
else
Serial.print('.');
}
}
// ------------------------------------------------------------
void decodeMonVer(const uint8_t *packet, size_t packetLength)
{
if (packetLength < 8)
{
Serial.println("MON-VER packet too short.");
return;
}
const uint16_t payloadLength =
packet[4] |
(static_cast<uint16_t>(packet[5]) << 8);
const size_t expectedLength =
8 + payloadLength;
if (packetLength != expectedLength)
{
Serial.printf(
"MON-VER length mismatch: packet=%u expected=%u\n",
static_cast<unsigned>(packetLength),
static_cast<unsigned>(expectedLength));
return;
}
uint8_t ckA;
uint8_t ckB;
// Checksum covers CLASS through PAYLOAD.
ubxChecksum(
&packet[2],
4 + payloadLength,
ckA,
ckB);
const uint8_t receivedA = packet[6 + payloadLength];
const uint8_t receivedB = packet[7 + payloadLength];
Serial.printf(
"Checksum: calculated %02X %02X, received %02X %02X -> %s\n",
ckA,
ckB,
receivedA,
receivedB,
(ckA == receivedA && ckB == receivedB)
? "VALID"
: "INVALID");
if (ckA != receivedA || ckB != receivedB)
return;
const uint8_t *payload = &packet[6];
Serial.println();
Serial.println("========== UBX-MON-VER ==========");
if (payloadLength >= 40)
{
Serial.print("Software Version : ");
printFixedString(payload, 30);
Serial.println();
Serial.print("Hardware Version : ");
printFixedString(payload + 30, 10);
Serial.println();
}
Serial.println("Extensions:");
for (size_t offset = 40;
offset + 30 <= payloadLength;
offset += 30)
{
Serial.print(" ");
printFixedString(payload + offset, 30);
Serial.println();
}
Serial.println("=================================");
Serial.println();
}
// ------------------------------------------------------------
// Receive one complete UBX packet.
// ------------------------------------------------------------
bool receiveUBXPacket(uint8_t *buffer,
size_t bufferSize,
size_t &packetLength,
uint32_t timeoutMs)
{
packetLength = 0;
const uint32_t start = millis();
int state = 0;
uint16_t payloadLength = 0;
while (millis() - start < timeoutMs)
{
while (GPS.available())
{
const uint8_t b = GPS.read();
if (state == 0)
{
if (b == 0xB5)
{
buffer[0] = b;
packetLength = 1;
state = 1;
}
continue;
}
if (state == 1)
{
if (b == 0x62)
{
buffer[1] = b;
packetLength = 2;
state = 2;
}
else
{
state = 0;
packetLength = 0;
}
continue;
}
if (state == 2)
{
if (packetLength >= bufferSize)
return false;
buffer[packetLength++] = b;
if (packetLength == 6)
{
payloadLength =
buffer[4] |
(static_cast<uint16_t>(buffer[5]) << 8);
const size_t totalLength =
8 + payloadLength;
if (totalLength > bufferSize)
{
Serial.printf(
"UBX packet too large: %u bytes\n",
static_cast<unsigned>(totalLength));
return false;
}
}
if (packetLength >= 8)
{
const size_t expectedLength =
8 + payloadLength;
if (packetLength == expectedLength)
return true;
}
}
}
delay(1);
}
return false;
}
// ------------------------------------------------------------
void sendMonVer()
{
Serial.println("Sending UBX-MON-VER poll...");
GPS.write(MON_VER, sizeof(MON_VER));
GPS.flush();
}
// ------------------------------------------------------------
void setup()
{
Serial.begin(115200);
delay(500);
Serial.println();
Serial.println("========================================");
Serial.println(" VK2828U7G5LF / u-blox identification");
Serial.println("========================================");
pinMode(GPS_PPS_PIN, INPUT);
pinMode(GPS_EN_PIN, OUTPUT);
digitalWrite(GPS_EN_PIN, HIGH);
delay(500);
GPS.begin(
GPS_BAUD,
SERIAL_8N1,
GPS_TX_PIN,
GPS_RX_PIN);
Serial.println("GPS UART started at 9600 8N1.");
Serial.println("UART RX = GPIO34");
Serial.println("UART TX = GPIO27");
Serial.println("PPS = GPIO35");
Serial.println("EN = GPIO32");
Serial.println();
delay(1000);
while (GPS.available())
GPS.read();
sendMonVer();
uint8_t packet[1024];
size_t packetLength;
if (receiveUBXPacket(
packet,
sizeof(packet),
packetLength,
3000))
{
Serial.println();
Serial.println("Received UBX packet:");
printHex(packet, packetLength);
const uint8_t cls = packet[2];
const uint8_t id = packet[3];
if (cls == 0x0A && id == 0x04)
{
decodeMonVer(packet, packetLength);
}
else
{
Serial.printf(
"Received UBX class %02X ID %02X\n",
cls,
id);
}
}
else
{
Serial.println(
"No complete UBX packet received within timeout.");
}
Serial.println();
Serial.println("Continuing to monitor GPS output...");
}
// ------------------------------------------------------------
void loop()
{
static uint32_t lastPoll = 0;
while (GPS.available())
{
uint8_t b = GPS.read();
if (b >= 32 && b <= 126)
Serial.write(b);
else if (b == '\r' || b == '\n')
Serial.write(b);
else
Serial.printf("[%02X]", b);
}
if (millis() - lastPoll >= 10000)
{
lastPoll = millis();
Serial.println();
Serial.println();
Serial.println("--- MON-VER diagnostic poll ---");
sendMonVer();
uint8_t packet[1024];
size_t packetLength;
if (receiveUBXPacket(
packet,
sizeof(packet),
packetLength,
3000))
{
Serial.print("UBX packet: ");
printHex(packet, packetLength);
if (packet[2] == 0x0A &&
packet[3] == 0x04)
{
decodeMonVer(packet, packetLength);
}
}
else
{
Serial.println("No UBX response.");
}
}
delay(1);
}
The sketch is intentionally close to the diagnostic code used while investigating this hardware. It is not intended to replace the u-blox receiver documentation or to be a general-purpose UBX parser.
A few practical points before wiring one
Power voltage is not logic voltage
The module can be powered from the published 3.3–5 V range, while its digital interface is a 3.3 V-class TTL interface. Feeding a 5 V UART signal into a 3.3 V input is not made safe simply because the module itself accepts 5 V power.
TX and RX are named from the module's perspective
Module TX connects to microcontroller RX; module RX connects to microcontroller TX. This sounds trivial, but it is one of the most common sources of “GPS does not work” wiring errors.
PPS is not normal serial data
PPS is a timing pulse output. It is useful for precise timing and synchronization and can be connected directly to an ESP32 input such as GPIO35.
EN is active high
The V.KEL documentation describes high as enabled and low as disabled. This makes EN useful when the host needs to shut the receiver down under software control.
What to expect from the UART
The module's documented default serial configuration is 9600 baud. Its normal output is NMEA, with common messages including GGA, GLL, GSA, GSV, RMC and VTG. The receiver can also speak u-blox's binary UBX protocol, which is why the same UART can be used to interrogate the receiver with MON-VER.
Do not assume that every unit is still at 9600 baud or has identical message configuration. u-blox receivers are configurable, and module sellers have historically advertised a wide range of supported serial rates. If a supposedly working unit produces nothing at 9600 baud, check wiring and power first, then consider that its stored configuration may differ.
Identification checklist
- Read the board: check the exact silkscreen and connector labels.
- Check the receiver: if the shield can be removed safely, read the marking on the u-blox IC.
- Check the firmware: send
UBX-MON-VERand record the complete response. - Check NMEA: at the documented default, look for normal NMEA output at 9600 baud.
- Do not infer storage: the published module documentation mentions SQI flash, but individual boards may differ.
- Record your exact hardware: if you are building a product around this module, document the actual receiver and firmware you tested.
Bottom line
The VK2828U7G5LF is best understood as a V.KEL module platform rather than as a guarantee of one
immutable receiver configuration. The published documentation points to a u-blox M8 implementation,
while boards encountered in practice can contain the older u-blox G7020-KT. Fortunately, the two are
documented as pin-compatible, and the actual receiver can be identified electronically with
UBX-MON-VER.
For an ESP32, the useful interface is straightforward: 3.3 V-class UART, PPS, and an active-high EN input, with GPIO34/35 making particularly convenient destinations for the two incoming signals. The important lesson is to identify the hardware you actually have rather than blindly trusting a marketplace listing or a silkscreen character.
Primary documentation and further reading
- u-blox UBX-G7020 series — official product information, interfaces, electrical limits and lifecycle status.
- u-blox UBX-G7020 Product Summary — receiver capabilities, electrical data, sensitivity and package information.
- u-blox UBX-M8030 series — official M8 family information and the M8030-KT product variant.
- u-blox 7 Receiver Description & Protocol Specification — NMEA/UBX protocol reference for u-blox 7 receivers.
- u-blox 8 / M8 Receiver Description & Protocol Specification — useful reference for MON-VER interpretation on M8-family receivers.
- Archived VK2828U7G5LF datasheet copy — the V.KEL module-level documentation commonly circulated online.