欢迎光临
我们一直在努力

ROS2小车调试记录

前言

ROS2好啊,ROS2得学。


0. 基础知识

0.0 ROS基础知识

0.0.1 什么是ros

0.0.2 ros和ros2有什么区别

0.1 ROS2系统信息相关指令

查询系统版本

printenv ROS_DISTRO

0.2 常用软件安装

一键安装ros 教程

wget http://fishros.com/install -O fishros && . fishros

1. ros2 新建工作空间和节点

1.1新建工作空间

1.1.1 工作空间简介

        ROS 2(Robot Operating System 2)中的工作空间(Workspace)是组织和管理 ROS 2 软件包(packages)的核心目录结构。它类似于一个“项目文件夹”,用于存放源代码、编译产物、依赖配置等,是开发、构建和运行 ROS 2 节点的基础环境。

1.1.2 创建工作空间

mkdir -p 工作空间名称_ws/src #src主要用来存放代码
cd 工作空间名称_ws #进入工作空间

mkdir 命令后面的 -p 选项是 “parents”(父目录) 的缩写,它的作用是:

递归创建目录 —— 如果指定的路径中某些父目录不存在,mkdir -p 会自动把它们一并创建出来;如果目录已存在,则不会报错,而是静默忽略。

1.1.3 编译工作空间

colcon build

构建完成后,我们应该看到build、install和log目录:

ROS系统中一个典型的工作空间结构如上所示,这个yahboomcar_ws就是工作空间的根目录,里边会有四个子目录,或者叫做四个子空间。

  • src,代码空间,未来编写的代码、脚本,都需要人为的放置到这里;

  • build,编译空间,保存编译过程中产生的中间文件;

  • install,安装空间,放置编译得到的可执行文件和脚本;

  • log,日志空间,编译和运行过程中,保存各种警告、错误、信息等日志。

总体来讲,这四个空间的文件夹,我们绝大部分操作都是在src中进行的,编译成功后,就会执行install里边的结果,build和log两个文件夹用的很少。

这里也要强调一点,工作空间的名称我们可以自己定义,数量也并不是唯一的,比如:

工作空间1:ros2_ws_a, 用于A机器人的开发

工作空间1:ros2_ws_b, 用于B机器人的开发

工作空间1:ros2_ws_c, 用于C机器人的开发

以上情况是完全允许的,就像是我们在集成开发环境中创建了多个新工程一样,都是并列存在的关系。

1.1.4 设置环境变量

编译成功后,为了让系统能够找到我们的功能包和可执行文件,还需要设置环境变量:

# 仅在当前终端生效
source install/setup.bash
# 所有终端均生效
echo "source install/setup.bash" >> ~/.bashrc

 至此,我们就完成了工作空间的创建、编译和配置。

1.2 新建功能包

1.2.1 功能包简介

每个机器人可能有很多功能,比如移动控制、视觉感知、自主导航等,如果我们把这些功能的源码都放到一起当然也是可以的,但是当我们想把其中某些功能分享给别人时,就会发现代码都混合到了一起,很难拆分出来。

功能包就是这个原理,我们把不同功能的代码划分到不同的功能包中,尽量降低他们之间的耦合关系,当需要在ROS社区中分享给别人的时候,只需要说明这个功能包该如何使用,别人很快就可以用起来了。

所以功能包的机制,是提高ROS中软件复用率的重要方法之一。

1.2.2 创建功能包

如何在ROS2中创建一个功能包呢?我们可以使用这个指令: 

ros2 pkg create <package_name> –build-type <build-type> –dependencies <dependencies> –node-name <node-name>

ros2命令中:

  • pkg:表示功能包相关的功能;

  • create:表示创建功能包;

  • package_name:新建功能包的名字;

  • build-type:表示新创建的功能包是C++还是Python的,如果使用C++或者C,那这里就跟ament_cmake,如果使用Python,就跟ament_python;

  • dependencies:表示功能包的依赖项,C++功能包需包含rclcpp,Python功能包需包含rclpy ,还有其它需要的依赖;

  • node-name:可执行程序的名称,会自动生成对应的源文件并生成配置文件;

比如在终端中分别创建C++和Python版本的功能包:

cd 工作空间名称_ws/src
# 创建C++功能包
ros2 pkg create pkg_helloworld_cpp –build-type ament_cmake –dependencies rclcpp –node-name helloworld
# 创建Python功能包
ros2 pkg create pkg_helloworld_py –build-type ament_python –dependencies rclpy –node-name helloworld

1.2.3 编译功能包

在创建好的功能包中,我们可以继续完成代码的编写,之后需要编译和配置环境变量,才能正常运行: 

cd ~/工作空间_ws

# 编译工作空间所有功能包

colcon build

# 编译指定功能包(一个或多个)

colcon build –packages-select 功能包列表

#配置环境变量

source install/setup.bash

1.2.4 带功能包的完整工作空间结构

ROS2工作空间的目录结构如下: 

WorkSpace — 自定义的工作空间。

    |— build:存储中间文件的目录,该目录下会为每一个功能包创建一个单独子目录。

    |— install:安装目录,该目录下会为每一个功能包创建一个单独子目录。

    |— log:日志目录,用于存储日志文件。

    |— src:用于存储功能包源码的目录。

        |– C++功能包

            |– package.xml:包信息,比如:包名、版本、作者、依赖项。

            |– CMakeLists.txt:配置编译规则,比如源文件、依赖项、目标文件。

            |– src:C++源文件目录。

            |– include:头文件目录。

            |– msg:消息接口文件目录。

            |– srv:服务接口文件目录。

            |– action:动作接口文件目录。

        |– Python功能包

            |– package.xml:包信息,比如:包名、版本、作者、依赖项。

            |– setup.py:与C++功能包的CMakeLists.txt类似。

            |– setup.cfg:功能包基本配置文件。

            |– resource:资源目录。

            |– test:存储测试相关文件。

            |– 功能包同名目录:Python源文件目录。

另外,无论是Python功能包还是C++功能包,都可以自定义一些配置文件相关的目录。 

|– C++或Python功能包

    |– launch:存储launch文件。

    |– rviz:存储rviz2配置相关文件。

    |– urdf:存储机器人建模文件。

    |– params:存储参数文件。

    |– world:存储仿真环境相关文件。

    |– map:存储导航所需地图文件。

    |– …… 

上述这些目录也可以定义为其他名称,或者根据需要创建其他一些目录。

1.3 新建工作节点

1.3.1 节点简介

        在通信时,不论采用何种方式,通信对象的构建都依赖于节点(Node),在ROS2中,一般情况下每个节点都对应某一单一的功能模块(例如:雷达驱动节点可能负责发布雷达消息,摄像头驱动节点可能负责发布图像消息)。一个完整的机器人系统可能由许多协同工作的节点组成,ROS2中的单个可执行文件(C++程序或Python程序)可以包含一个或多个节点。 

1.3.2 创建节点流程

  • 编程接口初始化

  • 创建节点并初始化

  • 实现节点功能

  • 销毁节点并关闭接口

  • 1.3.2 Hello World节点案例

    1.3.2.1 创建python功能包

    cd ~/工作空间名称_ros2_ws/工作空间名称_ws/src
    ros2 pkg create pkg_helloworld_py –build-type ament_python –dependencies rclpy –node-name helloworld

    1.3.2.2 编写代码

    执行上面命令后会创建pkg_helloworld_py,同时会创建helloworld.py文件来编写节点:

    删除原本 helloworld.py 的代码,写你自己想写的代码。

    完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

    1.3.2.3 编译功能包

    cd ~/工作空间名称_ros2_ws/工作空间名称_ws
    colcon build #编译全部
    colcon build –packages-select pkg_helloworld_py #pkg_helloworld_py:要编译的包名字
    source install/setup.bash #配置环境变量

    1.3.2.4 运行节点

    ros2 run pkg_helloworld_py helloworld

    运行成功后,可以在终端中看到循环打印“Hello World”字符串的效果:

    2. ROS2通信相关

    2.1 modbus RTU通信

    主要使用python3-pymodbus库

     # 查看是否有可用的官方包

    sudo apt search python3-pymodbus

    # 如果存在则安装

    sudo apt install python3-pymodbus

     # 查看版本

    python3 -c "from pymodbus import __version__; print(__version__)"

    查找USB串口

    lsusb

    # 查找设备文件名(通过插拔操作识别,通常是 /dev/ttyUSB0)

    ls /dev/tty*

    # 识别用户组

    ls -a -g /dev/ttyUSB*

    # 当前用户加入用户组

    sudo usermod -a -G dialout $USER

    #查看当前用户是否在设备组

    groups $USER

    应用代码和测试报文

    # pymodbus 3.6.9
    from pymodbus.client import ModbusSerialClient as ModbusClient
    from pymodbus.transaction import ModbusRtuFramer
    import time

    def run_rs485_master():
    # 创建Modbus RTU客户端
    client = ModbusClient(
    method='rtu',
    port='/dev/ttyUSB0',
    baudrate=9600,
    timeout=1, # 增加超时时间
    parity='N',
    stopbits=1,
    bytesize=8,
    retries=3, # 自动重试次数
    strict=True
    )

    # 连接设备
    connection = client.connect()
    print(f"Connected: {connection}")

    try:
    while True:
    # 读取保持寄存器 (功能码03)
    result = client.read_holding_registers(
    address=0,
    count=10,
    slave=1 # 从站地址
    )

    if not result.isError():
    print(f"保持寄存器值: {result.registers}")
    else:
    print(f"读取错误: {result}")

    # 写入单个寄存器 (功能码06)
    write_result = client.write_register(
    address=0,
    value=1234,
    slave=1
    )

    if not write_result.isError():
    print("写入寄存器成功")

    time.sleep(1)

    except KeyboardInterrupt:
    print("用户中断")
    finally:
    client.close()

    if __name__ == "__main__":
    run_rs485_master()

    测试报文

    主机(客户端): 01 03 00 00 00 0A C5 CD 从机(服务器): 01 03 14 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A 8F 16 主机(客户端): 01 06 00 00 04 D2 0B 57 从机(服务器): 01 06 00 00 04 D2 0B 57

    后面这块儿主要参考古月居上的文献:

    PLC与ROS2通信模型: 对于典型的PLC与ROS2系统的数据通信,通信的基本公式为:

    其中:

    • DataPLC表示从PLC读取到的数据。

    • Modbus()表示通过MODBUS协议与PLC进行通信。

    • IPPLC和PortPLC是PLC的IP地址和端口。

    • Function Code表示操作码,如读取寄存器等。

    • Data Address表示需要读取的PLC寄存器地址。

    ROS2节点的数据发布与订阅模型: ROS2使用发布-订阅模型进行数据交换,公式如下:

    其中:

    • MsgROS2为ROS2节点发布的数据消息。

    • f(DataPLC)表示ROS2对PLC数据进行处理后的消息生成过程。

    控制指令的生成与下发: 生成控制指令的基本过程可以表示为:

    其中:

    • CmdPLC是ROS2系统向PLC下发的控制指令。

    • Control Logic()表示控制逻辑算法,根据处理后的数据生成控制命令。

    • Processed Data是经过ROS2处理的数据。

    2.2 部署流程

    安装ROS2: 安装ROS2 Foxy版本,通过以下命令安装:

    sudo apt update
    sudo apt install ros-foxy-desktop

    配置ROS2工作空间:

    mkdir -p ~/ros2_ws/src
    cd ~/ros2_ws
    colcon build
    source install/setup.bash

    安装Modbus库: 安装用于与PLC进行Modbus通信的库,如modbus_tk:

    pip install modbus_tk

    配置PLC与ROS2的通信: 配置PLC与ROS2之间的通信,通过MODBUS/TCP协议进行数据交互。在ROS2节点中,通过modbus_tk库创建与PLC的连接。

    编写ROS2节点与PLC交互: 创建ROS2节点用于与PLC进行数据交互和控制,下面是一个简单的示例代码。

    #include <rclcpp/rclcpp.hpp>
    #include <modbus_tk/modbus_tcp.h>

    class PLCROS2Node : public rclcpp::Node {
    public:
    PLCROS2Node() : Node("plc_ros2_node") {
    modbus::TCPClient client;
    try {
    client.connect("192.168.1.100", 502); // PLC的IP地址和端口
    RCLCPP_INFO(this->get_logger(), "Connected to PLC");

    // 创建定时器每秒钟读取一次PLC数据
    timer_ = this->create_wall_timer(
    std::chrono::seconds(1), std::bind(&PLCROS2Node::read_data, this));
    } catch (const std::exception &e) {
    RCLCPP_ERROR(this->get_logger(), "Failed to connect to PLC: %s", e.what());
    }
    }

    private:
    void read_data() {
    try {
    // 从PLC读取寄存器值
    uint16_t data = client.read_holding_registers(0, 1)[0]; // 读取寄存器0的值
    RCLCPP_INFO(this->get_logger(), "Received data from PLC: %d", data);

    // 在这里,可以将数据发布到ROS2主题或进行进一步处理
    } catch (const std::exception &e) {
    RCLCPP_ERROR(this->get_logger(), "Failed to read data from PLC: %s", e.what());
    }
    }

    modbus::TCPClient client;
    rclcpp::TimerBase::SharedPtr timer_;
    };

    int main(int argc, char **argv) {
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<PLCROS2Node>());
    rclcpp::shutdown();
    return 0;
    }

    代码解读
  • modbus::TCPClient:这个类用于建立与PLC设备的TCP连接,发送Modbus请求并读取PLC寄存器。

  • read_data():该函数在每次定时器触发时运行,读取PLC的一个寄存器数据并输出。

  • timer_:定时器每隔一秒触发一次read_data(),实现周期性的读取PLC数据。

  • 连接与通信:client.connect("192.168.1.100", 502)

  • 用来连接PLC设备,client.read_holding_registers(0, 1)用来读取指定寄存器的数据。

    运行效果说明

    1. 系统初始化
    • PLC与ROS2节点的连接:在系统启动时,ROS2节点通过MODBUS协议连接到PLC设备。成功建立连接后,ROS2节点会在终端输出一条信息,表示PLC已成功连接。

      输出示例:

      [INFO] [1637841204.123456789] [plc_ros2_node]: Connected to PLC

    • 定时器启动:定时器在节点启动时也开始工作。每秒触发一次read_data函数。通过此定时器,ROS2节点会周期性地读取PLC的数据,并执行相应的处理逻辑。

      输出示例:

      [INFO] [1637841205.123456789] [plc_ros2_node]: Received data from PLC: 1023
      [INFO] [1637841206.123456789] [plc_ros2_node]: Received data from PLC: 1025

      在每次read_data函数被调用时,ROS2节点会尝试从PLC设备读取特定的寄存器(例如寄存器地址0),并输出从PLC中读取的数据。每次读取的值会根据PLC中传感器或控制器的状态而变化。

    2. 数据流
    • PLC数据采集:PLC系统周期性地收集来自现场设备(如传感器、执行器等)的数据。数据通常是实时变化的,如传感器采集到的温度、压力等值。这些数据被存储在PLC的寄存器中,ROS2节点通过MODBUS协议定期读取。

    • 数据交换:每次ROS2节点读取数据时,数据会通过MODBUS协议进行传输。典型的数据格式为16位的寄存器值,这些值在ROS2节点中可以进一步处理。

    3. 实时反馈
    • 数据处理:ROS2节点会将PLC中获取到的数据(如温度、压力等)进行处理。处理后的数据可以用来更新系统的状态或者生成控制指令。比如,可以对数据进行滤波、计算平均值、进行阈值判断等。

      输出示例(数据处理后的反馈):

      [INFO] [1637841207.123456789] [plc_ros2_node]: Processed data: 23.5 (Temperature)

      在该示例中,温度数据从PLC中获取,并经过处理(如单位转换)后,输出为23.5°C。

    • 控制指令生成:根据处理后的数据,ROS2节点可能会生成控制指令,反馈到PLC中控制设备。例如,如果温度超过预设的阈值,ROS2节点可以生成控制指令要求PLC启动冷却设备。

      控制指令输出示例:

      [INFO] [1637841210.123456789] [plc_ros2_node]: Control command: Turn on cooling system

    4. 异常处理与错误信息
    • 通信失败:如果在ROS2节点读取PLC数据时出现问题(如网络不通、PLC未响应等),系统会在终端输出错误信息。例如,当无法连接到PLC时,ROS2节点会打印出以下错误:

      [ERROR] [1637841211.123456789] [plc_ros2_node]: Failed to connect to PLC: Connection timeout

    • 数据读取失败:如果数据读取操作失败(如由于设备故障或其他原因),ROS2节点也会打印出相关的错误信息:

      [ERROR] [1637841212.123456789] [plc_ros2_node]: Failed to read data from PLC: Modbus error

    5. 系统监控与调试
    • 状态监控:在实际部署时,可以使用ROS2的rqt_graph等工具来监控ROS2系统中各个节点的状态。这些工具可以帮助开发人员确认PLC与ROS2节点之间的通信是否正常,是否有数据延迟或丢失。

      例如,使用以下命令启动ROS2的图形界面工具:

      ros2 run rqt_graph rqt_graph

      在rqt_graph中,用户可以清晰地看到各个ROS2节点之间的通信流和数据流。

    • 日志输出:在系统运行过程中,ROS2会记录日志信息,可以使用ros2 log命令查看详细日志。这些日志包括了各个节点的启动、通信、数据处理和错误信息,对于调试和故障排查非常有帮助。

      查看日志示例:

      ros2 log plc_ros2_node

    6. 性能与实时性
    • 数据采集频率:ROS2节点通过定时器控制数据的采集频率。在代码示例中,定时器每秒钟触发一次read_data函数。然而,实际应用中,数据采集频率可以根据需要进行调整(例如通过修改定时器的时间间隔)。

    • 响应时间:由于ROS2节点本身支持实时调度,可以确保在一定时间内(如毫秒级别)对PLC数据进行处理和响应。控制系统可以根据实时处理结果立即采取动作,保证工业自动化系统的实时性。

    • 系统稳定性:由于ROS2和PLC都具有高度稳定性,系统能够在长时间运行中保持稳定的通信与数据交换。即使在PLC设备或网络存在短暂波动时,ROS2节点也能通过异常处理机制重新连接并恢复通信。

    远程桌面设置

    使用windows自带的远程桌面

    ubuntu22.04安装IP查询工具

    sudo apt install net-tools

    IP查询命令:

    ifconfig -a

    SSH服务安装

    # 安装ssh服务: sudo apt-get install ssh   # 启动SSH服务: sudo /etc/init.d/ssh start # 开机自启 sudo systemctl enable ssh  

    安装防火墙

    sudo apt install firewalled

    # 如无法连接,开通22端口: firewall-cmd –add-port=22/tcp  –permanent firewall-cmd –reload

    开放3389端口

    # 开启3389端口:
    firewall-cmd –add-port=3389/tcp –permanent
    firewall-cmd –reload

    一、串口调试

    1. 安装串口调试工具

    安装串口调试工具:

    sudo apt-get install cutecom

    打开串口工具:

    Sudo cutecom

    2. MODBUS RTU通信

    # 查看是否有可用的官方包

    sudo apt search python3-pymodbus

    # 如果存在则安装

    sudo apt install python3-pymodbus

    #查看版本

    python3 -c "from pymodbus import __version__; print(__version__)"

    安装3.0以版本的pymodbus

    # 1. 卸载可能存在的旧版(可选)
    sudo apt remove python3-pymodbus

    # 2. 使用 –user 安装到用户目录(无需 sudo,不污染系统)
    pip3 install –user –upgrade pymodbus

    # 3. 验证版本
    python3 -c "from pymodbus import __version__; print(__version__)"

    验证是否可以用于ros2

    在你的 ROS 2 工作空间中运行:

    source /opt/ros/rolling/setup.bash
    python3 -c "from pymodbus.client import ModbusSerialClient; print('OK')"

    如果输出 OK,说明 ROS 2 能正常调用新版 pymodbus。

    验证pymodbus是否可用

    python3 -c "from pymodbus.client import ModbusSerialClient; print('✅ pymodbus 导入成功')"

    如果输出 ✅ pymodbus 导入成功,说明安装成功且 Python 能找到它。

    回到工作空间

    cd ~/huizhoucar_ws

    查看目录结构,安装tree命令

    sudo apt update
    sudo apt install tree

    查看目录结构

    tree

    编译工作空间

    colcon build –symlink-install

    –symlink-install 让 Python 文件修改后无需重新编译即可生效

    再次查看目录结构

    tree

    激活工作空间

    先激活系统级 ROS 2 环境(Humble)

    source /opt/ros/humble/setup.bash

    编译工作空间

    colcon build –symlink-install

    激活你的工作空间(叠加到系统环境)

    source install/setup.bash

    完整激活流程(每次新开终端都要做)

    # 1. 激活 ROS 2 Humble
    source /opt/ros/humble/setup.bash

    # 2. 进入工作空间
    cd ~/huizhoucar_ws

    # 3. 激活你的工作空间
    source install/setup.bash

    建议将前两行加入 ~/.bashrc,避免重复输入:

    echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
    echo "source ~/huizhoucar_ws/install/setup.bash" >> ~/.bashrc

    然后执行 

    source ~/.bashrc 

    或重启终端即可永久生效。

    验证激活是否成功

    1 检查环境变量

    echo $AMENT_PREFIX_PATH

    ✅ 正常输出应包含:

    /home/你的用户名/huizhoucar_ws/install:/opt/ros/humble

    2. 检查能否找到你的包

    ros2 pkg list | grep pkg_interfaces

    ✅ 如果看到 pkg_interfaces,说明自定义服务接口已加载。

    # 查找USB转串口

    lsusb

    # 查找设备文件名(通过插拔操作识别,通常是 /dev/ttyUSB0)

    ls /dev/tty*

    # 识别用户组

    ls -a -g /dev/ttyUSB*

    # 当前用户加入用户组

    sudo usermod -a -G dialout $USER

    #查看当前用户是否在设备组

    groups $USER

    应用代码及测试报文:

    # pymodbus 3.6.9

    from pymodbus.client import ModbusSerialClient as ModbusClient

    from pymodbus.transaction import ModbusRtuFramer

    import time

    def run_rs485_master():
        # 创建Modbus RTU客户端
        client = ModbusClient(
            method='rtu',
            port='/dev/ttyUSB0',
            baudrate=9600,
            timeout=1,          # 增加超时时间
            parity='N',
            stopbits=1,
            bytesize=8,
            retries=3,          # 自动重试次数
            strict=True
        )

       

        # 连接设备
        connection = client.connect()
        print(f"Connected: {connection}")
        try:
            while True:
                # 读取保持寄存器 (功能码03)
                result = client.read_holding_registers(
                    address=0,
                    count=10,
                    slave=1  # 从站地址
                )

               

                if not result.isError():
                    print(f"保持寄存器值: {result.registers}")
                else:
                    print(f"读取错误: {result}")
           
                # 写入单个寄存器 (功能码06)
                write_result = client.write_register(
                    address=0,
                    value=1234,
                    slave=1
                )

                if not write_result.isError():
                    print("写入寄存器成功")
                time.sleep(1)
       except KeyboardInterrupt:
            print("用户中断")

        finally:

            client.close()

    if __name__ == "__main__":

        run_rs485_master()

    总结

    提示:这里对文章进行总结: 例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

    参考文献:

    ROS2 编程-一般过程 -学习笔记 ubuntu24 + ros2 rolling+modbus-rtu通信 – 辛河 – 博客园

    三木地带你手搓ROS应用之传统PLC系统集成

    赞(0)
    未经允许不得转载:171主机测评 » ROS2小车调试记录
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址