Position in the full course
This is the sixth step of the course.
The full learning order is:
1. ROS 2 node/topic/launch review
2. TF2 frame concept
3. Static sensor frames
4. Dynamic odometry frame
5. URDF + robot_state_publisher
6. SLAM mapping
7. AMCL localization
8. Nav2 path planning
9. RViz goal navigation
10. TF2 debugging in Nav2
In Step 5, you used URDF and robot_state_publisher to publish robot structure frames:
base_link
βββ base_laser
βββ camera_link
βββ imu_link
βββ left_wheel_link
βββ right_wheel_link
In Step 6, you will use SLAM to build a map and add:
map β odom
After this step, your TF tree should become:
map
βββ odom
βββ base_link
βββ base_laser
βββ camera_link
βββ imu_link
βββ left_wheel_link
βββ right_wheel_link
1. Learning objectives
After this module, you should be able to:
- Explain what SLAM means.
- Explain why SLAM is needed before map-based navigation.
- Explain what
slam_toolboxdoes. - Understand the required inputs for 2D LiDAR SLAM.
- Check whether
/scan,/odom, and TF are available. - Launch
slam_toolbox. - Visualize
/map,/scan, and TF in RViz2. - Drive the robot to build a map.
- Save the generated map.
- Understand how the saved map will be used in AMCL localization.
2. Suggested teaching duration
Suggested duration: 2 hours
| Time | Activity |
| ----------- | ------------------------------------ |
| 0β15 min | Review TF tree from Step 5 |
| 15β30 min | Explain SLAM and occupancy grid maps |
| 30β45 min | Explain SLAM inputs and outputs |
| 45β65 min | Check /scan, /odom, and TF |
| 65β85 min | Launch slam_toolbox |
| 85β105 min | Build and visualize the map |
| 105β115 min | Save the map |
| 115β120 min | Summary and homework |
3. What is SLAM?
SLAM means:
Simultaneous Localization and Mapping
This means the robot does two things at the same time:
1. It estimates where it is.
2. It builds a map of the environment.
For example, when an AMR enters an unknown classroom, it does not already have a map.
The robot uses:
LiDAR scan
odometry
TF frames
to estimate its motion and build an occupancy grid map.
4. What is an occupancy grid map?
A 2D occupancy grid map divides the environment into small grid cells.
Each cell may represent:
free space
occupied space
unknown space
In RViz2, the map usually looks like:
white area: free space
black area: obstacle or wall
gray area: unknown space
The map is usually published on:
/map
The message type is:
nav_msgs/msg/OccupancyGrid
Later, Nav2 will use this map for localization and path planning.
5. What does slam_toolbox do?
slam_toolbox is a common ROS 2 package for 2D LiDAR SLAM.
In mapping mode, it usually does the following:
1. Subscribes to LiDAR scan data.
2. Uses odometry and TF to estimate robot movement.
3. Performs scan matching.
4. Builds a pose graph.
5. Detects loop closure when the robot revisits an area.
6. Publishes the map.
7. Publishes the transform map β odom.
For this course, you only need the beginner-level idea:
slam_toolboxuses/scanand odometry-related TF to create/mapandmap β odom.
6. Required inputs for SLAM mapping
Before launching SLAM, you should already have these items.
6.1 LiDAR scan topic
Usually:
/scan
Message type:
sensor_msgs/msg/LaserScan
Check it:
ros2 topic list
ros2 topic type /scan
ros2 topic echo /scan --once
If /scan does not exist, SLAM cannot build a 2D LiDAR map.
6.2 Odometry topic
Usually:
/odom
Message type:
nav_msgs/msg/Odometry
Check it:
ros2 topic list
ros2 topic type /odom
ros2 topic echo /odom --once
6.3 TF tree
You need at least:
odom β base_link
base_link β base_laser
The first transform tells SLAM how the robot moves.
The second transform tells SLAM where the LiDAR is mounted on the robot.
Check:
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser
If both transforms exist, the partial TF tree should be:
odom
βββ base_link
βββ base_laser
After SLAM starts, the TF tree should become:
map
βββ odom
βββ base_link
βββ base_laser
7. Important warning about Step 4 fake odometry
In Step 4, you created a fake odometry node.
That node is useful for learning TF2, but it is not enough for real SLAM unless you also have a real or simulated LiDAR scan.
For SLAM, you need:
real or simulated /scan
valid odom β base_link
valid base_link β base_laser
robot movement
So for classroom practice, the easiest option is usually:
Use a simulated robot that already publishes /scan and /odom.
For a real AMR, you should use:
real LiDAR driver
real wheel odometry
URDF or static TF for sensor frames
8. Install required packages
Open a terminal:
source /opt/ros/jazzy/setup.bash
Install SLAM Toolbox and Nav2 map server tools:
sudo apt update
sudo apt install ros-jazzy-slam-toolbox \
ros-jazzy-navigation2 \
ros-jazzy-nav2-bringup \
ros-jazzy-nav2-map-server \
ros-jazzy-rviz2 \
ros-jazzy-tf2-tools
If you use TurtleBot simulation for classroom practice, also install the minimal TurtleBot simulation packages:
sudo apt install ros-jazzy-nav2-minimal-tb*
9. Classroom practice option A: Use TurtleBot simulation
This is the recommended first classroom practice because it gives you:
simulated robot
simulated LiDAR
/scan topic
/odom topic
TF tree
RViz2
Gazebo
Terminal 1: Launch TurtleBot simulation with SLAM enabled
source /opt/ros/jazzy/setup.bash
ros2 launch nav2_bringup tb3_simulation_launch.py headless:=False slam:=True
This starts a simulation environment with SLAM.
In RViz2, you should see the robot, LiDAR scan, and map.
Terminal 2: Drive the robot
You can use teleoperation if available:
source /opt/ros/jazzy/setup.bash
ros2 run teleop_twist_keyboard teleop_twist_keyboard
If the package is missing, install it:
sudo apt install ros-jazzy-teleop-twist-keyboard
Drive the robot slowly around the environment.
You should see the map grow in RViz2.
10. Classroom practice option B: Launch slam_toolbox manually
If you already have your robot interface running, launch SLAM Toolbox manually.
You must already have:
LiDAR publishing /scan
Odometry publishing /odom
TF publishing odom β base_link
TF publishing base_link β base_laser
Launch SLAM Toolbox:
source /opt/ros/jazzy/setup.bash
ros2 launch slam_toolbox online_async_launch.py
If your installation does not provide online_async_launch.py, use:
ros2 launch slam_toolbox online_sync_launch.py
11. Verify SLAM output
Open a new terminal:
source /opt/ros/jazzy/setup.bash
Check the map topic:
ros2 topic list | grep map
ros2 topic type /map
ros2 topic echo /map --once
Expected type:
nav_msgs/msg/OccupancyGrid
Check the TF tree:
ros2 run tf2_tools view_frames
xdg-open frames.pdf
Expected TF tree:
map
βββ odom
βββ base_link
βββ base_laser
If you also use the URDF from Step 5, you may see:
map
βββ odom
βββ base_link
βββ base_laser
βββ camera_link
βββ imu_link
βββ left_wheel_link
βββ right_wheel_link
12. Configure RViz2 for SLAM mapping
Open RViz2:
rviz2
Set:
Fixed Frame = map
Add these displays:
Map
LaserScan
TF
RobotModel
Odometry
Recommended settings:
| Display | Setting |
| ---------- | -------------------------------- |
| Map | Topic: /map |
| LaserScan | Topic: /scan |
| TF | Show Names: enabled |
| RobotModel | Description: robot_description |
| Odometry | Topic: /odom |
You should observe:
The robot moves.
The LiDAR scan updates.
The map grows.
TF frames stay connected.
13. Mapping procedure
A good mapping procedure is more important than only launching SLAM.
Use this process:
Step 1: Start near a clear area
Start the robot in an open, recognizable area.
Avoid starting too close to walls or obstacles.
Step 2: Move slowly
Move slowly during mapping.
Good mapping movement:
slow forward motion
slow turns
smooth paths
return to previous areas
Bad mapping movement:
fast rotation
wheel slipping
sudden acceleration
hitting objects
moving people near the robot
Step 3: Cover the whole environment
Drive the robot along walls and corridors.
Try to observe all important areas.
For a classroom:
front area
back area
left wall
right wall
door area
teacher desk area
student desk area
Step 4: Create loop closure
A loop closure means the robot returns to an area it has already seen.
This helps SLAM correct accumulated error.
Example:
Start near the door.
Drive around the room.
Return near the door.
If loop closure works, the map usually becomes more consistent.
Step 5: Stop and inspect the map
Before saving the map, check:
Are the walls straight?
Are there duplicate walls?
Are important obstacles visible?
Is the map complete enough?
Is the robot still localized correctly?
14. Save the map
Create a folder for maps:
mkdir -p ~/ros2_ws/maps
Save the map:
ros2 run nav2_map_server map_saver_cli -f ~/ros2_ws/maps/classroom_map
This should create files similar to:
classroom_map.pgm
classroom_map.yaml
The .pgm file stores the map image.
The .yaml file stores map metadata, such as:
image file path
resolution
origin
occupied threshold
free threshold
15. Check the saved map files
Run:
ls ~/ros2_ws/maps
Expected result:
classroom_map.pgm
classroom_map.yaml
Open the image:
xdg-open ~/ros2_ws/maps/classroom_map.pgm
Open the YAML file:
cat ~/ros2_ws/maps/classroom_map.yaml
You should see something similar to:
image: classroom_map.pgm
mode: trinary
resolution: 0.05
origin: [0.0, 0.0, 0.0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.25
16. Create your own mapping launch package
This part is useful if you want a clean classroom package.
Create a package:
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python --license Apache-2.0 amr_mapping
Create folders:
cd ~/ros2_ws/src/amr_mapping
mkdir launch config maps
17. Create a simple SLAM launch file
Create:
nano launch/online_mapping.launch.py
Paste:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
package_name = 'amr_mapping'
slam_config = os.path.join(
get_package_share_directory(package_name),
'config',
'mapper_params_online_async.yaml'
)
slam_toolbox_node = Node(
package='slam_toolbox',
executable='async_slam_toolbox_node',
name='slam_toolbox',
output='screen',
parameters=[slam_config]
)
rviz_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen'
)
return LaunchDescription([
slam_toolbox_node,
rviz_node
])
This launch file starts:
slam_toolbox
rviz2
It assumes your robot interface is already running.
18. Create a SLAM parameter file
Create:
nano config/mapper_params_online_async.yaml
Paste this beginner configuration:
slam_toolbox:
ros__parameters:
use_sim_time: false
odom_frame: odom
map_frame: map
base_frame: base_link
scan_topic: /scan
mode: mapping
debug_logging: false
throttle_scans: 1
transform_publish_period: 0.02
map_update_interval: 1.0
resolution: 0.05
max_laser_range: 12.0
minimum_time_interval: 0.5
transform_timeout: 0.2
tf_buffer_duration: 30.0
stack_size_to_use: 40000000
use_scan_matching: true
use_scan_barycenter: true
minimum_travel_distance: 0.1
minimum_travel_heading: 0.1
do_loop_closing: true
Important settings:
| Parameter | Meaning |
| ----------------- | ---------------------------------- |
| odom_frame | Your odometry frame |
| map_frame | Your map frame |
| base_frame | Your robot body frame |
| scan_topic | Your LiDAR topic |
| resolution | Map resolution in meters per pixel |
| max_laser_range | Maximum LiDAR range used |
| do_loop_closing | Enables loop closure |
19. Modify setup.py
Open:
nano setup.py
Add imports at the top:
import os
from glob import glob
Modify data_files:
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
(os.path.join('share', package_name, 'config'), glob('config/*.yaml')),
(os.path.join('share', package_name, 'maps'), glob('maps/*')),
],
20. Modify package.xml
Open:
nano package.xml
Add dependencies before the closing </package> tag:
<exec_depend>slam_toolbox</exec_depend>
<exec_depend>rviz2</exec_depend>
<exec_depend>nav2_map_server</exec_depend>
<exec_depend>tf2_tools</exec_depend>
21. Build the mapping package
Run:
cd ~/ros2_ws
colcon build --packages-select amr_mapping
source install/setup.bash
22. Run your mapping launch file
Before running this launch file, make sure your robot interface is already running.
You need:
ros2 topic echo /scan --once
ros2 topic echo /odom --once
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser
Then run:
ros2 launch amr_mapping online_mapping.launch.py
In RViz2:
Set Fixed Frame to map.
Add Map, LaserScan, TF, RobotModel, and Odometry.
Drive the robot slowly and build the map.
23. Save the map after using your package
Save the map:
ros2 run nav2_map_server map_saver_cli -f ~/ros2_ws/maps/my_amr_map
Expected output files:
my_amr_map.pgm
my_amr_map.yaml
24. How Step 6 connects to Step 7
In Step 6, SLAM creates a map.
The map files are:
classroom_map.pgm
classroom_map.yaml
In Step 7, you will use the saved map for localization.
That means:
Step 6: Build and save the map.
Step 7: Load the saved map and localize the robot using AMCL.
During SLAM mapping, slam_toolbox provides:
map β odom
/map
During AMCL localization, AMCL will provide:
map β odom
So after Step 7 starts, you should not also run SLAM mapping at the same time unless you intentionally want navigation while mapping.
25. Common problems and solutions
Problem 1: /scan does not exist
Check:
ros2 topic list | grep scan
Possible reasons:
LiDAR driver is not running.
Simulation LiDAR plugin is not running.
LiDAR topic has a different name.
Solution:
Start the LiDAR driver or simulation.
Check the real scan topic name.
Change scan_topic in the SLAM parameter file if needed.
Problem 2: /map does not appear
Possible reasons:
slam_toolbox is not running.
No /scan data is received.
TF from odom to base_link is missing.
TF from base_link to base_laser is missing.
scan_topic parameter is wrong.
Check:
ros2 topic list
ros2 topic echo /scan --once
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser
Problem 3: RViz2 shows βNo transformβ
Possible reasons:
Fixed Frame is wrong.
map β odom is not published.
odom β base_link is missing.
base_link β base_laser is missing.
Frame names do not match the parameter file.
Solution:
Set Fixed Frame to map.
Check view_frames.
Check parameter names: map, odom, base_link, base_laser.
Problem 4: Map is distorted
Possible reasons:
Odometry is poor.
Robot moves too fast.
Wheel slipping occurs.
LiDAR frame is wrong.
LiDAR range setting is wrong.
Environment has many moving objects.
Solution:
Move slower.
Avoid moving people.
Check base_link β base_laser.
Improve odometry.
Try loop closure by returning to a known area.
Problem 5: Map is rotated or flipped
Possible reasons:
LiDAR frame orientation is wrong.
URDF sensor frame is wrong.
The LiDAR driver uses a different frame convention.
Check:
ros2 run tf2_ros tf2_echo base_link base_laser
Also inspect the LaserScan display in RViz2.
Problem 6: Saving map fails
Possible reasons:
nav2_map_server is not installed.
There is no /map topic.
The target folder does not exist.
Permission problem.
Solution:
sudo apt install ros-jazzy-nav2-map-server
mkdir -p ~/ros2_ws/maps
ros2 topic echo /map --once
ros2 run nav2_map_server map_saver_cli -f ~/ros2_ws/maps/test_map
26. Practice assignment
Assignment title
Build and Save a 2D Map Using slam_toolbox
Task 1: Check required topics and TF
Submit screenshots of:
ros2 topic list
ros2 topic echo /scan --once
ros2 topic echo /odom --once
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo base_link base_laser
Task 2: Launch SLAM
Run either:
ros2 launch nav2_bringup tb3_simulation_launch.py headless:=False slam:=True
or:
ros2 launch slam_toolbox online_async_launch.py
Submit a screenshot showing SLAM running.
Task 3: Build the map
Drive the robot slowly.
Submit a screenshot of RViz2 showing:
Map
LaserScan
TF
RobotModel
Task 4: Save the map
Run:
mkdir -p ~/ros2_ws/maps
ros2 run nav2_map_server map_saver_cli -f ~/ros2_ws/maps/classroom_map
Submit screenshots of:
ls ~/ros2_ws/maps
Expected files:
classroom_map.pgm
classroom_map.yaml
Task 5: Explain your map
Write a short explanation:
The scan topic is:
The odometry topic is:
The map topic is:
The map frame is:
The odom frame is:
The robot base frame is:
The LiDAR frame is:
The saved map files are:
One problem I observed during mapping was:
How I fixed or would fix it:
27. Mini quiz
- What does SLAM mean?
- What topic usually provides LiDAR data?
- What message type is used by
/scan? - What topic publishes the occupancy grid map?
- What message type is used by
/map? - What transform is added by SLAM?
- Why does SLAM need
odom β base_link? - Why does SLAM need
base_link β base_laser? - Why should the robot move slowly during mapping?
- Why is loop closure useful?
- What command saves the generated map?
- What two files are usually created when saving a map?
28. Key summary
In this module, you learned how to build a map using SLAM.
Before SLAM, you need:
LiDAR scan: /scan
Odometry: /odom
TF: odom β base_link
TF: base_link β base_laser
After SLAM starts, you should get:
Map topic: /map
TF: map β odom
Your TF tree becomes:
map
βββ odom
βββ base_link
βββ base_laser
If you also use the URDF model from Step 5, the tree becomes:
map
βββ odom
βββ base_link
βββ base_laser
βββ camera_link
βββ imu_link
βββ left_wheel_link
βββ right_wheel_link
You saved the map as:
classroom_map.pgm
classroom_map.yaml
In the next module, you will learn:
AMCL localization
The saved map from this module will be loaded in Step 7, and AMCL will localize the robot on that map.