news 2026/4/15 16:36:46

ROS2串口通信实战:从虚拟串口搭建到数据传输测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ROS2串口通信实战:从虚拟串口搭建到数据传输测试

1. 为什么需要ROS2串口通信?

在机器人开发中,串口通信就像机器人的"神经系统"。我做过一个智能小车的项目,需要通过串口向底盘发送速度指令。当时发现很多开发者卡在环境配置这一步,其实只要掌握几个关键点就能轻松打通ROS2和硬件设备的通信链路。

串口通信最大的优势是硬件兼容性强。市面上90%的嵌入式设备都支持串口协议,从Arduino到STM32,甚至工业PLC都能通过串口与ROS2交互。相比USB或网络通信,串口协议更底层,传输延迟通常能控制在10ms以内。

实际开发中常遇到三类问题:

  • 物理串口数量不足(笔记本通常只有1-2个物理串口)
  • 波特率配置错误导致乱码
  • 数据帧解析异常

这就引出了我们的解决方案:用虚拟串口工具socat模拟真实硬件环境。我在多次项目实践中验证过,这种方法能节省80%的硬件调试时间。下面我会手把手带你搭建完整的通信链路。

2. 搭建虚拟串口环境

2.1 安装socat工具

在Ubuntu终端执行这个命令:

sudo apt-get install socat

安装完成后,用这个命令创建一对虚拟串口:

socat -d -d pty,raw,echo=0 pty,raw,echo=0

你会看到类似这样的输出:

2023/08/15 10:23:45 socat[1234] N PTY is /dev/pts/3 2023/08/15 10:23:45 socat[1234] N PTY is /dev/pts/4

这里/dev/pts/3/dev/pts/4就是生成的虚拟串口对。我习惯用奇数端口做发送端,偶数端口做接收端,这样不容易搞混。

2.2 验证串口连通性

打开三个终端窗口:

  1. 第一个终端持续监听接收端:
cat < /dev/pts/4
  1. 第二个终端向发送端写入数据:
echo "Hello ROS2" > /dev/pts/3
  1. 第三个终端可以用ls -l /dev/pts/*查看端口状态。如果第一个终端显示了"Hello ROS2",说明虚拟串口工作正常。

注意:每次重启系统后虚拟串口路径会变化,需要重新生成。建议把socat命令保存为shell脚本方便重复使用。

3. 创建ROS2串口通信节点

3.1 建立工程结构

按这个顺序创建工程目录:

mkdir -p serial_ws/src cd serial_ws/src ros2 pkg create --build-type ament_cmake serial_demo

目录结构应该是这样:

serial_ws/ └── src/ └── serial_demo/ ├── include/ ├── src/ ├── CMakeLists.txt └── package.xml

3.2 编写串口核心类

include/serial_demo目录下新建serial_port.hpp,这是我优化过的版本:

#pragma once #include <fcntl.h> #include <termios.h> #include <unistd.h> class SerialPort { public: int Open(const char* port, int baudrate) { fd_ = open(port, O_RDWR | O_NOCTTY); if (fd_ < 0) return -1; struct termios options; tcgetattr(fd_, &options); cfsetispeed(&options, baudrate); cfsetospeed(&options, baudrate); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; tcsetattr(fd_, TCSANOW, &options); return fd_; } int Write(const char* data, int length) { return write(fd_, data, length); } int Close() { return close(fd_); } private: int fd_ = -1; };

这个类封装了三个关键方法:

  • Open():配置波特率、数据位、停止位等参数
  • Write():发送二进制数据
  • Close():释放串口资源

3.3 实现发布/订阅节点

src目录下创建两个节点文件:

publisher_node.cpp

#include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" class PublisherNode : public rclcpp::Node { public: PublisherNode() : Node("serial_publisher") { publisher_ = create_publisher<std_msgs::msg::String>("serial_topic", 10); timer_ = create_wall_timer( std::chrono::milliseconds(500), [this]() { auto message = std_msgs::msg::String(); message.data = "Data:" + std::to_string(counter_++); RCLCPP_INFO(get_logger(), "Publishing: '%s'", message.data.c_str()); publisher_->publish(message); }); } private: rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; rclcpp::TimerBase::SharedPtr timer_; int counter_ = 0; }; int main(int argc, char* argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<PublisherNode>()); rclcpp::shutdown(); return 0; }

subscriber_node.cpp

#include "serial_demo/serial_port.hpp" #include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" class SubscriberNode : public rclcpp::Node { public: SubscriberNode() : Node("serial_subscriber") { serial_.Open("/dev/pts/3", B115200); // 修改为你的发送端端口 subscription_ = create_subscription<std_msgs::msg::String>( "serial_topic", 10, [this](const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(get_logger(), "Received: '%s'", msg->data.c_str()); serial_.Write(msg->data.c_str(), msg->data.length()); }); } ~SubscriberNode() { serial_.Close(); } private: SerialPort serial_; rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_; }; int main(int argc, char* argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<SubscriberNode>()); rclcpp::shutdown(); return 0; }

4. 编译与系统配置

4.1 修改构建文件

更新CMakeLists.txt

find_package(rclcpp REQUIRED) find_package(std_msgs REQUIRED) add_library(serial_port SHARED src/serial_port.cpp) target_include_directories(serial_port PUBLIC include) add_executable(publisher_node src/publisher_node.cpp) ament_target_dependencies(publisher_node rclcpp std_msgs) add_executable(subscriber_node src/subscriber_node.cpp) ament_target_dependencies(subscriber_node rclcpp std_msgs) target_link_libraries(subscriber_node serial_port) install(TARGETS publisher_node subscriber_node serial_port DESTINATION lib/${PROJECT_NAME})

更新package.xml

<depend>rclcpp</depend> <depend>std_msgs</depend>

4.2 编译工程

执行以下命令:

cd ~/serial_ws colcon build --packages-select serial_demo source install/setup.bash

5. 完整通信测试

5.1 启动测试流程

需要四个终端窗口:

  1. 终端1:运行socat创建虚拟串口
  2. 终端2:监听接收端口
    cat < /dev/pts/4
  3. 终端3:启动订阅节点
    ros2 run serial_demo subscriber_node
  4. 终端4:启动发布节点
    ros2 run serial_demo publisher_node

5.2 预期结果

在终端4会看到持续输出的发布消息:

[INFO] [serial_publisher]: Publishing: 'Data:0' [INFO] [serial_publisher]: Publishing: 'Data:1'

终端3会显示接收到的消息:

[INFO] [serial_subscriber]: Received: 'Data:0' [INFO] [serial_subscriber]: Received: 'Data:1'

终端2会显示通过串口传输的原始数据:

Data:0Data:1Data:2

5.3 常见问题排查

如果数据没有正常传输,按这个顺序检查:

  1. 确认socat生成的端口号与代码中一致
  2. 检查串口权限:
    sudo chmod 777 /dev/pts/*
  3. 确认波特率设置一致(代码和硬件设备)
  4. 使用ros2 topic echo /serial_topic确认话题有数据

我在实际项目中遇到过最棘手的问题是串口死锁,后来发现是没处理异常关闭的情况。现在代码中在析构函数里主动关闭串口,避免了资源泄漏。

6. 进阶优化建议

6.1 增加数据校验

串口通信容易受到干扰,建议在数据帧中添加校验位。常用的CRC16校验实现:

uint16_t CalculateCRC16(const uint8_t* data, size_t length) { uint16_t crc = 0xFFFF; for (size_t i = 0; i < length; ++i) { crc ^= data[i]; for (int j = 0; j < 8; ++j) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }

6.2 使用自定义消息类型

标准字符串传输效率低,可以自定义二进制消息:

// 在msg目录下创建SerialMessage.msg uint16 header # 0xAA55 uint8 length uint8[] data uint16 checksum

6.3 多线程处理

对于高速数据传输,建议使用独立线程处理串口读写:

std::thread read_thread([this](){ while(running_) { uint8_t buffer[256]; int n = read(fd_, buffer, sizeof(buffer)); if(n > 0) { // 处理接收数据 } } });

记得在析构函数中设置running_ = false并调用read_thread.join()

7. 硬件对接注意事项

当过渡到真实硬件时,要注意:

  1. 电平匹配:确认设备是TTL电平(3.3V/5V)还是RS232电平(±12V)
  2. 接线规范:TX接RX,RX接TX,GND对接
  3. 波特率容错:工业现场建议使用9600以下波特率
  4. 抗干扰措施
    • 使用双绞线
    • 避免与电源线平行走线
    • 在信号线加磁环

我用示波器抓取过串口信号,发现长距离传输时信号边沿会变缓。这时需要在接收端添加适当的上拉电阻,通常4.7KΩ就能显著改善信号质量。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 23:01:37

龙虾白嫖指南,请查收~胃

1. 什么是 Apache SeaTunnel&#xff1f; Apache SeaTunnel 是一个非常易于使用、高性能、支持实时流式和离线批处理的海量数据集成平台。它的目标是解决常见的数据集成问题&#xff0c;如数据源多样性、同步场景复杂性以及资源消耗高的问题。 核心特性 丰富的数据源支持&#…

作者头像 李华
网站建设 2026/4/11 23:01:37

Redis:延迟双删的适用边界与落地细节使

pagehelper整合 引入依赖com.github.pagehelperpagehelper-spring-boot-starter2.1.0compile编写代码 GetMapping("/list/{pageNo}") public PageInfo findAll(PathVariable int pageNo) {// 设置当前页码和每页显示的条数PageHelper.startPage(pageNo, 10);// 查询数…

作者头像 李华
网站建设 2026/4/15 22:59:17

从领域驱动到本体论:AI 时代的架构方法论变了对

从0构建WAV文件&#xff1a;读懂计算机文件的本质 虽然接触计算机有一段时间了&#xff0c;但是我的视野一直局限于一个较小的范围之内&#xff0c;往往只能看到于算法竞赛相关的内容&#xff0c;计算机各种文件在我看来十分复杂&#xff0c;认为构建他们并能达到目的是一件困难…

作者头像 李华
网站建设 2026/4/16 2:14:08

华为-AC+FIT AP组网(web方式)

通过web方式配置ACFIT AP组网 一、拓扑图 二、基础配置 2.1 登录web界面 AC1&#xff1a; <AC6605>sys # 进入系统视图 [AC6605]un in en # 关闭信息中心 [AC6605]sys AC1 # 命名 [AC1]vlan 100 # 创建vlan100&#xff08;这个vlan作为web界面的登录地址&#xff09;…

作者头像 李华
网站建设 2026/4/15 23:36:31

BUUCTF(MISC)_[DDCTF2018]

目录 [DDCTF2018](╯□&#xff09;╯︵ ┻━┻ [DDCTF2018]流量分析 [DDCTF2018](╯□&#xff09;╯︵ ┻━┻ 点击跳转至题目 keyd4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9b2b2e1e2b9b9b7b4e1b4b7e3e4b3b2b2e3e6b4b3e2b5b0b6b1b0e6e1e5e1…

作者头像 李华
网站建设 2026/4/11 22:57:19

3个突破性技巧:彻底解决Amlogic S905L3B设备Armbian部署难题

3个突破性技巧&#xff1a;彻底解决Amlogic S905L3B设备Armbian部署难题 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, rk…

作者头像 李华