目录
一、使用rosserial串口包
二、使用C++直接读取串口数据
一、使用rosserial串口包
会参考以下几个教程,但是这几个都不完整
ros中使用serial包实现串口通信_鲁班班班七号的博客-CSDN博客_ros 串口通信
ROS使用serial库编译时出现“未定义的引用”解决办法_学术马的博客-CSDN博客_ros编译未定义的引用
ros中使用serial包实现串口通信_鲁班班班七号的博客-CSDN博客_ros 串口通信
ROS下创建第一个节点工程_weixin_30907523的博客-CSDN博客
首先创建工程和包
然后安装软件
再写代码
CMakeLists文件也比较容易出错
下面开始流水账大法:
1.安装cutecom并打开:
sudo apt-get install cutecom
sudo cutecom
安装ros-kinetic-serial 软件包
sudo apt install ros-kinetic-serial
插入串口设备,看是否能正常接收,可以的话,一定要关闭cutecom,否则后面将提示Unable to open port.
2.查看电脑链接的串口信息(名称):
dmesg | grep ttyS*
下载serial软件包,二选一:
sudo apt-get install ros-kinetic-serial #ros为Kinect版本
sudo apt install ros-melodic-serial #ros为melodic版本
进入下载的软件包的位置
roscd serial
若是安装成功会看到:
$:/opt/ros/kinetic/share/serial
创建一个catkin工作空间
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
打开工作空间,编译这个工作空间,即使工作空间是空的
cd ~/catkin_ws/
catkin_make
使用catkin_create_pkg脚本去创建一个新的安装包”serial_port”,这个包依赖于 std_msgs, roscpp, and rospy:
cd ~/catkin_ws/src
catkin_create_pkg serial_port std_msgs rospy roscpp
再编译一下
cd ~/catkin_ws/
catkin_make
在src目录中编写串口通信的代码
//serial_port.cpp
#include <ros/ros.h>
#include <serial/serial.h>
#include <iostream>
int main(int argc, char** argv)
{
ros::init(argc, argv, "serial_port");
//创建句柄(虽然后面没用到这个句柄,但如果不创建,运行时进程会出错)
ros::NodeHandle n;
//创建一个serial类
serial::Serial sp;
//创建timeout
serial::Timeout to = serial::Timeout::simpleTimeout(100);
//设置要打开的串口名称
sp.setPort("/dev/ttyUSB0");
//设置串口通信的波特率
sp.setBaudrate(115200);
//串口设置timeout
sp.setTimeout(to);
try
{
//打开串口
sp.open();
}
catch(serial::IOException& e)
{
ROS_ERROR_STREAM("Unable to open port.");
return -1;
}
//判断串口是否打开成功
if(sp.isOpen())
{
ROS_INFO_STREAM("/dev/ttyUSB0 is opened.");
}
else
{
return -1;
}
ros::Rate loop_rate(500);
while(ros::ok())
{
//获取缓冲区内的字节数
size_t n = sp.available();
if(n!=0)
{
uint8_t buffer[1024];
//读出数据
n = sp.read(buffer, n);
for(int i=0; i<n; i++)
{
//16进制的方式打印到屏幕
std::cout << std::hex << (buffer[i] & 0xff) << " ";
}
std::cout << std::endl;
//把数据发送回去
sp.write(buffer, n);
}
loop_rate.sleep();
}
//关闭串口
sp.close();
return 0;
}
更改CMakeList.txt文件,添加如下内容:
有可能提示编译器在链接时没有找到与serial相关的库,虽然在函数开头包了#include<serial/serial.h>,但是ros在使用catkin_make时会去CMakeLists找相关的库,原因就是在CMakeLists中没有将需要的库包含进来,通过下面第一段将serial包含进来,然后就不会报错了。
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
serial
)
add_executable(serial_port src/serial_port.cpp)
add_dependencies(serial_port ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(serial_port
${catkin_LIBRARIES}
)
再编译一下
cd ~/catkin_ws/
catkin_make
没有问题的话,继续向下执行。
权限问题
查看本机串口信息
ls -l /dev/ttyUSB*
输出:crw-rw---- 1 root dialout 188, 0 9月 14 19:22 /dev/ttyUSB0
查看串口设备
dmesg | grep ttyUSB0
串口权限,每次运行都要重新执行一次,而不是开机才需要
sudo chmod 666 /dev/ttyUSB0
或者采用以下方法:
运行roscore,运行节点看是否能打开串口。如果提示Unable to open port,是由于权限不够引起的,进行如下操作
创建文件:(若使用的是ttyACM将ttyusb替换即可)
sudo gedit /etc/udev/rules.d/70-ttyusb.rules
在打开的文件中添加
KERNEL=="ttyUSB[0-9]*", MODE="0666"
运行节点
教程汇总
应该能看到终端在输出串口的数据
此时可以看到一个叫/serial_port的node正在运行
rosnode list
/rosout
/serial_port
如果需要以topic的形式发布出去,则可以在.cpp文件中,添加publisher,以话题形式发布出去。
ubuntu 20.04 LTS 版本可以使用下面对应的教程。
以话题形式发布出去,使用rosbag功能将数据进行记录:
cd /home/abc/catkin_ws/src/serial_port
mkdir bagfile && cd bagfile
rosbag record -o magrawdata /tactile_data
ctrl+c 结束包的录制。
查看rosbag数据
rosbag info mybagfile.bag
将rosbag包转化为txt文件
rostopic echo -b magrawdata.bag -p /tactile_data > rawdata.txt
回放rosbag数据包
rosbag play magrawdatabag.bag
(1) rosbag在回放数据时相当于一个publisher程序;
(2) 重新运行订阅了bag包中发布的topic程序,即可接收到对应topic的信息。
bag.active恢复为.bag:
cd bagfile
rosbag reindex mybagfile,bag.active
rosbag fix mybagfile.bag.active resultname.bag
二、使用C++直接读取串口数据
比如,有传感器厂家有如下手册用于通过串口读取数据,本例子使用orin板卡的usb接口进行读取,RS485数据通过485转USB插头与orin板卡连接。
手册上有对数据格式进行说明,并提供了一帧读取6通道的指令
文心一言prompt"帮我写一下串口C++类,发送 01 03 07 E4 00 0C 04 8C 这些hex数据,运行在linux的,并且设置:19200 波特率,01 机码和无校验、八位数据位、一位停止位的数据格式。"
生成内容,复制SerialPort::SerialPort(const std::string& portName, speed_t baudRate)以下的内容部分。
文心一言prompt"再把串口的数据读回来,解析一下 01 03 18 00 00 04 AF FF FF FF DD 00 00 00 74 00 00 00 02 00 00 06 7F 00 00 00 C0 3E 2F 中间是6通道,每个通道4个字节的数据到int32_t 中"
生成的内容,将SerialPort::readData(size_t numBytes)函数添加到类中。
为了在Linux环境下使用C++编写一个串口类,并配置为19200波特率、01机码、无校验、8位数据位、1位停止位,我们可以使用termios库来配置串口。以下是一个基本的串口类实现,该类能够发送指定的十六进制数据:
cpp
#include <iostream>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <cstring>
#include <vector>
class SerialPort {
public:
SerialPort(const std::string& portName, speed_t baudRate);
~SerialPort();
bool open();
void close();
bool writeData(const std::vector<uint8_t>& data);
private:
std::string portName;
int fd;
termios tty;
bool isOpen;
};
SerialPort::SerialPort(const std::string& portName, speed_t baudRate)
: portName(portName), fd(-1), isOpen(false) {
memset(&tty, 0, sizeof tty);
// 设置波特率
cfsetospeed(&tty, baudRate);
cfsetispeed(&tty, baudRate);
// 设置数据位、停止位、校验等
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8位数据位
tty.c_iflag &= ~IGNBRK; // 禁用忽略break
tty.c_lflag = 0; // 非规范模式,不处理输入/输出字符
tty.c_oflag = 0; // 原始输出
tty.c_cc[VMIN] = 1; // 读取阻塞直到至少有一个字符
tty.c_cc[VTIME] = 5; // 读取超时0.5秒(5个十分之一秒)
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控制
tty.c_cflag |= (CLOCAL | CREAD); // 启用接收器,忽略调制解调器控制线
tty.c_cflag &= ~(PARENB | PARODD); // 禁用奇偶校验
tty.c_cflag &= ~CSTOPB; // 1位停止位
tty.c_cflag &= ~CRTSCTS; // 禁用硬件流控制
}
SerialPort::~SerialPort() {
close();
}
bool SerialPort::open() {
fd = open(portName.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
std::cerr << "Error " << errno << " opening " << portName << ": " << strerror(errno) << std::endl;
return false;
}
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
std::cerr << "Error " << errno << " from tcsetattr: " << strerror(errno) << std::endl;
close();
return false;
}
isOpen = true;
return true;
}
void SerialPort::close() {
if (isOpen && fd >= 0) {
close(fd);
fd = -1;
isOpen = false;
}
}
bool SerialPort::writeData(const std::vector<uint8_t>& data) {
if (!isOpen) {
std::cerr << "Serial port is not open." << std::endl;
return false;
}
ssize_t bytesWritten = write(fd, data.data(), data.size());
if (bytesWritten < 0) {
std::cerr << "Error " << errno << " writing to serial port: " << strerror(errno) << std::endl;
return false;
}
if (bytesWritten != data.size()) {
std::cerr << "Partial write to serial port." << std::endl;
return false;
}
return true;
}
int main() {
SerialPort serial("/dev/ttyS0", B19200); // 根据实际情况修改串口设备名
if (!serial.open()) {
return 1;
}
std::vector<uint8_t> hexData = {0x01, 0x03, 0x07, 0xE4, 0x00, 0x0C, 0x04, 0x8C};
if (!serial.writeData(hexData)) {
return 1;
}
std::cout << "Data written successfully." << std::endl;
serial.close();
return 0;
}
生成的数据读取函数
std::vector<uint8_t> SerialPort::readData(size_t numBytes) {
std::vector<uint8_t> buffer(numBytes);
ssize_t bytesRead = read(fd, buffer.data(), numBytes);
if (bytesRead < 0) {
std::cerr << "Error " << errno << " reading from serial port: " << strerror(errno) << std::endl;
buffer.clear(); // Clear the buffer on error
} else if (bytesRead != numBytes) {
buffer.resize(bytesRead); // Resize the buffer if partial read occurred
}
return buffer;
}
将类的声明定义在头文件serial.hpp中
针对该型号x530传感器进行类的实现,
prompt:
“class X503Sensor { public: private: std::shared_ptr<SerialPort> serial_port_; }; 帮忙写写”
“分成头文件和源文件的写法”
最后生成一个头文件.hpp,一个源文件.cpp
使用方法如下:
main函数中添加#include "x503.hpp",初始化部分添加hardware::X503Sensor::instance()->init();数据更新部分添加hardware::X503Sensor::instance()->update(in); 即可将串口读到的数据自动更新至in的结构体中。
x503.hpp
#pragma once
#include <thread>
#include <atomic>
#include <memory>
#include <iomanip>
#include "serial.hpp"
#include "motion_config.h"
namespace hardware {
class X503Sensor {
public:
X503Sensor();
~X503Sensor();
static X503Sensor *instance();
bool init();
void update(Motion::inputStruct &in);
int read_data();
private:
void thread_running();
int32_t bytes_to_int32(const std::vector<uint8_t>& responseData, int offset);
double int32_to_double(int32_t value);
private:
std::shared_ptr<SerialPort> serial_port_;
std::thread read_thread_;
std::atomic<bool> stopRequested{false};
double sensor_value_[2] = {0.0f};
};
}
x503.cpp
#include "x503.hpp"
namespace hardware {
X503Sensor::X503Sensor()
: serial_port_(nullptr)
{
}
X503Sensor::~X503Sensor()
{
stopRequested.store(true);
usleep(100);
if (serial_port_ != nullptr) {
serial_port_->close();
}
}
X503Sensor *X503Sensor::instance() {
static X503Sensor one_instance;
return &one_instance;
}
bool X503Sensor::init()
{
// 创建并初始化串口对象
std::string portName = "/dev/ttyUSB0";
speed_t baudRate = B19200;
serial_port_ = std::make_shared<SerialPort>(portName, baudRate);
if (!serial_port_->open()) { // 假设SerialPort类有open方法返回bool值
std::cerr << "Failed to open serial port: " << portName << std::endl;
return false;
}
read_thread_ = std::thread(&X503Sensor::thread_running, this);
return true;
}
void X503Sensor::thread_running() {
int rate = 200;
while (!stopRequested.load()) {
read_data();
usleep(1000*1000/rate);
}
std::cout << "X503Sensor::thread_running() exit !!" << std::endl;
exit(0);
}
int X503Sensor::read_data()
{
// 调用串口对象的读取方法(假设SerialPort类有readData方法)
if (serial_port_ == nullptr || !serial_port_->is_open()) {
std::cerr << "Serial port is not open or not initialized." << std::endl;
return -1;
}
std::vector<uint8_t> hexData = {0x01, 0x03, 0x07, 0xE4, 0x00, 0x0C, 0x04, 0x8C};
if (!serial_port_->write_data(hexData)) {
return -1;
}
// sleep(1);
usleep(1*1000);
// Read the expected response (adjust the size as needed)
const size_t expectedResponseSize = 29; // Based on the provided data length
std::vector<uint8_t> responseData = serial_port_->read_data(expectedResponseSize*2);
if (responseData.size() < expectedResponseSize) {
std::cerr << "Error: Expected " << expectedResponseSize << " bytes but received " << responseData.size() << " bytes." << std::endl;
return -1;
}
if ((responseData[0] == 0x01) && (responseData[1] == 0x03) && (responseData[2] == 0x18)) {
}
else {
std::cerr << "Error: Invalid response data." << std::endl;
for (auto byte : responseData) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " ";
}
std::cout << std::endl;
return -1;
}
size_t offset = 3;
if (0) {
std::cerr << "Recv :";
for (auto byte : responseData) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " ";
}
std::cout << std::endl;
}
std::vector<int32_t> channel_int32(6);
std::vector<double> channel_double(6);
for (size_t i = 0; i < 6; ++i) {
channel_int32[i] = bytes_to_int32(responseData, offset+i*4 );
channel_double[i] = int32_to_double(channel_int32[i]);
// std::cout << "Channel " << i + 1 << ": " << channel_double[i] << std::endl;
}
sensor_value_[0] = channel_double[1-1]; // 通道 1
sensor_value_[1] = channel_double[5-1]; // 通道 5
return 0;
}
int32_t X503Sensor::bytes_to_int32(const std::vector<uint8_t>& responseData, int offset) {
int32_t result = 0;
result |= responseData[offset+0] << 24;
result |= responseData[offset+1] << 16;
result |= responseData[offset+2] << 8;
result |= responseData[offset+3] << 0;
return result;
}
double X503Sensor::int32_to_double(int32_t value) {
double ret_val = (double)value / 1000.0f;
return ret_val;
}
void X503Sensor::update(Motion::inputStruct &in) {
for (int i = 0; i < 2; i++) {
in.force_sensor[i] = sensor_value_[i];
}
}
}