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 constantsum982_gps.c– Full driver implementation (~800 lines): UART abstraction, parser state machine, checksum validation, command buildermain.cpp– Integration point: globalUM982_GPS_t um982instance, initialization call, processing in main looptest_um982_gps.c– Unit test suite in the paralleltests/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 forGGA,RMC,THS, andVTGsentences with built‑in checksum validation. -
System integration in
main.cppinitializes 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →