ROS1从入门到精通 3:创建工作空间与功能包(从零开始的ROS项目)

【ROS1从入门到精通】第3篇:创建工作空间与功能包(从零开始的ROS项目)

🎯 本文目标:掌握ROS工作空间的创建与管理,学会创建和配置功能包,理解catkin编译系统的工作原理,能够独立搭建ROS项目框架。

📑 目录

  1. 工作空间概述
  2. 创建Catkin工作空间
  3. 功能包基础
  4. 创建第一个功能包
  5. 功能包配置详解
  6. 编译系统深入
  7. 工作空间管理技巧
  8. 实战项目:机器人控制包
  9. 常见问题与解决方案
  10. 总结与展望

1. 工作空间概述

1.1 什么是ROS工作空间?

ROS工作空间(Workspace)是组织和管理ROS项目的基本单元。它是一个包含ROS功能包的文件夹,提供了统一的编译环境和依赖管理机制。
工作空间 catkin_ws src 源代码 build 编译空间 devel 开发空间 install 安装空间 功能包1 功能包2 功能包N 源代码文件 配置文件 启动文件

1.2 工作空间的作用

  1. 代码组织:将相关的功能包组织在一起
  2. 依赖管理:自动处理功能包之间的依赖关系
  3. 编译管理:统一的编译环境和构建系统
  4. 环境隔离:不同工作空间之间相互独立
  5. 版本控制:方便进行代码版本管理

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 功能包命名规范

  1. 小写字母:全部使用小写字母
  2. 下划线分隔:使用下划线分隔单词(如:my_robot_controller)
  3. 语义清晰:名称应反映功能包的用途
  4. 避免冲突:检查是否与已有包重名

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 [...]

解决方案

  1. 确保发布者和订阅者使用相同的消息类型
  2. 重新编译所有相关功能包
  3. 清理工作空间并重新编译

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 本文总结

通过本文的学习,你已经掌握了:

  1. 工作空间管理:创建、配置和管理Catkin工作空间
  2. 功能包开发:从零开始创建功能包并进行配置
  3. 编译系统:深入理解CMake和Catkin编译机制
  4. 项目组织:合理组织代码结构和依赖关系
  5. 实战经验:完整的机器人控制包开发流程

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实战)》

敬请期待!

相关推荐
upper20203 小时前
数据挖掘11
人工智能·数据挖掘
*星星之火*3 小时前
【大白话 AI 答疑】 第7篇熵、交叉熵与交叉熵损失的概念梳理及计算示例
人工智能·机器学习·概率论
Wis4e3 小时前
基于PyTorch的深度学习——迁移学习3
人工智能·深度学习·迁移学习
z_lices3 小时前
财学堂张穗鸿老师课程体系投资策略战法知识体系剖析
人工智能
_Li.3 小时前
机器学习-Attention is All you need
人工智能·机器学习
微风企3 小时前
64.4%爆发式增长!智能体有哪些类型?
人工智能·ai
我怎么又饿了呀3 小时前
DataWhale RAG入门级教程(环境准备及注意事项)
人工智能