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/NavSatFixfor position and, for dual-antenna, a heading/attitude output (pose or IMU-style orientation). - [ ]
frame_idset to a real link in your URDF (for examplegnss_link), not left asgnsswith 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_linkto the URDF at the antenna phase centre position relative tobase_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: truefor 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: truein 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
mapframe, local inodom; 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
fromLLservice) rather than converting coordinates yourself. - [ ] Tune
xy_goal_toleranceto 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)