PS:本文需要的先行知识请自行补充

本文使用ROS提供的serial库,实现了部署于ROS中的串口通讯

环境部署

调试工具

测试环境 :VMware + Ubuntu 20.04.2 LTS + ROS-noetic

带界面的串口调试助手 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串口功能包

安装对应ROS版本的功能包:
sudo apt install ros-noetic-serial

serial使用

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

通讯的使用:

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 20.04.2 LTS + ROS-noetic

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

创建功能包:catkin_create_pkg myserial_pkg roscpp rospy std_msgs serial

代码设计

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

发布者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
#include <ros/ros.h>
#include "serial_pkg/Person.h"
#include <serial/serial.h>
#include <std_msgs/String.h>
#include <iostream>
#include <string>
#include <sstream>


int main(int argc,char **argv)
{
ros::init(argc,argv,"serial_publisher");

ros::NodeHandle seuNB;

ros::Publisher tree = seuNB.advertise<std_msgs::String>("Serial_Topic",10);

serial_pkg::Person person_msg;
person_msg.Name = "Tree";
/*----------------------------*/
serial::Serial ser;
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;

ser.setPort("/dev/ttyS0");
ser.setBaudrate(115200);
ser.setTimeout(to);
ser.setParity(pt);
ser.setBytesize(bt);
ser.setFlowcontrol(ft);
ser.setStopbits(st);

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;
}

ros::Rate loop_rate(1); //HZ
while(ros::ok())
{
size_t n = ser.available();
if(n!=0)
{
ROS_INFO_STREAM("Reading from serial port:\n");
std_msgs::String msg_s;
msg_s.data = ser.read(ser.available());
tree.publish(msg_s);
ROS_INFO_STREAM("Read:"<< msg_s.data);
}
tree.publish(person_msg);
ROS_INFO("Publish Info:%s",person_msg.Name.c_str());
loop_rate.sleep();


}
ser.close();
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
serial::Serial ser;

void Msg_callback(const std_msgs::String::ConstPtr &msg_p)
{
std::string msg = msg_p->data;
ser.write(msg.c_str());
ROS_INFO("serial_subscriber:%s",msg_p->data.c_str());

}

int main(int argc,char **argv)
{
setlocale(LC_CTYPE,"zh_CN.utf8");


ros::init(argc,argv,"serial_subscriber");

ros::NodeHandle seuWB;


ros::Subscriber Tree = seuWB.subscribe("Serial_Topic",10,Msg_callback);

ros::spin();

return 0;

}

配置CMakeLists.txt:

1
2
3
4
5
6
7
8
9
# 生成可执行文件
add_executable(myserial_pub src/myserial_pub.cpp)
# 链接库
target_link_libraries(myserial_pub ${catkin_LIBRARIES})

# 生成可执行文件
add_executable(myserial_sub src/myserial_sub.cpp)
# 链接库
target_link_libraries(myserial_sub ${catkin_LIBRARIES})

编写launch文件:

1
2
3
4
5
<launch>
<node pkg="serial_pkg" type="myserial_pub" name="pub_node" output="screen"/>
<node pkg="serial_pkg" type="myserial_sub" name="sub_node" output="screen"/>

</launch>

工程概述

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

本例程中使用/dev/ttyS0

设置串口相关参数:

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

功能:

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

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