Key takeaways
- Odometry is the foundation, and it is where most projects fail. If your map bends or drifts, fix wheel radius, wheelbase, encoder counts and IMU calibration before touching any SLAM parameter.
- Use ROS 2, not ROS 1. ROS 1 has reached end of life. ROS 2's DDS middleware also removes the single point of failure that the ROS 1 master represented.
- A 2D LiDAR sees one horizontal plane. Tables become four circles, descending stairs are invisible, and floor-level objects do not exist. Plan for supplementary sensing.
- Nav2 splits into layers: AMCL localises, the global planner routes, the local planner (DWB) emits velocity commands, and costmaps underpin both. The inflation radius is the parameter you will most often need to tune.
- Build in order: closed-loop motor control → encoder odometry → IMU fusion → SLAM → autonomous navigation. Each step depends on the previous one being correct.
What changed
Autonomous navigation used to be the preserve of well-funded research labs. Two things changed that. ROS 2 matured into a genuinely production-capable framework, and 360-degree LiDAR dropped to a price where a hobbyist can buy one.
The result is that a capable SLAM-driven robot can be built for a few hundred dollars in parts. What has not changed is the engineering. The parts are cheap; making them work together reliably is still the hard part, and that is what this article is about.
The hardware stack
Our reference platform in the lab uses parts that are widely available and well supported, which matters more than raw specification when you are debugging at midnight:
COMPUTE & SENSING
Raspberry Pi 4 (4 GB), RPLidar A2 for 360° scanning, MPU6050 IMU for orientation, quadrature encoders on both drive wheels
ACTUATION & POWER
Dual DC gear motors, L298N or a MOSFET H-bridge driver, 12 V Li-Po pack with a regulated 5 V rail for logic
The software side is Ubuntu 22.04 with ROS 2 Humble, Nav2 for the navigation stack, and either Cartographer or slam_toolbox for mapping.
One decision here is worth dwelling on. The Raspberry Pi is not a real-time device, and it should not be closing your motor control loop. We put a microcontroller between the Pi and the motors: the Pi decides where to go, and the microcontroller handles the PID loop that makes the wheels actually turn at the commanded velocity. This is the same separation-of-concerns argument that governs MCU selection, and it applies just as strongly here.
Odometry is the foundation, and it is where projects fail
Before SLAM, before Nav2, before any of the interesting parts, the robot must know how far it has moved. This is odometry, and getting it wrong poisons everything downstream.
Wheel encoders give you distance travelled per wheel, from which you derive position and heading. The problem is that wheels slip, carpet compresses, and your measured wheel radius is never quite the real wheel radius. These errors do not cancel out. They accumulate, and after a few minutes of driving your robot's belief about its position has drifted meaningfully from reality.
This is why the IMU is not optional. Fusing gyroscope data with wheel odometry corrects the heading errors that wheel slip introduces, and heading errors are the ones that hurt most, because a small angular error becomes a large positional error as the robot drives away from it.
If your map comes out bent or the robot slowly rotates its world view, do not start tuning SLAM parameters. Measure your wheel radius and wheelbase precisely, verify your encoder counts per revolution, and calibrate the IMU. The overwhelming majority of "SLAM is broken" reports are actually "odometry is wrong".
SLAM: building a map while you are lost in it
Simultaneous localisation and mapping solves a genuinely circular problem: to build a map you need to know where you are, and to know where you are you need a map. SLAM resolves this by estimating both at once and letting each refine the other.
In practice, the LiDAR produces a scan of the surrounding geometry many times per second. Scan matching compares the current scan against the accumulating map and asks: given what I expected to see from where I think I am, and given what I actually see, where must I really be? That correction gets folded back into the pose estimate, and the corrected scan is added to the map.
The 2D LiDAR sees a single horizontal plane. This is worth internalising, because it is the source of a whole class of confusing failures. A table with a thin top and legs appears to the robot as four small circles. A staircase going down is invisible. An object lying on the floor below the scan plane does not exist. Your robot will confidently drive into things that it cannot, by construction, see.
The mitigation is additional sensing: bump sensors as a last resort, downward-facing sensors for drop-offs, and a depth camera if you genuinely need to perceive three-dimensional obstacles.
The Nav2 architecture
Nav2 splits navigation into layers, and understanding the split makes the whole thing far easier to debug.
- Localisation (AMCL) answers "where am I on the known map?" using a particle filter that maintains many hypotheses about the robot's pose and lets sensor data kill off the wrong ones.
- The global planner computes a route from here to the goal across the whole map. It is strategic and it does not care about the obstacle that just walked in front of you.
- The local planner (DWB, in the default configuration) generates the actual velocity commands, continuously, taking into account the robot's kinematics and whatever the sensors are seeing right now.
- Costmaps underpin both planners: a global costmap from the static map, and a local costmap that updates in real time from live sensor data.
The inflation radius on the costmap is the parameter that most commonly needs attention. Set it too small and the robot clips corners and scrapes walls. Set it too large and it refuses to enter doorways it can physically fit through, reporting that no valid path exists. Tune it against your robot's actual footprint plus a sensible margin.
Things that will go wrong
Some failure modes are common enough to name in advance, because recognising them saves days.
The robot spins in place before moving. Usually a mismatch between the robot's declared kinematics and its real ones, or a local planner configured for a holonomic base when your robot is differential drive.
Localisation jumps suddenly. The particle filter has found a better hypothesis somewhere else. This happens in environments with repetitive geometry, such as a long corridor of identical doors, where the LiDAR scan is genuinely ambiguous. More distinct features, or better odometry to constrain the filter, is the answer.
The robot stops and refuses to plan. Check whether the goal is inside the inflation radius of an obstacle. From Nav2's point of view you have asked it to drive into a wall.
Everything works in simulation and fails on the real robot. The gap is almost always odometry and sensor noise, neither of which the simulator models honestly by default. Simulation is excellent for validating logic and useless for validating calibration.
Where to start
Build it in this order, and resist the temptation to skip ahead. Get the motors turning under closed-loop velocity control. Get encoder odometry publishing and verify it by driving a measured two metres and checking what the robot thinks it did. Fuse the IMU and verify heading. Only then bring up the LiDAR and SLAM. Only after you have a clean, non-drifting map should you attempt autonomous navigation.
Every step in that sequence depends on the one before it being correct. Teams that jump straight to Nav2 because that is the exciting part spend weeks debugging a navigation stack that was never going to work, because the robot underneath it did not know where it was.
Sources and further reading
Primary references for the standards, regulations and figures cited above:
- ROS 2 Humble documentation — Official ROS 2 documentation, including DDS middleware and the ROS 1 end-of-life notice.
- Nav2 — ROS 2 Navigation Stack — Official reference for AMCL, the DWB local planner, costmaps and inflation radius.
- slam_toolbox — The 2D SLAM package most widely used with ROS 2.
- Cartographer SLAM — Google's real-time SLAM library, an alternative mapping backend.