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

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 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 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 (often referred to as gps_thread) invokes the telemetry parser defined in 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.

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 to manage the USB connection. Once the device is opened, a background thread in GUI_V5.py polls the interface for 64-byte packets. The following snippet illustrates how the thread retrieves the current pitch and roll values:

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, 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, 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:

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):

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:

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:

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 exposes the STM32USBInterface.read_data() method that delivers 64-byte binary packets to the PC.
  • 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 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 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 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.

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 →