Flipper Zero · NEO-6M · Tactical GPS

TacNav GPS
v2.0

A full-featured tactical GPS application for the Flipper Zero, built around the NEO-6M module. Live position, trip odometer, satellite SNR charts — all in 128×64 pixels.

Get Started View Screens
Overview

What is TacNav GPS?

TacNav GPS is a custom Flipper Zero application that transforms the device into a pocket tactical GPS display. It parses NMEA sentences from a NEO-6M GPS module connected over UART and presents the data across five purpose-built screens.

Live Position

Real-time latitude, longitude, altitude, and UTC time parsed from RMC and GGA sentences with hemisphere-aware display.

Tactical HUD

Military-style heads-up display with fix indicator, speed in knots, heading in degrees, and cardinal direction labels.

Trip Odometer

Haversine-based distance accumulator tracks total distance traveled. Long-press OK to reset. Noise-filtered at <2 m jumps.

Satellite SNR Chart

Live bar chart of up to 12 satellites from GSV sentences, sorted by signal strength, with dBHz labels on strong signals.

Adjustable Baud Rate

Cycle through six baud rates (4800–115200) on the fly with the D-pad to match any NEO-6M configuration.

Thread-Safe UART

Dedicated worker thread with FuriStreamBuffer decouples ISR byte delivery from NMEA parsing. Mutex-protected shared state.

Hardware

NEO-6M GPS Module

The u-blox NEO-6M is a compact, high-performance GPS receiver that outputs standard NMEA 0183 sentences at configurable baud rates. It runs on 3.3 V — perfectly matched to the Flipper Zero GPIO header.

/* Flipper Zero GPIO Header */
/* (viewed from the back) */

┌──────────────────────┐
1 SWC ██ PWR 8
2 SIO ██ GND 9│ ← VCC 3.3V
3 NC ██ PC3 10
4 NC ██ PC1 11│ ← GND
5 NC ██ PC3 12
6 NC ██ RX 13│ ← GPS TX
7 NC ██ TX 14│ ← GPS RX
│ │
│ ██ ██ ██ │
│ (speaker/btn) │
└──────────────────────┘
Flipper PinNEO-6M PinTypeNotes
Pin 9 — 3.3VVCCPower3.3 V supply
Pin 11 — GNDGNDGroundCommon ground
Pin 13 — RXTXUARTGPS transmits NMEA sentences
Pin 14 — TXRXUARTFlipper sends baud commands

NEO-6M Specs

Protocol
NMEA 0183
Default Baud
9600
Update Rate
1 Hz
Satellites
Up to 16 tracked
Voltage
3.3 V
Accuracy
2.5 m CEP
TACNAV System

Custom Tactical Navigation

TACNAV is a bespoke navigation application built from scratch in C for the Flipper Zero SDK. It combines real-time NMEA parsing, a multi-screen tactical display, and a thread-safe UART driver into a single cohesive package deployed as a .fap external app.

  ┌────────────────────────────┐
  │    NEO-6M GPS Module    │
  │   NMEA sentences @ UART    │
  └────────────┬───────────────┘
               │ 3.3V UART (Pin 13/14)
               ▼
  ┌────────────────────────────┐
  │       gps_uart.c           │
  │  ISR → StreamBuffer        │
  │  Worker Thread → line cb   │
  └────────────┬───────────────┘
               │ NMEA line callback
               ▼
  ┌────────────────────────────┐
  │       gps_app.c            │
  │  minmea parser (RMC/GGA/   │
  │  GSV) → GpsData struct     │
  └────────────┬───────────────┘
               │ FuriMutex-protected
               ▼
  ┌────────────────────────────┐
  │      ViewPort / GUI         │
  │  draw_callback() renders   │
  │  active screen @ 10 fps    │
  └────────────────────────────┘
gps_app.c

Application Core

Main event loop, screen navigation state machine, draw callbacks for all five screens, and NMEA parsing dispatch. Registers the ViewPort with the Flipper GUI.

gps_app.h

Data Types

Defines GpsData struct (position, movement, signal, satellite arrays) and the GpsScreen enum. Central header shared across all modules.

gps_uart.c / .h

UART Driver

ISR-safe UART receiver using FuriStreamBuffer. Spawns a GpsUartWorker thread that assembles bytes into complete NMEA lines before invoking the line callback.

minmea.c / .h

NMEA Parser

Parses $GPRMC, $GPGGA, and $GPGSV sentences into typed structs. Handles multi-message GSV sequence assembly with atomic buffer swap.

application.fam

App Manifest

Registers the app as gps_tacsys_v2 in the GPIO category. Entry point is gps_tacsys_app(), targeting the GPIO app drawer.

Screen Reference

Five Tactical Screens

TacNav v2 provides five purpose-built screens navigable with the D-pad and OK button. The system auto-shows the splash screen whenever a GPS fix is lost.

Flipper Zero  128×64
0

Acquiring Signal Splash

Automatically displayed when no GPS fix is available. Inverted black background, double border frame, and corner crosshair brackets for a tactical aesthetic.

  • Animated acquisition dots cycling through 4 frames every ~400 ms
  • Live satellite count from GGA sentences even before a fix is achieved
  • Corner HUD brackets drawn with canvas_draw_line()
  • Auto-dismisses when RMC confirms a valid fix and the user is on the HUD screen
Flipper Zero  128×64
1

Main Tactical HUD

The primary display, showing full position data in a clean military-inspired layout. A solid disc indicates a valid fix; a blinking hollow circle means the fix is lost.

  • Latitude & longitude with hemisphere suffix (N/S/E/W)
  • Altitude in meters, speed in knots
  • True heading in degrees + 16-point cardinal direction
  • Satellite count from the last GGA sentence
  • Fix dot blinks at 2 Hz when searching, solid when locked
Flipper Zero  128×64
2

Movement & Trip Odometer

Dedicated screen for motion data. Speed is shown in both knots and km/h. The trip odometer accumulates Haversine distance and switches from meters to km once 1 km is exceeded.

  • Speed in KTS and KM/H simultaneously
  • Full heading with cardinal direction string
  • Trip distance: meters below 1 km, kilometers above
  • Haversine formula with noise filter — ignores jumps <2 m
  • Long-press OK to reset the trip odometer
Flipper Zero  128×64
3

Signal Quality

Diagnostic view showing GPS signal health. HDOP is labeled with a five-tier quality rating. Active baud rate shown top-right.

  • Satellite count from GGA tracked field
  • HDOP with quality label: IDEAL / EXLNT / GOOD / MOD / POOR
  • Fix type: NONE, GPS, or DGPS from GGA fix quality field
  • Altitude MSL (mean sea level) from GGA
  • Active baud rate displayed top-right, changeable with UP/DOWN
Flipper Zero  128×64
4

Satellite SNR Bar Chart

Visual signal-to-noise ratio chart assembled from multi-message GSV sequences. Up to 12 satellites sorted strongest-first. Bars are 7 px wide with 2 px gaps, centered horizontally.

  • SNR scale: 0–50 dBHz mapped to 0–36 px bar height
  • Satellites sorted descending by SNR each refresh
  • SNR value label above bars ≥10 px tall
  • 2 px stub for visible-but-not-tracking satellites (SNR = 0)
  • "NO DATA" placeholder shown until first GSV sentence arrives
  • GSV multi-message assembly with atomic buffer swap on last message
Controls

Button Reference

All navigation and settings are accessible through the Flipper Zero's D-pad and action buttons.

OK

Next Screen

Short press OK or Right to advance to the next screen (HUD → Move → Signal → Sats → HUD).

Previous Screen

Short press Left to go back one screen in the cycle.

Baud Up

Increase UART baud rate to the next step (4800 → 9600 → 19200 → 38400 → 57600 → 115200).

Baud Down

Decrease UART baud rate to the previous step. Wraps around at the bottom.

OK

Reset Odometer

Long-press OK while on the Movement screen to zero the trip distance accumulator.

BCK

Exit App

Short press Back at any time to cleanly exit. UART and GUI resources are properly freed.

Installation

Get Up and Running

TacNav GPS v2 ships as a pre-compiled .fap for convenience, or you can build from source with the official Flipper Zero firmware toolchain.

1

Wire the NEO-6M

Connect the GPS module to the Flipper Zero GPIO header: 3.3V → Pin 9, GND → Pin 11, GPS TX → Pin 13 (RX), GPS RX → Pin 14 (TX). No level shifter required — both devices run at 3.3 V.

2

Copy the .fap to your Flipper

Using qFlipper or a microSD card, copy gps_tacsys_v2.fap from the dist/ folder to SD:/apps/GPIO/ on your Flipper Zero.

3

Launch from the GPIO Menu

On the Flipper, navigate to Applications → GPIO → TacNav GPS v2. The splash screen will appear immediately and begin acquiring a fix.

4

Wait for Fix (typically 30–90 s)

The splash screen shows satellite count in real time. Once the NEO-6M achieves a valid RMC fix, the app automatically transitions to the Tactical HUD. Move to open sky for faster acquisition.

5

Build from Source (Optional)

Clone the Flipper Zero firmware, place the TACNAV-V2/ folder under applications_user/, then run ./fbt fap_gps_tacsys_v2 from the firmware root.

Source Reference

Code Architecture

Key implementation details for contributors and power users.

gps_uart.c — ISR callback + worker thread
/* ISR context: byte arrives → stream → signal worker */
static void gps_uart_rx_callback(FuriHalSerialHandle* handle,
    FuriHalSerialRxEvent event, void* ctx) {
    GpsUart* uart = (GpsUart*)ctx;
    if(event == FuriHalSerialRxEventData) {
        uint8_t byte = furi_hal_serial_async_rx(handle);
        furi_stream_buffer_send(uart->rx_stream, &byte, 1, 0);
        furi_thread_flags_set(furi_thread_get_id(uart->worker_thread), WorkerEventRxData);
    }
}
/* Worker thread: drain stream → assemble NMEA lines */
static int32_t gps_uart_worker(void* ctx) {
    GpsUart* uart = (GpsUart*)ctx;
    while(true) {
        uint32_t events = furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever);
        if(events & WorkerEventStop) break;
        if(events & WorkerEventRxData) {
            uint8_t byte;
            while(furi_stream_buffer_receive(uart->rx_stream, &byte, 1, 0) == 1) {
                if(byte == '\n' || byte == '\r') {
                    if(uart->line_pos > 0) { uart->line_buf[uart->line_pos] = '\0';
                        uart->line_callback(uart->line_buf, uart->callback_context);
                        uart->line_pos = 0; }
                } else if(uart->line_pos < GPS_NMEA_LINE_MAX - 1)
                    uart->line_buf[uart->line_pos++] = (char)byte;
                else uart->line_pos = 0;
            }
        }
    }
    return 0;
}
gps_app.c — Haversine distance + trip odometer
static float haversine_km(float lat1, float lon1, float lat2, float lon2) {
    float dlat = (lat2 - lat1) * DEG_TO_RAD;
    float dlon = (lon2 - lon1) * DEG_TO_RAD;
    float a = sinf(dlat * 0.5f) * sinf(dlat * 0.5f) +
              cosf(lat1 * DEG_TO_RAD) * cosf(lat2 * DEG_TO_RAD) *
              sinf(dlon * 0.5f) * sinf(dlon * 0.5f);
    return EARTH_R_KM * 2.0f * atan2f(sqrtf(a), sqrtf(1.0f - a));
}
/* Trip odometer — only accumulate with valid fix and movement */
if(g->valid && g->speed_knots > 0.1f) {
    if(was_valid && g->odo_has_last) {
        float dist = haversine_km(g->odo_last_lat, g->odo_last_lon, g->latitude, g->longitude);
        if(dist > 0.002f) g->trip_distance_km += dist; /* ignore <2m GPS noise */
    }
    g->odo_last_lat = g->latitude; g->odo_last_lon = g->longitude; g->odo_has_last = true;
}
gps_app.c — HDOP quality labels + course cardinal
static const char* hdop_label(float hdop) {
    if(hdop <= 1.0f)  return "IDEAL";
    if(hdop <= 2.0f)  return "EXLNT";
    if(hdop <= 5.0f)  return "GOOD ";
    if(hdop <= 10.0f) return "MOD  ";
    return "POOR ";
}
static const char* course_to_cardinal(float deg) {
    static const char* dirs[] = {
        "N","NNE","NE","ENE","E","ESE","SE","SSE",
        "S","SSW","SW","WSW","W","WNW","NW","NNW"
    };
    return dirs[((int)((deg + 11.25f) / 22.5f)) % 16];
}