PS:本文需要的先行知识请自行补充
本文通过移植serial功能包,实现了部署于ROS2中的串口通讯

环境部署

调试工具

测试环境 :VMware + Ubuntu 22.04.2 LTS + ROS2-humble

带界面的串口调试助手 CuteCom :sudo apt-get install cutecom

虚拟串口模拟器 socat:sudo apt-get install socat

1
2
3
4
5
6
7
8
9
10
11
虚拟串口生成 `socat -d -d pty,b115200 pty,b115200`

-d:启用调试输出

pty:生成伪终端

raw:使数据保存原始模式

echo = 0:禁用输入字符的回显

b“115200”:设置波特率为 “115200”

观察日志,会返回两个虚拟串口对,不同的设备生成的串口名称可能每次都会不一样,输出格式例:/dev/pts/4 和 /dev/pts/5;且终端需要一直开启,虚拟串口才会保持有效;

1
2
3
cat < /dev/pts/4   监听/dev/pts/4的数据

sudo echo "Tree" > /dev/pts/5 通过/dev/pts/5向/dev/pts/4发送数据"Tree"

serial安装

Github:https://github.com/ZhaoXiangBox/serial

截止2024-1:

在ROS1中可以直接安装官方提供的serial库,直接可用APT下载

而在ROS2中需要自行下载源码移植

安装ROS2-serial-driver:sudo apt install ros-humble-serial-driver

Git下载源码:
git clone https://github.com/ZhaoXiangBox/serial.git

编译源码:

1
2
3
4
5
cd serial
mkdir build

cd build
camke .. & make -j

这里不直接编译 install 文件,可以放到功能包后在编译;

serial使用

官网:http://wjwwood.io/serial/doc/1.1.0/index.html

与ROS1中的serial使用方式一致

安装的serial是可支持ROS - humble的

1、首先我们需要添加serial的头文件

#include "serial/serial.h"

2、相关初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
serial::Serial ser;
serial::Timeout to = serial::Timeout::simpleTimeout(1000); #方法设置超时时间为1000毫秒(1秒)
serial::parity_t pt = serial::parity_t::parity_none; #表示不使用奇偶校验
serial::bytesize_t bt = serial::bytesize_t::eightbits; #表示每个字节包含8位
serial::flowcontrol_t ft = serial::flowcontrol_t::flowcontrol_none; #表示不使用流控制
serial::stopbits_t st = serial::stopbits_t::stopbits_one; #表示每个字节后面有1个停止位

ser.setPort("/dev/ttyS0");
ser.setBaudrate(115200);
ser.setTimeout(to);
ser.setParity(pt); # 设置奇偶校验
ser.setBytesize(bt); #设置字节大小
ser.setFlowcontrol(ft); #设置流控制
ser.setStopbits(st); #设置停止位

3、开启串口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try
{
ser.open();

}
catch(const std::exception &e)
{
ROS_ERROR_STREAM("Unable to open port.");
return -1;
}

if(ser.isOpen())
{
ROS_INFO_STREAM("Serial Port is opende.\n");

}
else
{
return -1;
}

4、读写操作

1
2
3
ser.read(ser.available());

ser.write(msg.c_str());

以上均可结合测试环境和虚拟串口模拟器 socat 进行测试或验证代码

ROS 实现串口收发

工程环境:RK3588 + Ubuntu 22.04.2 LTS + ROS2-humble

默认工作空间已建立,环境变量已导入脚本;

创建功能包:
ros2 pkg create <package-name> --build-type {cmake,ament_cmake,ament_python} --dependencies <依赖名字>

代码设计

以下仅贴部分代码作演示:

发布者myserial_pub.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include <serial/serial.h>

#define BAUDRATE 115200

class tree_node : public rclcpp::Node
{
public:
tree_node(const std::string& name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"This is Tree's Test");

serial_publisher_ = this->create_publisher<std_msgs::msg::String>("serial_msg",10);

timer_ = this->create_wall_timer(std::chrono::milliseconds(1000), std::bind(&tree_node::timer_callback, this));

serial_Init();
}

private:

void timer_callback()
{
std_msgs::msg::String message;

message.data = "tree_test\r\n";

RCLCPP_INFO(this->get_logger(),"Publisher:'%s'",message.data.c_str());

// ros_ser.write(message.data.c_str());

serial_publisher_->publish(message);

size_t n = ros_ser.available();
if(n!=0)
{
std_msgs::msg::String msg_s;
msg_s.data = ros_ser.read(ros_ser.available());
serial_publisher_->publish(msg_s);
}
}



void serial_Init()
{


serial::Timeout to = serial::Timeout::simpleTimeout(1000);
serial::parity_t pt = serial::parity_t::parity_none;
serial::bytesize_t bt = serial::bytesize_t::eightbits;
serial::flowcontrol_t ft = serial::flowcontrol_t::flowcontrol_none;
serial::stopbits_t st = serial::stopbits_t::stopbits_one;


ros_ser.setPort("/dev/ttyS0");
// ros_ser.setPort("/dev/pts/5"); //TEST
ros_ser.setBaudrate(BAUDRATE);
ros_ser.setTimeout(to);
ros_ser.setParity(pt);
ros_ser.setBytesize(bt);
ros_ser.setFlowcontrol(ft);
ros_ser.setStopbits(st);

try
{
ros_ser.open();

}
catch(const std::exception &e)
{
RCLCPP_ERROR(this->get_logger(), "Failed to open the serial port.");
// return -1;
}

}

serial::Serial ros_ser;

rclcpp::TimerBase::SharedPtr timer_;

rclcpp::Publisher<std_msgs::msg::String>::SharedPtr serial_publisher_;
};

int main(int argc, char **argv)
{

// auto logger = rclcpp::get_logger("my_logger");


rclcpp::init(argc,argv);
auto node = std::make_shared<tree_node>("tree_new_node");

rclcpp::spin(node);

rclcpp::shutdown();
return 0;


}

订阅者myserial_sub.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class serial_sub_node : public rclcpp::Node
{
public:
serial_sub_node(std::string name) : Node(name)
{
serial_Init();
serial_subscriber_ = this->create_subscription<std_msgs::msg::String>("serial_msg", 10, std::bind(&serial_sub_node::command_callback, this, std::placeholders::_1));
}
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr serial_subscriber_;

void command_callback(const std_msgs::msg::String::SharedPtr msg)
{
ros_ser.write(msg->data.c_str());
}

void serial_Init()
{

/**/

}

serial::Serial ros_ser;
};

int main(int argc, char **argv)
{

// auto logger = rclcpp::get_logger("my_logger");

rclcpp::init(argc,argv);
auto node = std::make_shared<serial_sub_node>("tree_serial_sub_node");

rclcpp::spin(node);

rclcpp::shutdown();
return 0;

}

配置CMakeLists.txt:

1
2
3
4
5
6
7
8
9
10
add_executable(myserial_pub src/myserial_pub.cpp)
ament_target_dependencies(myserial_pub rclcpp std_msgs serial)

add_executable(myserial_sub src/myserial_sub.cpp)
ament_target_dependencies(myserial_sub rclcpp std_msgs serial)

install(TARGETS
myserial_pub
myserial_sub
DESTINATION lib/${PROJECT_NAME})

工程概述

在/dev/ 中可以查询设备的ttyUSB* 或ttys*,选择需要的串口即可

本例程中使用/dev/ttyS0

设置串口相关参数:

波特率115200,设置超时时间为1000毫秒(1秒),不使用奇偶校验,每个字节包含8位,不使用流控制,每个字节后面有1个停止位;

功能:

发布者代码负责从串口读取数据,并通过ROS2话题定时发布数据和消息。

订阅者代码负责订阅ROS2话题,并将接收到的数据和消息写入串口。