# IMU Integration with GY-85 for Pitch/Roll Correction in Target Coordinates: PLFM-RADAR Deep Dive

> Learn IMU integration with GY-85 for accurate pitch/roll correction in target coordinates within the PLFM-RADAR system. Enhance your georeferenced radar data.

- Repository: [NawfalMotii79/PLFM_RADAR](https://github.com/NawfalMotii79/PLFM_RADAR)
- Tags: deep-dive
- Published: 2026-08-20

---

**The PLFM-RADAR system streams pitch and roll measurements from a GY-85 IMU over USB CDC and subtracts the platform attitude offset from raw radar angles in [`GUI_V5.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/GUI_V5.py) before plotting georeferenced targets on the map.**

The NawfalMotii79/PLFM_RADAR repository implements a complete attitude-compensation pipeline that fuses GY-85 inertial measurements with phased-array radar detections. By integrating the IMU via I²C on the STM32 firmware and forwarding telemetry through a USB CDC interface, the PC-side Python GUI can apply real-time pitch and roll corrections to raw target coordinates. This article walks through the end-to-end data flow, the key source files, and the trigonometric correction algorithms used to align the antenna frame with the world frame.

## How GY-85 IMU Data Flows Through the PLFM-RADAR Stack

The system moves attitude data from the physical sensor to the corrected map display through five distinct layers:

1. **Measurement** — The STM32 MCU queries the GY-85 accelerometer, gyroscope, and magnetometer over I²C and computes pitch and roll in degrees.
2. **Transport** — The STM32 packages the IMU readings into a binary USB CDC telemetry frame alongside GPS data.
3. **Acquisition** — On the PC, the `STM32USBInterface` class in [`9_Firmware/9_3_GUI/v7/hardware.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/v7/hardware.py) enumerates devices with `list_devices`, opens the selected port with `open_device`, and continuously calls `read_data` to ingest the binary stream.
4. **Parsing** — A background thread in [`9_Firmware/9_3_GUI/GUI_V5.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/GUI_V5.py) (often referred to as `gps_thread`) invokes the telemetry parser defined in [`9_Firmware/9_3_GUI/radar_protocol.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/radar_protocol.py) to extract fields such as `imu_pitch`, `imu_roll`, `gps_lat`, and `gps_lon`.
5. **Correction & Display** — The GUI applies `apply_pitch_correction` to the radar elevation angle and can apply an analogous roll correction to azimuth before forwarding the results to [`9_Firmware/9_3_GUI/v7/map_widget.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/v7/map_widget.py).

This chain ensures that every target plotted on the map reflects the true physical orientation of the antenna array.

## Acquiring GY-85 Telemetry via USB CDC

The Python layer relies on [`hardware.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/hardware.py) to manage the USB connection. Once the device is opened, a background thread in [`GUI_V5.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/GUI_V5.py) polls the interface for 64-byte packets. The following snippet illustrates how the thread retrieves the current pitch and roll values:

```python
def process_gps_data(self):
    """Continuously read GPS + IMU telemetry from the STM32."""
    while True:
        raw = self.stm32_usb_interface.read_data(64)   # 64-byte packet

        if raw:
            # Payload layout (binary, defined in STM32 firmware):

            # [gps_lat, gps_lon, gps_alt, imu_pitch, imu_roll, ...]

            packet = self.telemetry_parser.parse(raw)
            self.current_pitch = packet.imu_pitch   # degrees

            self.current_roll  = packet.imu_roll    # degrees

            # GPS fields are also stored for map centering

            self.current_lat = packet.gps_lat
            self.current_lon = packet.gps_lon

```

The `telemetry_parser.parse(raw)` call delegates the binary decoding to the packet format defined in [`radar_protocol.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/radar_protocol.py), isolating the IMU fields from the GPS payload.

## IMU-Based Pitch and Roll Correction for Target Coordinates

With fresh pitch and roll values available as instance variables, the GUI corrects each incoming radar frame before visualization.

### Pitch Correction on Elevation

The currently implemented routine, `apply_pitch_correction` in [`GUI_V5.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/GUI_V5.py), converts both the raw elevation angle and the IMU pitch to radians, subtracts the platform tilt, normalises the result to the `[0, 180)` degree range, and returns the corrected elevation:

```python
def apply_pitch_correction(self, raw_elevation: float, pitch_angle: float) -> float:
    """
    Adjust the measured elevation angle by the platform pitch.

    Parameters
    ----------
    raw_elevation : float
        Elevation returned by the radar processing pipeline (degrees).
    pitch_angle : float
        Pitch reported by the GY-85 IMU (degrees, positive when the nose is up).

    Returns
    -------
    float
        Corrected elevation angle (degrees, wrapped to [0, 180)).
    """
    raw_rad   = math.radians(raw_elevation)
    pitch_rad = math.radians(pitch_angle)

    corrected_rad = raw_rad - pitch_rad

    corrected_deg = math.degrees(corrected_rad) % 180
    if corrected_deg < 0:
        corrected_deg += 180
    return corrected_deg

```

### Roll Correction on Azimuth

Although the V5 GUI currently implements pitch correction only, extending the logic to roll follows the identical pattern. Replacing elevation with azimuth and pitch with roll produces `apply_roll_correction`, which normalises to `[0, 360)`:

```python
def apply_roll_correction(self, raw_azimuth: float, roll_angle: float) -> float:
    """
    Adjust the measured azimuth angle by the platform roll.

    Parameters
    ----------
    raw_azimuth : float
        Azimuth returned by the radar processing pipeline (degrees).
    roll_angle : float
        Roll reported by the GY-85 IMU (degrees, positive when right wing down).

    Returns
    -------
    float
        Corrected azimuth angle (degrees, wrapped to [0, 360)).
    """
    raw_rad  = math.radians(raw_azimuth)
    roll_rad = math.radians(roll_angle)

    corrected_rad = raw_rad - roll_rad
    corrected_deg = math.degrees(corrected_rad) % 360
    if corrected_deg < 0:
        corrected_deg += 360
    return corrected_deg

```

### Integration in the Rendering Pipeline

The corrected angles feed directly into the map widget. Inside the radar-frame processing loop, the GUI calls both routines before updating the target plot:

```python
elev_raw = radar_frame.elevation   # degrees from FPGA processing

azim_raw = radar_frame.azimuth     # degrees from FPGA processing

# Apply IMU-based attitude compensation

elev_corr = self.apply_pitch_correction(elev_raw, self.current_pitch)
azim_corr = self.apply_roll_correction(azim_raw, self.current_roll)

# Feed corrected angles to the map widget

self.map_widget.update_target(elev_corr, azim_corr,
                              lat=self.current_lat,
                              lon=self.current_lon)

```

## Why Pitch and Roll Compensation Matters for Phased-Array Radar

Without real-time attitude correction, the PLFM-RADAR pipeline suffers from three systematic error sources:

- **Beam-steering errors** — The phased-array beam is defined in the antenna frame; any tilt of the platform introduces angular offsets that skew the perceived elevation and azimuth.
- **Geolocation drift** — Uncorrected attitude propagates into GPS-tagged target positions, producing larger errors for high-elevation or low-SNR detections.
- **Dynamic platform instability** — Mobile deployments such as UAVs or ground vehicles experience rapid pitch and roll changes; continuous compensation keeps the world-frame mapping consistent across frames.

## Key Source Files for IMU Integration

Attitude compensation in PLFM-RADAR spans both firmware and PC-side Python code. The files responsible for the IMU pipeline include:

- **[`README.md`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/README.md)** — Describes the hardware architecture, including the GY-85 IMU and GPS integration strategy.
- **[`9_Firmware/9_3_GUI/v7/hardware.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/v7/hardware.py)** — Implements the `STM32USBInterface` class that enumerates, opens, and reads the USB CDC stream from the STM32.
- **[`9_Firmware/9_3_GUI/GUI_V5.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/GUI_V5.py)** — Hosts `apply_pitch_correction`, the background GPS/IMU thread, and the logic that integrates corrected angles into the map view.
- **[`9_Firmware/9_3_GUI/radar_protocol.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/radar_protocol.py)** — Defines the binary packet format carrying GPS and IMU data from the STM32 to the PC.
- **[`9_Firmware/9_3_GUI/v7/map_widget.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/v7/map_widget.py)** — Renders targets on a geographical map using the corrected elevation, azimuth, and GPS fix.
- **[`docs/architecture.html`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/docs/architecture.html)** — Provides the system-architecture diagram illustrating the end-to-end IMU data path.

## Summary

- The **GY-85 IMU** measures pitch and roll over I²C on the STM32 and forwards them inside a USB CDC telemetry frame.
- **[`hardware.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/hardware.py)** exposes the `STM32USBInterface.read_data()` method that delivers 64-byte binary packets to the PC.
- **[`GUI_V5.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/GUI_V5.py)** parses the stream and stores `current_pitch` and `current_roll` for every incoming radar frame.
- **`apply_pitch_correction`** subtracts the platform pitch from the raw elevation angle and wraps the result to `[0, 180)`.
- **Roll correction** follows the same trigonometric pattern on the azimuth axis, wrapping to `[0, 360)`.
- Corrected coordinates are passed to **[`map_widget.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/map_widget.py)** for georeferenced display that accounts for real-time platform attitude.

## Frequently Asked Questions

### What IMU sensor does the PLFM-RADAR system use for attitude sensing?

The system uses the **GY-85**, an I²C-based module that combines an accelerometer, gyroscope, and magnetometer. The STM32 firmware reads this sensor to compute real-time pitch and roll angles that are streamed to the PC-side GUI.

### How does IMU data travel from the STM32 to the Python application?

The STM32 packages IMU and GPS readings into a binary telemetry frame and transmits them over a **USB CDC** connection. On the PC, the `STM32USBInterface` class in [`hardware.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/hardware.py) enumerates the device and continuously calls `read_data(64)` to ingest the stream.

### Which function in the PLFM-RADAR codebase corrects the radar elevation for platform pitch?

The `apply_pitch_correction` method inside **[`9_Firmware/9_3_GUI/GUI_V5.py`](https://github.com/NawfalMotii79/PLFM_RADAR/blob/main/9_Firmware/9_3_GUI/GUI_V5.py)** performs the correction. It converts the raw elevation and IMU pitch to radians, subtracts the pitch offset, and normalises the output to the `[0, 180)` degree range.

### Can the existing code correct for roll in addition to pitch?

Yes. While the V5 GUI currently implements pitch correction only, the repository demonstrates that **roll correction** can be added analogously. The `apply_roll_correction` routine uses the same subtraction logic on the azimuth angle and wraps the result to `[0, 360)`, making the extension straightforward.