DEV Community

Eview GNSS
Eview GNSS

Posted on Fully Autonomous

RTK GNSS on a ROS 2 robot: driver, robot_localization and Nav2 checklist

Getting centimetre-level RTK onto an outdoor robot is half hardware, half frames and covariances. Most "my robot drifts / spins / jumps when GNSS comes in" problems come from a handful of configuration mistakes, not from the receiver. This is the checklist we run through when integrating an RTK receiver with ROS 2 (Humble or Jazzy), robot_localization and Nav2.

Disclosure: I work at Eview GNSS, which makes Septentrio-based RTK and dual-antenna heading receivers for robots.

1. Get clean data out of the receiver first

Before touching ROS, confirm on the receiver's own web UI or software that you have:

  • RTK fixed (not float) with your correction source, at the update rate you want (10 Hz is plenty for most ground robots; 20 Hz+ for fast ones).
  • Heading, if you use a dual-antenna receiver, with the antenna baseline entered correctly.
  • Correct antenna positions relative to the receiver's reference point.

If it's not stable here, it won't be stable in ROS.

2. Pick and configure the driver

For Septentrio-based receivers, the maintained ROS 2 driver is septentrio_gnss_driver (available as a binary package for Humble and Jazzy). Other receiver families have their own drivers; the checklist below still applies.

Driver checklist:

  • [ ] Connection type and port match the receiver (USB, serial or TCP/IP). TCP over Ethernet avoids most serial bandwidth problems.
  • [ ] Output rate set explicitly, not left at default.
  • [ ] Publish the standard messages your stack needs: sensor_msgs/NavSatFix for position and, for dual-antenna, a heading/attitude output (pose or IMU-style orientation).
  • [ ] frame_id set to a real link in your URDF (for example gnss_link), not left as gnss with no transform.
  • [ ] Corrections: either configure NTRIP in the receiver itself (simplest) or feed RTCM from ROS. Don't do both.

3. URDF and TF: the step people skip

  • [ ] Add gnss_link to the URDF at the antenna phase centre position relative to base_link. A 40 cm lever arm on a robot that turns on the spot shows up as a 40 cm circle in your GNSS track.
  • [ ] For dual-antenna heading, note which antenna is "main" and the baseline direction. Add the mounting yaw offset to the driver or to your heading conversion.
  • [ ] Check with ros2 run tf2_ros tf2_echo base_link gnss_link.

4. Heading conventions

This catches almost everyone once:

  • GNSS heading: 0° = north, clockwise positive.
  • ROS (REP 103 / REP 105, ENU): yaw 0 = east, counter-clockwise positive.

So yaw (rad) = π/2 − heading (rad), wrapped to ±π. If your robot drives 90° off or mirrors left/right, this is why. robot_localization's navsat_transform also has a yaw_offset parameter for IMUs that report 0 at north.

5. Covariances must be honest

The EKF weighs inputs by their covariance, so:

  • [ ] Use the receiver's reported accuracy (RTK fixed ≈ 0.01–0.02 m horizontal), not zeros and not a fixed 1 m.
  • [ ] When the fix drops to float or standalone, the covariance must grow. A driver that keeps reporting 2 cm while in float will drag the filter off.
  • [ ] Never publish zero covariance; the filter treats it as infinite confidence.

6. robot_localization: dual EKF + navsat_transform

The standard pattern (see the robot_localization docs):

Node world_frame Inputs
ekf_local (odom) odom wheel odometry, IMU (continuous, no jumps)
ekf_global (map) map wheel odometry, IMU, GNSS odometry from navsat_transform
navsat_transform – NavSatFix, IMU/heading, filtered odometry → outputs odometry/gps

navsat_transform checklist:

  • [ ] magnetic_declination_radians: set for your site if your heading source is magnetic; 0 if it is true heading from dual-antenna GNSS.
  • [ ] yaw_offset: match your heading convention (see section 4).
  • [ ] zero_altitude: true for flat ground robots.
  • [ ] wait_for_datum / datum: fix the datum if you want repeatable map coordinates between runs (field robots, farm rows).
  • [ ] two_d_mode: true in both EKFs for ground robots.
  • [ ] Only fuse GNSS position (x, y) in ekf_global. Fuse heading from the IMU/dual-antenna source, not twice.

7. Nav2 with GPS waypoints

  • [ ] Global costmap in map frame, local in odom; rolling-window costmaps work well outdoors where there is no prior map.
  • [ ] Use the GPS waypoint following support in Nav2 (convert lat/lon to the map frame through navsat_transform's fromLL service) rather than converting coordinates yourself.
  • [ ] Tune xy_goal_tolerance to what RTK actually gives you; 0.1 m is realistic, 0.01 m is not with real motion.

8. Test like it will fail

  • [ ] Drive a known straight line or a surveyed point; compare.
  • [ ] Turn on the spot: the GNSS track should not draw a circle (lever arm check).
  • [ ] Drive under trees or next to a building: watch covariance grow and the EKF lean on odometry, without jumps when RTK comes back.
  • [ ] Log the raw receiver data alongside your rosbag. It makes post-analysis possible when something goes wrong.

Quick troubleshooting

Symptom Likely cause
Robot position jumps when RTK fixes Covariance too large in float / too small in fix; or datum reset
Heading 90° off or mirrored NED vs ENU convention, yaw_offset
GNSS track draws circles when turning Missing or wrong gnss_link lever arm
Drift in odom frame GNSS fused into the local EKF (it should only be in the map EKF)
Works in the open, fails near buildings Multipath; check fix type and let covariance rise

If you're choosing hardware for an outdoor robot, we've summarised receiver options for robots here: GNSS for robotics, and the HB56 is our compact RTK receiver used on ROS 2 robots.

Top comments (0)