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

> Implement GPS integration with the UM982 dual-antenna receiver for radar positioning. Explore a complete guide and firmware implementation details for enhanced navigation accuracy.

- Repository: [NawfalMotii79/PLFM_RADAR](https://github.com/NawfalMotii79/PLFM_RADAR)
- Tags: how-to-guide
- Published: 2026-08-20

---

**The PLFM RADAR firmware integrates a UM982 dual‑antenna GNSS receiver through a three‑layer architecture: a dedicated driver ([`um982_gps.h`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/um982_gps.h)/[`um982_gps.c`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/um982_gps.c)), system integration in [`main.cpp`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/main.cpp), and unit tests in [`test_um982_gps.c`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/um982_gps.h) and [`um982_gps.c`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/main.cpp).

### Initialization Sequence

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

```cpp
// 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:

```cpp
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:

```cpp
// 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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/test_um982_gps.c) suite validates driver correctness with **synthetic NMEA streams**:

```cpp
// 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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/um982_gps.h)** – Structure definitions, API prototypes, NMEA constants
- **[`um982_gps.c`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/um982_gps.c)** – Full driver implementation (~800 lines): UART abstraction, parser state machine, checksum validation, command builder
- **[`main.cpp`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/main.cpp)** – Integration point: global `UM982_GPS_t um982` instance, initialization call, processing in main loop
- **[`test_um982_gps.c`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/um982_gps.h)/[`um982_gps.c`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/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`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/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.