GPS Integration with UM982 Dual‑Antenna Receiver for Radar Positioning: Complete Implementation Guide

The PLFM RADAR firmware integrates a UM982 dual‑antenna GNSS receiver through a three‑layer architecture: a dedicated driver (um982_gps.h/um982_gps.c), system integration in main.cpp, and unit tests in test_um982_gps.c to provide centimeter‑level position, heading, and velocity data.

GPS integration with the UM982 dual‑antenna receiver enables the PLFM RADAR platform to achieve precise radar positioning and beam steering without drift from magnetometer errors. This implementation uses a non‑blocking, self‑contained driver that parses NMEA sentences directly from UART5 and exposes clean getters for latitude, longitude, heading, and velocity.

UM982 Driver Architecture

The core of the GPS integration is the UM982 driver (um982_gps.h and um982_gps.c), a fully self‑contained GNSS stack that requires no external libraries.

NMEA Sentence Parsing

The driver parses four critical NMEA sentences for radar‑level positioning:

  • GGA – position fix data (latitude, longitude, altitude, fix quality)
  • RMC – recommended minimum specific GNSS data (position, velocity, time)
  • THS – true heading and status (dual‑antenna heading output)
  • VTG – track made good and ground speed

Each sentence undergoes checksum validation before processing. The driver maintains a ring buffer and line assembler to handle raw UART bytes without blocking the main radar loop.

Public API Design

The driver exposes a minimal, poll‑friendly interface:

Function Purpose
um982_init() Initialize UART, send configuration commands, verify VERSIONA response
um982_process() Non‑blocking pump—call every loop iteration to process pending bytes
um982_feed() Manual byte injection (used for testing or alternative UART sources)
um982_get_latitude() / um982_get_longitude() Access latest valid position
um982_get_heading() Access dual‑antenna derived heading
um982_is_position_valid() / um982_is_heading_valid() Boolean validity checks with age thresholds
um982_position_age() / um982_heading_age() Milliseconds since last valid update

All state lives in a single UM982_GPS_t structure, making the driver thread‑safe for single‑producer, single‑consumer use.

System Integration in main.cpp

The radar firmware connects the UM982 driver to the broader system through main.cpp.

Initialization Sequence

During systemPowerUpSequence, the driver is configured with baseline and tolerance parameters for dual‑antenna heading mode:

// From main.cpp - UM982 initialization
UART_HandleTypeDef huart5;                // UART5 configured for 115200 baud
float baseline_cm   = 0.0f;               // 0 = use module's default antenna spacing
float tolerance_cm  = 0.0f;               // 0 = use module's default tolerance

if (um982_init(&um982, &huart5, baseline_cm, tolerance_cm)) {
    DIAG("GPS", "UM982 init OK");
} else {
    DIAG_WARN("GPS", "UM982 init FAILED – no VERSIONA response");
}

A zero baseline configures the UM982 with its factory antenna spacing—typically 10‑30 cm depending on the specific module variant.

Main Loop Integration

The central loop calls um982_process() on every iteration, keeping GPS latency minimal:

while (true) {
    // ... other radar tasks: sampling, DSP, GUI updates ...

    // Pump the GPS driver with any newly arrived UART bytes
    um982_process(&um982);

    // Override magnetometer heading when GPS heading is fresh
    if (um982_is_heading_valid(&um982)) {
        DIAG_SECTION("GPS INIT (UM982)");
        float gps_heading = um982_get_heading(&um982);
        // Replace IMU-derived heading for navigation calculations
    }

    // Update global radar position variables for GUI and targeting
    if (um982_is_position_valid(&um982)) {
        RADAR_Longitude = um982_get_longitude(&um982);
        RADAR_Latitude  = um982_get_latitude(&um982);
    }

    // ... rest of main loop ...
}

Health Monitoring

The firmware implements a 30‑second watchdog on GPS communications:

// Health check in main.cpp - raises ERROR_GPS_COMM if stale
if (um982_position_age(&um982) > 30000) {  // milliseconds
    set_error(ERROR_GPS_COMM);
}

This ensures the radar system degrades gracefully if the UM982 loses power, antenna connection, or satellite lock.

Testing and Validation

The test_um982_gps.c suite validates driver correctness with synthetic NMEA streams:

// Example test pattern for checksum validation and parsing
const uint8_t test_gga[] = 
    "$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";

um982_feed(&um982, test_gga, sizeof(test_gga) - 1);
assert(um982_is_position_valid(&um982));
assert(fabs(um982_get_latitude(&um982) - 48.1173) < 0.001);

Tests cover:

  • Checksum computation and rejection of corrupt sentences
  • Coordinate conversion from NMEA format (DDMM.MMMM) to decimal degrees
  • Heading validity state transitions
  • Buffer overflow handling under high‑byte‑rate conditions

Key Configuration Parameters

Parameter Typical Value Description
UART baud rate 115200 UM982 default; configurable up to 921600
Update rate 10 Hz Position and heading output frequency
Dual‑antenna baseline 10‑30 cm Physical separation between GNSS antennas
Position validity age threshold 500 ms Maximum age before is_position_valid() returns false
Heading validity age threshold 500 ms Maximum age before is_heading_valid() returns false
Communication timeout 30 s Triggers ERROR_GPS_COMM system error

Implementation File Reference

All source files reside in the 9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/ directory:

  • um982_gps.h – Structure definitions, API prototypes, NMEA constants
  • um982_gps.c – Full driver implementation (~800 lines): UART abstraction, parser state machine, checksum validation, command builder
  • main.cpp – Integration point: global UM982_GPS_t um982 instance, initialization call, processing in main loop
  • test_um982_gps.c – Unit test suite in the parallel tests/ directory

The README.md contains a dedicated GPS module (UM982) section with wiring diagrams and antenna placement guidelines.

Summary

  • The UM982 driver (um982_gps.h/um982_gps.c) provides complete, non‑blocking NMEA parsing for GGA, RMC, THS, and VTG sentences with built‑in checksum validation.

  • System integration in main.cpp initializes the driver on UART5, processes data every loop iteration, overrides magnetometer heading with dual‑antenna GNSS heading, and monitors communication health with a 30‑second timeout.

  • Testing infrastructure (test_um982_gps.c) enables regression testing with synthetic data streams, ensuring parser correctness across firmware releases.

  • The architecture supports hot‑swappable antenna configurations through runtime baseline parameters and degrades gracefully via age‑based validity checks.

Frequently Asked Questions

What NMEA sentences does the UM982 driver require for full radar positioning?

The driver parses four sentences: GGA for position fix quality and altitude, RMC for time and velocity, THS for true heading from the dual‑antenna solution, and VTG for ground speed. GGA and THS are sufficient for basic radar operation; RMC and VTG provide redundant velocity sources.

How does the radar system handle GPS signal loss?

The driver tracks message age through um982_position_age() and um982_heading_age(). When age exceeds 500 ms, validity flags return false. The main loop in main.cpp checks um982_position_age() against a 30‑second threshold and raises ERROR_GPS_COMM, allowing the radar to fall back to dead‑reckoning or signal degraded operation to the operator.

Can the UM982 driver run on a different UART or at higher baud rates?

Yes. The um982_init() function accepts any UART_HandleTypeDef pointer. The driver is baud‑rate agnostic—it sees only the byte stream fed via um982_process() or um982_feed(). Configure the UART peripheral before initialization; the driver sends the CONFIG command set at the configured rate and expects VERSIONA response at that same rate.

What antenna spacing is required for accurate heading output?

The baseline parameter in um982_init() configures the physical antenna separation. A zero value uses the UM982 module's default (typically 10 cm for embedded variants, up to 30 cm for high‑precision cards). For custom installations, measure the phase center distance and pass it in centimeters—heading accuracy improves proportionally with baseline length up to the receiver's maximum supported spacing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →