一次成功流水帐 ros中使用serial包实现串口通信__以及依据通信协议C++串口通信

目录

一、使用rosserial串口包

二、使用C++直接读取串口数据


一、使用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];
    }
}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值