【ROS1从入门到精通】第3篇:创建工作空间与功能包(从零开始的ROS项目)
🎯 本文目标:掌握ROS工作空间的创建与管理,学会创建和配置功能包,理解catkin编译系统的工作原理,能够独立搭建ROS项目框架。
📑 目录
1. 工作空间概述
1.1 什么是ROS工作空间?
ROS工作空间(Workspace)是组织和管理ROS项目的基本单元。它是一个包含ROS功能包的文件夹,提供了统一的编译环境和依赖管理机制。
工作空间 catkin_ws src 源代码 build 编译空间 devel 开发空间 install 安装空间 功能包1 功能包2 功能包N 源代码文件 配置文件 启动文件
1.2 工作空间的作用
- 代码组织:将相关的功能包组织在一起
- 依赖管理:自动处理功能包之间的依赖关系
- 编译管理:统一的编译环境和构建系统
- 环境隔离:不同工作空间之间相互独立
- 版本控制:方便进行代码版本管理
1.3 工作空间结构详解
bash
catkin_ws/ # 工作空间根目录
├── src/ # 源代码空间(放置功能包)
│ ├── CMakeLists.txt # 顶层CMake文件(自动生成)
│ ├── package_1/ # 功能包1
│ ├── package_2/ # 功能包2
│ └── ...
├── build/ # 编译空间(自动生成)
│ ├── CMakeCache.txt
│ ├── CMakeFiles/
│ └── ...
├── devel/ # 开发空间(自动生成)
│ ├── setup.bash # 环境配置脚本
│ ├── lib/ # 编译生成的库文件
│ └── ...
└── install/ # 安装空间(可选)
├── setup.bash
└── ...
2. 创建Catkin工作空间
2.1 Catkin编译系统简介
Catkin是ROS的官方编译系统,基于CMake构建,提供了更加便捷的包管理和依赖处理机制。
Catkin vs Rosbuild对比:
| 特性 | Catkin | Rosbuild(已废弃) |
|---|---|---|
| 基础 | CMake | CMake + 自定义脚本 |
| 编译速度 | 快(并行编译) | 慢(串行编译) |
| 依赖管理 | 自动解析 | 手动指定 |
| 安装支持 | 完整支持 | 有限支持 |
| ROS2兼容 | 是 | 否 |
2.2 创建工作空间步骤
bash
# 1. 创建工作空间目录
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/
# 2. 初始化工作空间
cd src
catkin_init_workspace
# 3. 编译工作空间(即使没有功能包也需要初次编译)
cd ~/catkin_ws/
catkin_make
# 4. 配置环境变量
source devel/setup.bash
# 5. 验证工作空间
echo $ROS_PACKAGE_PATH
2.3 环境变量配置
为了每次打开终端都能使用工作空间,需要将环境配置添加到.bashrc:
bash
# 编辑.bashrc文件
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc
# 验证配置
roscd # 应该进入工作空间的devel目录
2.4 多工作空间管理
ROS支持同时使用多个工作空间,通过overlay机制实现:
bash
# 工作空间叠加顺序示例
source /opt/ros/noetic/setup.bash # 底层:ROS系统
source ~/catkin_ws/devel/setup.bash # 中层:主工作空间
source ~/overlay_ws/devel/setup.bash # 顶层:覆盖工作空间
3. 功能包基础
3.1 功能包的概念
功能包(Package)是ROS中软件组织的基本单元,包含了节点、库、配置文件、启动文件等。每个功能包都是一个独立的项目,可以单独编译和发布。
3.2 功能包的结构
标准ROS功能包结构:
my_package/
├── CMakeLists.txt # CMake编译配置
├── package.xml # 功能包清单文件
├── src/ # C++源代码
│ ├── node1.cpp
│ └── node2.cpp
├── scripts/ # Python脚本
│ ├── script1.py
│ └── script2.py
├── include/my_package/ # C++头文件
│ └── my_header.h
├── launch/ # 启动文件
│ └── my_launch.launch
├── config/ # 配置文件
│ └── params.yaml
├── msg/ # 自定义消息
│ └── MyMessage.msg
├── srv/ # 自定义服务
│ └── MyService.srv
├── action/ # 自定义动作
│ └── MyAction.action
├── urdf/ # 机器人模型文件
│ └── robot.urdf
└── README.md # 说明文档
3.3 功能包命名规范
- 小写字母:全部使用小写字母
- 下划线分隔:使用下划线分隔单词(如:my_robot_controller)
- 语义清晰:名称应反映功能包的用途
- 避免冲突:检查是否与已有包重名
4. 创建第一个功能包
4.1 使用catkin_create_pkg创建
bash
# 进入工作空间的src目录
cd ~/catkin_ws/src
# 创建功能包(指定依赖)
catkin_create_pkg my_first_package std_msgs rospy roscpp
# 查看创建的功能包结构
tree my_first_package/
4.2 手动创建功能包
有时需要手动创建功能包以获得更多控制:
bash
# 1. 创建功能包目录
mkdir -p ~/catkin_ws/src/manual_package
# 2. 创建package.xml
cat > ~/catkin_ws/src/manual_package/package.xml << 'EOF'
<?xml version="1.0"?>
<package format="2">
<name>manual_package</name>
<version>0.1.0</version>
<description>手动创建的ROS功能包</description>
<maintainer email="you@example.com">Your Name</maintainer>
<license>MIT</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
</package>
EOF
# 3. 创建CMakeLists.txt
cat > ~/catkin_ws/src/manual_package/CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.0.2)
project(manual_package)
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
)
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs
)
include_directories(
${catkin_INCLUDE_DIRS}
)
EOF
4.3 编写第一个节点
创建一个简单的发布者节点(C++版本):
cpp
// src/hello_publisher.cpp
#include <ros/ros.h>
#include <std_msgs/String.h>
#include <sstream>
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "hello_publisher");
ros::NodeHandle nh;
// 创建发布者
ros::Publisher pub = nh.advertise<std_msgs::String>("hello_topic", 10);
// 设置循环频率
ros::Rate loop_rate(1); // 1Hz
int count = 0;
while (ros::ok())
{
// 创建消息
std_msgs::String msg;
std::stringstream ss;
ss << "Hello ROS! Count: " << count++;
msg.data = ss.str();
// 发布消息
ROS_INFO("%s", msg.data.c_str());
pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
Python版本:
python
#!/usr/bin/env python
# scripts/hello_publisher.py
import rospy
from std_msgs.msg import String
def publisher():
# 初始化节点
rospy.init_node('hello_publisher', anonymous=True)
# 创建发布者
pub = rospy.Publisher('hello_topic', String, queue_size=10)
# 设置频率
rate = rospy.Rate(1) # 1Hz
count = 0
while not rospy.is_shutdown():
# 创建消息
msg = String()
msg.data = f"Hello ROS! Count: {count}"
# 发布消息
rospy.loginfo(msg.data)
pub.publish(msg)
count += 1
rate.sleep()
if __name__ == '__main__':
try:
publisher()
except rospy.ROSInterruptException:
pass
5. 功能包配置详解
5.1 package.xml配置
package.xml是功能包的清单文件,包含了功能包的元信息和依赖关系:
xml
<?xml version="1.0"?>
<package format="2">
<!-- 基本信息 -->
<name>my_robot_package</name>
<version>1.0.0</version>
<description>机器人控制功能包</description>
<!-- 维护者信息 -->
<maintainer email="maintainer@example.com">张三</maintainer>
<!-- 许可证 -->
<license>BSD</license>
<!-- 项目信息 -->
<url type="website">http://wiki.ros.org/my_robot_package</url>
<url type="repository">https://github.com/user/my_robot_package</url>
<url type="bugtracker">https://github.com/user/my_robot_package/issues</url>
<!-- 作者信息 -->
<author email="author@example.com">李四</author>
<!-- 编译工具依赖 -->
<buildtool_depend>catkin</buildtool_depend>
<!-- 编译依赖 -->
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>sensor_msgs</build_depend>
<build_depend>geometry_msgs</build_depend>
<build_depend>message_generation</build_depend>
<!-- 运行依赖 -->
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>message_runtime</exec_depend>
<!-- 测试依赖 -->
<test_depend>rostest</test_depend>
<test_depend>gtest</test_depend>
<!-- 导出信息 -->
<export>
<!-- 可以添加插件等导出信息 -->
</export>
</package>
5.2 CMakeLists.txt配置
CMakeLists.txt定义了功能包的编译规则:
cmake
cmake_minimum_required(VERSION 3.0.2)
project(my_robot_package)
## 编译选项
add_compile_options(-std=c++11)
## 查找依赖包
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
sensor_msgs
geometry_msgs
message_generation
)
## 系统依赖
find_package(Boost REQUIRED COMPONENTS system thread)
find_package(Eigen3 REQUIRED)
## Python模块支持
# catkin_python_setup()
## 消息/服务/动作文件
add_message_files(
FILES
RobotStatus.msg
SensorData.msg
)
add_service_files(
FILES
GetPosition.srv
SetVelocity.srv
)
add_action_files(
FILES
Navigate.action
)
## 生成消息、服务和动作
generate_messages(
DEPENDENCIES
std_msgs
sensor_msgs
geometry_msgs
)
## 声明catkin包
catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}_lib
CATKIN_DEPENDS
roscpp
rospy
std_msgs
sensor_msgs
geometry_msgs
message_runtime
DEPENDS Boost EIGEN3
)
## 包含目录
include_directories(
include
${catkin_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
${EIGEN3_INCLUDE_DIRS}
)
## 编译库
add_library(${PROJECT_NAME}_lib
src/robot_controller.cpp
src/sensor_processor.cpp
)
add_dependencies(${PROJECT_NAME}_lib
${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS}
)
target_link_libraries(${PROJECT_NAME}_lib
${catkin_LIBRARIES}
${Boost_LIBRARIES}
)
## 编译可执行文件
add_executable(robot_node src/robot_node.cpp)
add_dependencies(robot_node
${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS}
)
target_link_libraries(robot_node
${PROJECT_NAME}_lib
${catkin_LIBRARIES}
)
add_executable(sensor_node src/sensor_node.cpp)
add_dependencies(sensor_node
${${PROJECT_NAME}_EXPORTED_TARGETS}
${catkin_EXPORTED_TARGETS}
)
target_link_libraries(sensor_node
${PROJECT_NAME}_lib
${catkin_LIBRARIES}
)
## 安装规则
install(TARGETS ${PROJECT_NAME}_lib robot_node sensor_node
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
install(DIRECTORY include/${PROJECT_NAME}/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
)
install(DIRECTORY launch/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
)
install(DIRECTORY config/
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/config
)
## 测试
if(CATKIN_ENABLE_TESTING)
find_package(rostest REQUIRED)
add_rostest_gtest(${PROJECT_NAME}_test
test/test_robot.launch
test/test_robot.cpp
)
target_link_libraries(${PROJECT_NAME}_test
${PROJECT_NAME}_lib
${catkin_LIBRARIES}
)
endif()
6. 编译系统深入
6.1 Catkin编译选项
bash
# 基本编译
catkin_make
# 指定编译特定功能包
catkin_make --pkg package_name
# 并行编译(使用所有CPU核心)
catkin_make -j$(nproc)
# 限制并行任务数
catkin_make -j4
# 清理编译
catkin_make clean
# 调试模式编译
catkin_make -DCMAKE_BUILD_TYPE=Debug
# 发布模式编译(优化)
catkin_make -DCMAKE_BUILD_TYPE=Release
# 安装到指定目录
catkin_make install -DCMAKE_INSTALL_PREFIX=/custom/path
6.2 catkin_tools高级编译
catkin_tools提供了更强大的编译管理功能:
bash
# 安装catkin_tools
sudo apt-get install python3-catkin-tools
# 初始化工作空间
catkin init
# 配置编译选项
catkin config --extend /opt/ros/noetic
catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release
# 编译所有包
catkin build
# 编译特定包
catkin build package_name
# 清理编译
catkin clean
# 查看配置
catkin config
# 列出所有包
catkin list
# 运行测试
catkin test
6.3 编译缓存和优化
bash
# 使用ccache加速编译
sudo apt-get install ccache
# 配置ccache
catkin config --cmake-args -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
# 查看ccache统计
ccache -s
7. 工作空间管理技巧
7.1 工作空间切换脚本
创建快速切换工作空间的脚本:
bash
# ~/.ros_workspaces
#!/bin/bash
# 定义工作空间别名
alias ws_main='source ~/catkin_ws/devel/setup.bash && echo "Switched to main workspace"'
alias ws_dev='source ~/dev_ws/devel/setup.bash && echo "Switched to dev workspace"'
alias ws_test='source ~/test_ws/devel/setup.bash && echo "Switched to test workspace"'
# 显示当前工作空间
function current_ws() {
echo "Current ROS_PACKAGE_PATH:"
echo $ROS_PACKAGE_PATH | tr ':' '\n'
}
# 列出所有工作空间
function list_ws() {
echo "Available workspaces:"
find ~ -maxdepth 3 -name "devel" -type d 2>/dev/null | \
while read dir; do
ws_dir=$(dirname $dir)
if [ -f "$dir/setup.bash" ]; then
echo " - $ws_dir"
fi
done
}
7.2 依赖管理工具
使用rosdep管理系统依赖:
bash
# 初始化rosdep
sudo rosdep init
rosdep update
# 安装功能包的所有依赖
cd ~/catkin_ws
rosdep install --from-paths src --ignore-src -r -y
# 检查缺失的依赖
rosdep check --from-paths src --ignore-src
# 生成依赖列表
rosdep keys --from-paths src --ignore-src
7.3 版本控制最佳实践
bash
# .gitignore for ROS workspace
cat > ~/catkin_ws/.gitignore << 'EOF'
# 编译产物
build/
devel/
install/
# IDE配置
.vscode/
.idea/
*.swp
*.swo
*~
# Python
*.pyc
__pycache__/
# 日志文件
*.log
.ros/
# 核心转储文件
core
# Qt生成文件
*.autosave
ui_*.h
EOF
# 使用vcstool管理多个仓库
sudo apt-get install python3-vcstool
# 创建仓库配置文件
cat > ~/catkin_ws/src/.repos << 'EOF'
repositories:
package1:
type: git
url: https://github.com/user/package1.git
version: main
package2:
type: git
url: https://github.com/user/package2.git
version: develop
EOF
# 导入所有仓库
vcs import ~/catkin_ws/src < ~/catkin_ws/src/.repos
# 更新所有仓库
vcs pull ~/catkin_ws/src
# 查看状态
vcs status ~/catkin_ws/src
8. 实战项目:机器人控制包
让我们创建一个完整的机器人控制功能包,包含传感器数据处理、运动控制和状态监控。
8.1 项目结构设计
robot_control/
├── CMakeLists.txt
├── package.xml
├── src/
│ ├── motion_controller.cpp # 运动控制节点
│ ├── sensor_processor.cpp # 传感器处理节点
│ └── status_monitor.cpp # 状态监控节点
├── scripts/
│ ├── teleop_keyboard.py # 键盘控制脚本
│ └── data_recorder.py # 数据记录脚本
├── include/robot_control/
│ ├── motion_controller.h
│ ├── sensor_processor.h
│ └── common.h
├── launch/
│ ├── robot_control.launch # 主启动文件
│ ├── simulation.launch # 仿真启动文件
│ └── hardware.launch # 硬件启动文件
├── config/
│ ├── robot_params.yaml # 机器人参数
│ ├── controller_config.yaml # 控制器配置
│ └── sensor_config.yaml # 传感器配置
├── msg/
│ ├── RobotState.msg # 机器人状态消息
│ └── ControlCommand.msg # 控制命令消息
├── srv/
│ ├── SetMode.srv # 设置模式服务
│ └── GetStatus.srv # 获取状态服务
└── test/
├── test_controller.cpp # 控制器测试
└── test_sensor.py # 传感器测试
8.2 实现运动控制节点
cpp
// include/robot_control/motion_controller.h
#ifndef MOTION_CONTROLLER_H
#define MOTION_CONTROLLER_H
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <nav_msgs/Odometry.h>
#include <tf2_ros/transform_broadcaster.h>
#include <tf2/LinearMath/Quaternion.h>
class MotionController {
public:
MotionController(ros::NodeHandle& nh);
void run();
private:
void cmdVelCallback(const geometry_msgs::Twist::ConstPtr& msg);
void updateOdometry();
void publishTransform();
ros::NodeHandle nh_;
ros::Subscriber cmd_vel_sub_;
ros::Publisher odom_pub_;
tf2_ros::TransformBroadcaster tf_broadcaster_;
// 机器人状态
double x_, y_, theta_;
double vx_, vy_, vtheta_;
// 参数
double wheel_base_;
double wheel_radius_;
double max_linear_vel_;
double max_angular_vel_;
ros::Time last_time_;
};
#endif
cpp
// src/motion_controller.cpp
#include "robot_control/motion_controller.h"
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
MotionController::MotionController(ros::NodeHandle& nh)
: nh_(nh), x_(0), y_(0), theta_(0), vx_(0), vy_(0), vtheta_(0) {
// 加载参数
nh_.param("wheel_base", wheel_base_, 0.5);
nh_.param("wheel_radius", wheel_radius_, 0.1);
nh_.param("max_linear_vel", max_linear_vel_, 1.0);
nh_.param("max_angular_vel", max_angular_vel_, 1.0);
// 订阅和发布
cmd_vel_sub_ = nh_.subscribe("cmd_vel", 10,
&MotionController::cmdVelCallback, this);
odom_pub_ = nh_.advertise<nav_msgs::Odometry>("odom", 50);
last_time_ = ros::Time::now();
}
void MotionController::cmdVelCallback(const geometry_msgs::Twist::ConstPtr& msg) {
// 限制速度
vx_ = std::min(std::max(msg->linear.x, -max_linear_vel_), max_linear_vel_);
vy_ = std::min(std::max(msg->linear.y, -max_linear_vel_), max_linear_vel_);
vtheta_ = std::min(std::max(msg->angular.z, -max_angular_vel_), max_angular_vel_);
}
void MotionController::updateOdometry() {
ros::Time current_time = ros::Time::now();
double dt = (current_time - last_time_).toSec();
// 更新位置(简化的运动模型)
double delta_x = (vx_ * cos(theta_) - vy_ * sin(theta_)) * dt;
double delta_y = (vx_ * sin(theta_) + vy_ * cos(theta_)) * dt;
double delta_theta = vtheta_ * dt;
x_ += delta_x;
y_ += delta_y;
theta_ += delta_theta;
// 发布里程计
nav_msgs::Odometry odom;
odom.header.stamp = current_time;
odom.header.frame_id = "odom";
odom.child_frame_id = "base_link";
// 设置位置
odom.pose.pose.position.x = x_;
odom.pose.pose.position.y = y_;
odom.pose.pose.position.z = 0.0;
// 设置方向
tf2::Quaternion q;
q.setRPY(0, 0, theta_);
odom.pose.pose.orientation = tf2::toMsg(q);
// 设置速度
odom.twist.twist.linear.x = vx_;
odom.twist.twist.linear.y = vy_;
odom.twist.twist.angular.z = vtheta_;
odom_pub_.publish(odom);
last_time_ = current_time;
}
void MotionController::publishTransform() {
geometry_msgs::TransformStamped transform;
transform.header.stamp = ros::Time::now();
transform.header.frame_id = "odom";
transform.child_frame_id = "base_link";
transform.transform.translation.x = x_;
transform.transform.translation.y = y_;
transform.transform.translation.z = 0.0;
tf2::Quaternion q;
q.setRPY(0, 0, theta_);
transform.transform.rotation = tf2::toMsg(q);
tf_broadcaster_.sendTransform(transform);
}
void MotionController::run() {
ros::Rate rate(50); // 50Hz
while (ros::ok()) {
updateOdometry();
publishTransform();
ros::spinOnce();
rate.sleep();
}
}
int main(int argc, char** argv) {
ros::init(argc, argv, "motion_controller");
ros::NodeHandle nh("~");
MotionController controller(nh);
controller.run();
return 0;
}
8.3 配置文件
yaml
# config/robot_params.yaml
robot:
# 机械参数
wheel_base: 0.5 # 轮距(米)
wheel_radius: 0.1 # 轮半径(米)
robot_radius: 0.25 # 机器人半径(米)
# 运动限制
max_linear_vel: 1.0 # 最大线速度(米/秒)
max_angular_vel: 1.0 # 最大角速度(弧度/秒)
max_acceleration: 0.5 # 最大加速度(米/秒²)
# 传感器配置
sensors:
lidar:
enabled: true
frame_id: "laser"
topic: "/scan"
range_min: 0.15
range_max: 12.0
camera:
enabled: true
frame_id: "camera"
topic: "/image_raw"
width: 640
height: 480
fps: 30
imu:
enabled: true
frame_id: "imu"
topic: "/imu/data"
rate: 100
# 控制器参数
controller:
type: "pid"
pid:
linear:
p: 1.0
i: 0.1
d: 0.05
angular:
p: 2.0
i: 0.2
d: 0.1
8.4 启动文件
xml
<!-- launch/robot_control.launch -->
<launch>
<!-- 加载参数 -->
<rosparam file="$(find robot_control)/config/robot_params.yaml" command="load" />
<!-- 运动控制节点 -->
<node name="motion_controller" pkg="robot_control" type="motion_controller" output="screen">
<remap from="cmd_vel" to="/robot/cmd_vel" />
<remap from="odom" to="/robot/odom" />
</node>
<!-- 传感器处理节点 -->
<node name="sensor_processor" pkg="robot_control" type="sensor_processor" output="screen">
<param name="sensor_frame" value="base_link" />
<param name="publish_rate" value="10" />
</node>
<!-- 状态监控节点 -->
<node name="status_monitor" pkg="robot_control" type="status_monitor" output="screen">
<param name="monitor_rate" value="1" />
<param name="enable_diagnostics" value="true" />
</node>
<!-- 键盘控制(可选) -->
<node name="teleop_keyboard" pkg="robot_control" type="teleop_keyboard.py"
output="screen" if="$(arg teleop)">
<remap from="cmd_vel" to="/robot/cmd_vel" />
</node>
<!-- RViz可视化(可选) -->
<node name="rviz" pkg="rviz" type="rviz"
args="-d $(find robot_control)/config/robot.rviz"
if="$(arg rviz)" />
<!-- 记录数据(可选) -->
<node name="rosbag_record" pkg="rosbag" type="record"
args="-a -O $(find robot_control)/bags/robot_data.bag"
if="$(arg record)" />
</launch>
8.5 自定义消息
# msg/RobotState.msg
# 机器人状态消息定义
Header header
# 位置信息
geometry_msgs/Pose pose
geometry_msgs/Twist velocity
# 传感器状态
bool lidar_ok
bool camera_ok
bool imu_ok
# 系统状态
float32 battery_voltage
float32 cpu_usage
float32 memory_usage
# 运行模式
string mode # MANUAL, AUTO, EMERGENCY
# srv/SetMode.srv
# 设置运行模式服务
# 请求
string mode # MANUAL, AUTO, EMERGENCY
---
# 响应
bool success
string message
9. 常见问题与解决方案
9.1 编译错误处理
问题1:找不到头文件
fatal error: xxx.h: No such file or directory
解决方案:
cmake
# 在CMakeLists.txt中添加
include_directories(
include
${catkin_INCLUDE_DIRS}
/path/to/custom/include # 添加自定义路径
)
问题2:链接错误
undefined reference to `xxx::xxx()'
解决方案:
cmake
# 确保正确链接库
target_link_libraries(your_node
${catkin_LIBRARIES}
your_custom_library
)
问题3:依赖包未找到
Could not find a package configuration file provided by "xxx"
解决方案:
bash
# 安装缺失的包
sudo apt-get install ros-noetic-xxx
# 或使用rosdep
rosdep install --from-paths src --ignore-src -r -y
9.2 运行时错误
问题1:节点名称冲突
ERROR: Unable to contact my own server at [http://xxx:xxx/].
解决方案:
python
# Python中使用anonymous参数
rospy.init_node('node_name', anonymous=True)
cpp
// C++中添加时间戳
std::string node_name = "node_" + std::to_string(ros::Time::now().toSec());
ros::init(argc, argv, node_name);
问题2:话题类型不匹配
[ERROR]: Client [/xxx] wants topic /xxx to have datatype/md5sum [...], but our version has [...]
解决方案:
- 确保发布者和订阅者使用相同的消息类型
- 重新编译所有相关功能包
- 清理工作空间并重新编译
9.3 性能优化
优化编译时间
bash
# 使用ninja替代make
sudo apt-get install ninja-build
catkin config --cmake-args -GNinja
# 启用编译缓存
catkin config --cmake-args -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
减少内存使用
cpp
// 使用智能指针管理内存
typedef boost::shared_ptr<sensor_msgs::PointCloud2> PointCloudPtr;
// 避免不必要的拷贝
void callback(const sensor_msgs::PointCloud2::ConstPtr& msg) {
// 直接使用const指针,避免拷贝
}
10. 总结与展望
10.1 本文总结
通过本文的学习,你已经掌握了:
- ✅ 工作空间管理:创建、配置和管理Catkin工作空间
- ✅ 功能包开发:从零开始创建功能包并进行配置
- ✅ 编译系统:深入理解CMake和Catkin编译机制
- ✅ 项目组织:合理组织代码结构和依赖关系
- ✅ 实战经验:完整的机器人控制包开发流程
10.2 知识要点回顾
| 概念 | 要点 | 实践建议 |
|---|---|---|
| 工作空间 | src/build/devel三层结构 | 使用overlay管理多个工作空间 |
| 功能包 | ROS软件的基本单元 | 遵循命名规范,保持功能单一 |
| CMakeLists | 定义编译规则 | 正确配置依赖和链接关系 |
| package.xml | 声明包信息和依赖 | 区分build和exec依赖 |
| catkin_make | 基础编译工具 | 大项目推荐使用catkin build |
| rosdep | 依赖管理工具 | 定期更新,自动安装依赖 |
10.3 下一步学习
在下一篇文章中,我们将深入学习:
- 话题通信机制:Publisher/Subscriber详细实现
- 消息传递原理:序列化、缓冲区、QoS配置
- 实时性优化:减少延迟,提高吞吐量
- 调试工具使用:rostopic、rosmsg等工具详解
10.4 扩展阅读
作者 :机器人开发工程师
最后更新 :2024年12月
版权声明:本文为原创文章,转载请注明出处
💡 学习建议:本文内容较多,建议分阶段学习。先掌握基本的工作空间和功能包创建,再深入学习CMake配置和高级特性。记住,实践是最好的老师!
附录:快速参考
A. 常用命令速查
bash
# 工作空间操作
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws && catkin_make
source devel/setup.bash
# 功能包操作
catkin_create_pkg pkg_name dep1 dep2
rospack list
rospack find package_name
roscd package_name
# 编译命令
catkin_make
catkin_make --pkg package_name
catkin build package_name
catkin clean
# 依赖管理
rosdep install --from-paths src --ignore-src -r -y
rosdep check --from-paths src
# 环境变量
echo $ROS_PACKAGE_PATH
printenv | grep ROS
下一篇预告:《【ROS1从入门到精通】第4篇:话题通信详解(Publisher/Subscriber实战)》
敬请期待!