The nav2 architecture
nav2 is a collection of ROS2 nodes that together handle: localization (AMCL), path planning (NavFn or Smac planner), path following (DWB controller), and recovery behaviors (spin, backup, wait). You configure it via a single large YAML params file.
amclAdaptive Monte Carlo Localization — estimates robot pose in the saved map using LIDAR
nav2_plannerGlobal planner — finds a path from current pose to goal pose in the static map
nav2_controllerLocal controller (DWB) — follows the global path while avoiding obstacles in real time
nav2_costmap_2dBuilds layered costmaps: static map layer + obstacle layer from LIDAR
nav2_bt_navigatorBehavior tree navigator — orchestrates the whole pipeline, handles recovery
Minimal nav2_params.yaml
yaml (nav2_params.yaml — key sections)
amcl:
ros__parameters:
use_sim_time: false
min_particles: 500
max_particles: 2000
laser_model_type: likelihood_field
max_beams: 60
update_min_d: 0.25 # meters
update_min_a: 0.2 # radians
resample_interval: 1
controller_server:
ros__parameters:
controller_frequency: 20.0
min_x_velocity_threshold: 0.001
min_theta_velocity_threshold: 0.001
FollowPath:
plugin: "nav2_dwb_controller::DWBLocalPlanner"
max_vel_x: 0.3 # m/s — match your robot's safe speed
max_vel_theta: 1.0 # rad/s
min_speed_xy: 0.0
acc_lim_x: 1.0
decel_lim_x: -2.0
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 5.0
publish_frequency: 2.0
width: 3
height: 3
resolution: 0.05
robot_radius: 0.175 # meters — half your robot width + margin
plugins: ["obstacle_layer", "inflation_layer"]
obstacle_layer:
plugin: "nav2_costmap_2d::ObstacleLayer"
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
clearing: true
marking: true
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
inflation_radius: 0.35 # keep robot away from wallsLaunch nav2 with AMCL
bash
# Assumes you have a saved map from slam_toolbox
ros2 launch nav2_bringup bringup_launch.py \
map:=/home/robot/maps/room.yaml \
params_file:=/home/robot/config/nav2_params.yaml \
use_sim_time:=false
# Open RViz2 for visualization
ros2 launch nav2_bringup rviz_launch.pySet initial pose and navigate
bash
# 1. In RViz2: use "2D Pose Estimate" tool to set initial location
# Click where the robot is on the map, drag to set heading.
# Watch AMCL particles converge — when clustered, localization is good.
# 2. Send a navigation goal from command line (for testing):
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \
"{pose: {header: {frame_id: map}, pose: {position: {x: 2.0, y: 1.5}, orientation: {w: 1.0}}}}"
# 3. In RViz2: use "Nav2 Goal" button to click-and-go.
# 4. Cancel navigation:
ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \
"{}" --cancel-after-secs 0Common nav2 issues
| Issue | Likely cause | Fix |
|---|---|---|
| Robot stuck at start | Footprint too large, hits first obstacle | Reduce robot_radius in costmap params |
| Goal rejected | Goal is inside obstacle or too close to wall | Use inflation_radius > robot half-width |
| AMCL not converging | Wrong initial pose or sparse map | Drive robot briefly to collect more scans |
| Oscillating near goal | DWB angular velocity too high | Reduce max_vel_theta to 0.5 rad/s |
| Path not found | Global costmap marks goal as occupied | Check map resolution matches params resolution |
| TF error on launch | Missing odom → base_link transform | Ensure motor driver publishes odometry or add static TF |
FULL STACK
You've covered the full pipeline: power → motors → sensors → ROS2 → SLAM → nav2. The Room-Nav kit is the reference build for this exact stack.
Build it: Room-Nav BOM →