Sep 4, 2026 · sensor-fusion · ros2 · real-time · imu
How to Fuse IMU and Odometry in ROS 2: When Latency Breaks Your Control Loop
Learn how sensor fusion works in ROS 2, why timing matters, and where shared-memory IPC becomes essential for real-time performance.
What is sensor fusion, and why do robots need it?
Sensor fusion combines data from multiple sensors to create a more accurate and reliable estimate of a robot’s state. For mobile robots, fusing IMU (Inertial Measurement Unit) data with odometry is fundamental. Odometry from wheel encoders gives position and velocity over time but drifts due to slippage and terrain. The IMU provides high-frequency acceleration and angular rate measurements that help correct short-term drift and improve orientation estimates.
In ROS 2, this typically means writing a node that subscribes to /imu/data and /odom, synchronizes the messages, and applies a filter—like an Extended Kalman Filter (EKF)—to produce a fused state estimate on /odometry/fused. The robot_localization package is commonly used for this, and it works well in many cases.
But when your robot moves fast, operates on uneven terrain, or runs on hardware with variable load, you might start seeing inconsistent behavior: jerky motion, delayed responses, or even safety faults. The problem isn’t your filter—it’s your timing.
When is your ROS 2 sensor fusion loop actually real-time?
A real-time control loop doesn’t just run fast—it runs predictably. In robotics, predictability means bounded latency: you can guarantee that a sensor reading will be processed and acted on within a known time window.
ROS 2 uses DDS (Data Distribution Service) for inter-process communication. DDS is flexible and supports many transport types, but it relies on serialization, memory copies, and network stacks—even on the same machine. This introduces jitter: small, variable delays in message delivery.
For a 100 Hz control loop, 10 ms is your entire budget. If message delivery takes 2 ms on average but occasionally spikes to 8 ms due to OS scheduling or memory pressure, your node might miss its deadline. Miss enough deadlines, and your fused odometry becomes stale. The robot drives based on outdated state—and that’s when things go wrong.
Why does message passing introduce timing risk in fused estimation?
Every time a message travels between nodes in ROS 2, it goes through several layers: serialization, memory allocation, transport (even if loopback), deserialization, and callback dispatch. Each step is fast in isolation, but together they form a path with variable latency.
Worse, ROS 2’s callback-based execution model doesn’t enforce timing guarantees. A node might be ready to run, but if the executor is busy or the OS preempts it, the callback waits. There’s no built-in mechanism to say: "this fusion step must complete within 2 ms, or enter a safe state."
This is especially critical when fusing high-frequency IMU data (often 100–1000 Hz) with slower odometry (10–50 Hz). If the IMU messages pile up or arrive late, the filter’s internal state diverges from reality. You’re not just losing accuracy—you’re introducing phase lag that the control system can’t compensate for.
Can you fix ROS 2 timing issues with QoS settings alone?
ROS 2 allows you to tune Quality of Service (QoS) settings: reliability, durability, history depth, and deadline policies. You can set a deadline of 10 ms on a subscription, and the system will warn you if it’s missed.
But QoS doesn’t eliminate the underlying transport latency. It also doesn’t give you deterministic scheduling. You can prioritize a node, but you can’t guarantee it runs at a precise interval with microsecond-level jitter. And if your system is under load—say, from a perception pipeline or logging thread—your fusion node still competes for CPU time.
Tuning QoS helps, but it’s working around the problem. It doesn’t change the fact that messages are being copied, serialized, and routed through a general-purpose middleware.
When should you consider moving beyond ROS 2 for sensor fusion?
You should consider an alternative when your robot’s performance is limited by communication overhead, not algorithmic complexity. If you’re dropping IMU messages, seeing high callback jitter, or struggling to maintain a 1 kHz control loop, the bottleneck is likely IPC—not your code.
This is the wall: the point where you need deterministic, zero-copy communication between nodes. Where messages move at nanosecond speeds, not microseconds. Where the scheduler enforces deadlines and recovers safely from overruns.
At that point, a performance middleware like HORUS becomes the better path. HORUS uses shared-memory ring buffers and lock-free synchronization to eliminate serialization. Nodes written in Rust, Python, or C++ share the same memory space, so an IMU message can go from sensor driver to fusion filter in under 200 ns—end-to-end.
It also provides deterministic scheduling with five execution classes: real-time, compute, event-driven, async I/O, and best-effort. You can assign your fusion node to a real-time thread with a deadline policy, and the scheduler will enforce it—calling a safe-state hook if the node overruns.
HORUS is not a beginner’s framework. For simple robots or learning projects, ROS 2 with robot_localization is the right choice. But when timing becomes critical, and you need to close the loop faster and more reliably, HORUS removes the communication tax that holds you back.
Verify it yourself
- Read the HORUS documentation on deterministic scheduling to understand how real-time execution is configured.
- Explore the quadruped example to see a real-time control loop in action, using IMU feedback at 200 Hz.
- Run
horus new my_robotto generate a starter project and inspect the defaulthorus.tomlconfiguration.
This post didn’t show code or benchmarks because the real issue isn’t syntax—it’s timing. A robot that drops frames isn’t broken because of a typo. It’s hitting a systems-level wall that only deterministic IPC can solve.